"torchvision/git@developer.sourcefind.cn:OpenDAS/vision.git" did not exist on "793c4e824bf7762a0082e07da22944499d10ce25"
ndarray.cc 16.2 KB
Newer Older
Minjie Wang's avatar
Minjie Wang committed
1
/*!
2
 *  Copyright (c) 2017-2022 by Contributors
Minjie Wang's avatar
Minjie Wang committed
3
4
5
 * \file ndarray.cc
 * \brief NDArray container infratructure.
 */
6
#include <string.h>
Minjie Wang's avatar
Minjie Wang committed
7
8
9
10
#include <dmlc/logging.h>
#include <dgl/runtime/ndarray.h>
#include <dgl/runtime/c_runtime_api.h>
#include <dgl/runtime/device_api.h>
11
12
#include <dgl/runtime/shared_mem.h>
#include <dgl/zerocopy_serializer.h>
13
#include <dgl/runtime/tensordispatch.h>
Minjie Wang's avatar
Minjie Wang committed
14
15
#include "runtime_base.h"

16
namespace dgl {
17

18
constexpr DGLDataType DGLDataTypeTraits<int8_t>::dtype;
19
constexpr DGLDataType DGLDataTypeTraits<uint8_t>::dtype;
20
21
22
23
24
constexpr DGLDataType DGLDataTypeTraits<int16_t>::dtype;
constexpr DGLDataType DGLDataTypeTraits<int32_t>::dtype;
constexpr DGLDataType DGLDataTypeTraits<int64_t>::dtype;
constexpr DGLDataType DGLDataTypeTraits<uint32_t>::dtype;
constexpr DGLDataType DGLDataTypeTraits<uint64_t>::dtype;
25
#ifdef DGL_USE_CUDA
26
constexpr DGLDataType DGLDataTypeTraits<__half>::dtype;
27
28
29
30
#if BF16_ENABLED
constexpr DGLDataType DGLDataTypeTraits<__nv_bfloat16>::dtype;
#endif  // BF16_ENABLED
#endif  // DGL_USE_CUDA
31
32
constexpr DGLDataType DGLDataTypeTraits<float>::dtype;
constexpr DGLDataType DGLDataTypeTraits<double>::dtype;
33

Minjie Wang's avatar
Minjie Wang committed
34
35
namespace runtime {

36
inline void VerifyDataType(DGLDataType dtype) {
Minjie Wang's avatar
Minjie Wang committed
37
  CHECK_GE(dtype.lanes, 1);
38
  if (dtype.code == kDGLFloat) {
Minjie Wang's avatar
Minjie Wang committed
39
40
41
42
43
44
45
    CHECK_EQ(dtype.bits % 8, 0);
  } else {
    CHECK_EQ(dtype.bits % 8, 0);
  }
  CHECK_EQ(dtype.bits & (dtype.bits - 1), 0);
}

46
inline size_t GetDataSize(const DGLArray& arr) {
Minjie Wang's avatar
Minjie Wang committed
47
  size_t size = 1;
48
  for (dgl_index_t i = 0; i < arr.ndim; ++i) {
Minjie Wang's avatar
Minjie Wang committed
49
50
51
52
53
54
    size *= arr.shape[i];
  }
  size *= (arr.dtype.bits * arr.dtype.lanes + 7) / 8;
  return size;
}

55
inline size_t GetDataAlignment(const DGLArray& arr) {
Minjie Wang's avatar
Minjie Wang committed
56
57
58
59
60
  size_t align = (arr.dtype.bits / 8) * arr.dtype.lanes;
  if (align < kAllocAlignment) return kAllocAlignment;
  return align;
}

61
62
63
64
65
66
67
68
void NDArray::Internal::DefaultDeleter(NDArray::Container* ptr) {
  using dgl::runtime::NDArray;
  if (ptr->manager_ctx != nullptr) {
    static_cast<NDArray::Container*>(ptr->manager_ctx)->DecRef();
  } else if (ptr->mem) {
    ptr->mem = nullptr;
  } else if (ptr->dl_tensor.data != nullptr) {
    // if the array is still pinned before freeing, unpin it.
69
70
    if (ptr->pinned_by_dgl_)
      UnpinContainer(ptr);
71
72
    dgl::runtime::DeviceAPI::Get(ptr->dl_tensor.ctx)->FreeDataSpace(
        ptr->dl_tensor.ctx, ptr->dl_tensor.data);
Minjie Wang's avatar
Minjie Wang committed
73
  }
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
  delete ptr;
}

NDArray NDArray::Internal::Create(std::vector<int64_t> shape,
                                  DGLDataType dtype, DGLContext ctx) {
  VerifyDataType(dtype);
  // critical zone
  NDArray::Container* data = new NDArray::Container();
  data->deleter = DefaultDeleter;
  NDArray ret(data);
  ret.data_ = data;
  // RAII now in effect
  // setup shape
  data->shape_ = std::move(shape);
  data->dl_tensor.shape = dmlc::BeginPtr(data->shape_);
  data->dl_tensor.ndim = static_cast<int>(data->shape_.size());
  // setup stride (this should be optional, but some framework
  //   does not support NULL stride and thus will crash the program).
  data->stride_.resize(data->dl_tensor.ndim, 1);
  for (int i = data->dl_tensor.ndim - 2; i >= 0; --i) {
    data->stride_[i] = data->shape_[i+1] * data->stride_[i+1];
Minjie Wang's avatar
Minjie Wang committed
95
  }
96
97
98
99
100
101
102
103
104
105
106
107
108
109
  data->dl_tensor.strides = dmlc::BeginPtr(data->stride_);
  // setup dtype
  data->dl_tensor.dtype = dtype;
  // setup ctx
  data->dl_tensor.ctx = ctx;
  return ret;
}

DGLArray* NDArray::Internal::MoveAsDGLArray(NDArray arr) {
  DGLArray* tensor = reinterpret_cast<DGLArray*>(arr.data_);
  CHECK(tensor == const_cast<DGLArray*>(arr.operator->()));
  arr.data_ = nullptr;
  return tensor;
}
Minjie Wang's avatar
Minjie Wang committed
110

111
112
113
114
size_t NDArray::GetSize() const {
  return GetDataSize(data_->dl_tensor);
}

115
int64_t NDArray::NumElements() const {
116
117
  if (data_->dl_tensor.ndim == 0)
    return 0;
118
119
120
121
122
123
124
  int64_t size = 1;
  for (int i = 0; i < data_->dl_tensor.ndim; ++i) {
    size *= data_->dl_tensor.shape[i];
  }
  return size;
}

125
126
127
128
bool NDArray::IsContiguous() const {
  CHECK(data_ != nullptr);
  if (data_->dl_tensor.strides == nullptr)
    return true;
129
130
131
132
133
134
135
136
137
138

  // See https://github.com/dmlc/dgl/issues/2118 and PyTorch's compute_contiguous() implementation
  int64_t z = 1;
  for (int64_t i = data_->dl_tensor.ndim - 1; i >= 0; --i) {
    if (data_->dl_tensor.shape[i] != 1) {
      if (data_->dl_tensor.strides[i] == z)
        z *= data_->dl_tensor.shape[i];
      else
        return false;
    }
139
  }
140
  return true;
141
142
}

Minjie Wang's avatar
Minjie Wang committed
143
NDArray NDArray::CreateView(std::vector<int64_t> shape,
144
                            DGLDataType dtype,
145
                            int64_t offset) {
Minjie Wang's avatar
Minjie Wang committed
146
  CHECK(data_ != nullptr);
147
  CHECK(IsContiguous()) << "Can only create view for compact tensor";
Minjie Wang's avatar
Minjie Wang committed
148
149
150
151
152
153
154
155
156
157
  NDArray ret = Internal::Create(shape, dtype, data_->dl_tensor.ctx);
  ret.data_->dl_tensor.byte_offset =
      this->data_->dl_tensor.byte_offset;
  size_t curr_size = GetDataSize(this->data_->dl_tensor);
  size_t view_size = GetDataSize(ret.data_->dl_tensor);
  CHECK_LE(view_size, curr_size)
      << "Tries to create a view that has bigger memory than current one";
  // increase ref count
  this->data_->IncRef();
  ret.data_->manager_ctx = this->data_;
158
159
  ret.data_->dl_tensor.data =
    static_cast<char*>(this->data_->dl_tensor.data) + offset;
Minjie Wang's avatar
Minjie Wang committed
160
161
162
  return ret;
}

163
164
NDArray NDArray::EmptyShared(const std::string &name,
                       std::vector<int64_t> shape,
165
166
                       DGLDataType dtype,
                       DGLContext ctx, bool is_create) {
167
168
169
170
171
  NDArray ret = Internal::Create(shape, dtype, ctx);
  // setup memory content
  size_t size = GetDataSize(ret.data_->dl_tensor);
  auto mem = std::make_shared<SharedMemory>(name);
  if (is_create) {
172
    ret.data_->dl_tensor.data = mem->CreateNew(size);
173
  } else {
174
    ret.data_->dl_tensor.data = mem->Open(size);
175
176
177
178
179
180
  }

  ret.data_->mem = mem;
  return ret;
}

Minjie Wang's avatar
Minjie Wang committed
181
NDArray NDArray::Empty(std::vector<int64_t> shape,
182
183
                       DGLDataType dtype,
                       DGLContext ctx) {
184
  NDArray ret = Internal::Create(shape, dtype, ctx);
Minjie Wang's avatar
Minjie Wang committed
185
186
187
  // setup memory content
  size_t size = GetDataSize(ret.data_->dl_tensor);
  size_t alignment = GetDataAlignment(ret.data_->dl_tensor);
188
189
190
191
  if (size > 0)
    ret.data_->dl_tensor.data =
        DeviceAPI::Get(ret->ctx)->AllocDataSpace(
            ret->ctx, size, alignment, ret->dtype);
Minjie Wang's avatar
Minjie Wang committed
192
193
194
  return ret;
}

195
196
void NDArray::CopyFromTo(DGLArray* from,
                         DGLArray* to) {
Minjie Wang's avatar
Minjie Wang committed
197
198
199
  size_t from_size = GetDataSize(*from);
  size_t to_size = GetDataSize(*to);
  CHECK_EQ(from_size, to_size)
200
    << "DGLArrayCopyFromTo: The size must exactly match";
Minjie Wang's avatar
Minjie Wang committed
201
202

  CHECK(from->ctx.device_type == to->ctx.device_type
203
204
        || from->ctx.device_type == kDGLCPU
        || to->ctx.device_type == kDGLCPU)
Minjie Wang's avatar
Minjie Wang committed
205
206
207
208
    << "Can not copy across different ctx types directly";

  // Use the context that is *not* a cpu context to get the correct device
  // api manager.
209
  DGLContext ctx = from->ctx.device_type != kDGLCPU ? from->ctx : to->ctx;
Minjie Wang's avatar
Minjie Wang committed
210

211
  // default: local current cuda stream
Minjie Wang's avatar
Minjie Wang committed
212
  DeviceAPI::Get(ctx)->CopyDataFromTo(
213
214
215
      from->data, static_cast<size_t>(from->byte_offset),
      to->data, static_cast<size_t>(to->byte_offset),
      from_size, from->ctx, to->ctx, from->dtype);
Minjie Wang's avatar
Minjie Wang committed
216
217
}

218
219
220
void NDArray::PinContainer(NDArray::Container* ptr) {
  if (IsContainerPinned(ptr)) return;
  auto* tensor = &(ptr->dl_tensor);
221
  CHECK_EQ(tensor->ctx.device_type, kDGLCPU)
222
    << "Only NDArray on CPU can be pinned";
223
  DeviceAPI::Get(kDGLCUDA)->PinData(tensor->data, GetDataSize(*tensor));
224
  ptr->pinned_by_dgl_ = true;
225
226
}

227
228
229
230
231
232
233
234
235
void NDArray::UnpinContainer(NDArray::Container* ptr) {
  auto container_is_pinned = IsContainerPinned(ptr);
  // The tensor may be pinned outside of DGL via a different CUDA API,
  // so we cannot unpin it with cudaHostUnregister.
  CHECK(ptr->pinned_by_dgl_ || !container_is_pinned)
    << "Cannot unpin a tensor that is pinned outside of DGL.";
  // 1. not pinned, do nothing
  if (!container_is_pinned) return;
  // 2. pinned by DGL, unpin it
236
  DeviceAPI::Get(kDGLCUDA)->UnpinData(ptr->dl_tensor.data);
237
  ptr->pinned_by_dgl_ = false;
238
239
}

240
241
242
void NDArray::RecordStream(DGLArray* tensor, DGLStreamHandle stream) {
  TensorDispatcher* td = TensorDispatcher::Global();
  CHECK(td->IsAvailable()) << "RecordStream only works when TensorAdaptor is available.";
243
  CHECK_EQ(tensor->ctx.device_type, kDGLCUDA)
244
245
246
247
248
    << "RecordStream only works with GPU tensors.";

  td->RecordStream(tensor->data, stream, tensor->ctx.device_id);
}

249
template<typename T>
250
251
NDArray NDArray::FromVector(const std::vector<T>& vec, DGLContext ctx) {
  const DGLDataType dtype = DGLDataTypeTraits<T>::dtype;
252
  int64_t size = static_cast<int64_t>(vec.size());
253
  NDArray ret = NDArray::Empty({size}, dtype, ctx);
254
255
256
257
258
259
  DeviceAPI::Get(ctx)->CopyDataFromTo(
      vec.data(),
      0,
      static_cast<T*>(ret->data),
      0,
      size * sizeof(T),
260
      DGLContext{kDGLCPU, 0},
261
      ctx,
262
      dtype);
263
264
265
  return ret;
}

266
267
268
269
270
271
272
273
274
NDArray NDArray::CreateFromRaw(const std::vector<int64_t>& shape,
    DGLDataType dtype, DGLContext ctx, void* raw, bool auto_free) {
  NDArray ret = Internal::Create(shape, dtype, ctx);
  ret.data_->dl_tensor.data = raw;
  if (!auto_free)
    ret.data_->deleter = nullptr;
  return ret;
}

275
// export specializations
276
277
278
279
280
281
template NDArray NDArray::FromVector<int32_t>(const std::vector<int32_t>&, DGLContext);
template NDArray NDArray::FromVector<int64_t>(const std::vector<int64_t>&, DGLContext);
template NDArray NDArray::FromVector<uint32_t>(const std::vector<uint32_t>&, DGLContext);
template NDArray NDArray::FromVector<uint64_t>(const std::vector<uint64_t>&, DGLContext);
template NDArray NDArray::FromVector<float>(const std::vector<float>&, DGLContext);
template NDArray NDArray::FromVector<double>(const std::vector<double>&, DGLContext);
282

283
284
template<typename T>
std::vector<T> NDArray::ToVector() const {
285
  const DGLDataType dtype = DGLDataTypeTraits<T>::dtype;
286
287
288
289
290
  CHECK(data_->dl_tensor.ndim == 1) << "ToVector() only supported for 1D arrays";
  CHECK(data_->dl_tensor.dtype == dtype) << "dtype mismatch";

  int64_t size = data_->dl_tensor.shape[0];
  std::vector<T> vec(size);
291
  const DGLContext &ctx = data_->dl_tensor.ctx;
292
293
294
295
296
297
298
  DeviceAPI::Get(ctx)->CopyDataFromTo(
      static_cast<T*>(data_->dl_tensor.data),
      0,
      vec.data(),
      0,
      size * sizeof(T),
      ctx,
299
      DGLContext{kDGLCPU, 0},
300
      dtype);
301
302
303
304
305
306
307
308
309
  return vec;
}

template std::vector<int32_t> NDArray::ToVector<int32_t>() const;
template std::vector<int64_t> NDArray::ToVector<int64_t>() const;
template std::vector<uint32_t> NDArray::ToVector<uint32_t>() const;
template std::vector<uint64_t> NDArray::ToVector<uint64_t>() const;
template std::vector<float> NDArray::ToVector<float>() const;
template std::vector<double> NDArray::ToVector<double>() const;
310

311
312
313
314
std::shared_ptr<SharedMemory> NDArray::GetSharedMem() const {
  return this->data_->mem;
}

315
316
317
318
bool NDArray::IsContainerPinned(NDArray::Container* ptr) {
  if (ptr->pinned_by_dgl_)
    return true;
  auto* tensor = &(ptr->dl_tensor);
319
  // Can only be pinned if on CPU...
320
  if (tensor->ctx.device_type != kDGLCPU)
321
322
    return false;
  // ... and CUDA device API is enabled, and the tensor is indeed in pinned memory.
323
  auto device = DeviceAPI::Get(kDGLCUDA, true);
324
325
  return device && device->IsPinned(tensor->data);
}
326
327

void NDArray::Save(dmlc::Stream* strm) const {
328
  auto zc_strm = dynamic_cast<StreamWithBuffer*>(strm);
329
330
331
332
  if (zc_strm) {
    zc_strm->PushNDArray(*this);
    return;
  }
333
  SaveDGLArray(strm, const_cast<DGLArray*>(operator->()));
334
335
336
}

bool NDArray::Load(dmlc::Stream* strm) {
337
  auto zc_strm = dynamic_cast<StreamWithBuffer*>(strm);
338
339
340
341
342
343
  if (zc_strm) {
    *this = zc_strm->PopNDArray();
    return true;
  }
  uint64_t header, reserved;
  CHECK(strm->Read(&header))
344
      << "Invalid DGLArray file format";
345
  CHECK(strm->Read(&reserved))
346
      << "Invalid DGLArray file format";
347
  CHECK(header == kDGLNDArrayMagic)
348
349
      << "Invalid DGLArray file format";
  DGLContext ctx;
350
  int ndim;
351
  DGLDataType dtype;
352
  CHECK(strm->Read(&ctx))
353
      << "Invalid DGLArray file format";
354
  CHECK(strm->Read(&ndim))
355
      << "Invalid DGLArray file format";
356
  CHECK(strm->Read(&dtype))
357
358
359
      << "Invalid DGLArray file format";
  CHECK_EQ(ctx.device_type, kDGLCPU)
      << "Invalid DGLArray context: can only save as CPU tensor";
360
361
362
  std::vector<int64_t> shape(ndim);
  if (ndim != 0) {
    CHECK(strm->ReadArray(&shape[0], ndim))
363
        << "Invalid DGLArray file format";
364
365
366
367
368
369
370
371
372
  }
  NDArray ret = NDArray::Empty(shape, dtype, ctx);
  int64_t num_elems = 1;
  int elem_bytes = (ret->dtype.bits + 7) / 8;
  for (int i = 0; i < ret->ndim; ++i) {
    num_elems *= ret->shape[i];
  }
  int64_t data_byte_size;
  CHECK(strm->Read(&data_byte_size))
373
      << "Invalid DGLArray file format";
374
  CHECK(data_byte_size == num_elems * elem_bytes)
375
      << "Invalid DGLArray file format";
376
377
378
379
  if (data_byte_size != 0)  {
    // strm->Read will return the total number of elements successfully read.
    // Therefore if data_byte_size is zero, the CHECK below would fail.
    CHECK(strm->Read(ret->data, data_byte_size))
380
        << "Invalid DGLArray file format";
381
382
383
384
385
386
387
388
389
  }
  if (!DMLC_IO_NO_ENDIAN_SWAP) {
    dmlc::ByteSwap(ret->data, elem_bytes, num_elems);
  }
  *this = ret;
  return true;
}


Minjie Wang's avatar
Minjie Wang committed
390
}  // namespace runtime
391
}  // namespace dgl
Minjie Wang's avatar
Minjie Wang committed
392

