| 1280 | |
| 1281 | template <typename Backend> |
| 1282 | std::shared_ptr<TensorList<Backend>> TensorListFromListOfDLPackObjects( |
| 1283 | py::list &list_of_objects, |
| 1284 | const std::optional<std::string> &layout, |
| 1285 | py::object stream, |
| 1286 | bool contiguous) { |
| 1287 | constexpr uint32_t rangeColor = TensorListDLPackRangeColor<Backend>(); |
| 1288 | DomainTimeRange range(TensorListDLPackRangeName<Backend>(), rangeColor); |
| 1289 | |
| 1290 | if (list_of_objects.empty()) { |
| 1291 | auto ptr = std::make_shared<TensorList<Backend>>(); |
| 1292 | if (layout.has_value()) { |
| 1293 | ptr->set_sample_dim(layout->length()); |
| 1294 | ptr->SetLayout(*layout); |
| 1295 | } |
| 1296 | return ptr; |
| 1297 | } |
| 1298 | |
| 1299 | AccessOrder copy_order = AccessOrder::host(); |
| 1300 | if constexpr (std::is_same_v<Backend, GPUBackend>) { |
| 1301 | if (!stream.is_none()) |
| 1302 | copy_order = AccessOrderFromPythonStreamObj(stream); |
| 1303 | } |
| 1304 | |
| 1305 | // Preflight pass: validate device compatibility before consuming any capsule. |
| 1306 | // __dlpack__() is single-use, so mismatches must be caught before the handshake starts. |
| 1307 | int expected_device_id = -1; |
| 1308 | for (size_t i = 0; i < list_of_objects.size(); ++i) { |
| 1309 | py::object obj = list_of_objects[i]; |
| 1310 | CheckDLPackDeviceBeforeCapsule<Backend>(obj, i, expected_device_id, copy_order); |
| 1311 | } |
| 1312 | |
| 1313 | if constexpr (std::is_same_v<Backend, GPUBackend>) { |
| 1314 | // If no __dlpack_device__ was found and no explicit stream was provided, fall back to the |
| 1315 | // current CUDA device so every __dlpack__() call receives a concrete consumer stream. |
| 1316 | if (copy_order == AccessOrder::host()) { |
| 1317 | int current_dev = -1; |
| 1318 | CUDA_CALL(cudaGetDevice(¤t_dev)); |
| 1319 | copy_order = AccessOrder(UserStream::Get()->GetStream(static_cast<size_t>(current_dev))); |
| 1320 | } |
| 1321 | } |
| 1322 | |
| 1323 | // Derive the DLPack consumer-stream handle from copy_order so the producer always |
| 1324 | // synchronizes with the exact same stream that DALI will use for the copy. |
| 1325 | py::object stream_handle = py::none(); |
| 1326 | if constexpr (std::is_same_v<Backend, GPUBackend>) { |
| 1327 | if (copy_order.is_device()) { |
| 1328 | stream_handle = py::int_(reinterpret_cast<int64_t>(copy_order.stream())); |
| 1329 | } |
| 1330 | } |
| 1331 | |
| 1332 | std::optional<TensorList<Backend>> non_contiguous_tmp; |
| 1333 | std::shared_ptr<TensorList<Backend>> non_contiguous_out; |
| 1334 | |
| 1335 | if (contiguous) |
| 1336 | non_contiguous_tmp = TensorList<Backend>(list_of_objects.size()); |
| 1337 | else |
| 1338 | non_contiguous_out = std::make_shared<TensorList<Backend>>(list_of_objects.size()); |
| 1339 |
nothing calls this directly
no test coverage detected