tvm
Loading...
Searching...
No Matches
tensor.h
Go to the documentation of this file.
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
24#ifndef TVM_RUNTIME_TENSOR_H_
25#define TVM_RUNTIME_TENSOR_H_
26
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>
33#include <tvm/runtime/base.h>
35#include <tvm/support/io.h>
37
38#include <algorithm>
39#include <atomic>
40#include <functional>
41#include <limits>
42#include <mutex>
43#include <unordered_map>
44#include <utility>
45#include <vector>
46
47namespace tvm {
48namespace runtime {
49
57TVM_RUNTIME_DLL ffi::Tensor Empty(ffi::Shape shape, DLDataType dtype, Device dev,
58 ffi::Optional<ffi::String> mem_scope = std::nullopt);
59
64TVM_RUNTIME_DLL void CopyFromTo(ffi::TensorView source, ffi::TensorView destination,
65 TVMStreamHandle stream = nullptr);
66
71TVM_RUNTIME_DLL ffi::Tensor CopyTo(ffi::TensorView source, Device dev,
72 ffi::Optional<ffi::String> mem_scope = std::nullopt);
73
82TVM_RUNTIME_DLL ffi::Tensor CreateView(ffi::Tensor source, ffi::Shape shape, DLDataType dtype,
83 uint64_t relative_byte_offset = 0);
84
89TVM_RUNTIME_DLL void CopyFromBytes(ffi::TensorView destination, const void* source_bytes,
90 size_t nbytes, TVMStreamHandle stream = nullptr);
91
96TVM_RUNTIME_DLL void CopyToBytes(ffi::TensorView source, void* destination_bytes, size_t nbytes,
97 TVMStreamHandle stream = nullptr);
98
100constexpr uint64_t kTVMTensorMagic = 0xDD5E40F096B4A13F;
101
103inline void SaveTensor(ffi::TensorView source, support::Stream* strm) {
104 const DLTensor* tensor = ffi::AnyView(source).cast<DLTensor*>();
105 uint64_t header = kTVMTensorMagic, reserved = 0;
106 strm->Write(header);
107 strm->Write(reserved);
108 // Always save data as CPU context
109 //
110 // Parameters that get serialized should be in CPU by default.
111 // So even the array's context is GPU, it will be stored as CPU array.
112 // This is used to prevent case when another user loads the parameters
113 // back on machine that do not have GPU or related context.
114 //
115 // We can always do array.CopyTo(target_dev) to get a corresponding
116 // array in the target context.
117 Device cpu_dev;
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;
124 strm->WriteArray(tensor->shape, 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];
129 }
130 int64_t data_byte_size = type_bytes * num_elems;
131 strm->Write(data_byte_size);
132
133 if (TVM_FFI_IO_NO_ENDIAN_SWAP && tensor->device.device_type == kDLCPU &&
134 ffi::IsContiguous(*tensor) && tensor->byte_offset == 0) {
135 // quick path
136 strm->Write(tensor->data, data_byte_size);
137 } else {
138 std::vector<uint8_t> bytes(data_byte_size);
139 CopyToBytes(source, bytes.data(), data_byte_size);
140 if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
141 ffi::ByteSwap(bytes.data(), type_bytes, num_elems);
142 }
143 strm->Write(bytes.data(), data_byte_size);
144 }
145}
146
148inline ffi::Tensor LoadTensor(support::Stream* strm) {
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";
153 Device dev;
154 int ndim;
155 DLDataType dtype;
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);
162 if (ndim != 0) {
163 TVM_FFI_ICHECK(strm->ReadArray(&shape[0], ndim)) << "Invalid DLTensor file format";
164 }
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];
170 }
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);
175 // Only check non-empty data
176 if (ndim > 0 && shape[0] != 0) {
177 TVM_FFI_ICHECK(read_ret) << "Invalid DLTensor file format";
178 }
179 if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
180 ffi::ByteSwap(ret.GetDLTensorPtr()->data, elem_bytes, num_elems);
181 }
182 return ret;
183}
184
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};
196 } else {
197 // Fallback to CPU.
198 return Device{DLDeviceType::kDLCPU, 0};
199 }
200}
201
202class TensorMemoryPool;
203
205TVM_RUNTIME_DLL ffi::ObjectPtr<TensorMemoryPool> CreateTensorMemoryPool(DLDevice device);
206
218class TensorMemoryPool : public ffi::Object {
219 public:
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()),
223 ValueError);
224 }
225
226 virtual ~TensorMemoryPool() { Clear(); }
227
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") {
233 return DeviceAPI::Get(device)->Empty(shape, dtype, device, mem_scope);
234 }
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),
238 ValueError)
239 << "Rounded tensor allocation size overflows";
240 size_t size = ((std::max<size_t>(nbytes, 1) + page_size_ - 1) / page_size_) * page_size_;
241 size_t alignment = kAllocAlignment;
242 size_t element_bytes = (static_cast<size_t>(dtype.bits) * dtype.lanes + 7) / 8;
243 while (alignment < element_bytes) alignment *= 2;
244
245 ffi::Tensor backing;
246 auto it = memory_pool_.find(size);
247 if (it != memory_pool_.end()) {
248 auto& pool = it->second;
249 bool addressable = DeviceAPI::Get(device)->SupportsDevicePointerArithmeticsOnHost();
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);
254 });
255 if (candidate != pool.end()) {
256 backing = std::move(*candidate);
257 pool.erase(candidate);
258 }
259 }
260 if (!backing.defined()) {
261 // Collective backends cannot retry by unilaterally releasing cached buffers.
262 // Any allocation-pressure retry belongs to the non-collective adapter.
263 backing = AllocBacking(size, alignment, device);
264 used_memory_.fetch_add(size, std::memory_order_relaxed);
265 }
266 Lease lease{ffi::GetObjectPtr<TensorMemoryPool>(this), std::move(backing)};
267 try {
268 return ffi::Tensor::FromNDAlloc(lease, shape, dtype, device);
269 } catch (...) {
270 // Tensor metadata allocation may fail before the owning tensor exists.
271 Recycle(std::move(lease.backing));
272 throw;
273 }
274 }
275
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")),
283 ValueError)
284 << "Unsupported tensor view memory scope";
285 auto view = runtime::CreateView(std::move(backing), std::move(shape), dtype, byte_offset);
286 if (view.device().device_type == kDLHexagon &&
287 (!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope == "global")) {
288 // Hexagon kernels receive the adjusted pointer rather than a byte offset.
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;
292 }
293 tensor->byte_offset = 0;
294 }
295 return view;
296 }
297
299 void Clear() {
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);
303 }
304 memory_pool_.clear();
305 }
306
308 size_t UsedMemory() const { return used_memory_.load(std::memory_order_relaxed); }
309
310 static constexpr bool _type_mutable = true;
311 TVM_FFI_DECLARE_OBJECT_INFO("runtime.TensorMemoryPool", TensorMemoryPool, ffi::Object);
312
313 protected:
320 virtual ffi::Tensor AllocBacking(size_t nbytes, size_t alignment, DLDevice device) {
321 auto allocate = [&]() {
322 auto* api = DeviceAPI::Get(device);
323 DLDataType bytes{kDLUInt, 8, 1};
324 if (alignment <= kAllocAlignment) {
325 return api->Empty({static_cast<int64_t>(nbytes)}, bytes, device);
326 }
327 // Preserve the backend's dtype alignment contract for wide vector elements.
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);
331 return runtime::CreateView(std::move(owner), {static_cast<int64_t>(nbytes)}, bytes);
332 };
333 try {
334 return allocate();
335 } catch (const ffi::Error&) {
336 // This default allocator is non-collective; release cached memory and retry.
337 Clear();
338 return allocate();
339 }
340 }
341
342 private:
343 struct Lease {
344 ffi::ObjectPtr<TensorMemoryPool> pool;
345 ffi::Tensor backing;
346
347 void AllocData(DLTensor* tensor) { tensor->data = backing.data_ptr(); }
348 void FreeData(DLTensor*) { pool->Recycle(std::move(backing)); }
349 };
350
351 void Recycle(ffi::Tensor backing) {
352 std::lock_guard<std::recursive_mutex> lock(mu_);
353 size_t size = ffi::GetDataSize(backing);
354 try {
355 memory_pool_[size].push_back(std::move(backing));
356 } catch (const std::bad_alloc&) {
357 // A cache insertion failure must not escape tensor destruction.
358 used_memory_.fetch_sub(size, std::memory_order_relaxed);
359 }
360 }
361
362 size_t page_size_;
363 std::atomic<size_t> used_memory_;
364 std::unordered_map<size_t, std::vector<ffi::Tensor>> memory_pool_;
365 std::recursive_mutex mu_;
366};
367
368} // namespace runtime
369} // namespace tvm
370
371namespace std {
372template <>
373struct hash<tvm::Device> {
374 std::size_t operator()(const tvm::Device& dev) const {
375 return ((dev.device_id << 8) | dev.device_type);
376 }
377};
378
379template <>
380struct equal_to<tvm::Device> {
381 bool operator()(const tvm::Device& lhs, const tvm::Device& rhs) const {
382 return (lhs.device_type == rhs.device_type && lhs.device_id == rhs.device_id);
383 }
384};
385} // namespace std
386
387#endif // TVM_RUNTIME_TENSOR_H_
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.