393
using namespace dgl::runtime;
Minjie Wang's avatar
Minjie Wang committed
394

395
int DGLArrayAlloc(const dgl_index_t* shape,
Minjie Wang's avatar
Minjie Wang committed
396
397
398
399
400
401
                  int ndim,
                  int dtype_code,
                  int dtype_bits,
                  int dtype_lanes,
                  int device_type,
                  int device_id,
402
                  DGLArrayHandle* out) {
Minjie Wang's avatar
Minjie Wang committed
403
  API_BEGIN();
404
  DGLDataType dtype;
Minjie Wang's avatar
Minjie Wang committed
405
406
407
  dtype.code = static_cast<uint8_t>(dtype_code);
  dtype.bits = static_cast<uint8_t>(dtype_bits);
  dtype.lanes = static_cast<uint16_t>(dtype_lanes);
408
409
  DGLContext ctx;
  ctx.device_type = static_cast<DGLDeviceType>(device_type);
Minjie Wang's avatar
Minjie Wang committed
410
  ctx.device_id = device_id;
411
  *out = NDArray::Internal::MoveAsDGLArray(
Minjie Wang's avatar
Minjie Wang committed
412
413
414
415
      NDArray::Empty(std::vector<int64_t>(shape, shape + ndim), dtype, ctx));
  API_END();
}

