/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*! * \file tvm/runtime/tensor.h * \brief A device-independent managed Tensor abstraction. */ #ifndef TVM_RUNTIME_TENSOR_H_ #define TVM_RUNTIME_TENSOR_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tvm { namespace runtime { /*! * \brief Managed Tensor. * The array is backed by reference counted blocks. */ class Tensor : public tvm::ffi::Tensor { public: Tensor() = default; /*! * \brief constructor. * \param data ffi::ObjectPtr to the data container. */ explicit Tensor(ffi::ObjectPtr data) : tvm::ffi::Tensor(data) {} explicit Tensor(ffi::UnsafeInit tag) : tvm::ffi::Tensor(tag) {} Tensor(ffi::Tensor&& other) : tvm::ffi::Tensor(std::move(other)) {} // NOLINT(*) Tensor(const ffi::Tensor& other) : tvm::ffi::Tensor(other) {} // NOLINT(*) ffi::ShapeView Shape() const { return this->shape(); } DLDataType DataType() const { return this->dtype(); } // DLPack handling static Tensor FromDLPack(DLManagedTensor* tensor) { return tvm::ffi::Tensor::FromDLPack(tensor, kAllocAlignment, true); } static Tensor FromDLPackVersioned(DLManagedTensorVersioned* tensor) { return tvm::ffi::Tensor::FromDLPackVersioned(tensor, kAllocAlignment, true); } inline const DLTensor* operator->() const { return this->get(); } /*! * \brief Copy data content from another array. * \param other The source array to be copied from. * \note The copy may happen asynchronously if it involves a GPU context. * TVMSynchronize is necessary. */ inline void CopyFrom(const DLTensor* other); inline void CopyFrom(const Tensor& other); /*! * \brief Copy data content from a byte buffer. * \param data The source bytes to be copied from. * \param nbytes The size of the buffer in bytes * Must be equal to the size of the Tensor. * \note The copy always triggers a TVMSynchronize. */ TVM_RUNTIME_DLL void CopyFromBytes(const void* data, size_t nbytes); /*! * \brief Copy data content into another array. * \param other The source array to be copied from. * \note The copy may happen asynchronously if it involves a GPU context. * TVMSynchronize is necessary. */ inline void CopyTo(DLTensor* other) const; inline void CopyTo(const Tensor& other) const; /*! * \brief Copy data content into another array. * \param data The source bytes to be copied from. * \param nbytes The size of the data buffer. * Must be equal to the size of the Tensor. * \note The copy always triggers a TVMSynchronize. */ TVM_RUNTIME_DLL void CopyToBytes(void* data, size_t nbytes) const; /*! * \brief Copy the data to another device. * \param dev The target device. * \param mem_scope The memory scope of the target array. * \return The array under another device. * \note The copy always triggers a TVMSynchronize. */ TVM_RUNTIME_DLL Tensor CopyTo(const Device& dev, ffi::Optional mem_scope = std::nullopt) const; /*! * \brief Load Tensor from stream * \param stream The input data stream * \return Whether load is successful */ inline bool Load(support::Stream* stream); /*! * \brief Save Tensor to stream * \param stream The output data stream */ inline void Save(support::Stream* stream) const; /*! * \brief Create a Tensor 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 relative_byte_offset The offset of the output Tensor, * relative to the current byte offset. * * By default, the offset of the view is the same as the offset * of the current array. * * \note The new array must not allow access of addresses which * would be out of bounds in the current array. If the new * array is larger than the current array, or if the * `relative_byte_offset` would place the end of the new array * outside the bounds of the current array, this function will * raise an exception. */ TVM_RUNTIME_DLL Tensor CreateView(ffi::Shape shape, DLDataType dtype, uint64_t relative_byte_offset = 0) const; /*! * \brief Create an empty Tensor. * \param shape The shape of the new array. * \param dtype The data type of the new array. * \param dev The device of the array. * \param mem_scope The memory scope of the array. * \return The created Array */ TVM_RUNTIME_DLL static Tensor Empty(ffi::Shape shape, DLDataType dtype, Device dev, ffi::Optional mem_scope = std::nullopt); /*! * \brief Function to copy data from one array to another. * \param from The source array. * \param to The target array. * \param stream The stream used in copy. */ TVM_RUNTIME_DLL static void CopyFromTo(const DLTensor* from, DLTensor* to, TVMStreamHandle stream = nullptr); /*! * \brief Function to copy data from one array to a byte buffer. * \param from The source array. * \param to The target byte buffer. * \param nbytes The size of the data buffer. * \param stream The stream used in copy. */ TVM_RUNTIME_DLL static void CopyToBytes(const DLTensor* from, void* to, size_t nbytes, TVMStreamHandle stream = nullptr); /*! * \brief Function to copy data from one array to a byte buffer. * \param from The source array. * \param to The target byte buffer. * \param nbytes The size of the data buffer. * \param stream The stream used in copy. */ TVM_RUNTIME_DLL static void CopyFromBytes(const DLTensor* to, void* from, size_t nbytes, TVMStreamHandle stream = nullptr); /*! * \brief Check if two tensors share the same underlying storage. * * This detects runtime storage aliasing (e.g. views from CreateView, etc.) but does * not imply either tensor was created by CreateView. * * \param a The first tensor. * \param b The second tensor. * \return True if the tensors share the same storage. */ TVM_RUNTIME_DLL static bool IsStorageShared(const DLTensor* a, const DLTensor* b); /*! * \brief Tensor overload of IsStorageShared. * \param a The first tensor. * \param b The second tensor. * \return True if the tensors share the same storage. */ static bool IsStorageShared(const Tensor& a, const Tensor& b); }; /*! * \brief Save a DLTensor to stream * \param strm The output stream * \param tensor The tensor to be saved. */ inline bool SaveDLTensor(support::Stream* strm, const DLTensor* tensor); inline void Tensor::CopyFrom(const DLTensor* other) { TVM_FFI_ICHECK(data_ != nullptr); CopyFromTo(other, get_mutable()); } inline void Tensor::CopyFrom(const Tensor& other) { TVM_FFI_ICHECK(data_ != nullptr); TVM_FFI_ICHECK(other.data_ != nullptr); CopyFromTo(other.get_mutable(), get_mutable()); } inline void Tensor::CopyTo(DLTensor* other) const { TVM_FFI_ICHECK(data_ != nullptr); CopyFromTo(get_mutable(), other); } inline void Tensor::CopyTo(const Tensor& other) const { TVM_FFI_ICHECK(data_ != nullptr); TVM_FFI_ICHECK(other.data_ != nullptr); CopyFromTo(get_mutable(), other.get_mutable()); } /*! \brief Magic number for Tensor file */ constexpr uint64_t kTVMTensorMagic = 0xDD5E40F096B4A13F; inline bool SaveDLTensor(support::Stream* strm, const DLTensor* tensor) { uint64_t header = kTVMTensorMagic, 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_dev) to get a corresponding // array in the target context. Device cpu_dev; cpu_dev.device_type = kDLCPU; cpu_dev.device_id = 0; strm->Write(cpu_dev); strm->Write(tensor->ndim); strm->Write(tensor->dtype); int ndim = tensor->ndim; strm->WriteArray(tensor->shape, ndim); int type_bytes = (tensor->dtype.bits + 7) / 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 (TVM_FFI_IO_NO_ENDIAN_SWAP && tensor->device.device_type == kDLCPU && ffi::IsContiguous(*tensor) && tensor->byte_offset == 0) { // quick path strm->Write(tensor->data, data_byte_size); } else { std::vector bytes(data_byte_size); Tensor::CopyToBytes(const_cast(tensor), bytes.data(), data_byte_size); if (!TVM_FFI_IO_NO_ENDIAN_SWAP) { ffi::ByteSwap(bytes.data(), type_bytes, num_elems); } strm->Write(bytes.data(), data_byte_size); } return true; } inline void Tensor::Save(support::Stream* strm) const { SaveDLTensor(strm, operator->()); } inline bool Tensor::Load(support::Stream* strm) { uint64_t header, reserved; TVM_FFI_ICHECK(strm->Read(&header)) << "Invalid DLTensor file format"; TVM_FFI_ICHECK(strm->Read(&reserved)) << "Invalid DLTensor file format"; TVM_FFI_ICHECK(header == kTVMTensorMagic) << "Invalid DLTensor file format"; Device dev; int ndim; DLDataType dtype; TVM_FFI_ICHECK(strm->Read(&dev)) << "Invalid DLTensor file format"; TVM_FFI_ICHECK(strm->Read(&ndim)) << "Invalid DLTensor file format"; TVM_FFI_ICHECK(strm->Read(&dtype)) << "Invalid DLTensor file format"; TVM_FFI_ICHECK_EQ(dev.device_type, kDLCPU) << "Invalid DLTensor device: can only save as CPU tensor"; std::vector shape(ndim); if (ndim != 0) { TVM_FFI_ICHECK(strm->ReadArray(&shape[0], ndim)) << "Invalid DLTensor file format"; } Tensor ret = Tensor::Empty(ffi::Shape(shape), dtype, dev); 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; TVM_FFI_ICHECK(strm->Read(&data_byte_size)) << "Invalid DLTensor file format"; TVM_FFI_ICHECK(data_byte_size == num_elems * elem_bytes) << "Invalid DLTensor file format"; auto read_ret = strm->Read(ret->data, data_byte_size); // Only check non-empty data if (ndim > 0 && shape[0] != 0) { TVM_FFI_ICHECK(read_ret) << "Invalid DLTensor file format"; } if (!TVM_FFI_IO_NO_ENDIAN_SWAP) { ffi::ByteSwap(ret->data, elem_bytes, num_elems); } *this = ret; return true; } /*! * \brief Get the preferred host device from the input device. * - For CUDA and ROCm, CUDAHost and ROCMHost will be returned for pinned memory, * since pinned memory reduces copy overhead. * - For other devices, CPU is returned as a fallback. */ inline Device GetPreferredHostDevice(Device device) { if (device.device_type == DLDeviceType::kDLCUDA) { return Device{DLDeviceType::kDLCUDAHost, 0}; } else if (device.device_type == DLDeviceType::kDLROCM) { return Device{DLDeviceType::kDLROCMHost, 0}; } else { // Fallback to CPU. return Device{DLDeviceType::kDLCPU, 0}; } } } // namespace runtime } // namespace tvm namespace std { template <> struct hash { std::size_t operator()(const tvm::Device& dev) const { return ((dev.device_id << 8) | dev.device_type); } }; template <> struct equal_to { bool operator()(const tvm::Device& lhs, const tvm::Device& rhs) const { return (lhs.device_type == rhs.device_type && lhs.device_id == rhs.device_id); } }; } // namespace std #endif // TVM_RUNTIME_TENSOR_H_