Skip to content

Commit b9bb1d5

Browse files
Merge branch 'main' into initial-stats-sweep
2 parents 2223e5a + 7a450b4 commit b9bb1d5

16 files changed

Lines changed: 405 additions & 190 deletions

File tree

cpp/tensorrt_llm/executor/cache_transmission/nixl_utils/agentBindings.cpp

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#endif
2727

2828
#include <nanobind/nanobind.h>
29+
#include <nanobind/ndarray.h>
2930
#include <nanobind/stl/optional.h>
3031
#include <nanobind/stl/string.h>
3132
#include <nanobind/stl/tuple.h>
@@ -83,6 +84,47 @@ NB_MODULE(tensorrt_llm_transfer_agent_binding, m)
8384
new (self) kvc::MemoryDescs(type, std::move(descs));
8485
},
8586
nb::arg("type"), nb::arg("tuples"))
87+
// Classmethod: batch construction from numpy arrays
88+
.def_static(
89+
"from_arrays",
90+
[](kvc::MemoryType type, nb::ndarray<int64_t, nb::ndim<1>, nb::c_contig, nb::device::cpu> addrs,
91+
nb::ndarray<int64_t, nb::ndim<1>, nb::c_contig, nb::device::cpu> sizes,
92+
nb::ndarray<int32_t, nb::ndim<1>, nb::c_contig, nb::device::cpu> deviceIds)
93+
{
94+
size_t n = addrs.shape(0);
95+
auto const* a = addrs.data();
96+
auto const* s = sizes.data();
97+
auto const* d = deviceIds.data();
98+
std::vector<kvc::MemoryDesc> descs;
99+
descs.reserve(n);
100+
for (size_t i = 0; i < n; ++i)
101+
{
102+
descs.emplace_back(
103+
static_cast<uintptr_t>(a[i]), static_cast<size_t>(s[i]), static_cast<uint32_t>(d[i]));
104+
}
105+
return kvc::MemoryDescs(type, std::move(descs));
106+
},
107+
nb::arg("type"), nb::arg("addrs"), nb::arg("sizes"), nb::arg("device_ids"),
108+
nb::call_guard<nb::gil_scoped_release>())
109+
// Classmethod: batch construction with uniform device_id (avoids np.full allocation)
110+
.def_static(
111+
"from_arrays_uniform_device",
112+
[](kvc::MemoryType type, nb::ndarray<int64_t, nb::ndim<1>, nb::c_contig, nb::device::cpu> addrs,
113+
nb::ndarray<int64_t, nb::ndim<1>, nb::c_contig, nb::device::cpu> sizes, uint32_t deviceId)
114+
{
115+
size_t n = addrs.shape(0);
116+
auto const* a = addrs.data();
117+
auto const* s = sizes.data();
118+
std::vector<kvc::MemoryDesc> descs;
119+
descs.reserve(n);
120+
for (size_t i = 0; i < n; ++i)
121+
{
122+
descs.emplace_back(static_cast<uintptr_t>(a[i]), static_cast<size_t>(s[i]), deviceId);
123+
}
124+
return kvc::MemoryDescs(type, std::move(descs));
125+
},
126+
nb::arg("type"), nb::arg("addrs"), nb::arg("sizes"), nb::arg("device_id"),
127+
nb::call_guard<nb::gil_scoped_release>())
86128
.def_prop_ro("type", &kvc::MemoryDescs::getType)
87129
.def_prop_ro("descs", &kvc::MemoryDescs::getDescs);
88130

@@ -105,9 +147,24 @@ NB_MODULE(tensorrt_llm_transfer_agent_binding, m)
105147
});
106148