416
417
418
419
420
421
422
423
424
int DGLArrayAllocSharedMem(const char *mem_name,
                           const dgl_index_t *shape,
                           int ndim,
                           int dtype_code,
                           int dtype_bits,
                           int dtype_lanes,
                           bool is_create,
                           DGLArrayHandle* out) {
  API_BEGIN();
425
  DGLDataType dtype;
426
427
428
429
430
  dtype.code = static_cast<uint8_t>(dtype_code);
  dtype.bits = static_cast<uint8_t>(dtype_bits);
  dtype.lanes = static_cast<uint16_t>(dtype_lanes);
  std::vector<int64_t> shape_vec(shape, shape + ndim);
  NDArray arr = NDArray::EmptyShared(mem_name, shape_vec, dtype,
431
432
                                     DGLContext{kDGLCPU, 0}, is_create);
  *out = NDArray::Internal::MoveAsDGLArray(arr);
433
434
435
  API_END();
}

436
int DGLArrayFree(DGLArrayHandle handle) {
Minjie Wang's avatar
Minjie Wang committed
437
438
439
440
441
  API_BEGIN();
  reinterpret_cast<NDArray::Container*>(handle)->DecRef();
  API_END();
}

442
int DGLArrayCopyFromTo(DGLArrayHandle from,
443
                       DGLArrayHandle to) {
Minjie Wang's avatar
Minjie Wang committed
444
  API_BEGIN();
445
  NDArray::CopyFromTo(from, to);
Minjie Wang's avatar
Minjie Wang committed
446
447
448
  API_END();
}

