blob: 86306cadf4689923d5a1d6c4515f7d55592557a7 [file] [log] [blame]
Tim Hall3b1578e2023-01-13 17:57:25 +00001# SPDX-FileCopyrightText: Copyright 2020-2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
Tim Hall79d07d22020-04-27 18:20:16 +01002#
3# SPDX-License-Identifier: Apache-2.0
4#
5# Licensed under the Apache License, Version 2.0 (the License); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an AS IS BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Rickard Bolinbc6ee582022-11-04 08:24:29 +000016#
Tim Hall79d07d22020-04-27 18:20:16 +010017# Description:
18# Internal representation of a Neural Network Tensor.
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +010019import copy
Tim Hall79d07d22020-04-27 18:20:16 +010020import enum
Tim Hall79d07d22020-04-27 18:20:16 +010021import uuid
Jacob Bohlin1a666972020-09-11 10:04:15 +020022from collections import defaultdict
Diqing Zhongf842b692020-12-11 13:07:37 +010023from enum import auto
Louis Verhaard9db529a2020-09-23 10:27:11 +020024from functools import lru_cache
Louis Verhaard6c74c3b2020-12-17 13:54:09 +010025from functools import total_ordering
Louis Verhaard93719a92020-12-08 10:02:31 +010026from typing import Dict
27from typing import List
28from typing import Optional
29from typing import Tuple
30from typing import Union
31from uuid import UUID
Diego Russoea6111a2020-04-14 18:41:58 +010032
33import numpy as np
34
35from . import numeric_util
Tim Hall93582962020-09-09 21:58:15 +010036from .data_type import BaseType
Michael McGeagh5778ffd2020-08-06 17:31:02 +010037from .data_type import DataType
Michael McGeagh528a56d2020-12-16 11:33:21 +000038from .errors import UnsupportedFeatureError
39from .errors import VelaError
Patrik Gustavsson2349d422020-12-01 16:02:29 +010040from .numeric_util import full_shape
Louis Verhaardaee5d752020-09-30 09:01:52 +020041from .operation import Op
Michael McGeagh5778ffd2020-08-06 17:31:02 +010042from .operation import Operation
patrik.gustavssoneeb85152020-12-21 17:10:40 +000043from .shape4d import Shape4D
Louis Verhaard93719a92020-12-08 10:02:31 +010044
45Shape = List
Tim Hall79d07d22020-04-27 18:20:16 +010046
47
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020048class MemType(enum.IntFlag):
49 Unknown = 0
50 Permanent_NPU = 1
51 Permanent_CPU = 2
52 Scratch = 3
53 Scratch_fast = 4
54 Size = Scratch_fast + 1
55
Louis Verhaard93719a92020-12-08 10:02:31 +010056 def display_name(self) -> str:
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020057 return ("Unknown", "Permanent_NPU", "Permanent_CPU", "Scratch", "Scratch_fast", "Size")[self.value]
58
Louis Verhaard93719a92020-12-08 10:02:31 +010059 def identifier_name(self) -> str:
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020060 return ("unknown", "permanent_npu", "permanent_cpu", "scratch", "scratch_fast", "size")[self.value]
61
Louis Verhaard93719a92020-12-08 10:02:31 +010062 @staticmethod
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020063 def all():
64 return (MemType.Permanent_NPU, MemType.Permanent_CPU, MemType.Scratch, MemType.Scratch_fast)
65
66 def __str__(self):
67 return self.name
68
69
Diqing Zhongf842b692020-12-11 13:07:37 +010070class BandwidthDirection(enum.IntEnum):
71 Read = 0
72 Write = auto()
73 Size = auto()
74
75 def display_name(self):
76 return self.name
77
78 def identifier_name(self):
79 return self.name.lower()
80
81 @staticmethod
82 def all():
83 return (BandwidthDirection.Read, BandwidthDirection.Write)
84
85
Tim Hall79d07d22020-04-27 18:20:16 +010086class MemArea(enum.IntFlag):
87 Unknown = 0
88 Sram = 1
89 Dram = 2
90 OnChipFlash = 3
91 OffChipFlash = 4
Louis Verhaard0b8268a2020-08-05 16:11:29 +020092 Shram = 5 # for LUT
93 Size = Shram + 1
Tim Hall79d07d22020-04-27 18:20:16 +010094
Louis Verhaard93719a92020-12-08 10:02:31 +010095 def display_name(self) -> str:
Louis Verhaard0b8268a2020-08-05 16:11:29 +020096 return ("Unknown", "SRAM", "DRAM", "On-chip Flash", "Off-chip Flash", "SHRAM", "Size")[self.value]
Tim Hall79d07d22020-04-27 18:20:16 +010097
Louis Verhaard93719a92020-12-08 10:02:31 +010098 def identifier_name(self) -> str:
Louis Verhaard0b8268a2020-08-05 16:11:29 +020099 return ("unknown", "sram", "dram", "on_chip_flash", "off_chip_flash", "shram", "size")[self.value]
Tim Hall79d07d22020-04-27 18:20:16 +0100100
Louis Verhaard93719a92020-12-08 10:02:31 +0100101 @staticmethod
Tim Hall79d07d22020-04-27 18:20:16 +0100102 def all():
Louis Verhaard0b8268a2020-08-05 16:11:29 +0200103 return (MemArea.Sram, MemArea.Dram, MemArea.OnChipFlash, MemArea.OffChipFlash, MemArea.Shram)
Tim Hall79d07d22020-04-27 18:20:16 +0100104
105 def __str__(self):
106 return self.name
107
108
109class TensorPurpose(enum.IntFlag):
110 Unknown = 0
111 Weights = 1
112 FeatureMap = 2
113 Scratch = 3
Fredrik Svedberge22ba8c2021-01-27 16:53:41 +0100114 ScratchFast = 4
115 LUT = 5
116 FSBias = 6
Johan Alfven9070f0f2023-02-07 13:01:03 +0100117 Virtual = 7
118 Size = 8
Tim Hall79d07d22020-04-27 18:20:16 +0100119
Louis Verhaard93719a92020-12-08 10:02:31 +0100120 def display_name(self) -> str:
Fredrik Svedberge22ba8c2021-01-27 16:53:41 +0100121 return ("Unknown", "Weights", "FeatureMap", "Scratch", "ScratchFast", "LUT", "FastStorageBias", "Size")[
122 self.value
123 ]
Tim Hall79d07d22020-04-27 18:20:16 +0100124
Louis Verhaard93719a92020-12-08 10:02:31 +0100125 def identifier_name(self) -> str:
Fredrik Svedberge22ba8c2021-01-27 16:53:41 +0100126 return ("unknown", "weights", "feature_map", "scratch", "scratch_fast", "lut", "fast_storage_bias", "size")[
127 self.value
128 ]
Tim Hall79d07d22020-04-27 18:20:16 +0100129
Louis Verhaard93719a92020-12-08 10:02:31 +0100130 @staticmethod
Tim Hall79d07d22020-04-27 18:20:16 +0100131 def all():
Andreas Nevalainen897cc142020-10-28 15:42:08 +0100132 return (TensorPurpose.Weights, TensorPurpose.FeatureMap, TensorPurpose.FSBias)
Tim Hall79d07d22020-04-27 18:20:16 +0100133
134
135class TensorSubPurpose(enum.Enum):
136 Standard = 0
137 DoubleBuffer = 1
138 RollingBufferX = 2
139 RollingBufferY = 3
140 RollingBufferXY = 4
141
Louis Verhaard93719a92020-12-08 10:02:31 +0100142 def display_name(self) -> str:
Tim Hall79d07d22020-04-27 18:20:16 +0100143 return ("Standard", "Double Buffer", "Rolling Buffer X", "Rolling Buffer Y", "Rolling Buffer XY")[self.value]
144
Louis Verhaard93719a92020-12-08 10:02:31 +0100145 def identifier_name(self) -> str:
Tim Hall79d07d22020-04-27 18:20:16 +0100146 return ("standard", "double_buffer", "rolling_buffer_x", "rolling_buffer_y", "rolling_buffer_xy")[self.value]
147
Louis Verhaard93719a92020-12-08 10:02:31 +0100148 @staticmethod
Tim Hall79d07d22020-04-27 18:20:16 +0100149 def all():
150 return (
151 TensorSubPurpose.Standard,
152 TensorSubPurpose.DoubleBuffer,
153 TensorSubPurpose.RollingBufferX,
154 TensorSubPurpose.RollingBufferY,
155 TensorSubPurpose.RollingBufferXY,
156 )
157
158
159class TensorFormat(enum.Flag):
160 Unknown = 0
161 WeightsCompressed = 1
162 NHWC = 2
163 NHCWB16 = 3
164
165 def __str__(self):
166 return self.name
167
168
169class TensorBlockTraversal(enum.Enum):
170 Default = 0
171 DepthWise = 1
172 DepthFirst = 2
173 PartKernelFirst = 3
174
175
Louis Verhaard93719a92020-12-08 10:02:31 +0100176def shape_num_elements(shp: Shape) -> Optional[int]:
Tim Hall79d07d22020-04-27 18:20:16 +0100177 elems = 1
178 if shp is None:
179 return None
180 for d in shp:
181 if d is None:
182 return None
183 elems *= d
184 return elems
185
186
Louis Verhaard93719a92020-12-08 10:02:31 +0100187def shape_fully_defined(shp: Shape) -> bool:
Tim Hall79d07d22020-04-27 18:20:16 +0100188 if shp is None:
189 return False
190 for d in shp:
191 if d is None:
192 return False
193 return True
194
195
Louis Verhaard93719a92020-12-08 10:02:31 +0100196def shape_round_to_quantum(shp: Shape, quantum: Tuple) -> Shape:
Tim Hall79d07d22020-04-27 18:20:16 +0100197 new_shp = list(shp)
198
199 # Traverse backwards using length of shape since there may be more rounding quantums than shape elements
200 for i in range(-1, -len(shp) - 1, -1):
201 if new_shp[i] is not None:
202 new_shp[i] = numeric_util.round_up(new_shp[i], quantum[i])
203 return new_shp
204
205
Louis Verhaard9db529a2020-09-23 10:27:11 +0200206@lru_cache(maxsize=None)
Louis Verhaard93719a92020-12-08 10:02:31 +0100207def create_equivalence_id(key) -> UUID:
Louis Verhaard9db529a2020-09-23 10:27:11 +0200208 # Generates equivalence_id based on the given key.
209 return uuid.uuid4()
210
211
Tim Hall79d07d22020-04-27 18:20:16 +0100212class QuantizationParameters:
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100213 __slots__ = (
214 "min",
215 "max",
216 "num_bits",
217 "narrow_range",
Rickard Bolinfea15162022-07-04 16:19:16 +0000218 "next_after",
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100219 "scale_f32",
220 "zero_point",
221 "quant_min",
222 "quant_max",
223 "quant_dim",
224 )
Tim Hall79d07d22020-04-27 18:20:16 +0100225
Louis Verhaard93719a92020-12-08 10:02:31 +0100226 def __init__(
227 self,
228 min: Union[float, np.ndarray, None] = None,
229 max: Union[float, np.ndarray, None] = None,
230 num_bits=None,
231 narrow_range=None,
232 ):
Tim Hall79d07d22020-04-27 18:20:16 +0100233 self.min = min
234 self.max = max
235
236 self.num_bits = num_bits
237 self.narrow_range = narrow_range
238
Rickard Bolinfea15162022-07-04 16:19:16 +0000239 # Use the 'next after' float value of scale_f32 when converting to scale and shift. It can be combined with
240 # natural rounding to perform rounding away from zero. This only affects the ofm scale and bias tensor, it has
241 # no affect on global scaling i.e. the ofm_scale register
242 self.next_after = False
Louis Verhaard93719a92020-12-08 10:02:31 +0100243 self.scale_f32: Union[float, np.ndarray, None] = None
244 self.zero_point: Union[int, np.ndarray, None] = None
245 self.quant_min: Optional[float] = None
246 self.quant_max: Optional[float] = None
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100247 self.quant_dim: Optional[int] = None
Tim Hall79d07d22020-04-27 18:20:16 +0100248
249 def __str__(self):
Rickard Bolinfea15162022-07-04 16:19:16 +0000250 return (
251 f"<nng.QuantizationParameters min={self.min}, max={self.max}, num_bits={self.num_bits}, "
252 f"scale={self.scale_f32}, zero_point={self.zero_point}, next={self.next_after}>"
Tim Hall79d07d22020-04-27 18:20:16 +0100253 )
254
255 __repr__ = __str__
256
Louis Verhaard93719a92020-12-08 10:02:31 +0100257 def clone(self) -> "QuantizationParameters":
Tim Hall79d07d22020-04-27 18:20:16 +0100258 res = QuantizationParameters()
259 res.min = self.min
260 res.max = self.max
261
262 res.num_bits = self.num_bits
263 res.narrow_range = self.narrow_range
264
Rickard Bolinfea15162022-07-04 16:19:16 +0000265 res.next_after = self.next_after
Tim Hall79d07d22020-04-27 18:20:16 +0100266 res.scale_f32 = self.scale_f32
267 res.zero_point = self.zero_point
268 res.quant_min = self.quant_min
269 res.quant_max = self.quant_max
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100270 res.quant_dim = self.quant_dim
Tim Hall79d07d22020-04-27 18:20:16 +0100271 return res
272
James Peet7519d502021-07-19 16:47:58 +0100273 def dequantize(self, values) -> np.ndarray:
274 return np.subtract(values, self.zero_point) * self.scale_f32
Tim Hall79d07d22020-04-27 18:20:16 +0100275
Louis Verhaard93719a92020-12-08 10:02:31 +0100276 def is_scaling_equal(self, other: Optional["QuantizationParameters"]) -> bool:
Tim Halla3fe6652022-03-03 17:43:16 +0000277 """
278 Returns True if the scale and zero point of self and other are equal. If other is None then the scaling is
279 not considered equal because the tensor is assumed to not be quantised and False will be returned
280 """
Tim Hall93582962020-09-09 21:58:15 +0100281
Tim Hall89567612020-10-27 11:57:57 +0000282 if not isinstance(other, QuantizationParameters):
Tim Halle3786ac2020-07-28 17:40:50 +0100283 return False
284
285 return self.scale_f32 == other.scale_f32 and self.zero_point == other.zero_point
286
Louis Verhaard93719a92020-12-08 10:02:31 +0100287 def is_valid(self) -> bool:
Tim Halla3fe6652022-03-03 17:43:16 +0000288 """Return True if the quantisation parameters have a scale and zero point"""
Tim Hall93582962020-09-09 21:58:15 +0100289
Dwight Lidman4caf29d2021-10-08 14:26:54 +0200290 return self.scale_f32 is not None and self.zero_point is not None
Tim Hall93582962020-09-09 21:58:15 +0100291
Louis Verhaard93719a92020-12-08 10:02:31 +0100292 def is_per_axis(self) -> bool:
Dwight Lidman4caf29d2021-10-08 14:26:54 +0200293 """Returns True if either the scale, zero point, minimum or maximum values have more than one value"""
Tim Halla3fe6652022-03-03 17:43:16 +0000294
Dwight Lidmanc7187432020-11-16 17:40:46 +0100295 for attr in ("scale_f32", "zero_point", "min", "max"):
Dwight Lidman4caf29d2021-10-08 14:26:54 +0200296 if np.size(getattr(self, attr)) > 1:
Dwight Lidmanc7187432020-11-16 17:40:46 +0100297 return True
298 return False
299
Tim Hall79d07d22020-04-27 18:20:16 +0100300
Johan Alfven9070f0f2023-02-07 13:01:03 +0100301def create_virtual_tensor(
302 name: str,
303):
304 virtual_tensor = Tensor([], DataType.int8, name)
305 virtual_tensor.purpose = TensorPurpose.Virtual
306 return virtual_tensor
307
308
Louis Verhaard93719a92020-12-08 10:02:31 +0100309def create_const_tensor(
310 name: str,
311 shape: Shape,
Tim Hall3b1578e2023-01-13 17:57:25 +0000312 dtype: DataType, # datatype of the tensor
313 values: Optional[Union[np.ndarray, list]], # list-like data of some type, or scalar (skip mypy), or None
Louis Verhaard93719a92020-12-08 10:02:31 +0100314 purpose: TensorPurpose = TensorPurpose.Unknown,
Tim Hall3b1578e2023-01-13 17:57:25 +0000315 quantization: Optional[QuantizationParameters] = None,
Louis Verhaard93719a92020-12-08 10:02:31 +0100316):
Tim Hall3b1578e2023-01-13 17:57:25 +0000317 assert isinstance(dtype, DataType)
318
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100319 # Tensor
320 const_tensor = Tensor(shape, dtype, name + "_0")
321 const_tensor.purpose = purpose
322 const_tensor.quantization = quantization
Tim Hall3b1578e2023-01-13 17:57:25 +0000323
324 # if the tensor datatype does not match that of the values then np.array() will perform a cast operation. this can
325 # result in undefined behaviour if casting from a numpy float to a numpy unsigned integer. therefore, we need to
326 # avoid this undefined behaviour by converting the numpy floats to python floats as these give the desired behaviour
327 # when casting to unsigned integers
328 if (
329 values is not None
330 and shape != [] # values are not a scalar
331 and isinstance(values[0], np.floating)
332 and dtype.type == BaseType.Unsigned
333 ):
334 values = [float(v) for v in values]
335
336 const_tensor.values = np.array(values, dtype=dtype.as_numpy_type())
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100337 # Operator
Louis Verhaardaee5d752020-09-30 09:01:52 +0200338 const_op = Operation(Op.Const, name)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100339 const_op.set_output_tensor(const_tensor)
patrik.gustavssoneeb85152020-12-21 17:10:40 +0000340 const_op.set_ifm_ofm_shapes()
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100341 return const_tensor
342
343
Jacob Bohlin1a666972020-09-11 10:04:15 +0200344# class that keeps track of all tensor addresses in the different memory types
345class TensorAddressMap:
Louis Verhaard93719a92020-12-08 10:02:31 +0100346 address_map: Dict = defaultdict(dict) # dict (tens.equivalence_id -> dict (mem_type -> address))
Jacob Bohlin1a666972020-09-11 10:04:15 +0200347
348 @classmethod
Louis Verhaard93719a92020-12-08 10:02:31 +0100349 def get_address_for_tens(cls, tens_id: UUID, mem_type: MemType) -> int:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200350 return cls.address_map[tens_id].get(mem_type)
351
352 @classmethod
Louis Verhaard93719a92020-12-08 10:02:31 +0100353 def set_address_for_tens(cls, tens_id: UUID, mem_type: MemType, address: int):
Jacob Bohlin1a666972020-09-11 10:04:15 +0200354 # Check previous address if there is one
355 previous_address = cls.address_map[tens_id].get(mem_type)
Louis Verhaard0b9c9a32020-09-15 14:05:38 +0200356 if address is not None and previous_address is not None:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200357 assert previous_address == address, "Two different addresses cannot be assigned to the same tensor."
358
359 # Set tensor's address for memory type
360 cls.address_map[tens_id][mem_type] = address
361
362
Louis Verhaard6c74c3b2020-12-17 13:54:09 +0100363@total_ordering
Tim Hall79d07d22020-04-27 18:20:16 +0100364class Tensor:
365 __slots__ = (
366 "shape",
Johan Alfvénb9f81592022-10-31 14:39:02 +0100367 "_original_shape",
Tim Hall79d07d22020-04-27 18:20:16 +0100368 "storage_shape",
369 "bandwidth_shape",
370 "dtype",
371 "name",
Fredrik Svedberg8d0f4892021-02-16 21:59:50 +0100372 "is_variable",
Tim Halld8339a72021-05-27 18:49:40 +0100373 "pre_buffer",
Tim Hall79d07d22020-04-27 18:20:16 +0100374 "ops",
375 "consumer_list",
376 "values",
Tim Hall79d07d22020-04-27 18:20:16 +0100377 "compressed_values",
Tim Hallf7e810a2020-06-25 15:04:31 +0100378 "compressed_values_substream_offsets",
Tim Hall79d07d22020-04-27 18:20:16 +0100379 "mem_area",
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200380 "mem_type",
Tim Hall79d07d22020-04-27 18:20:16 +0100381 "format",
382 "purpose",
383 "sub_purpose",
384 "alignment",
385 "weight_transpose_depthwise",
386 "storage_compression_scale",
387 "bandwidth_compression_scale",
388 "compression_scale_for_worst_weight_stream",
389 "weight_compression_scales",
390 "weight_compression_config",
Louis Verhaard9db529a2020-09-23 10:27:11 +0200391 "value_id",
Tim Hall79d07d22020-04-27 18:20:16 +0100392 "storage_rounding_quantum",
393 "brick_size",
Tim Hall79d07d22020-04-27 18:20:16 +0100394 "quantization",
395 "weight_compressed_offsets",
396 "element_size_bytes",
Tim Hall79d07d22020-04-27 18:20:16 +0100397 "block_traversal",
Tim Hall79d07d22020-04-27 18:20:16 +0100398 "equivalence_id",
Tim Halld8339a72021-05-27 18:49:40 +0100399 "src_tensor",
Patrik Gustavssonee99bb12021-04-08 09:04:00 +0200400 "needs_linear_format",
Johan Alfvén8d57aaa2022-02-04 11:19:17 +0100401 "ifm_write_protected",
Tim Hall79d07d22020-04-27 18:20:16 +0100402 )
403 AllocationQuantum = 16
404
Louis Verhaard93719a92020-12-08 10:02:31 +0100405 def __init__(self, shape: Shape, dtype: DataType, name: str):
Tim Hall79d07d22020-04-27 18:20:16 +0100406 self.shape = shape
Johan Alfvénb9f81592022-10-31 14:39:02 +0100407 self._original_shape = shape
Tim Hall79d07d22020-04-27 18:20:16 +0100408 self.storage_shape = shape
409 self.bandwidth_shape = shape
410 self.dtype = dtype
411 self.name = name
Fredrik Svedberg8d0f4892021-02-16 21:59:50 +0100412 self.is_variable = False
Tim Halld8339a72021-05-27 18:49:40 +0100413 self.pre_buffer = False
Louis Verhaard93719a92020-12-08 10:02:31 +0100414 self.equivalence_id: UUID = uuid.uuid4()
Tim Hall79d07d22020-04-27 18:20:16 +0100415
Louis Verhaard93719a92020-12-08 10:02:31 +0100416 self.ops: List[Operation] = []
417 self.consumer_list: List[Operation] = []
Tim Hall79d07d22020-04-27 18:20:16 +0100418
James Peet7519d502021-07-19 16:47:58 +0100419 self.values: Optional[np.ndarray] = None # elements are of type self.dtype
Louis Verhaard93719a92020-12-08 10:02:31 +0100420 self.compressed_values: Optional[np.ndarray] = None
421 self.compressed_values_substream_offsets: Optional[List] = None
422 self.mem_area: MemArea = MemArea.Unknown
423 self.mem_type: MemType = MemType.Unknown
424 self.format: TensorFormat = TensorFormat.Unknown
425 self.purpose: TensorPurpose = TensorPurpose.Unknown
426 self.sub_purpose: TensorSubPurpose = TensorSubPurpose.Standard
427 self.alignment: int = Tensor.AllocationQuantum
428 self.weight_transpose_depthwise: bool = False
Tim Hall79d07d22020-04-27 18:20:16 +0100429
Louis Verhaard93719a92020-12-08 10:02:31 +0100430 self.storage_compression_scale: float = 1.0
431 self.bandwidth_compression_scale: float = 1.0
432 self.compression_scale_for_worst_weight_stream: float = 1.0
433 self.weight_compression_scales: Optional[np.ndarray] = None
Louis Verhaard9db529a2020-09-23 10:27:11 +0200434 # if two tensors have the same weight_compression_config, then they have the same compressed values
Tim Hall79d07d22020-04-27 18:20:16 +0100435 self.weight_compression_config = None
Louis Verhaard9db529a2020-09-23 10:27:11 +0200436 # if two tensors have the same value_id, then they have the same values
Louis Verhaard93719a92020-12-08 10:02:31 +0100437 self.value_id: UUID = uuid.uuid4()
438 self.weight_compressed_offsets: List = []
439 self.storage_rounding_quantum: Tuple = (1, 1, 1, 1)
440 self.brick_size: Tuple = (1, 1, 1, 1)
441 self.element_size_bytes: int = 0
Tim Hall79d07d22020-04-27 18:20:16 +0100442
443 # quantization parameters
Louis Verhaard93719a92020-12-08 10:02:31 +0100444 self.quantization: Optional[QuantizationParameters] = None
445 self.block_traversal: TensorBlockTraversal = TensorBlockTraversal.Default
Tim Hall79d07d22020-04-27 18:20:16 +0100446
Patrik Gustavssonee99bb12021-04-08 09:04:00 +0200447 self.needs_linear_format = True
Johan Alfvén8d57aaa2022-02-04 11:19:17 +0100448 self.ifm_write_protected = False
Patrik Gustavsson458a2082020-08-13 13:41:05 +0200449
Tim Halld8339a72021-05-27 18:49:40 +0100450 # Reference to parent-tensor if this tensor is a clone
Jonas Ohlsson845e2322022-03-01 12:39:55 +0100451 self.src_tensor: Optional[Tensor] = None
Tim Halld8339a72021-05-27 18:49:40 +0100452
Jacob Bohlin1a666972020-09-11 10:04:15 +0200453 @property
Johan Alfvénb9f81592022-10-31 14:39:02 +0100454 def original_shape(self):
455 return self._original_shape
456
457 @property
Louis Verhaard93719a92020-12-08 10:02:31 +0100458 def address(self) -> int:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200459 return TensorAddressMap.get_address_for_tens(self.equivalence_id, self.mem_type)
460
461 @address.setter
Louis Verhaard93719a92020-12-08 10:02:31 +0100462 def address(self, address: int):
Jacob Bohlin1a666972020-09-11 10:04:15 +0200463 TensorAddressMap.set_address_for_tens(self.equivalence_id, self.mem_type, address)
464
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100465 @property
466 def is_standard_fm(self) -> bool:
467 return self.sub_purpose == TensorSubPurpose.Standard and self.purpose == TensorPurpose.FeatureMap
468
Johan Alfvén0f2e59f2022-10-21 11:21:38 +0200469 @property
470 def is_const(self) -> bool:
471 return self.ops != [] and self.ops[0].type == Op.Const
472
473 @property
474 def is_scalar(self) -> bool:
475 return self.shape == [] and self.elements() == 1
476
477 def is_broadcast(self, ofm) -> bool:
478 return self.shape != ofm.shape
479
Louis Verhaard93719a92020-12-08 10:02:31 +0100480 def element_size(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100481 if self.element_size_bytes == 0:
Diqing Zhonge3d18b02021-11-15 13:53:10 +0100482 return self.dtype.size_in_bits() // 8
Tim Hall79d07d22020-04-27 18:20:16 +0100483 return self.element_size_bytes
484
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100485 # Returns a copy, renamed to self.name + suffix
486 # The references to Operators will be empty when returned
487 # Depending on set_unique, the copy is shallow, or deep
488 # For set_unique==True, a new equivalence_id will be set
Louis Verhaard93719a92020-12-08 10:02:31 +0100489 def clone(self, suffix="_clone", set_unique: bool = False) -> "Tensor":
erik.andersson@arm.com42b94ed2021-02-11 14:02:08 +0100490 res = copy.copy(self)
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100491 if set_unique:
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100492 res.equivalence_id = uuid.uuid4()
erik.andersson@arm.com42b94ed2021-02-11 14:02:08 +0100493 res.storage_shape = list(self.storage_shape)
494 res.bandwidth_shape = list(self.bandwidth_shape)
495 if self.quantization is not None:
496 res.quantization = self.quantization.clone()
Tim Hall79d07d22020-04-27 18:20:16 +0100497
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100498 res.name = res.name + suffix
Tim Hall79d07d22020-04-27 18:20:16 +0100499 res.ops = []
500 res.consumer_list = []
Tim Hall79d07d22020-04-27 18:20:16 +0100501
Tim Hall79d07d22020-04-27 18:20:16 +0100502 return res
503
Johan Alfven126558e2023-03-09 08:36:10 +0100504 def clone_into_shram(self, arch) -> "Tensor":
505 res = self.clone(suffix="_shram")
506 res.mem_area = MemArea.Shram
Tim Halld8339a72021-05-27 18:49:40 +0100507 res.src_tensor = self
Tim Hall79d07d22020-04-27 18:20:16 +0100508 return res
509
Tim Hall92cd33b2022-11-03 12:25:33 +0000510 def as_1D(self):
511 self.shape = [np.prod(self.shape)]
512 if self.values is not None:
513 self.values = self.values.reshape(self.shape)
514
515 def transpose(self, reorder):
516 self.shape = [self.shape[idx] for idx in reorder]
517 self._original_shape = [self._original_shape[idx] for idx in reorder]
518 if self.values is not None:
519 self.values = self.values.transpose(reorder)
520
Louis Verhaard93719a92020-12-08 10:02:31 +0100521 def copy_compressed_weight_info(self, src_tens: "Tensor"):
Louis Verhaard3c07c972020-05-07 08:12:58 +0200522 # Copies compressed values + all related weight compression info from the given tensor
Louis Verhaard9db529a2020-09-23 10:27:11 +0200523 self.equivalence_id = src_tens.equivalence_id
Louis Verhaard3c07c972020-05-07 08:12:58 +0200524 self.compressed_values = src_tens.compressed_values
Tim Hallf7e810a2020-06-25 15:04:31 +0100525 self.compressed_values_substream_offsets = src_tens.compressed_values_substream_offsets
Louis Verhaard3c07c972020-05-07 08:12:58 +0200526 self.storage_shape = src_tens.storage_shape
527 self.brick_size = src_tens.brick_size
528 self.weight_compression_scales = src_tens.weight_compression_scales
529 self.weight_compressed_offsets = src_tens.weight_compressed_offsets
530 self.weight_transpose_depthwise = src_tens.weight_transpose_depthwise
531 self.compression_scale_for_worst_weight_stream = src_tens.compression_scale_for_worst_weight_stream
532 self.storage_compression_scale = src_tens.storage_compression_scale
Diqing Zhong7e1d1d12020-10-30 15:10:46 +0100533 self.bandwidth_compression_scale = src_tens.bandwidth_compression_scale
Louis Verhaard3c07c972020-05-07 08:12:58 +0200534 self.block_traversal = src_tens.block_traversal
535 self.weight_compression_config = src_tens.weight_compression_config
Louis Verhaard9db529a2020-09-23 10:27:11 +0200536 self.value_id = src_tens.value_id
Louis Verhaard3c07c972020-05-07 08:12:58 +0200537
Louis Verhaard93719a92020-12-08 10:02:31 +0100538 def set_format(self, fmt: TensorFormat, arch):
Tim Hall79d07d22020-04-27 18:20:16 +0100539 self.format = fmt
540 shape_len = 0
541 try:
542 shape_len = len(self.shape)
543 except TypeError:
544 pass
545
Louis Verhaard0411edb2020-11-16 16:37:11 +0100546 if shape_len > 4:
547 return
Louis Verhaard04bd3e92021-08-19 16:36:32 +0200548 assert not (self.needs_linear_format and fmt == TensorFormat.NHCWB16)
Tim Hall79d07d22020-04-27 18:20:16 +0100549 self.storage_rounding_quantum = arch.storage_rounding_quantums[self.format]
Louis Verhaard93719a92020-12-08 10:02:31 +0100550 self.storage_rounding_quantum = tuple(self.storage_rounding_quantum[-shape_len:])
Tim Hall79d07d22020-04-27 18:20:16 +0100551 self.brick_size = arch.brick_sizes[self.format]
Louis Verhaard93719a92020-12-08 10:02:31 +0100552 self.brick_size = tuple(self.brick_size[-shape_len:])
Tim Hall79d07d22020-04-27 18:20:16 +0100553 if self.shape is None:
554 return
555
556 self.bandwidth_shape = shape_round_to_quantum(self.shape, self.brick_size)
557 self.storage_shape = shape_round_to_quantum(self.shape, self.storage_rounding_quantum)
558
559 if fmt == TensorFormat.WeightsCompressed:
560 compression_ratio = 5 / 8
561 self.storage_compression_scale = compression_ratio
562 self.bandwidth_compression_scale = compression_ratio
563 self.compression_scale_for_worst_weight_stream = compression_ratio
564
Louis Verhaard93719a92020-12-08 10:02:31 +0100565 def storage_elements(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100566 elems = shape_num_elements(self.storage_shape)
567 if elems is None:
568 return 0
569 return elems
570
Louis Verhaard93719a92020-12-08 10:02:31 +0100571 def elements(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100572 elems = shape_num_elements(self.shape)
573 if elems is None:
574 return 0
575 return elems
576
Louis Verhaard93719a92020-12-08 10:02:31 +0100577 def has_fully_defined_shape(self) -> bool:
Tim Hall79d07d22020-04-27 18:20:16 +0100578 return shape_fully_defined(self.shape)
579
Louis Verhaard93719a92020-12-08 10:02:31 +0100580 def storage_size(self, scale: float = 1.0) -> int:
Patrik Gustavsson90831bc2020-08-24 16:26:11 +0200581 raw_size = self.storage_elements() * self.element_size() * scale
Tim Hall79d07d22020-04-27 18:20:16 +0100582 if raw_size == 0:
583 raw_size = 1 # force it to take up space
584 rounded_size = numeric_util.round_up(numeric_util.round_up_to_int(raw_size), self.alignment)
585 return rounded_size
586
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100587 def storage_size_for_shape(self, op_storage_shape: Shape) -> int:
588 elems = shape_num_elements(op_storage_shape)
589 elems = elems if elems else 0
590 raw_size = elems * self.element_size()
591 if raw_size == 0:
592 raw_size = 1 # force it to take up space
593 rounded_size = numeric_util.round_up(numeric_util.round_up_to_int(raw_size), self.alignment)
594 return rounded_size
595
Louis Verhaard93719a92020-12-08 10:02:31 +0100596 def storage_shape_for_sub_purpose(
597 self, sub_purpose: TensorSubPurpose, param_a: Optional[int], param_b: Optional[int]
598 ) -> Shape:
Tim Hall79d07d22020-04-27 18:20:16 +0100599 if sub_purpose == TensorSubPurpose.DoubleBuffer:
Jacob Bohline843d332020-06-23 12:12:56 +0200600 shp = list(self.shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100601 assert len(shp) >= 2
Louis Verhaard93719a92020-12-08 10:02:31 +0100602 assert param_a is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100603 shp[-1] = min(shp[-1], param_a * 2)
Tim Hall79d07d22020-04-27 18:20:16 +0100604 else:
Jacob Bohlinfad72042021-08-24 21:51:41 +0200605 shp = full_shape(4, self.storage_shape, 1)
Jacob Bohline843d332020-06-23 12:12:56 +0200606 if sub_purpose == TensorSubPurpose.RollingBufferX:
607 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100608 assert param_a is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200609 shp[0] = 1
610 shp[2] = min(shp[2], param_a)
611 elif sub_purpose == TensorSubPurpose.RollingBufferY:
612 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100613 assert param_a is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200614 shp[0] = 1
615 shp[1] = min(shp[1], param_a)
616 elif sub_purpose == TensorSubPurpose.RollingBufferXY:
617 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100618 assert param_a is not None
619 assert param_b is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200620 shp[0] = 1
621 shp[2] = min(shp[2], param_a)
622 shp[1] = min(shp[1], param_b)
623 elif sub_purpose == TensorSubPurpose.Standard:
624 pass
625 else:
626 assert 0, "did not expect new sub purpose %s" % (sub_purpose,)
627
Tim Hall79d07d22020-04-27 18:20:16 +0100628 return shp
629
Louis Verhaard93719a92020-12-08 10:02:31 +0100630 def set_new_sub_purpose(self, sub_purpose: TensorSubPurpose, param_a=None, param_b=None):
Tim Hall79d07d22020-04-27 18:20:16 +0100631 self.storage_shape = self.storage_shape_for_sub_purpose(sub_purpose, param_a, param_b)
632 self.sub_purpose = sub_purpose
633 if sub_purpose == TensorSubPurpose.DoubleBuffer:
634 self.storage_compression_scale = self.compression_scale_for_worst_weight_stream
635
Louis Verhaard93719a92020-12-08 10:02:31 +0100636 def bandwidth(self) -> float:
Tim Hall79d07d22020-04-27 18:20:16 +0100637 elems = shape_num_elements(self.bandwidth_shape)
638 if elems is None:
639 return 0
640 return elems * self.element_size() * self.bandwidth_compression_scale
641
Louis Verhaard93719a92020-12-08 10:02:31 +0100642 def consumers(self) -> List[Operation]:
Tim Hall79d07d22020-04-27 18:20:16 +0100643 return self.consumer_list
644
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100645 def get_4D_storage_shape_for_shape(self, op_shape4D: Shape4D) -> Shape4D:
646 rounding_quantum = full_shape(4, list(self.storage_rounding_quantum), 1)
647 return Shape4D(shape_round_to_quantum(op_shape4D.as_list(), rounding_quantum))
648
Rickard Bolin17e53b52022-09-06 16:09:01 +0000649 def addresses_for_rolling_buffer(
650 self, start_coord: Shape, end_coord: Shape, strides: List[int], op_shape4D: Shape4D
651 ) -> Tuple:
Tim Hall79d07d22020-04-27 18:20:16 +0100652 # returns ( box_height0, box_height1, box_width, [address_tl, address_tr, address_bl, address_br] )
653
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100654 if self.storage_shape == []:
655 return (
656 1,
657 1,
658 1,
Rickard Bolin17e53b52022-09-06 16:09:01 +0000659 [self.address_for_coordinate(start_coord, strides, op_shape4D), 0, 0, 0],
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100660 )
Tim Hall79d07d22020-04-27 18:20:16 +0100661
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100662 if self.is_standard_fm:
663 storage_shape_4D = self.get_4D_storage_shape_for_shape(op_shape4D)
664 else:
665 storage_shape_4D = Shape4D(self.storage_shape)
666
667 crossing_y = numeric_util.round_up(start_coord[1] + 1, storage_shape_4D.height)
668 crossing_x = numeric_util.round_up(start_coord[2] + 1, storage_shape_4D.width)
Tim Hall79d07d22020-04-27 18:20:16 +0100669
670 crossing_y = min(crossing_y, end_coord[1])
671 crossing_x = min(crossing_x, end_coord[2])
672
673 box_height0 = crossing_y - start_coord[1]
674 box_width = crossing_x - start_coord[2]
675
Rickard Bolin9ae34552022-06-09 13:07:17 +0000676 addresses: List = [0] * 4
Rickard Bolin17e53b52022-09-06 16:09:01 +0000677 addresses[0] = self.address_for_coordinate(start_coord, strides, op_shape4D)
Tim Hall79d07d22020-04-27 18:20:16 +0100678
679 if end_coord[2] > crossing_x:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100680 addresses[1] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000681 [start_coord[0], start_coord[1], crossing_x, start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100682 )
Michael McGeagh528a56d2020-12-16 11:33:21 +0000683 raise UnsupportedFeatureError("Striping in vertical direction is not supported")
Tim Hall79d07d22020-04-27 18:20:16 +0100684 if end_coord[1] > crossing_y:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100685 addresses[2] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000686 [start_coord[0], crossing_y, start_coord[2], start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100687 )
Tim Hall79d07d22020-04-27 18:20:16 +0100688 if end_coord[1] > crossing_y and end_coord[2] > crossing_x:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100689 addresses[3] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000690 [start_coord[0], crossing_y, crossing_x, start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100691 )
Tim Hall79d07d22020-04-27 18:20:16 +0100692
693 return box_height0, box_height0, box_width, addresses
694
Rickard Bolin17e53b52022-09-06 16:09:01 +0000695 def get_strides(self, shape4D: Optional[Shape4D]) -> List[int]:
Tim Hall79d07d22020-04-27 18:20:16 +0100696
Rickard Bolin17e53b52022-09-06 16:09:01 +0000697 augmented_shape = self.get_augmented_shape(shape4D)
698 assert len(augmented_shape) == 5
Louis Verhaard93719a92020-12-08 10:02:31 +0100699 strides: List = [0] * len(augmented_shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100700 stride = self.element_size() * self.storage_compression_scale
701
702 if self.format != TensorFormat.NHCWB16:
Louis Verhaard93719a92020-12-08 10:02:31 +0100703 stride_order = [4, 1, 3, 2, 0]
Tim Hall79d07d22020-04-27 18:20:16 +0100704 for i in stride_order:
705 strides[i] = stride
706 stride *= augmented_shape[i]
707 else:
Tim Hall79d07d22020-04-27 18:20:16 +0100708 strides[4] = stride
Patrik Gustavsson2213e902020-05-05 17:49:35 +0200709 strides[3] = 16 * stride # STRIDE_X
Tim Hall79d07d22020-04-27 18:20:16 +0100710 strides[1] = strides[3] * augmented_shape[2] # STRIDE_C
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200711 strides[2] = augmented_shape[2] * augmented_shape[3] * stride # STRIDE_Y
Tim Hall79d07d22020-04-27 18:20:16 +0100712 strides[0] = strides[2] * augmented_shape[1] # STRIDE_N
713
Tim Hall79d07d22020-04-27 18:20:16 +0100714 return strides
715
Rickard Bolin17e53b52022-09-06 16:09:01 +0000716 def get_augmented_shape(self, shape4D: Optional[Shape4D] = None) -> Optional[Shape]:
717
718 if shape4D and self.is_standard_fm:
719 augmented_shape = self.get_4D_storage_shape_for_shape(shape4D).as_list()
720 else:
721 augmented_shape = full_shape(4, self.storage_shape, 1)
722
723 if self.format == TensorFormat.NHWC:
724 augmented_shape = [augmented_shape[0], augmented_shape[3]] + augmented_shape[1:3] + [1]
725
726 elif self.format == TensorFormat.NHCWB16:
727 augmented_shape = augmented_shape[0:4] + [1]
728
729 if augmented_shape[1] == 0:
730 augmented_shape[1] = 1
731
732 else:
733 assert self.format in (TensorFormat.Unknown, TensorFormat.WeightsCompressed)
734 return None
735
736 return augmented_shape
737
738 def get_augmented_coord(self, coord: Optional[Shape] = None) -> Optional[Shape]:
739 if coord is None:
740 coord = [0] * min(len(self.storage_shape), 4)
741
742 missing_len = 4 - len(coord)
743 augmented_coord = ([0] * missing_len) + coord
744
745 if self.format == TensorFormat.NHWC:
746 augmented_coord = [augmented_coord[0], augmented_coord[3]] + augmented_coord[1:3] + [0]
747
748 elif self.format == TensorFormat.NHCWB16:
749 channel_divisor = 16
750 augmented_coord = (
751 [augmented_coord[0], augmented_coord[3] // channel_divisor]
752 + augmented_coord[1:3]
753 + [augmented_coord[3] % channel_divisor]
754 )
755 else:
756 assert self.format in (TensorFormat.Unknown, TensorFormat.WeightsCompressed)
757 return None
758
759 return augmented_coord
760
Louis Verhaard93719a92020-12-08 10:02:31 +0100761 def find_npu_op(self) -> Optional[Operation]:
Tim Halld8339a72021-05-27 18:49:40 +0100762 # Returns the NPU operator that uses this tensor
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200763 for op in self.consumers():
Dwight Lidman940fdee2020-08-13 13:11:48 +0200764 if op.run_on_npu:
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200765 return op
Louis Verhaard93719a92020-12-08 10:02:31 +0100766 return None
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200767
Louis Verhaard93719a92020-12-08 10:02:31 +0100768 def compressed_stream_index_from_coord(self, coord: Shape) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100769 assert self.format == TensorFormat.WeightsCompressed
Louis Verhaard93719a92020-12-08 10:02:31 +0100770 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100771 assert len(self.compressed_values) > 0
772 assert len(self.compressed_values) + 1 == len(self.weight_compressed_offsets)
773
774 depth = coord[-1]
775 brick_depth = self.brick_size[-1]
776 # Clamp position at final element index
777 if depth > self.shape[-1]:
778 depth = self.shape[-1]
779
780 # Always round up to next boundary
Michael McGeagh8d3216f2020-08-10 11:35:57 +0100781 index = numeric_util.round_up_divide(depth, brick_depth)
Tim Hall79d07d22020-04-27 18:20:16 +0100782
783 # Check boundaries on all but last weight set (which may be shorter
784 # than the brick we divided it up into)
785 if index < len(self.weight_compressed_offsets) - 1:
786 # There are no half-way points in the weights
787 if (depth % brick_depth) != 0:
Michael McGeagh528a56d2020-12-16 11:33:21 +0000788 raise UnsupportedFeatureError("Offset into weights must be aligned to a brick")
Tim Hall79d07d22020-04-27 18:20:16 +0100789
790 return index
791
Louis Verhaard93719a92020-12-08 10:02:31 +0100792 def size_of_compressed_stream(self, index: int) -> int:
793 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100794 assert 0 <= index < len(self.compressed_values)
795 return len(self.compressed_values[index])
796
Louis Verhaard93719a92020-12-08 10:02:31 +0100797 def is_last_index_in_compressed_stream(self, index: int) -> bool:
798 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100799 assert 0 <= index < len(self.compressed_values)
800 return index == len(self.compressed_values) - 1
801
Rickard Bolin17e53b52022-09-06 16:09:01 +0000802 def address_for_coordinate(
803 self,
804 orig_coord: Shape,
805 strides: Optional[List[int]] = None,
806 op_shape4D: Optional[Shape4D] = None,
807 is_top_box: bool = False,
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100808 ) -> Optional[int]:
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000809
Tim Hall79d07d22020-04-27 18:20:16 +0100810 address_offset = 0
Tim Halld8339a72021-05-27 18:49:40 +0100811 assert self.purpose != TensorPurpose.Weights
Tim Hall79d07d22020-04-27 18:20:16 +0100812
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000813 # Strides may be passed as an argument, for example when creating feature maps as the strides may be modified
814 # by the "ofm_stride_multiplier" operation attribute. If not, they are calculated here.
815 if not strides:
816 strides = self.get_strides(op_shape4D)
817
818 coord = orig_coord
819 if is_top_box:
820 coord = [c - 1 for c in orig_coord]
821 address_offset += 1 * strides[-1] # one element
822
Tim Hall79d07d22020-04-27 18:20:16 +0100823 if self.sub_purpose == TensorSubPurpose.Standard:
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100824 shape = op_shape4D.as_list() if op_shape4D else self.shape
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000825 for _coord, _shape in zip(coord, shape):
826 assert _coord >= 0 and _coord < _shape
827
Tim Halld8339a72021-05-27 18:49:40 +0100828 if op_shape4D and self.is_standard_fm:
829 storage_shape = self.get_4D_storage_shape_for_shape(op_shape4D).as_list()
830 storage_size = self.storage_size_for_shape(storage_shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100831 else:
Tim Halld8339a72021-05-27 18:49:40 +0100832 storage_shape = self.storage_shape
833 coord = coord[-len(storage_shape) :]
834 storage_size = self.storage_size()
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100835
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000836 # Handle wraparound for partial buffers. Make sure to do this after subtracting top box
837 coord = [_coord % _shape for _coord, _shape in zip(coord, storage_shape)]
Tim Hall79d07d22020-04-27 18:20:16 +0100838
Rickard Bolin17e53b52022-09-06 16:09:01 +0000839 augmented_coord = self.get_augmented_coord(coord)
840 assert augmented_coord is not None
841
Tim Halld8339a72021-05-27 18:49:40 +0100842 address_offset += np.dot(augmented_coord, strides)
Tim Hall79d07d22020-04-27 18:20:16 +0100843
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000844 assert address_offset >= 0 and address_offset <= storage_size
Rickard Bolin17e53b52022-09-06 16:09:01 +0000845 return self.address + address_offset
Tim Hall79d07d22020-04-27 18:20:16 +0100846
Louis Verhaard93719a92020-12-08 10:02:31 +0100847 def is_allocated_in_tensor_arena(self, scratch_tensor_mem_area: MemArea) -> bool:
Michael McGeaghf3e3ad72020-12-02 12:39:03 +0000848 return (self.mem_area == scratch_tensor_mem_area) and (self.mem_type in (MemType.Scratch, MemType.Scratch_fast))
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200849
Louis Verhaard93719a92020-12-08 10:02:31 +0100850 def equivalent(self, tens: "Tensor") -> bool:
Louis Verhaard0b8268a2020-08-05 16:11:29 +0200851 return self.equivalence_id == tens.equivalence_id
852
Louis Verhaard93719a92020-12-08 10:02:31 +0100853 def set_all_shapes(self, shape: Shape):
Michael McGeagh6a8d4242020-07-28 12:17:59 +0100854 self.shape = shape
855 self.storage_shape = shape
856 self.bandwidth_shape = shape
857
Louis Verhaard93719a92020-12-08 10:02:31 +0100858 def get_full_shape(self) -> Shape:
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100859 d = len(self.shape)
860 if d in (1, 3):
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100861 return full_shape(4, self.shape, 1)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100862 elif d == 2:
863 return [self.shape[0], 1, 1, self.shape[1]]
864 else:
Fredrik Svedberg835d8e12020-09-04 09:46:17 +0200865 return self.shape.copy()
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100866
Louis Verhaard93719a92020-12-08 10:02:31 +0100867 def is_quantized(self) -> bool:
Tim Hall93582962020-09-09 21:58:15 +0100868 # a tensor is quantized if it has an integral type and it contains valid quantization params
869
Tim Hall89567612020-10-27 11:57:57 +0000870 if not isinstance(self.quantization, QuantizationParameters):
Tim Hall93582962020-09-09 21:58:15 +0100871 return False
872
Tim Hall89567612020-10-27 11:57:57 +0000873 return (self.dtype.type & BaseType.Int) != 0 and self.quantization.is_valid()
Tim Hall93582962020-09-09 21:58:15 +0100874
James Peet7519d502021-07-19 16:47:58 +0100875 def get_scalar(self):
876 """
877 return: Unquantized or dequantized scalar value
878 rtype: self.dtype (if unquantized) or float (if dequantized)
879 """
880 assert self.values.size == 1, "get_scalar called on non-scalar tensor"
881 if self.is_quantized():
882 return self.quantization.dequantize(self.values).item(0)
883 else:
884 return self.values.item(0)
885
Ayaan Masooda2ec5aa2022-04-21 14:28:03 +0100886 def get_shape_as_2d(self, dimension_2_size: int) -> Optional[Shape4D]:
887
888 elms = self.elements()
889 dimension_1_size = elms // dimension_2_size
890 # Checks if the reduction works and shape is not 1D
891 is_reducible = dimension_1_size * dimension_2_size == elms and not (len(self.shape) == 1)
892
893 new_shape = None
894 if is_reducible:
895 new_shape = Shape4D([dimension_1_size, 1, 1, dimension_2_size])
896
897 return new_shape
898
Louis Verhaard6c74c3b2020-12-17 13:54:09 +0100899 def __lt__(self, other: "Tensor") -> bool:
900 return self.equivalence_id < other.equivalence_id
901
Tim Hall79d07d22020-04-27 18:20:16 +0100902 def __str__(self):
903 return "<nng.Tensor '%s' shape=%s dtype=%s>" % (self.name, self.shape, self.dtype)
904
905 __repr__ = __str__
Tim Hall93582962020-09-09 21:58:15 +0100906
Michael McGeagh528a56d2020-12-16 11:33:21 +0000907 def error(self, msg):
908 """
909 Raises a VelaError exception for errors encountered when parsing a Tensor
910
911 :param self: Tensor object that resulted in the error
912 :param msg: str object that contains a description of the specific error encountered
913 """
914
915 def _print_operators(ops):
916 lines = []
917 for idx, op in enumerate(ops):
918 op_type = getattr(op, "type", "Not an Operation")
919 op_id = getattr(op, "op_index", "-")
920 lines.append(f" {idx} = {op_type} ({op_id})")
921 return lines
922
923 lines = [f"Invalid {self.name} tensor. {msg}"]
924
925 lines += [" Driving operators:"]
926 lines += _print_operators(self.ops)
927
928 lines += [" Consuming operators:"]
929 lines += _print_operators(self.consumer_list)
930
931 raise VelaError("\n".join(lines))
932
Tim Hall93582962020-09-09 21:58:15 +0100933
Louis Verhaard93719a92020-12-08 10:02:31 +0100934def check_quantized_tens_scaling_equal(tens_a: Tensor, tens_b: Tensor) -> bool:
Tim Hall93582962020-09-09 21:58:15 +0100935 # checks that the scaling of two quantized tensors are equal
936
Tim Hall89567612020-10-27 11:57:57 +0000937 return tens_a.is_quantized() and tens_b.is_quantized() and tens_a.quantization.is_scaling_equal(tens_b.quantization)