107149
// TransferRequest class
150+
//
151+
// NOTE: The constructor uses std::move to transfer ownership of src_descs / dst_descs
152+
// into the TransferRequest. This avoids an O(n) copy of the internal
153+
// std::vector<MemoryDesc> (24 bytes * n). For 40k descriptors this saves ~937 KB
154+
// of memcpy and turns a ~58 us copy into an O(1) pointer swap (~0.4 us).
155+
//
156+
// IMPORTANT: After construction, the Python MemoryDescs objects passed as src_descs
157+
// and dst_descs are left in a moved-from state — their internal descriptor list
158+
// becomes empty. Do NOT access them after passing to TransferRequest.
108159
nb::class_<kvc::TransferRequest>(m, "TransferRequest")
109-
.def(nb::init<kvc::TransferOp, kvc::TransferDescs, kvc::TransferDescs, std::string const&,
110-
std::optional<kvc::SyncMessage>>(),
160+
.def(
161+
"__init__",
162+
[](kvc::TransferRequest* self, kvc::TransferOp op, kvc::TransferDescs& srcDescs,
163+
kvc::TransferDescs& dstDescs, std::string const& remoteName,
164+
std::optional<kvc::SyncMessage> syncMessage) {
165+
new (self) kvc::TransferRequest(
166+
op, std::move(srcDescs), std::move(dstDescs), remoteName, std::move(syncMessage));
167+
},
111168
nb::arg("op"), nb::arg("src_descs"), nb::arg("dst_descs"), nb::arg("remote_name"),
112169
nb::arg("sync_message") = std::nullopt)
113170
.def_prop_ro("op", &kvc::TransferRequest::getOp)

cpp/tensorrt_llm/executor/cache_transmission/nixl_utils/transferAgent.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "tensorrt_llm/executor/cache_transmission/nixl_utils/transferAgent.h"
1919
#include "tensorrt_llm/common/envUtils.h"
2020
#include "tensorrt_llm/common/logger.h"
21+
#include "tensorrt_llm/common/nvtxUtils.h"
2122
#include "tensorrt_llm/executor/transferAgent.h"
2223
#include "tensorrt_llm/runtime/utils/mpiUtils.h"
2324

@@ -789,13 +790,15 @@ void NixlTransferAgent::invalidateRemoteAgent(std::string const& name)
789790
// Set TRTLLM_NIXL_ENABLE_COALESCE=1 to enable this optimization
790791
if (common::getEnvNixlEnableCoalesce())
791792
{
793+
NVTX3_SCOPED_RANGE(coalesceTransferDescs_CreateXferReq);
792794
auto [coalescedSrc, coalescedDst] = NixlHelper::coalesceTransferDescs(splitSrc, splitDst);
793795
status
794796
= mRawAgent->createXferReq(NixlHelper::convert(request.getOp()), NixlHelper::convertXferDist(coalescedSrc),
795797
NixlHelper::convertXferDist(coalescedDst), request.getRemoteName(), handle, &mExtraParams);
796798
}
797799
else
798800
{
801+
NVTX3_SCOPED_RANGE(createXferReq);
799802
status = mRawAgent->createXferReq(NixlHelper::convert(request.getOp()), NixlHelper::convertXferDist(splitSrc),
800803
NixlHelper::convertXferDist(splitDst), request.getRemoteName(), handle, &mExtraParams);
801804
}
@@ -804,8 +807,10 @@ void NixlTransferAgent::invalidateRemoteAgent(std::string const& name)
804807
" rank: %d createXferReq failed with status: %s selfname: %s remoteAgent name: %s",
805808
mpi::MpiComm::world().getRank(), nixlEnumStrings::statusStr(status).c_str(), mName.c_str(),
806809
request.getRemoteName().c_str());
807-
808-
status = mRawAgent->postXferReq(handle, &mExtraParams);
810+
{
811+
NVTX3_SCOPED_RANGE(postXferReq);
812+
status = mRawAgent->postXferReq(handle, &mExtraParams);
813+
}
809814
return std::make_unique<NixlTransferStatus>(mRawAgent.get(), handle);
810815
}
811816