449
int DGLArrayCopyFromBytes(DGLArrayHandle handle,
Minjie Wang's avatar
Minjie Wang committed
450
451
452
                          void* data,
                          size_t nbytes) {
  API_BEGIN();
453
  DGLContext cpu_ctx;
454
  cpu_ctx.device_type = kDGLCPU;
Minjie Wang's avatar
Minjie Wang committed
455
456
457
  cpu_ctx.device_id = 0;
  size_t arr_size = GetDataSize(*handle);
  CHECK_EQ(arr_size, nbytes)
458
      << "DGLArrayCopyFromBytes: size mismatch";
Minjie Wang's avatar
Minjie Wang committed
459
460
461
  DeviceAPI::Get(handle->ctx)->CopyDataFromTo(
      data, 0,
      handle->data, static_cast<size_t>(handle->byte_offset),
462
      nbytes, cpu_ctx, handle->ctx, handle->dtype);
Minjie Wang's avatar
Minjie Wang committed
463
464
465
  API_END();
}

466
int DGLArrayCopyToBytes(DGLArrayHandle handle,
Minjie Wang's avatar
Minjie Wang committed
467
468
469
                        void* data,
                        size_t nbytes) {
  API_BEGIN();
470
  DGLContext cpu_ctx;
471
  cpu_ctx.device_type = kDGLCPU;
Minjie Wang's avatar
Minjie Wang committed
472
473
474
  cpu_ctx.device_id = 0;
  size_t arr_size = GetDataSize(*handle);
  CHECK_EQ(arr_size, nbytes)
475
      << "DGLArrayCopyToBytes: size mismatch";
Minjie Wang's avatar
Minjie Wang committed
476
477
478
  DeviceAPI::Get(handle->ctx)->CopyDataFromTo(
      handle->data, static_cast<size_t>(handle->byte_offset),
      data, 0,
479
      nbytes, handle->ctx, cpu_ctx, handle->dtype);
Minjie Wang's avatar
Minjie Wang committed
480
481
  API_END();
}
482
483

int DGLArrayPinData(DGLArrayHandle handle,
484
                    DGLContext ctx) {
485
  API_BEGIN();
486
487
  auto* nd_container = reinterpret_cast<NDArray::Container*>(handle);
  NDArray::PinContainer(nd_container);
488
489
490
491
  API_END();
}

int DGLArrayUnpinData(DGLArrayHandle handle,
492
                      DGLContext ctx) {
493
  API_BEGIN();
494
495
  auto* nd_container = reinterpret_cast<NDArray::Container*>(handle);
  NDArray::UnpinContainer(nd_container);
496
497
  API_END();
}
498
499
500
501
502
503

int DGLArrayRecordStream(DGLArrayHandle handle, DGLStreamHandle stream) {
  API_BEGIN();
  NDArray::RecordStream(handle, stream);
  API_END();
}