24#ifndef TVM_RUNTIME_TENSOR_H_
25#define TVM_RUNTIME_TENSOR_H_
27#include <tvm/ffi/cast.h>
28#include <tvm/ffi/container/shape.h>
29#include <tvm/ffi/container/tensor.h>
30#include <tvm/ffi/dtype.h>
31#include <tvm/ffi/optional.h>
32#include <tvm/ffi/string.h>
43#include <unordered_map>
58 ffi::Optional<ffi::String> mem_scope = std::nullopt);
72 ffi::Optional<ffi::String> mem_scope = std::nullopt);
83 uint64_t relative_byte_offset = 0);
104 const DLTensor* tensor = ffi::AnyView(source).cast<DLTensor*>();
107 strm->
Write(reserved);
118 cpu_dev.device_type = kDLCPU;
119 cpu_dev.device_id = 0;
120 strm->
Write(cpu_dev);
121 strm->
Write(tensor->ndim);
122 strm->
Write(tensor->dtype);
123 int ndim = tensor->ndim;
125 int type_bytes = (tensor->dtype.bits + 7) / 8;
126 int64_t num_elems = 1;
127 for (
int i = 0; i < ndim; ++i) {
128 num_elems *= tensor->shape[i];
130 int64_t data_byte_size = type_bytes * num_elems;
131 strm->
Write(data_byte_size);
133 if (TVM_FFI_IO_NO_ENDIAN_SWAP && tensor->device.device_type == kDLCPU &&
134 ffi::IsContiguous(*tensor) && tensor->byte_offset == 0) {
136 strm->
Write(tensor->data, data_byte_size);
138 std::vector<uint8_t> bytes(data_byte_size);
140 if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
141 ffi::ByteSwap(bytes.data(), type_bytes, num_elems);
143 strm->
Write(bytes.data(), data_byte_size);
149 uint64_t header, reserved;
150 TVM_FFI_ICHECK(strm->
Read(&header)) <<
"Invalid DLTensor file format";
151 TVM_FFI_ICHECK(strm->
Read(&reserved)) <<
"Invalid DLTensor file format";
152 TVM_FFI_ICHECK(header ==
kTVMTensorMagic) <<
"Invalid DLTensor file format";
156 TVM_FFI_ICHECK(strm->
Read(&dev)) <<
"Invalid DLTensor file format";
157 TVM_FFI_ICHECK(strm->
Read(&ndim)) <<
"Invalid DLTensor file format";
158 TVM_FFI_ICHECK(strm->
Read(&dtype)) <<
"Invalid DLTensor file format";
159 TVM_FFI_ICHECK_EQ(dev.device_type, kDLCPU)
160 <<
"Invalid DLTensor device: can only save as CPU tensor";
161 std::vector<int64_t> shape(ndim);
163 TVM_FFI_ICHECK(strm->
ReadArray(&shape[0], ndim)) <<
"Invalid DLTensor file format";
165 ffi::Tensor ret =
Empty(ffi::Shape(shape), dtype, dev);
166 int64_t num_elems = 1;
167 int elem_bytes = (ret.GetDLTensorPtr()->dtype.bits + 7) / 8;
168 for (
int i = 0; i < ret.GetDLTensorPtr()->ndim; ++i) {
169 num_elems *= ret.shape()[i];
171 int64_t data_byte_size;
172 TVM_FFI_ICHECK(strm->
Read(&data_byte_size)) <<
"Invalid DLTensor file format";
173 TVM_FFI_ICHECK(data_byte_size == num_elems * elem_bytes) <<
"Invalid DLTensor file format";
174 auto read_ret = strm->
Read(ret.GetDLTensorPtr()->data, data_byte_size);
176 if (ndim > 0 && shape[0] != 0) {
177 TVM_FFI_ICHECK(read_ret) <<
"Invalid DLTensor file format";
179 if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
180 ffi::ByteSwap(ret.GetDLTensorPtr()->data, elem_bytes, num_elems);
192 if (device.device_type == DLDeviceType::kDLCUDA) {
193 return Device{DLDeviceType::kDLCUDAHost, 0};
194 }
else if (device.device_type == DLDeviceType::kDLROCM) {
195 return Device{DLDeviceType::kDLROCMHost, 0};
198 return Device{DLDeviceType::kDLCPU, 0};
202class TensorMemoryPool;
220 explicit TensorMemoryPool(
size_t page_size = 4096) : page_size_(page_size), used_memory_(0) {
221 TVM_FFI_CHECK_GT(page_size_, 0, ValueError) <<
"Pool page size must be positive";
222 TVM_FFI_CHECK_LE(page_size_,
static_cast<size_t>(std::numeric_limits<int64_t>::max()),
229 virtual ffi::Tensor
Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device,
230 ffi::Optional<ffi::String> mem_scope = std::nullopt) {
231 std::lock_guard<std::recursive_mutex> lock(mu_);
232 if (mem_scope.has_value() && !(*mem_scope).empty() && *mem_scope !=
"global") {
235 size_t nbytes = ffi::GetDataSize(shape.Product(), dtype);
236 TVM_FFI_CHECK_LE(std::max<size_t>(nbytes, 1),
237 static_cast<size_t>(std::numeric_limits<int64_t>::max()) - (page_size_ - 1),
239 <<
"Rounded tensor allocation size overflows";
240 size_t size = ((std::max<size_t>(nbytes, 1) + page_size_ - 1) / page_size_) * page_size_;
242 size_t element_bytes = (
static_cast<size_t>(dtype.bits) * dtype.lanes + 7) / 8;
243 while (alignment < element_bytes) alignment *= 2;
246 auto it = memory_pool_.find(size);
247 if (it != memory_pool_.end()) {
248 auto& pool = it->second;
250 auto candidate = std::find_if(pool.begin(), pool.end(), [&](
const ffi::Tensor& tensor) {
251 return tensor.device().device_type == device.device_type &&
252 tensor.device().device_id == device.device_id &&
253 (!addressable || reinterpret_cast<uintptr_t>(tensor.data_ptr()) % alignment == 0);
255 if (candidate != pool.end()) {
256 backing = std::move(*candidate);
257 pool.erase(candidate);
260 if (!backing.defined()) {
264 used_memory_.fetch_add(size, std::memory_order_relaxed);
266 Lease lease{ffi::GetObjectPtr<TensorMemoryPool>(
this), std::move(backing)};
268 return ffi::Tensor::FromNDAlloc(lease, shape, dtype, device);
271 Recycle(std::move(lease.backing));
277 virtual ffi::Tensor
CreateView(ffi::Tensor backing, ffi::Shape shape, DLDataType dtype,
278 uint64_t byte_offset = 0,
279 ffi::Optional<ffi::String> mem_scope = std::nullopt) {
280 TVM_FFI_CHECK(!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope ==
"global" ||
281 (backing.device().device_type == kDLHexagon &&
282 (*mem_scope ==
"global.vtcm" || *mem_scope ==
"global.ddr")),
284 <<
"Unsupported tensor view memory scope";
286 if (view.device().device_type == kDLHexagon &&
287 (!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope ==
"global")) {
289 auto* tensor =
const_cast<DLTensor*
>(view.GetDLTensorPtr());
290 if (tensor->byte_offset != 0) {
291 tensor->data =
static_cast<char*
>(tensor->data) + tensor->byte_offset;
293 tensor->byte_offset = 0;
300 std::lock_guard<std::recursive_mutex> lock(mu_);
301 for (
const auto& [size, pool] : memory_pool_) {
302 used_memory_.fetch_sub(size * pool.size(), std::memory_order_relaxed);
304 memory_pool_.clear();
308 size_t UsedMemory()
const {
return used_memory_.load(std::memory_order_relaxed); }
320 virtual ffi::Tensor
AllocBacking(
size_t nbytes,
size_t alignment, DLDevice device) {
321 auto allocate = [&]() {
323 DLDataType bytes{kDLUInt, 8, 1};
325 return api->Empty({
static_cast<int64_t
>(nbytes)}, bytes, device);
328 TVM_FFI_CHECK_LE(alignment, std::numeric_limits<uint16_t>::max(), ValueError);
329 auto owner = api->Empty({
static_cast<int64_t
>((nbytes + alignment - 1) / alignment)},
330 DLDataType{kDLUInt, 8,
static_cast<uint16_t
>(alignment)}, device);
335 }
catch (
const ffi::Error&) {
344 ffi::ObjectPtr<TensorMemoryPool> pool;
347 void AllocData(DLTensor* tensor) { tensor->data = backing.data_ptr(); }
348 void FreeData(DLTensor*) { pool->Recycle(std::move(backing)); }
351 void Recycle(ffi::Tensor backing) {
352 std::lock_guard<std::recursive_mutex> lock(mu_);
353 size_t size = ffi::GetDataSize(backing);
355 memory_pool_[size].push_back(std::move(backing));
356 }
catch (
const std::bad_alloc&) {
358 used_memory_.fetch_sub(size, std::memory_order_relaxed);
363 std::atomic<size_t> used_memory_;
364 std::unordered_map<size_t, std::vector<ffi::Tensor>> memory_pool_;
365 std::recursive_mutex mu_;
374 std::size_t operator()(
const tvm::Device& dev)
const {
375 return ((dev.device_id << 8) | dev.device_type);
382 return (lhs.device_type == rhs.device_type && lhs.device_id == rhs.device_id);
static DeviceAPI * Get(Device dev, bool allow_missing=false)
Get device API based on device.
virtual bool SupportsDevicePointerArithmeticsOnHost()
Whether pointer arithmetics on a device owned pointer may be performed on the host.
Definition device_api.h:249
virtual ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device, ffi::Optional< ffi::String > mem_scope=std::nullopt)=0
Create an owning, compact tensor with uninitialized contents.
A cache of owning device tensors.
Definition tensor.h:218
virtual ffi::Tensor CreateView(ffi::Tensor backing, ffi::Shape shape, DLDataType dtype, uint64_t byte_offset=0, ffi::Optional< ffi::String > mem_scope=std::nullopt)
Create a bounded owning view of a backing tensor.
Definition tensor.h:277
static constexpr bool _type_mutable
Definition tensor.h:310
void Clear()
Release cached backing; live tensors remain valid and may return later.
Definition tensor.h:299
TVM_FFI_DECLARE_OBJECT_INFO("runtime.TensorMemoryPool", TensorMemoryPool, ffi::Object)
virtual ffi::Tensor AllocBacking(size_t nbytes, size_t alignment, DLDevice device)
Allocate fresh owning uint8[nbytes] backing at the requested alignment.
Definition tensor.h:320
size_t UsedMemory() const
Bytes held by this pool, including checked-out backing.
Definition tensor.h:308
TensorMemoryPool(size_t page_size=4096)
Definition tensor.h:220
virtual ~TensorMemoryPool()
Definition tensor.h:226
virtual ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device, ffi::Optional< ffi::String > mem_scope=std::nullopt)
Return an owning compact tensor in ordinary global memory.
Definition tensor.h:229
Abstract binary stream for serialization.
Definition io.h:57
void WriteArray(const T *data, size_t num_elems)
Write an array of typed values element by element.
Definition io.h:107
virtual size_t Read(void *ptr, size_t size)=0
Read raw bytes from the stream.
bool ReadArray(T *data, size_t num_elems)
Read an array of typed values element by element.
Definition io.h:118
virtual size_t Write(const void *ptr, size_t size)=0
Write raw bytes to the stream.
Abstract device memory management API.
void * TVMStreamHandle
The stream that is specific to device can be NULL, which indicates the default one.
Definition device_api.h:40
Binary stream I/O interface.
void SaveTensor(ffi::TensorView source, support::Stream *strm)
Save a tensor in the portable CPU tensor format.
Definition tensor.h:103
TVM_RUNTIME_DLL ffi::ObjectPtr< TensorMemoryPool > CreateTensorMemoryPool(DLDevice device)
Create the default pool for a device.
TVM_RUNTIME_DLL void CopyFromTo(ffi::TensorView source, ffi::TensorView destination, TVMStreamHandle stream=nullptr)
Copy between existing tensors with exactly matching byte sizes.
Device GetPreferredHostDevice(Device device)
Get the preferred host device from the input device.
Definition tensor.h:191
TVM_RUNTIME_DLL ffi::Tensor Empty(ffi::Shape shape, DLDataType dtype, Device dev, ffi::Optional< ffi::String > mem_scope=std::nullopt)
Allocate an uninitialized tensor through the runtime device allocator.
TVM_RUNTIME_DLL void CopyToBytes(ffi::TensorView source, void *destination_bytes, size_t nbytes, TVMStreamHandle stream=nullptr)
Copy a contiguous tensor into a byte buffer of exactly nbytes.
TVM_RUNTIME_DLL void CopyFromBytes(ffi::TensorView destination, const void *source_bytes, size_t nbytes, TVMStreamHandle stream=nullptr)
Copy exactly nbytes from a byte buffer into a contiguous tensor.
TVM_RUNTIME_DLL ffi::Tensor CopyTo(ffi::TensorView source, Device dev, ffi::Optional< ffi::String > mem_scope=std::nullopt)
Allocate a fresh tensor on dev, copy source, and synchronize the copy.
constexpr int kAllocAlignment
Number of bytes each allocation must align to.
Definition device_api.h:91
ffi::Tensor LoadTensor(support::Stream *strm)
Load a tensor from the portable format into CPU storage.
Definition tensor.h:148
TVM_RUNTIME_DLL ffi::Tensor CreateView(ffi::Tensor source, ffi::Shape shape, DLDataType dtype, uint64_t relative_byte_offset=0)
Create a compact view retaining ownership of source.
constexpr uint64_t kTVMTensorMagic
Magic number for Tensor file.
Definition tensor.h:100
An object that builds and maintains block scope and StmtSref mapping for Dependence analysis.
Definition attr.h:23
DLDevice Device
Definition device_api.h:45
#define TVM_RUNTIME_DLL
Definition base.h:100
Serializer<T> specializations for tvm::support::Stream.