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)
0 commit comments