@@ -932,6 +937,7 @@ MemoryDescs NixlTransferAgent::splitDescsFromRegistry(MemoryDescs const& descs)
932937
std::pair<MemoryDescs, MemoryDescs> NixlTransferAgent::splitTransferDescsFromRegistry(
933938
MemoryDescs const& srcDescs, MemoryDescs const& dstDescs) const
934939
{
940+
NVTX3_SCOPED_RANGE(splitTransferDescsFromRegistry);
935941
if (srcDescs.getType() != MemoryType::kVRAM)
936942
return {srcDescs, dstDescs};
937943

tensorrt_llm/_torch/disaggregation/base/agent.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from dataclasses import dataclass
44
from typing import List, NamedTuple, Optional, Tuple
55

6+
import numpy as np
7+
68
from tensorrt_llm import logger
79

810

@@ -27,10 +29,42 @@ class MemoryDesc(NamedTuple):
2729
device_id: int
2830

2931

30-
@dataclass
3132
class MemoryDescs:
32-
type: str
33-
descs: List[MemoryDesc]
33+
"""Describes a set of memory regions with a common type.
34+
35+
descs: List of (ptr, size, device_id) tuples.
36+
"""
37+
38+
__slots__ = ("type", "descs")
39+
40+
def __init__(self, type: str, descs: List[tuple[int, int, int]]):
41+
self.type = type
42+
self.descs = descs
43+
44+
@classmethod
45+
def from_arrays(
46+
cls, type: str, addrs: np.ndarray, sizes: np.ndarray, device_ids: np.ndarray
47+
) -> "MemoryDescs":
48+
"""Batch-construct from numpy arrays of addrs, sizes, device_ids.
49+
50+
Pure-Python fallback; the C++ binding overrides this with a version
51+
that reads numpy raw pointers directly.
52+
"""
53+
descs = np.stack([addrs, sizes, device_ids], axis=1).tolist()
54+
return cls(type, [tuple(d) for d in descs])
55+
56+
@classmethod
57+
def from_arrays_uniform_device(
58+
cls, type: str, addrs: np.ndarray, sizes: np.ndarray, device_id: int
59+
) -> "MemoryDescs":
60+
"""Batch-construct from numpy arrays with a single device_id for all entries.
61+
62+
Pure-Python fallback; the C++ binding overrides this with a version
63+
that reads numpy raw pointers directly.
64+
"""
65+
dev_ids = np.full(addrs.size, device_id, dtype=np.int32)
66+
descs = np.stack([addrs, sizes, dev_ids], axis=1).tolist()
67+
return cls(type, [tuple(d) for d in descs])
3468

3569

3670
@dataclass

tensorrt_llm/_torch/disaggregation/base/region.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from enum import IntFlag, auto
44
from typing import List, NamedTuple, Optional
55

6+
import numpy as np
7+
68

79
@dataclass(frozen=True)
810
class IndexRange:
@@ -33,7 +35,7 @@ class MemRegion(NamedTuple):
3335
class MemRegionGroup(NamedTuple):
3436
"""Describes a block of memory by starting pointer and size in bytes."""
3537

36-
ptrs: List[int]
38+
ptrs: np.ndarray # dtype=np.int64
3739
bytes_per_region: int
3840

3941

@@ -89,10 +91,10 @@ class RegionExtractorBase(ABC):
8991
"""
9092

9193
@abstractmethod
92-
def extract(self, region_ids: Optional[List[int]] = None) -> List[SpecRegion]:
94+
def extract(self, region_ids: Optional[np.ndarray] = None) -> List[SpecRegion]:
9395
"""
9496
Args:
95-
region_ids: (Optional) List of integer region identifiers to extract.
97+
region_ids: (Optional) np.ndarray of integer region identifiers to extract.
9698
Returns:
9799
List of Regions for corresponding regions.
98100
"""

tensorrt_llm/_torch/disaggregation/base/transfer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from enum import Enum
77
from typing import List, Optional, cast
88

9+
import numpy as np
10+
911
from tensorrt_llm import DisaggregatedParams
1012
from tensorrt_llm._torch.pyexecutor.llm_request import LlmRequest
1113

@@ -46,9 +48,9 @@ class KVSlice:
4648

4749
token_range: Optional[TokenRange] = None
4850
layer_range: Optional[LayerRange] = None
49-
block_ids_per_layer_groups: List[List[int]] = field(
51+
block_ids_per_layer_groups: List[np.ndarray] = field(
5052
default_factory=list
51-
) # Physical block IDs per layer group
53+
) # Physical block IDs per layer group, each np.ndarray(dtype=np.int64)
5254
is_last_slice: bool = False
5355

5456

tensorrt_llm/_torch/disaggregation/native/auxiliary.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,33 @@
33
from dataclasses import dataclass, field
44
from typing import Any
55

6+
import numpy as np
67
import torch
78

89
from tensorrt_llm._torch.pyexecutor.llm_request import LlmRequest
910

1011

1112
@dataclass
1213
class AuxBufferMeta:
13-
ptrs: list[int]
14-
size: list[int]
15-
item_sizes: list[int] = field(default_factory=list)
14+
ptrs: np.ndarray # dtype=np.int64
15+
size: np.ndarray # dtype=np.int64
16+
item_sizes: np.ndarray = field(default_factory=lambda: np.array([], dtype=np.int64))
1617
device: str = "cpu"
1718

1819
def to_dict(self) -> dict[str, Any]:
1920
return {
20-
"ptrs": self.ptrs,
21-
"size": self.size,
22-
"item_sizes": self.item_sizes,
21+
"ptrs": self.ptrs.tolist(),
22+
"size": self.size.tolist(),
23+
"item_sizes": self.item_sizes.tolist(),
2324
"device": self.device,
2425
}
2526

2627
@classmethod
2728
def from_dict(cls, data: dict[str, Any]) -> "AuxBufferMeta":
2829
return cls(
29-
ptrs=data["ptrs"],
30-
size=data["size"],
31-
item_sizes=data.get("item_sizes", []),
30+
ptrs=np.array(data["ptrs"], dtype=np.int64),
31+
size=np.array(data["size"], dtype=np.int64),
32+
item_sizes=np.array(data.get("item_sizes", []), dtype=np.int64),
3233
device=data.get("device", "cpu"),
3334
)
3435

@@ -109,21 +110,30 @@ def __init__(self, max_slot_num: int, beam_width: int, max_draft_len: int, devic
109110
)
110111

111112
self._meta = AuxBufferMeta(
112-
ptrs=[
113-
self._first_tokens_buffer.data_ptr(),
114-
self._draft_tokens_buffer.data_ptr(),
115-
self._token_counts_buffer.data_ptr(),
116-
],
117-
size=[
118-
self._first_tokens_buffer.numel() * self._first_tokens_buffer.element_size(),
119-
self._draft_tokens_buffer.numel() * self._draft_tokens_buffer.element_size(),
120-
self._token_counts_buffer.numel() * self._token_counts_buffer.element_size(),
121-
],
122-
item_sizes=[
123-
self._first_tokens_buffer[0].numel() * self._first_tokens_buffer.element_size(),
124-
self._draft_tokens_buffer[0].numel() * self._draft_tokens_buffer.element_size(),
125-
self._token_counts_buffer[0].numel() * self._token_counts_buffer.element_size(),
126-
],
113+
ptrs=np.array(
114+
[
115+
self._first_tokens_buffer.data_ptr(),
116+
self._draft_tokens_buffer.data_ptr(),
117+
self._token_counts_buffer.data_ptr(),
118+
],
119+
dtype=np.int64,
120+
),
121+
size=np.array(
122+
[
123+
self._first_tokens_buffer.numel() * self._first_tokens_buffer.element_size(),
124+
self._draft_tokens_buffer.numel() * self._draft_tokens_buffer.element_size(),
125+
self._token_counts_buffer.numel() * self._token_counts_buffer.element_size(),
126+
],
127+
dtype=np.int64,
128+
),
129+
item_sizes=np.array(
130+
[
131+
self._first_tokens_buffer[0].numel() * self._first_tokens_buffer.element_size(),
132+
self._draft_tokens_buffer[0].numel() * self._draft_tokens_buffer.element_size(),
133+
self._token_counts_buffer[0].numel() * self._token_counts_buffer.element_size(),
134+
],
135+
dtype=np.int64,
136+
),
127137
device=self._device,
128138
)
129139

0 commit comments

Comments
 (0)