npu_data.py Source File

npu_data.py Source File#

SDK qb Runtime Library: npu_data.py Source File
SDK qb Runtime Library v1.5
MCS001-KR
npu_data.py
Go to the documentation of this file.
1
4
5from typing import Tuple
6
7import numpy as np
8
9import qbruntime.qbruntime as _cQbRuntime
10from .accelerator import Accelerator
11
12_Shape = Tuple[int, ...]
13
14__all__ = ["NPUData"]
15
16# Maps the C++ DataType enum to the corresponding numpy dtype.
17_DTYPE_MAP = {
18 _cQbRuntime.DataType.Float32: np.float32,
19 _cQbRuntime.DataType.Float16: np.float16,
20 _cQbRuntime.DataType.Int8: np.int8,
21 _cQbRuntime.DataType.Uint8: np.uint8,
22}
23
24
27
28
29class NPUData:
30 """@brief A model input or output tensor that can reside on the host (CPU) or NPU.
31
32 An NPUData backs a single model input/output tensor: it owns its host buffer,
33 holds NPU memory that belongs to the `Accelerator` while the data is on the NPU,
34 and lets the data be moved between CPU and NPU. Instances are created through
35 `Model.acquire_input_npu_data` / `Model.acquire_output_npu_data`, not constructed
36 directly.
37
38 The data is accessible as a numpy array (via indexing, e.g. `nd[...]`) only while
39 it resides on the CPU. Bring it back with `cpu` before reading results that were
40 produced on the NPU.
41
42 An NPU-resident NPUData may be passed to any model that expects a tensor of the
43 same shape and dtype on the same accelerator, so several models can share one
44 NPU-resident tensor without copying it back to the host in between.
45
46 An NPUData is not owned by the `Model` it came from and may outlive it. Its NPU
47 memory belongs to the `Accelerator` it resides on: if that accelerator is
48 destroyed while the data is still on the NPU, the memory is reclaimed and the
49 NPUData becomes invalid (`cpu`, `launch`, `copy_from` and indexing raise, and
50 `dev_no` returns -1). A CPU-resident NPUData is unaffected.
51
52 @note This is an advanced API rather than a typical usage.
53 @warning This API is in beta: it may still contain bugs, and its behavior may
54 change in a future release.
55 @warning NPUData objects are not fully thread-safe; the client must serialize
56 access to an NPUData from different threads. In particular, `launch` and
57 `cpu` change where the data resides and must not run concurrently with
58 each other.
59 """
60
61 def __init__(self, _npu_data: _cQbRuntime.NPUData):
62 """@brief Internal constructor.
63
64 @param _npu_data The underlying C++ NPUData object. Use
65 `Model.acquire_input_npu_data` / `Model.acquire_output_npu_data`
66 to create instances.
67 """
68 self._npu_data = _npu_data
69 # Zero-copy numpy view over the host buffer, built once here (like PinnedMemory)
70 # so indexing does not rebuild it. Unlike PinnedMemory, an NPUData is only
71 # host-accessible while CPU-resident, so this is None while the data lives on
72 # the NPU (acquired with upload=True or after launch()) and is (re)built when
73 # the data is brought back via cpu(). Shares memory with the host buffer and is
74 # only valid while this NPUData is alive and CPU-resident.
75 self._array = self._make_array()
76
77 def _make_array(self):
78 """@brief Builds the numpy view, or returns None if not CPU-resident.
79
80 The residency is checked first (dev_no() == -1 means the data is on the host):
81 requesting the buffer protocol while the data is on the NPU would propagate a
82 C++ exception out of pybind's `extern "C"` buffer slot and call std::terminate,
83 so it must not be attempted.
84 """
85 if self._npu_data.dev_no() != -1:
86 return None
87 return np.asarray(self._npu_data)
88
89 def launch(self, acc: Accelerator) -> None:
90 """@brief Uploads the data to the NPU memory of the given accelerator.
91
92 @warning Not thread-safe: see the class-level note.
93
94 @param acc The accelerator on which to place the data.
95 """
96 self._npu_data.launch(acc._accelerator)
97 # No longer host-accessible.
98 self._array = None
99
100 def cpu(self) -> None:
101 """@brief Brings the data back to host (CPU) memory.
102
103 @warning Not thread-safe: see the class-level note.
104 """
105 self._npu_data.cpu()
106 # Host buffer is available again; rebuild the view.
107 self._array = self._make_array()
108
109 def copy_from(self, src: "NPUData") -> None:
110 """@brief Copies the data from another NPUData.
111
112 Both tensors must have the same shape and dtype; the source may reside on
113 either side (a CPU-to-NPU or NPU-to-CPU copy is staged through the host).
114
115 @param src The source NPUData to copy from.
116 """
117 self._npu_data.copy_from(src._npu_data)
118
119 def __getitem__(self, key):
120 """@brief Reads element(s) from the host buffer via numpy indexing."""
121 if self._array is None:
122 raise ValueError("NPUData is not on the CPU; call cpu() first.")
123 return self._array[key]
124
125 def __setitem__(self, key, value):
126 """@brief Writes value(s) into the host buffer via numpy indexing."""
127 if self._array is None:
128 raise ValueError("NPUData is not on the CPU; call cpu() first.")
129 self._array[key] = value
130
131 def __len__(self) -> int:
132 """@brief Returns the length of the buffer's first dimension."""
133 return self.shape[0]
134
135 @property
136 def shape(self) -> _Shape:
137 """@brief Returns the shape of the data."""
138 return tuple(self._npu_data.shape())
139
140 @property
141 def dtype(self) -> np.dtype:
142 """@brief Returns the numpy dtype of the data's elements."""
143 data_type = self._npu_data.data_type()
144 if data_type not in _DTYPE_MAP:
145 # NPUData rejects 16-bit float types at acquisition, so this only happens
146 # for a corrupted object.
147 raise ValueError(f"NPUData has no numpy equivalent for {data_type}.")
148 return np.dtype(_DTYPE_MAP[data_type])
149
150 @property
151 def dev_no(self) -> int:
152 """@brief Device number of the accelerator the data resides on.
153
154 @return The device number when the data is on the NPU, or -1 when it is on the
155 host (CPU) or has been invalidated (see the class-level note on
156 accelerator destruction).
157 """
158 return self._npu_data.dev_no()
159
160 @property
161 def hardware_name(self) -> str:
162 """@brief Hardware name (device type) of the accelerator the data resides on.
163
164 Device numbers are assigned per hardware type (e.g. an "aries-rb" and a
165 "regulus-ra" can both be device #0), so ``dev_no`` alone does not identify a
166 physical device; pair it with this name to distinguish devices.
167
168 @return The hardware name (e.g. "aries-rb") when the data is on the NPU, or an
169 empty string when it is on the host (CPU) or has been invalidated (see
170 the class-level note on accelerator destruction).
171 """
172 return self._npu_data.hardware_name()
173
174
175
A model input or output tensor that can reside on the host (CPU) or NPU.
Definition npu_data.py:29
np.dtype dtype(self)
Returns the numpy dtype of the data's elements.
Definition npu_data.py:141
_make_array(self)
Builds the numpy view, or returns None if not CPU-resident.
Definition npu_data.py:77
None launch(self, Accelerator acc)
Uploads the data to the NPU memory of the given accelerator.
Definition npu_data.py:89
__getitem__(self, key)
Reads element(s) from the host buffer via numpy indexing.
Definition npu_data.py:119
None copy_from(self, "NPUData" src)
Copies the data from another NPUData.
Definition npu_data.py:109
str hardware_name(self)
Hardware name (device type) of the accelerator the data resides on.
Definition npu_data.py:161
__init__(self, _cQbRuntime.NPUData _npu_data)
Internal constructor.
Definition npu_data.py:61
_Shape shape(self)
Returns the shape of the data.
Definition npu_data.py:136
__setitem__(self, key, value)
Writes value(s) into the host buffer via numpy indexing.
Definition npu_data.py:125
int dev_no(self)
Device number of the accelerator the data resides on.
Definition npu_data.py:151
int __len__(self)
Returns the length of the buffer's first dimension.
Definition npu_data.py:131
None cpu(self)
Brings the data back to host (CPU) memory.
Definition npu_data.py:100