/** * Copyright (c) 2017-2022 by Contributors * @file dgl/runtime/ndarray.h * @brief Abstract device memory management API */ #ifndef DGL_RUNTIME_NDARRAY_H_ #define DGL_RUNTIME_NDARRAY_H_ #include #include #include #include #include #include "bfloat16.h" #include "c_runtime_api.h" #include "serializer.h" #include "shared_mem.h" #ifdef DGL_USE_CUDA #include #define BF16_ENABLED (defined(CUDART_VERSION) && CUDART_VERSION >= 11000) #include #if BF16_ENABLED #include #endif // BF16_ENABLED #endif // DGL_USE_CUDA // forward declaration inline std::ostream& operator<<(std::ostream& os, DGLDataType t); namespace dgl { /** * @brief Type traits that converts a C type to a DGLDataType. * * Usage: * DGLDataTypeTraits::dtype == dtype */ template struct DGLDataTypeTraits { static constexpr DGLDataType dtype{0, 0, 0}; // dummy }; #define GEN_DGLDATATYPETRAITS_FOR(T, code, bits) \ template <> \ struct DGLDataTypeTraits { \ static constexpr DGLDataType dtype{code, bits, 1}; \ } GEN_DGLDATATYPETRAITS_FOR(int8_t, kDGLInt, 8); GEN_DGLDATATYPETRAITS_FOR(uint8_t, kDGLUInt, 8); GEN_DGLDATATYPETRAITS_FOR(int16_t, kDGLInt, 16); GEN_DGLDATATYPETRAITS_FOR(int32_t, kDGLInt, 32); GEN_DGLDATATYPETRAITS_FOR(int64_t, kDGLInt, 64); // XXX(BarclayII) most DL frameworks do not support unsigned int and long // arrays, so I'm just converting uints to signed DTypes. GEN_DGLDATATYPETRAITS_FOR(uint32_t, kDGLInt, 32); GEN_DGLDATATYPETRAITS_FOR(uint64_t, kDGLInt, 64); #ifdef DGL_USE_CUDA GEN_DGLDATATYPETRAITS_FOR(__half, kDGLFloat, 16); #if BF16_ENABLED GEN_DGLDATATYPETRAITS_FOR(__nv_bfloat16, kDGLBfloat, 16); #endif // BF16_ENABLED #endif // DGL_USE_CUDA GEN_DGLDATATYPETRAITS_FOR(float, kDGLFloat, 32); GEN_DGLDATATYPETRAITS_FOR(double, kDGLFloat, 64); #undef GEN_DGLDATATYPETRAITS_FOR namespace runtime { /** * @brief DLPack converter. */ struct DLPackConvert; /** * @brief Managed NDArray. * The array is backed by reference counted blocks. */ class NDArray { public: // internal container type struct Container; /** @brief default constructor */ NDArray() {} /** * @brief cosntruct a NDArray that refers to data * @param data The data this NDArray refers to */ explicit inline NDArray(Container* data); /** * @brief copy constructor * @param other The value to be copied */ inline NDArray(const NDArray& other); // NOLINT(*) /** * @brief move constructor * @param other The value to be moved */ NDArray(NDArray&& other) // NOLINT(*) : data_(other.data_) { other.data_ = nullptr; } /** @brief destructor */ ~NDArray() { this->reset(); } /** * @brief Swap this array with another NDArray * @param other The other NDArray */ void swap(NDArray& other) { // NOLINT(*) std::swap(data_, other.data_); } /** * @brief copy assignmemt * @param other The value to be assigned. * @return reference to self. */ NDArray& operator=(const NDArray& other) { // NOLINT(*) // copy-and-swap idiom NDArray(other).swap(*this); // NOLINT(*) return *this; } /** * @brief move assignmemt * @param other The value to be assigned. * @return reference to self. */ NDArray& operator=(NDArray&& other) { // NOLINT(*) // copy-and-swap idiom NDArray(std::move(other)).swap(*this); // NOLINT(*) return *this; } /** @return If NDArray is defined */ bool defined() const { return data_ != nullptr; } /** @return If both NDArray reference the same container */ bool same_as(const NDArray& other) const { return data_ == other.data_; } /** @brief reset the content of NDArray to be nullptr */ inline void reset(); /** * @return the reference counter * @note this number is approximate in multi-threaded setting. */ inline int use_count() const; /** @return Pointer to content of DGLArray */ inline const DGLArray* operator->() const; /** @return True if the ndarray is contiguous. */ bool IsContiguous() const; /** @return the data pointer with type. */ template inline T* Ptr() const { if (!defined()) return nullptr; else return static_cast(operator->()->data); } /** * @brief Copy data content from/into another array. * @param other The source array to be copied from. * @note The copy runs on the dgl internal stream if it involves a GPU * context. */ inline void CopyFrom(DGLArray* other); inline void CopyFrom(const NDArray& other); inline void CopyTo(DGLArray* other) const; inline void CopyTo(const NDArray& other) const; /** * @brief Copy the data to another context. * @param ctx The target context. * @return The array under another context. */ inline NDArray CopyTo(const DGLContext& ctx) const; /** * @brief Return a new array with a copy of the content. */ inline NDArray Clone() const; /** * @brief Return a copy of the current instance of NDArray in pinned * (page-locked) memory. * @note This is an out-of-place method, which utilizes PyTorch's * CachingHostAllocator for allocating pinned memory and copying data * from the current NDAarray. As a result, PyTorch is responsible for * managing the lifecycle of the returned NDArray, including deciding * when to flush the data for reuse or call cudaFreeHost. The current * context must be kDGLCPU, otherwise, an error will be thrown. */ inline NDArray PinMemory(); /** * @brief In-place method to pin the current array by calling PinContainer * on the underlying NDArray:Container. * @note This is an in-place method that flags the memory as page-locked by * utilizing cudaHostRegister at the underlying level to pin the current * instance of NDArray. The current context must be kDGLCPU, otherwise, * an error will be thrown. */ inline void PinMemory_(); /** * @brief In-place method to unpin the current array by calling UnpinContainer * on the underlying NDArray:Container. * @note This is an in-place method. Behavior depends on the current context, * IsPinned: will be unpinned; * others: directly return. */ inline void UnpinMemory_(); /** * @brief Check if the array is pinned. */ inline bool IsPinned() const; /** * @brief Record streams that are using the underlying tensor. * @param stream The stream that is using the underlying tensor. */ inline void RecordStream(DGLStreamHandle stream) const; /** * @brief Load NDArray from stream * @param stream The input data stream * @return Whether load is successful */ bool Load(dmlc::Stream* stream); /** * @brief Save NDArray to stream * @param stream The output data stream */ void Save(dmlc::Stream* stream) const; /** * @brief Create a NDArray that shares the data memory with the current one. * @param shape The shape of the new array. * @param dtype The data type of the new array. * @param offset The offset (in bytes) of the starting pointer. * @note The memory size of new array must be smaller than the current one. */ DGL_DLL NDArray CreateView(std::vector shape, DGLDataType dtype, int64_t offset = 0); /** * @brief Create an empty NDArray. * @param shape The shape of the new array. * @param dtype The data type of the new array. * @param ctx The context of the array. * @return The created Array */ DGL_DLL static NDArray Empty( std::vector shape, DGLDataType dtype, DGLContext ctx); /** * @brief Create an empty NDArray in pinned memory. * @param shape The shape of the new array. * @param dtype The data type of the new array. * @param ctx The context of the array. * @return The created array. */ DGL_DLL static NDArray PinnedEmpty( std::vector shape, DGLDataType dtype, DGLContext ctx); /** * @brief Create an empty NDArray with shared memory. * @param name The name of shared memory. * @param shape The shape of the new array. * @param dtype The data type of the new array. * @param ctx The context of the array. * @param is_create whether to create shared memory. * @return The created Array */ DGL_DLL static NDArray EmptyShared( const std::string& name, std::vector shape, DGLDataType dtype, DGLContext ctx, bool is_create); /** * @brief Get the size of the array in the number of bytes. */ size_t GetSize() const; /** * @brief Get the number of elements in this array. */ int64_t NumElements() const; /** * @brief Create a NDArray by copying from std::vector. * @tparam T Type of vector data. Determines the dtype of returned array. */ template DGL_DLL static NDArray FromVector( const std::vector& vec, DGLContext ctx = DGLContext{kDGLCPU, 0}); /** * @brief Create a NDArray from a raw pointer. */ DGL_DLL static NDArray CreateFromRaw( const std::vector& shape, DGLDataType dtype, DGLContext ctx, void* raw, bool auto_free); /** * @brief Create a std::vector from a 1D NDArray. * @tparam T Type of vector data. * @note Type casting is NOT performed. The caller has to make sure that the * vector type matches the dtype of NDArray. */ template std::vector ToVector() const; std::shared_ptr GetSharedMem() const; /** * @brief Function to copy data from one array to another. * @param from The source array. * @param to The target array. * @param (optional) stream The stream used in copy. */ DGL_DLL static void CopyFromTo(DGLArray* from, DGLArray* to); DGL_DLL static void CopyFromTo( DGLArray* from, DGLArray* to, DGLStreamHandle stream); /** * @brief Function to copy data between device and CPU while recording the * event. * @param from The source array. * @param to The target array. * @param pytorch_ctx The context pointer from PyTorch's CachingHostAllocator. * @note This function fuses data-copy and event recording to ensure * CachingHostAllocator works properly. */ DGL_DLL static void RecordedCopyFromTo( DGLArray* from, DGLArray* to, void* pytorch_ctx); /** * @brief Function to pin the DGLArray of a Container. * @param ptr The container to be pinned. * @note Data of the given array will be pinned inplace. * Behavior depends on the current context, * kDGLCPU: will be pinned; * IsPinned: directly return; * kDGLCUDA: invalid, will throw an error. */ DGL_DLL static void PinContainer(Container* ptr); /** * @brief Function to unpin the DGLArray of a Container. * @param ptr The container to be unpinned. * @note Data of the given array will be unpinned inplace. * Behavior depends on the current context, * IsPinned: will be unpinned; * others: directly return. */ DGL_DLL static void UnpinContainer(Container* ptr); /** * @brief Function check if the DGLArray of a Container is pinned. * @param ptr The container to be checked. * @return true if pinned. */ DGL_DLL static bool IsContainerPinned(Container* ptr); /** * @brief Record streams that are using this tensor. * @param ptr Pointer of the tensor to be recorded. * @param stream The stream that is using this tensor. */ DGL_DLL static void RecordStream(DGLArray* tensor, DGLStreamHandle stream); // internal namespace struct Internal { // Default deleter for the container static void DefaultDeleter(NDArray::Container* ptr); // Local create function which allocates tensor metadata // but does not allocate space for the data. static NDArray Create( std::vector shape, DGLDataType dtype, DGLContext ctx); // Implementation of API function static DGLArray* MoveAsDGLArray(NDArray arr); }; private: /** @brief Internal Data content */ Container* data_{nullptr}; // enable internal functions friend struct Internal; friend struct DLPackConvert; friend class DGLRetValue; friend class DGLArgsSetter; }; /** * @brief Save a DGLArray to stream * @param strm The outpu stream * @param tensor The tensor to be saved. */ inline bool SaveDGLArray(dmlc::Stream* strm, const DGLArray* tensor); /** * @brief Reference counted Container object used to back NDArray. * * This object is DGLArray compatible: * the pointer to the NDArrayContainer can be directly * interpreted as a DGLArray* * * @note: do not use this function directly, use NDArray. */ struct NDArray::Container { public: /** NOTE: the first part of this structure is the same as * DLManagedTensor, note that, however, the deleter * is only called when the reference counter goes to 0 */ /** * @brief Tensor structure. * @note it is important that the first field is DGLArray * So that this data structure is DGLArray compatible. * The head ptr of this struct can be viewed as DGLArray*. */ DGLArray dl_tensor; /** * @brief addtional context, reserved for recycling * @note We can attach additional content here * which the current container depend on * (e.g. reference to original memory when creating views). */ void* manager_ctx{nullptr}; /** * @brief Customized deleter * * @note The customized deleter is helpful to enable * different ways of memory allocator that are not * currently defined by the system. */ void (*deleter)(Container* self) = nullptr; /** @brief default constructor */ Container() { dl_tensor.data = nullptr; dl_tensor.ndim = 0; dl_tensor.shape = nullptr; dl_tensor.strides = nullptr; dl_tensor.byte_offset = 0; } /** @brief pointer to shared memory */ std::shared_ptr mem; /** @brief developer function, increases reference counter */ void IncRef() { ref_counter_.fetch_add(1, std::memory_order_relaxed); } /** @brief developer function, decrease reference counter */ void DecRef() { if (ref_counter_.fetch_sub(1, std::memory_order_release) == 1) { std::atomic_thread_fence(std::memory_order_acquire); if (this->deleter != nullptr) { (*this->deleter)(this); } } } private: friend struct DLPackConvert; friend class NDArray; friend class RPCWrappedFunc; /** * @brief The shape container, * can be used for shape data. */ std::vector shape_; /** * @brief The stride container, * can be used for stride data. */ std::vector stride_; /** @brief The internal array object */ std::atomic ref_counter_{0}; /** @brief Whether underlying dl_tensor is pinned by DGL. */ bool pinned_by_dgl_{false}; /** @brief Whether underlying dl_tensor is pinned by PyTorch * (CachingHostAllocator). */ bool pinned_by_pytorch_{false}; /** @brief The PyTorch storage ctx ptr if pinned_by_pytorch_ = True. */ void* pytorch_ctx_{nullptr}; /** @brief Pointer to the corresp. PyTorch deleter if pinned_by_pytorch_ = * True. */ void* pytorch_raw_deleter_{nullptr}; }; // implementations of inline functions // the usages of functions are documented in place. inline NDArray::NDArray(Container* data) : data_(data) { if (data_) data_->IncRef(); } inline NDArray::NDArray(const NDArray& other) : data_(other.data_) { if (data_) data_->IncRef(); } inline void NDArray::reset() { if (data_) { data_->DecRef(); data_ = nullptr; } } inline void NDArray::CopyFrom(DGLArray* other) { CHECK(data_ != nullptr); CopyFromTo(other, &(data_->dl_tensor)); } inline void NDArray::CopyFrom(const NDArray& other) { CHECK(other.data_ != nullptr); // Copy between two devices if (data_->dl_tensor.ctx.device_type != other.data_->dl_tensor.ctx.device_type) { CHECK(data_ != nullptr); auto to_ctx_type = data_->dl_tensor.ctx.device_type; auto cpu_data = (to_ctx_type == kDGLCPU ? data_ : other.data_); // Pinned by PyTorch if (cpu_data->pinned_by_pytorch_) { // To ensure correct behavior, the event must be recorded after // cudaMemcpyAsync as long as the memory is pinned by PyTorch. void* pytorch_ctx = cpu_data->pytorch_ctx_; RecordedCopyFromTo( &(other.data_->dl_tensor), &(data_->dl_tensor), pytorch_ctx); return; } } CopyFrom(&(other.data_->dl_tensor)); } inline void NDArray::CopyTo(DGLArray* other) const { CHECK(data_ != nullptr); CopyFromTo(&(data_->dl_tensor), other); } inline void NDArray::CopyTo(const NDArray& other) const { CHECK(other.data_ != nullptr); // copy between two devices if (data_->dl_tensor.ctx.device_type != other.data_->dl_tensor.ctx.device_type) { CHECK(data_ != nullptr); auto from_ctx_type = data_->dl_tensor.ctx.device_type; auto cpu_data = (from_ctx_type == kDGLCPU ? data_ : other.data_); // pinned by PyTorch if (cpu_data->pinned_by_pytorch_) { // To ensure correct behavior, the event must be recorded after // cudaMemcpyAsync as long as the memory is pinned by PyTorch. void* pytorch_ctx = cpu_data->pytorch_ctx_; RecordedCopyFromTo( &(data_->dl_tensor), &(other.data_->dl_tensor), pytorch_ctx); return; } } CopyTo(&(other.data_->dl_tensor)); } inline NDArray NDArray::CopyTo(const DGLContext& ctx) const { CHECK(data_ != nullptr); const DGLArray* array = operator->(); NDArray ret = Empty( std::vector(array->shape, array->shape + array->ndim), array->dtype, ctx); this->CopyTo(ret); return ret; } inline NDArray NDArray::Clone() const { CHECK(data_ != nullptr); const DGLArray* array = operator->(); return this->CopyTo(array->ctx); } inline NDArray NDArray::PinMemory() { CHECK(data_ != nullptr); const DGLArray* array = operator->(); auto ctx = array->ctx; NDArray ret = PinnedEmpty( std::vector(array->shape, array->shape + array->ndim), array->dtype, ctx); this->CopyTo(ret); return ret; } inline void NDArray::PinMemory_() { CHECK(data_ != nullptr); PinContainer(data_); } inline void NDArray::UnpinMemory_() { CHECK(data_ != nullptr); UnpinContainer(data_); } inline bool NDArray::IsPinned() const { CHECK(data_ != nullptr); return IsContainerPinned(data_); } inline void NDArray::RecordStream(DGLStreamHandle stream) const { CHECK(data_ != nullptr); RecordStream(&(data_->dl_tensor), stream); } inline int NDArray::use_count() const { if (data_ == nullptr) return 0; return data_->ref_counter_.load(std::memory_order_relaxed); } inline const DGLArray* NDArray::operator->() const { return &(data_->dl_tensor); } /** @brief Magic number for NDArray file */ constexpr uint64_t kDGLNDArrayMagic = 0xDD5E40F096B4A13F; inline bool SaveDGLArray(dmlc::Stream* strm, DGLArray* tensor) { uint64_t header = kDGLNDArrayMagic, reserved = 0; strm->Write(header); strm->Write(reserved); // Always save data as CPU context // // Parameters that get serialized should be in CPU by default. // So even the array's context is GPU, it will be stored as CPU array. // This is used to prevent case when another user loads the parameters // back on machine that do not have GPU or related context. // // We can always do array.CopyTo(target_ctx) to get a corresponding // array in the target context. DGLContext cpu_ctx; cpu_ctx.device_type = kDGLCPU; cpu_ctx.device_id = 0; strm->Write(cpu_ctx); strm->Write(tensor->ndim); strm->Write(tensor->dtype); int ndim = tensor->ndim; strm->WriteArray(tensor->shape, ndim); int type_bytes = tensor->dtype.bits / 8; int64_t num_elems = 1; for (int i = 0; i < ndim; ++i) { num_elems *= tensor->shape[i]; } int64_t data_byte_size = type_bytes * num_elems; strm->Write(data_byte_size); if (DMLC_IO_NO_ENDIAN_SWAP && tensor->ctx.device_type == kDGLCPU && tensor->strides == nullptr && tensor->byte_offset == 0) { // quick path strm->Write(tensor->data, data_byte_size); } else { std::vector bytes(data_byte_size); CHECK_EQ( DGLArrayCopyToBytes(tensor, dmlc::BeginPtr(bytes), data_byte_size), 0) << DGLGetLastError(); if (!DMLC_IO_NO_ENDIAN_SWAP) { dmlc::ByteSwap(dmlc::BeginPtr(bytes), type_bytes, num_elems); } strm->Write(dmlc::BeginPtr(bytes), data_byte_size); } return true; } /** * @brief Convert type code to its name * @param type_code The type code . * @return The name of type code. */ inline const char* TypeCode2Str(int type_code) { switch (type_code) { case kDGLInt: return "int"; case kDGLUInt: return "uint"; case kDGLFloat: return "float"; case kStr: return "str"; case kBytes: return "bytes"; case kHandle: return "handle"; case kNull: return "NULL"; case kObjectHandle: return "ObjectHandle"; case kArrayHandle: return "ArrayHandle"; case kDGLDataType: return "DGLDataType"; case kDGLContext: return "DGLContext"; case kFuncHandle: return "FunctionHandle"; case kModuleHandle: return "ModuleHandle"; case kNDArrayContainer: return "NDArrayContainer"; default: LOG(FATAL) << "unknown type_code=" << static_cast(type_code); return ""; } } /** * @brief Convert device type code to its name * @param device_type The device type code. * @return The name of the device. */ inline const char* DeviceTypeCode2Str(DGLDeviceType device_type) { switch (device_type) { case kDGLCPU: return "cpu"; case kDGLCUDA: return "cuda"; default: LOG(FATAL) << "Unsupported device type code=" << static_cast(device_type); return ""; } } /** * @brief convert a string to DGL type. * @param s The string to be converted. * @return The corresponding dgl type. */ inline DGLDataType String2DGLDataType(std::string s) { DGLDataType t; t.bits = 32; t.lanes = 1; const char* scan; if (s.substr(0, 3) == "int") { t.code = kDGLInt; scan = s.c_str() + 3; } else if (s.substr(0, 4) == "uint") { t.code = kDGLUInt; scan = s.c_str() + 4; } else if (s.substr(0, 5) == "float") { t.code = kDGLFloat; scan = s.c_str() + 5; } else if (s.substr(0, 6) == "handle") { t.code = kHandle; t.bits = 64; // handle uses 64 bit by default. scan = s.c_str() + 6; } else { scan = s.c_str(); LOG(FATAL) << "unknown type " << s; } char* xdelim; // emulate sscanf("%ux%u", bits, lanes) uint8_t bits = static_cast(strtoul(scan, &xdelim, 10)); if (bits != 0) t.bits = bits; if (*xdelim == 'x') { t.lanes = static_cast(strtoul(xdelim + 1, nullptr, 10)); } return t; } /** * @brief convert a DGL type to string. * @param t The type to be converted. * @return The corresponding dgl type in string. */ inline std::string DGLDataType2String(DGLDataType t) { #ifndef _LIBCPP_SGX_NO_IOSTREAMS std::ostringstream os; os << t; return os.str(); #else std::string repr = ""; repr += TypeCode2Str(t.code); if (t.code == kHandle) return repr; repr += std::to_string(static_cast(t.bits)); if (t.lanes != 1) { repr += "x" + std::to_string(static_cast(t.lanes)); } return repr; #endif } // macro to check type code. #define DGL_CHECK_TYPE_CODE(CODE, T) \ CHECK_EQ(CODE, T) << " expected " << TypeCode2Str(T) << " but get " \ << TypeCode2Str(CODE) } // namespace runtime } // namespace dgl namespace dmlc { DMLC_DECLARE_TRAITS(has_saveload, dgl::runtime::NDArray, true); } // namespace dmlc ///////////////// Operator overloading for NDArray ///////////////// dgl::runtime::NDArray operator+( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator-( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator*( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator/( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator%( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator+(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator-(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator*(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator/(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator%(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator+(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator-(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator*(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator/(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator%(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator-(const dgl::runtime::NDArray& array); dgl::runtime::NDArray operator>( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator<( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator>=( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator<=( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator==( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator!=( const dgl::runtime::NDArray& a1, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator>(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator<(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator>=(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator<=(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator==(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator!=(const dgl::runtime::NDArray& a1, int64_t rhs); dgl::runtime::NDArray operator>(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator<(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator>=(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator<=(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator==(int64_t lhs, const dgl::runtime::NDArray& a2); dgl::runtime::NDArray operator!=(int64_t lhs, const dgl::runtime::NDArray& a2); std::ostream& operator<<(std::ostream& os, dgl::runtime::NDArray array); ///////////////// Operator overloading for DGLDataType ///////////////// /** @brief Check whether two data types are the same.*/ inline bool operator==(const DGLDataType& ty1, const DGLDataType& ty2) { return ty1.code == ty2.code && ty1.bits == ty2.bits && ty1.lanes == ty2.lanes; } /** @brief Check whether two data types are different.*/ inline bool operator!=(const DGLDataType& ty1, const DGLDataType& ty2) { return !(ty1 == ty2); } #ifndef _LIBCPP_SGX_NO_IOSTREAMS inline std::ostream& operator<<(std::ostream& os, DGLDataType t) { os << dgl::runtime::TypeCode2Str(t.code); if (t.code == kHandle) return os; os << static_cast(t.bits); if (t.lanes != 1) { os << 'x' << static_cast(t.lanes); } return os; } #endif ///////////////// Operator overloading for DGLContext ///////////////// /** @brief Check whether two device contexts are the same.*/ inline bool operator==(const DGLContext& ctx1, const DGLContext& ctx2) { return ctx1.device_type == ctx2.device_type && ctx1.device_id == ctx2.device_id; } /** @brief Check whether two device contexts are different.*/ inline bool operator!=(const DGLContext& ctx1, const DGLContext& ctx2) { return !(ctx1 == ctx2); } #ifndef _LIBCPP_SGX_NO_IOSTREAMS inline std::ostream& operator<<(std::ostream& os, const DGLContext& ctx) { return os << dgl::runtime::DeviceTypeCode2Str(ctx.device_type) << ":" << ctx.device_id; } #endif #endif // DGL_RUNTIME_NDARRAY_H_