@@ -147,11 +147,13 @@ inline void _strided_copy(
147147}
148148
149149// Copy data from SlimTensor to ETensor, rearranging if strides differ.
150- // When stream is non-null, GPU copies use that stream (async fast path).
151- // When stream is null, GPU copies are synchronous.
150+ // dst_device selects the destination memory space (CPU for D2H, a CUDA device
151+ // for D2D). When stream is non-null, GPU copies use that stream (async fast
152+ // path). When stream is null, GPU copies are synchronous.
152153inline executorch::runtime::Error _copy_slimtensor_to_etensor_impl (
153154 const executorch::backends::aoti::slim::SlimTensor* slim_tensor,
154155 executorch::runtime::etensor::Tensor* etensor,
156+ const executorch::backends::aoti::slim::c10::Device& dst_device,
155157 cudaStream_t stream) {
156158 ET_CHECK_OK_OR_RETURN_ERROR (_check_tensor_metadata (slim_tensor, etensor));
157159
@@ -165,7 +167,7 @@ inline executorch::runtime::Error _copy_slimtensor_to_etensor_impl(
165167
166168 if (_strides_match (slim_tensor, etensor)) {
167169 // Fast path: strides match, raw byte copy
168- if (slim_tensor->is_cpu ()) {
170+ if (slim_tensor->is_cpu () && dst_device. is_cpu () ) {
169171 std::memcpy (dst_data, src_data, nbytes);
170172 } else if (stream) {
171173 executorch::backends::aoti::slim::DeviceTraits<
@@ -174,7 +176,7 @@ inline executorch::runtime::Error _copy_slimtensor_to_etensor_impl(
174176 dst_data,
175177 src_data,
176178 nbytes,
177- executorch::backends::aoti::slim:: CPU_DEVICE ,
179+ dst_device ,
178180 slim_tensor->device (),
179181 stream);
180182 } else {
@@ -184,13 +186,14 @@ inline executorch::runtime::Error _copy_slimtensor_to_etensor_impl(
184186 dst_data,
185187 src_data,
186188 nbytes,
187- executorch::backends::aoti::slim:: CPU_DEVICE ,
189+ dst_device ,
188190 slim_tensor->device ());
189191 }
190192 } else {
191193 // Slow path: strides differ (e.g., AOTI delegate output layout differs
192- // from .pte's dim_order). Copy to a temp CPU buffer, then rearrange
193- // element-by-element to match the ETensor's expected layout.
194+ // from .pte's dim_order). Copy to a temp CPU buffer, rearrange
195+ // element-by-element to match the ETensor's expected layout, then move the
196+ // result to the destination (CPU stays in place; GPU gets an H2D copy).
194197 std::vector<char > tmp (nbytes);
195198 if (slim_tensor->is_cpu ()) {
196199 std::memcpy (tmp.data (), src_data, nbytes);
@@ -218,13 +221,38 @@ inline executorch::runtime::Error _copy_slimtensor_to_etensor_impl(
218221
219222 size_t elem_size = executorch::backends::aoti::slim::c10::elementSize (
220223 slim_tensor->dtype ());
221- _strided_copy (
222- dst_data,
223- tmp.data (),
224- elem_size,
225- sizes_vec,
226- src_strides_vec,
227- dst_strides_vec);
224+
225+ if (dst_device.is_cpu ()) {
226+ _strided_copy (
227+ dst_data,
228+ tmp.data (),
229+ elem_size,
230+ sizes_vec,
231+ src_strides_vec,
232+ dst_strides_vec);
233+ } else {
234+ // Rearrange into a CPU staging buffer, then copy to the GPU destination.
235+ std::vector<char > rearranged (nbytes);
236+ _strided_copy (
237+ rearranged.data (),
238+ tmp.data (),
239+ elem_size,
240+ sizes_vec,
241+ src_strides_vec,
242+ dst_strides_vec);
243+ if (stream) {
244+ ET_CUDA_CHECK_OR_RETURN_ERROR (cudaMemcpyAsync (
245+ dst_data,
246+ rearranged.data (),
247+ nbytes,
248+ cudaMemcpyHostToDevice,
249+ stream));
250+ ET_CUDA_CHECK_OR_RETURN_ERROR (cudaStreamSynchronize (stream));
251+ } else {
252+ ET_CUDA_CHECK_OR_RETURN_ERROR (cudaMemcpy (
253+ dst_data, rearranged.data (), nbytes, cudaMemcpyHostToDevice));
254+ }
255+ }
228256 }
229257
230258 return executorch::runtime::Error::Ok;
@@ -251,7 +279,39 @@ inline executorch::runtime::Error copy_slimtensor_to_etensor_async(
251279 const executorch::backends::aoti::slim::SlimTensor* slim_tensor,
252280 executorch::runtime::etensor::Tensor* etensor,
253281 cudaStream_t stream) {
254- return _copy_slimtensor_to_etensor_impl (slim_tensor, etensor, stream);
282+ return _copy_slimtensor_to_etensor_impl (
283+ slim_tensor,
284+ etensor,
285+ executorch::backends::aoti::slim::CPU_DEVICE ,
286+ stream);
287+ }
288+
289+ /* *
290+ * Copies data from a SlimTensor to a GPU-resident ETensor asynchronously
291+ * (device-to-device).
292+ *
293+ * Used when the destination ETensor's storage lives in a planned GPU arena.
294+ * The destination device is taken from the source SlimTensor, so this only
295+ * supports same-device D2D copies (source and destination on the same GPU).
296+ *
297+ * When strides match (common case), performs a fast async D2D copy on the
298+ * provided stream. When strides differ, falls back to a staged copy with
299+ * element-by-element rearrangement on the host.
300+ *
301+ * NOTE: In the fast path the copy is asynchronous. The caller must synchronize
302+ * the stream before consuming the ETensor data.
303+ *
304+ * @param slim_tensor Pointer to the source SlimTensor (must not be null).
305+ * @param etensor Pointer to the destination GPU ETensor (must not be null).
306+ * @param stream The CUDA stream to use for async copy.
307+ * @return Error::Ok on success, or an appropriate error code on failure.
308+ */
309+ inline executorch::runtime::Error copy_slimtensor_to_device_etensor_async (
310+ const executorch::backends::aoti::slim::SlimTensor* slim_tensor,
311+ executorch::runtime::etensor::Tensor* etensor,
312+ cudaStream_t stream) {
313+ return _copy_slimtensor_to_etensor_impl (
314+ slim_tensor, etensor, slim_tensor->device (), stream);
255315}
256316
257317/* *
@@ -267,7 +327,11 @@ inline executorch::runtime::Error copy_slimtensor_to_etensor_async(
267327inline executorch::runtime::Error copy_slimtensor_to_etensor (
268328 const executorch::backends::aoti::slim::SlimTensor* slim_tensor,
269329 executorch::runtime::etensor::Tensor* etensor) {
270- return _copy_slimtensor_to_etensor_impl (slim_tensor, etensor, nullptr );
330+ return _copy_slimtensor_to_etensor_impl (
331+ slim_tensor,
332+ etensor,
333+ executorch::backends::aoti::slim::CPU_DEVICE ,
334+ nullptr );
271335}
272336
273337/* *
0 commit comments