VLink  2.0.0
A high-performance communication middleware
tensor.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file tensor.h
26  * @brief Zero-copy N-dimensional dense tensor container with shape, strides, and dtype metadata.
27  *
28  * @details
29  * @c Tensor is the canonical neural-network input / output message for the
30  * VLink autonomous-driving and embodied-intelligence stacks: camera feature
31  * maps, point-cloud features, language-model hidden states, action heads,
32  * diffusion latents, INT8 quantised activations, and so on. Each tensor
33  * carries the element buffer, shape (up to @c kMaxRank dimensions), strides,
34  * dtype, layout label, optional model identifier, optional INT8 quantisation
35  * scale / zero point, device hint, plus a 40-byte @c Header for sequencing.
36  *
37  * @par DType table
38  * | Enum | C++ type | Element size |
39  * | --------------- | ----------- | ------------ |
40  * | @c kBool | @c bool | 1 |
41  * | @c kInt8 | @c int8_t | 1 |
42  * | @c kUint8 | @c uint8_t | 1 |
43  * | @c kInt16 | @c int16_t | 2 |
44  * | @c kUint16 | @c uint16_t | 2 |
45  * | @c kInt32 | @c int32_t | 4 |
46  * | @c kUint32 | @c uint32_t | 4 |
47  * | @c kInt64 | @c int64_t | 8 |
48  * | @c kUint64 | @c uint64_t | 8 |
49  * | @c kFloat16 | half | 2 |
50  * | @c kBfloat16 | bf16 | 2 |
51  * | @c kFloat32 | @c float | 4 |
52  * | @c kFloat64 | @c double | 8 |
53  *
54  * @par Shape diagram
55  * @code
56  * rank: 1..kMaxRank (kMaxRank = 8)
57  * shape [d0, d1, ..., d{rank-1}] e.g. NCHW = [N, C, H, W]
58  * strides[s0, s1, ..., s{rank-1}] elements (not bytes) to step one index
59  *
60  * Contiguous row-major (computed by set_shape):
61  * s{rank-1} = 1
62  * s{i} = s{i+1} * shape[i+1]
63  *
64  * Strided view example (slice the C axis of an NCHW tensor):
65  * shape = [N, C', H, W]
66  * strides = [s0, s1, s2, s3] s1 may exceed H*W to skip slabs
67  * @endcode
68  *
69  * @par Wire format
70  * @c Tensor is POD; @c memcpy serialises the struct plus the element bytes.
71  * The @c sizeof value is locked by @c static_assert and forms a permanent
72  * contract: @c vlink::zerocopy::* containers have NO forward and NO backward
73  * binary compatibility, and every field, including reserved bytes, is
74  * wire-locked.
75  * @code
76  * static_assert(sizeof(Tensor) == 248, "Sizeof must be 248 bytes.");
77  * @endcode
78  *
79  * @par Memory layout
80  * @code
81  * Offset Size Field
82  * ------ ---- ------------------------------------
83  * 0 40 Header header
84  * 40 8 uint8_t* data_
85  * 48 8 size_t size_
86  * 56 8 uint64_t update_time_ns_
87  * 64 8 uint64_t num_elements_
88  * 72 32 char name_[32]
89  * 104 32 char model_id_[32]
90  * 136 16 char layout_[16]
91  * 152 32 uint32_t shape_[kMaxRank]
92  * 184 32 uint32_t strides_[kMaxRank]
93  * 216 4 uint32_t channel_
94  * 220 4 uint32_t freq_
95  * 224 4 uint32_t batch_size_
96  * 228 4 float quant_scale_
97  * 232 4 int32_t quant_zero_point_
98  * 236 1 DataType dtype_
99  * 237 1 uint8_t rank_
100  * 238 1 Device device_
101  * 239 1 bool is_owner_
102  * 240 1 uint8_t element_size_
103  * 241 1 uint8_t reserved_buf_
104  * 242 2 uint16_t reserved_buf2_
105  * 244 4 uint32_t reserved_buf3_
106  * ------ ---- ------------------------------------
107  * Total 248 bytes (alignas 8)
108  *
109  * Wire envelope:
110  * [ magic_begin (4) | version (4) | Tensor struct (248) | element bytes (num_elements*element_size) | magic_end (4) ]
111  * @endcode
112  *
113  * @par Reserved bytes
114  * @c reserved_buf_, @c reserved_buf2_, @c reserved_buf3_ are exposed through
115  * @c get_reserved* helpers and survive @c clear() and the copy / move helpers.
116  * These slots MUST NOT be repurposed by application code: future library
117  * revisions may bind them to real fields, silently breaking peers that abused
118  * the slot.
119  *
120  * @par Example
121  * @code
122  * vlink::zerocopy::Tensor t;
123  * t.set_name("image");
124  * t.set_layout("NCHW");
125  * t.set_dtype(vlink::zerocopy::Tensor::kFloat32);
126  * uint32_t shape[] = {1, 3, 224, 224};
127  * t.set_shape(shape, 4);
128  * t.create(t.num_elements() * sizeof(float));
129  *
130  * vlink::Bytes wire;
131  * t >> wire;
132  * @endcode
133  */
134 
135 #pragma once
136 
137 #include <cstdint>
138 #include <string_view>
139 
140 #include "../base/bytes.h"
141 #include "./header.h"
142 
143 namespace vlink {
144 
145 namespace zerocopy {
146 
147 /**
148  * @struct Tensor
149  * @brief 248-byte POD container holding a dense N-D tensor plus shape / dtype / device metadata.
150  *
151  * @details
152  * The struct size is locked at 248 bytes on 64-bit targets via
153  * @c static_assert. Up to @c kMaxRank dimensions are supported; the shape
154  * and stride arrays are inline so that strided slices round-trip without
155  * extra metadata.
156  */
157 struct VLINK_EXPORT_AND_ALIGNED(8) Tensor final {
158  /**
159  * @brief Maximum number of dimensions a single @c Tensor may carry.
160  */
161  static constexpr uint8_t kMaxRank{8};
162 
163  /**
164  * @brief Scalar element type tag (drives @c element_size()).
165  */
166  enum DataType : uint8_t {
167  kDataUnknown = 0, ///< Uninitialised / unspecified element type.
168  kBool = 1, ///< @c bool (1 byte).
169  kInt8 = 2, ///< @c int8_t (1 byte).
170  kUint8 = 3, ///< @c uint8_t (1 byte).
171  kInt16 = 4, ///< @c int16_t (2 bytes).
172  kUint16 = 5, ///< @c uint16_t (2 bytes).
173  kInt32 = 6, ///< @c int32_t (4 bytes).
174  kUint32 = 7, ///< @c uint32_t (4 bytes).
175  kInt64 = 8, ///< @c int64_t (8 bytes).
176  kUint64 = 9, ///< @c uint64_t (8 bytes).
177  kFloat16 = 10, ///< IEEE-754 half precision (2 bytes).
178  kBfloat16 = 11, ///< Brain float (2 bytes).
179  kFloat32 = 12, ///< IEEE-754 single precision (4 bytes).
180  kFloat64 = 13, ///< IEEE-754 double precision (8 bytes).
181  };
182 
183  /**
184  * @brief Device hint indicating where the data buffer physically lives.
185  */
186  enum Device : uint8_t {
187  kDeviceCpu = 0, ///< Host / CPU memory.
188  kDeviceGpu = 1, ///< Discrete or integrated GPU memory.
189  kDeviceNpu = 2, ///< Neural processing unit (e.g. automotive NPU).
190  kDeviceDsp = 3, ///< Digital signal processor.
191  };
192 
193  /**
194  * @brief Default-constructs an empty tensor and asserts the 248-byte contract.
195  */
196  Tensor() noexcept;
197 
198  /**
199  * @brief Frees the owned data buffer when @c is_owner() is @c true.
200  */
201  ~Tensor() noexcept;
202 
203  /**
204  * @brief Deep-copies @p target into a freshly allocated tensor.
205  *
206  * @param target Source tensor to clone.
207  */
208  Tensor(const Tensor& target) noexcept;
209 
210  /**
211  * @brief Steals @p target's allocation and metadata; @p target ends empty.
212  *
213  * @param target Source tensor moved from.
214  */
215  Tensor(Tensor&& target) noexcept;
216 
217  /**
218  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
219  *
220  * @param target Source tensor to clone.
221  * @return Reference to @c *this.
222  */
223  Tensor& operator=(const Tensor& target) noexcept;
224 
225  /**
226  * @brief Move-assigns @p target; self-assignment is a no-op.
227  *
228  * @param target Source tensor moved from.
229  * @return Reference to @c *this.
230  */
231  Tensor& operator=(Tensor&& target) noexcept;
232 
233  /**
234  * @brief Deserialises a @c Tensor from @p bytes with zero-copy borrowing semantics.
235  *
236  * @details
237  * Two fields are sanitised after the struct snapshot is restored:
238  * @c rank_ is clamped to @c kMaxRank, and @c element_size_ is re-derived
239  * from @c dtype_ to stay consistent regardless of producer-side mistakes.
240  *
241  * @param bytes Wire buffer previously produced by @c operator>>.
242  * @return @c true on success.
243  */
244  bool operator<<(const Bytes& bytes) noexcept;
245 
246  /**
247  * @brief Serialises the struct snapshot plus element bytes into @p bytes.
248  *
249  * @param bytes Output buffer; resized automatically when its size differs from the serialized size.
250  * @return Always @c true.
251  */
252  bool operator>>(Bytes& bytes) const noexcept;
253 
254  /**
255  * @brief Validates that @p bytes carries a well-formed @c Tensor envelope.
256  *
257  * @param bytes Wire buffer to inspect.
258  * @return @c true when both magic sentinels match and the minimum size holds.
259  */
260  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
261 
262  /**
263  * @brief Total bytes that @c operator>> would write for this tensor.
264  *
265  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(Tensor) + @c size() + @c sizeof(magic_end).
266  */
267  [[nodiscard]] size_t get_serialized_size() const noexcept;
268 
269  /**
270  * @brief Whether the data buffer pointer is non-null and the byte size is positive.
271  *
272  * @return @c true when the tensor holds usable data.
273  */
274  [[nodiscard]] bool is_valid() const noexcept;
275 
276  /**
277  * @brief Borrows @p target's data buffer without copying.
278  *
279  * @param target Source tensor whose buffer must outlive @c *this.
280  * @return @c false on self-borrow, otherwise @c true.
281  */
282  bool shallow_copy(const Tensor& target) noexcept;
283 
284  /**
285  * @brief Allocates (or reuses) an owned buffer and copies @p target's elements.
286  *
287  * @param target Source tensor to clone.
288  * @return @c false on self-copy, otherwise @c true.
289  */
290  bool deep_copy(const Tensor& target) noexcept;
291 
292  /**
293  * @brief Transfers ownership from @p target; @p target ends empty.
294  *
295  * @param target Source tensor moved from.
296  * @return @c false on self-move, otherwise @c true.
297  */
298  bool move_copy(Tensor& target) noexcept;
299 
300  /**
301  * @brief Allocates an uninitialised owned data buffer of @p size bytes.
302  *
303  * @details
304  * The caller is responsible for keeping @p size consistent with
305  * @c num_elements() * @c element_size().
306  *
307  * @param size Byte count; must be non-zero.
308  * @return @c false when @p size is zero, otherwise @c true.
309  */
310  bool create(size_t size) noexcept;
311 
312  /**
313  * @brief Releases the owned buffer (if any) and resets metadata except reserved fields.
314  */
315  void clear() noexcept;
316 
317  /**
318  * @brief Borrows an externally owned data buffer without copying.
319  *
320  * @param data Non-null source pointer that must outlive @c *this.
321  * @param size Buffer length in bytes; must be non-zero.
322  * @return @c false on invalid arguments or unchanged pointer, otherwise @c true.
323  */
324  bool shallow_copy(uint8_t* data, size_t size) noexcept;
325 
326  /**
327  * @brief Copies @p size bytes from @p data into an owned buffer.
328  *
329  * @param data Non-null source pointer.
330  * @param size Number of bytes to copy; must be non-zero.
331  * @return @c false on invalid arguments or aliasing, otherwise @c true.
332  */
333  bool deep_copy(uint8_t* data, size_t size) noexcept;
334 
335  /**
336  * @brief Compatibility alias for @c deep_copy(uint8_t*, size_t).
337  *
338  * @param data Source pointer.
339  * @param size Number of bytes.
340  * @return Result of the delegated @c deep_copy call.
341  */
342  bool fill_data(uint8_t* data, size_t size) noexcept;
343 
344  /**
345  * @brief Producer-side tensor timestamp in nanoseconds.
346  *
347  * @return Stored value.
348  */
349  [[nodiscard]] uint64_t update_time_ns() const noexcept;
350 
351  /**
352  * @brief Cached total element count (product of @c shape() dimensions).
353  *
354  * @return Stored element count.
355  */
356  [[nodiscard]] uint64_t num_elements() const noexcept;
357 
358  /**
359  * @brief Tensor name (e.g. @c "image", @c "hidden_state").
360  *
361  * @return Non-owning view into the embedded buffer.
362  */
363  [[nodiscard]] std::string_view name() const noexcept;
364 
365  /**
366  * @brief Source model identifier.
367  *
368  * @return Non-owning view into the embedded buffer.
369  */
370  [[nodiscard]] std::string_view model_id() const noexcept;
371 
372  /**
373  * @brief Layout label (e.g. @c "NCHW", @c "NHWC", @c "BLC").
374  *
375  * @return Non-owning view into the embedded buffer.
376  */
377  [[nodiscard]] std::string_view layout() const noexcept;
378 
379  /**
380  * @brief Read-only pointer to the @c kMaxRank-sized shape array.
381  *
382  * @return Pointer into the embedded array; never @c nullptr.
383  */
384  [[nodiscard]] const uint32_t* shape() const noexcept;
385 
386  /**
387  * @brief Shape value at dimension @p dim, or 0 if @p dim is out of range.
388  *
389  * @param dim Dimension index.
390  * @return Shape entry.
391  */
392  [[nodiscard]] uint32_t shape_at(uint8_t dim) const noexcept;
393 
394  /**
395  * @brief Read-only pointer to the @c kMaxRank-sized stride array (in elements, not bytes).
396  *
397  * @return Pointer into the embedded array; never @c nullptr.
398  */
399  [[nodiscard]] const uint32_t* strides() const noexcept;
400 
401  /**
402  * @brief Stride value at dimension @p dim, or 0 if @p dim is out of range.
403  *
404  * @param dim Dimension index.
405  * @return Stride entry.
406  */
407  [[nodiscard]] uint32_t stride_at(uint8_t dim) const noexcept;
408 
409  /**
410  * @brief Sensor / producer / output-port channel identifier.
411  *
412  * @return Stored channel id.
413  */
414  [[nodiscard]] uint32_t channel() const noexcept;
415 
416  /**
417  * @brief Nominal publish frequency in Hz.
418  *
419  * @return Stored value.
420  */
421  [[nodiscard]] uint32_t freq() const noexcept;
422 
423  /**
424  * @brief Cached batch size (typically @c shape_at(0)).
425  *
426  * @return Stored batch size.
427  */
428  [[nodiscard]] uint32_t batch_size() const noexcept;
429 
430  /**
431  * @brief INT8 quantisation scale (0 when unused).
432  *
433  * @return Stored scale.
434  */
435  [[nodiscard]] float quant_scale() const noexcept;
436 
437  /**
438  * @brief INT8 quantisation zero point (0 when unused).
439  *
440  * @return Stored zero point.
441  */
442  [[nodiscard]] int32_t quant_zero_point() const noexcept;
443 
444  /**
445  * @brief Scalar element type tag.
446  *
447  * @return @c DataType enum value.
448  */
449  [[nodiscard]] DataType dtype() const noexcept;
450 
451  /**
452  * @brief Actual tensor rank in the range 1..@c kMaxRank.
453  *
454  * @return Stored rank.
455  */
456  [[nodiscard]] uint8_t rank() const noexcept;
457 
458  /**
459  * @brief Device hint indicating where the data buffer lives.
460  *
461  * @return @c Device enum value.
462  */
463  [[nodiscard]] Device device() const noexcept;
464 
465  /**
466  * @brief Cached byte size of one element derived from @c dtype().
467  *
468  * @return Element size in bytes.
469  */
470  [[nodiscard]] uint8_t element_size() const noexcept;
471 
472  /**
473  * @brief Read-only pointer to the element bytes.
474  *
475  * @return Pointer to payload start.
476  */
477  [[nodiscard]] const uint8_t* data() const noexcept;
478 
479  /**
480  * @brief Element buffer size in bytes.
481  *
482  * @return Byte count.
483  */
484  [[nodiscard]] size_t size() const noexcept;
485 
486  /**
487  * @brief Whether this tensor owns its data buffer.
488  *
489  * @return @c true when the destructor would free the buffer.
490  */
491  [[nodiscard]] bool is_owner() const noexcept;
492 
493  /**
494  * @brief Stores the tensor timestamp.
495  *
496  * @param update_time_ns Timestamp in nanoseconds.
497  */
498  void set_update_time_ns(uint64_t update_time_ns) noexcept;
499 
500  /**
501  * @brief Stores the tensor name (truncated to @c sizeof(name) - 1 bytes).
502  *
503  * @param name Tensor name.
504  */
505  void set_name(std::string_view name) noexcept;
506 
507  /**
508  * @brief Stores the source model identifier (truncated to @c sizeof(model_id) - 1 bytes).
509  *
510  * @param model_id Model identifier.
511  */
512  void set_model_id(std::string_view model_id) noexcept;
513 
514  /**
515  * @brief Stores the layout label (truncated to @c sizeof(layout) - 1 bytes).
516  *
517  * @param layout Layout descriptor.
518  */
519  void set_layout(std::string_view layout) noexcept;
520 
521  /**
522  * @brief Stores the full shape array and recomputes row-major strides plus @c num_elements.
523  *
524  * @details
525  * @p rank is clamped to @c kMaxRank. Strides are derived assuming a
526  * contiguous row-major layout (last dimension changes fastest).
527  * @c batch_size is cached from @p shape[0] when @p rank > 0.
528  *
529  * @param shape Pointer to a shape array of length @p rank.
530  * @param rank Number of valid dimensions.
531  */
532  void set_shape(const uint32_t* shape, uint8_t rank) noexcept;
533 
534  /**
535  * @brief Stores a single shape entry without recomputing strides.
536  *
537  * @param dim Dimension index.
538  * @param value Shape value.
539  */
540  void set_shape_at(uint8_t dim, uint32_t value) noexcept;
541 
542  /**
543  * @brief Stores a single stride entry.
544  *
545  * @param dim Dimension index.
546  * @param value Stride value (in elements, not bytes).
547  */
548  void set_stride_at(uint8_t dim, uint32_t value) noexcept;
549 
550  /**
551  * @brief Stores the sensor / producer / output-port channel identifier.
552  *
553  * @param channel Channel id.
554  */
555  void set_channel(uint32_t channel) noexcept;
556 
557  /**
558  * @brief Stores the nominal publish frequency.
559  *
560  * @param freq Frequency in Hz.
561  */
562  void set_freq(uint32_t freq) noexcept;
563 
564  /**
565  * @brief Stores the cached batch size.
566  *
567  * @param batch_size Stored value.
568  */
569  void set_batch_size(uint32_t batch_size) noexcept;
570 
571  /**
572  * @brief Stores the INT8 quantisation scale.
573  *
574  * @param quant_scale Stored value.
575  */
576  void set_quant_scale(float quant_scale) noexcept;
577 
578  /**
579  * @brief Stores the INT8 quantisation zero point.
580  *
581  * @param quant_zero_point Stored value.
582  */
583  void set_quant_zero_point(int32_t quant_zero_point) noexcept;
584 
585  /**
586  * @brief Stores the scalar element type tag and caches the derived element size.
587  *
588  * @param dtype @c DataType enum value.
589  */
590  void set_dtype(DataType dtype) noexcept;
591 
592  /**
593  * @brief Stores the device hint.
594  *
595  * @param device @c Device enum value.
596  */
597  void set_device(Device device) noexcept;
598 
599  /**
600  * @brief Returns the byte size of one element of @p dtype.
601  *
602  * @param dtype Element type tag.
603  * @return Element size in bytes (0 for @c kDataUnknown).
604  */
605  [[nodiscard]] static uint8_t element_size_of(DataType dtype) noexcept;
606 
607  /**
608  * @brief Mutable accessor for the first wire-locked reserved field (@c uint8_t).
609  *
610  * @return Reference to @c reserved_buf_.
611  */
612  uint8_t& get_reserved() noexcept { return reserved_buf_; }
613 
614  /**
615  * @brief Mutable accessor for the second wire-locked reserved field (@c uint16_t).
616  *
617  * @return Reference to @c reserved_buf2_.
618  */
619  uint16_t& get_reserved2() noexcept { return reserved_buf2_; }
620 
621  /**
622  * @brief Mutable accessor for the third wire-locked reserved field (@c uint32_t).
623  *
624  * @return Reference to @c reserved_buf3_.
625  */
626  uint32_t& get_reserved3() noexcept { return reserved_buf3_; }
627 
628  Header header; ///< Sequencing and timestamp metadata prefix.
629 
630  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
631 
632  private:
633  uint8_t* data_{nullptr};
634  size_t size_{0};
635  uint64_t update_time_ns_{0};
636  uint64_t num_elements_{0};
637  char name_[32]{0};
638  char model_id_[32]{0};
639  char layout_[16]{0};
640  uint32_t shape_[kMaxRank]{0};
641  uint32_t strides_[kMaxRank]{0};
642  uint32_t channel_{0};
643  uint32_t freq_{0};
644  uint32_t batch_size_{0};
645  float quant_scale_{0};
646  int32_t quant_zero_point_{0};
647  DataType dtype_{kDataUnknown};
648  uint8_t rank_{0};
649  Device device_{kDeviceCpu};
650  bool is_owner_{false};
651  uint8_t element_size_{0};
652  uint8_t reserved_buf_{0};
653  uint16_t reserved_buf2_{0};
654  uint32_t reserved_buf3_{0};
655 
656  static constexpr uint32_t kMagicNumberBegin{0x98B7F19A};
657  static constexpr uint32_t kMagicNumberEnd{0x98B7F19F};
658 };
659 
660 } // namespace zerocopy
661 
662 } // namespace vlink
Fixed-size timestamp and sequencing prefix embedded by every VLink zero-copy container.
40-byte timestamp / sequencing metadata prefix shared by all zero-copy containers.
248-byte POD container holding a dense N-D tensor plus shape / dtype / device metadata.