blob: 6a95bad427be1a4e0d4dccba2fe5695f5d409007 [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
117 Size = 7
Tim Hall79d07d22020-04-27 18:20:16 +0100118
Louis Verhaard93719a92020-12-08 10:02:31 +0100119 def display_name(self) -> str:
Fredrik Svedberge22ba8c2021-01-27 16:53:41 +0100120 return ("Unknown", "Weights", "FeatureMap", "Scratch", "ScratchFast", "LUT", "FastStorageBias", "Size")[
121 self.value
122 ]
Tim Hall79d07d22020-04-27 18:20:16 +0100123
Louis Verhaard93719a92020-12-08 10:02:31 +0100124 def identifier_name(self) -> str:
Fredrik Svedberge22ba8c2021-01-27 16:53:41 +0100125 return ("unknown", "weights", "feature_map", "scratch", "scratch_fast", "lut", "fast_storage_bias", "size")[
126 self.value
127 ]
Tim Hall79d07d22020-04-27 18:20:16 +0100128
Louis Verhaard93719a92020-12-08 10:02:31 +0100129 @staticmethod
Tim Hall79d07d22020-04-27 18:20:16 +0100130 def all():
Andreas Nevalainen897cc142020-10-28 15:42:08 +0100131 return (TensorPurpose.Weights, TensorPurpose.FeatureMap, TensorPurpose.FSBias)
Tim Hall79d07d22020-04-27 18:20:16 +0100132
133
134class TensorSubPurpose(enum.Enum):
135 Standard = 0
136 DoubleBuffer = 1
137 RollingBufferX = 2
138 RollingBufferY = 3
139 RollingBufferXY = 4
140
Louis Verhaard93719a92020-12-08 10:02:31 +0100141 def display_name(self) -> str:
Tim Hall79d07d22020-04-27 18:20:16 +0100142 return ("Standard", "Double Buffer", "Rolling Buffer X", "Rolling Buffer Y", "Rolling Buffer XY")[self.value]
143
Louis Verhaard93719a92020-12-08 10:02:31 +0100144 def identifier_name(self) -> str:
Tim Hall79d07d22020-04-27 18:20:16 +0100145 return ("standard", "double_buffer", "rolling_buffer_x", "rolling_buffer_y", "rolling_buffer_xy")[self.value]
146
Louis Verhaard93719a92020-12-08 10:02:31 +0100147 @staticmethod
Tim Hall79d07d22020-04-27 18:20:16 +0100148 def all():
149 return (
150 TensorSubPurpose.Standard,
151 TensorSubPurpose.DoubleBuffer,
152 TensorSubPurpose.RollingBufferX,
153 TensorSubPurpose.RollingBufferY,
154 TensorSubPurpose.RollingBufferXY,
155 )
156
157
158class TensorFormat(enum.Flag):
159 Unknown = 0
160 WeightsCompressed = 1
161 NHWC = 2
162 NHCWB16 = 3
163
164 def __str__(self):
165 return self.name
166
167
168class TensorBlockTraversal(enum.Enum):
169 Default = 0
170 DepthWise = 1
171 DepthFirst = 2
172 PartKernelFirst = 3
173
174
Louis Verhaard93719a92020-12-08 10:02:31 +0100175def shape_num_elements(shp: Shape) -> Optional[int]:
Tim Hall79d07d22020-04-27 18:20:16 +0100176 elems = 1
177 if shp is None:
178 return None
179 for d in shp:
180 if d is None:
181 return None
182 elems *= d
183 return elems
184
185
Louis Verhaard93719a92020-12-08 10:02:31 +0100186def shape_fully_defined(shp: Shape) -> bool:
Tim Hall79d07d22020-04-27 18:20:16 +0100187 if shp is None:
188 return False
189 for d in shp:
190 if d is None:
191 return False
192 return True
193
194
Louis Verhaard93719a92020-12-08 10:02:31 +0100195def shape_round_to_quantum(shp: Shape, quantum: Tuple) -> Shape:
Tim Hall79d07d22020-04-27 18:20:16 +0100196 new_shp = list(shp)
197
198 # Traverse backwards using length of shape since there may be more rounding quantums than shape elements
199 for i in range(-1, -len(shp) - 1, -1):
200 if new_shp[i] is not None:
201 new_shp[i] = numeric_util.round_up(new_shp[i], quantum[i])
202 return new_shp
203
204
Louis Verhaard9db529a2020-09-23 10:27:11 +0200205@lru_cache(maxsize=None)
Louis Verhaard93719a92020-12-08 10:02:31 +0100206def create_equivalence_id(key) -> UUID:
Louis Verhaard9db529a2020-09-23 10:27:11 +0200207 # Generates equivalence_id based on the given key.
208 return uuid.uuid4()
209
210
Tim Hall79d07d22020-04-27 18:20:16 +0100211class QuantizationParameters:
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100212 __slots__ = (
213 "min",
214 "max",
215 "num_bits",
216 "narrow_range",
Rickard Bolinfea15162022-07-04 16:19:16 +0000217 "next_after",
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100218 "scale_f32",
219 "zero_point",
220 "quant_min",
221 "quant_max",
222 "quant_dim",
223 )
Tim Hall79d07d22020-04-27 18:20:16 +0100224
Louis Verhaard93719a92020-12-08 10:02:31 +0100225 def __init__(
226 self,
227 min: Union[float, np.ndarray, None] = None,
228 max: Union[float, np.ndarray, None] = None,
229 num_bits=None,
230 narrow_range=None,
231 ):
Tim Hall79d07d22020-04-27 18:20:16 +0100232 self.min = min
233 self.max = max
234
235 self.num_bits = num_bits
236 self.narrow_range = narrow_range
237
Rickard Bolinfea15162022-07-04 16:19:16 +0000238 # Use the 'next after' float value of scale_f32 when converting to scale and shift. It can be combined with
239 # natural rounding to perform rounding away from zero. This only affects the ofm scale and bias tensor, it has
240 # no affect on global scaling i.e. the ofm_scale register
241 self.next_after = False
Louis Verhaard93719a92020-12-08 10:02:31 +0100242 self.scale_f32: Union[float, np.ndarray, None] = None
243 self.zero_point: Union[int, np.ndarray, None] = None
244 self.quant_min: Optional[float] = None
245 self.quant_max: Optional[float] = None
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100246 self.quant_dim: Optional[int] = None
Tim Hall79d07d22020-04-27 18:20:16 +0100247
248 def __str__(self):
Rickard Bolinfea15162022-07-04 16:19:16 +0000249 return (
250 f"<nng.QuantizationParameters min={self.min}, max={self.max}, num_bits={self.num_bits}, "
251 f"scale={self.scale_f32}, zero_point={self.zero_point}, next={self.next_after}>"
Tim Hall79d07d22020-04-27 18:20:16 +0100252 )
253
254 __repr__ = __str__
255
Louis Verhaard93719a92020-12-08 10:02:31 +0100256 def clone(self) -> "QuantizationParameters":
Tim Hall79d07d22020-04-27 18:20:16 +0100257 res = QuantizationParameters()
258 res.min = self.min
259 res.max = self.max
260
261 res.num_bits = self.num_bits
262 res.narrow_range = self.narrow_range
263
Rickard Bolinfea15162022-07-04 16:19:16 +0000264 res.next_after = self.next_after
Tim Hall79d07d22020-04-27 18:20:16 +0100265 res.scale_f32 = self.scale_f32
266 res.zero_point = self.zero_point
267 res.quant_min = self.quant_min
268 res.quant_max = self.quant_max
Fredrik Svedbergcc8569f2021-11-01 14:25:29 +0100269 res.quant_dim = self.quant_dim
Tim Hall79d07d22020-04-27 18:20:16 +0100270 return res
271
James Peet7519d502021-07-19 16:47:58 +0100272 def dequantize(self, values) -> np.ndarray:
273 return np.subtract(values, self.zero_point) * self.scale_f32
Tim Hall79d07d22020-04-27 18:20:16 +0100274
Louis Verhaard93719a92020-12-08 10:02:31 +0100275 def is_scaling_equal(self, other: Optional["QuantizationParameters"]) -> bool:
Tim Halla3fe6652022-03-03 17:43:16 +0000276 """
277 Returns True if the scale and zero point of self and other are equal. If other is None then the scaling is
278 not considered equal because the tensor is assumed to not be quantised and False will be returned
279 """
Tim Hall93582962020-09-09 21:58:15 +0100280
Tim Hall89567612020-10-27 11:57:57 +0000281 if not isinstance(other, QuantizationParameters):
Tim Halle3786ac2020-07-28 17:40:50 +0100282 return False
283
284 return self.scale_f32 == other.scale_f32 and self.zero_point == other.zero_point
285
Louis Verhaard93719a92020-12-08 10:02:31 +0100286 def is_valid(self) -> bool:
Tim Halla3fe6652022-03-03 17:43:16 +0000287 """Return True if the quantisation parameters have a scale and zero point"""
Tim Hall93582962020-09-09 21:58:15 +0100288
Dwight Lidman4caf29d2021-10-08 14:26:54 +0200289 return self.scale_f32 is not None and self.zero_point is not None
Tim Hall93582962020-09-09 21:58:15 +0100290
Louis Verhaard93719a92020-12-08 10:02:31 +0100291 def is_per_axis(self) -> bool:
Dwight Lidman4caf29d2021-10-08 14:26:54 +0200292 """Returns True if either the scale, zero point, minimum or maximum values have more than one value"""
Tim Halla3fe6652022-03-03 17:43:16 +0000293
Dwight Lidmanc7187432020-11-16 17:40:46 +0100294 for attr in ("scale_f32", "zero_point", "min", "max"):
Dwight Lidman4caf29d2021-10-08 14:26:54 +0200295 if np.size(getattr(self, attr)) > 1:
Dwight Lidmanc7187432020-11-16 17:40:46 +0100296 return True
297 return False
298
Tim Hall79d07d22020-04-27 18:20:16 +0100299
Louis Verhaard93719a92020-12-08 10:02:31 +0100300def create_const_tensor(
301 name: str,
302 shape: Shape,
Tim Hall3b1578e2023-01-13 17:57:25 +0000303 dtype: DataType, # datatype of the tensor
304 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 +0100305 purpose: TensorPurpose = TensorPurpose.Unknown,
Tim Hall3b1578e2023-01-13 17:57:25 +0000306 quantization: Optional[QuantizationParameters] = None,
Louis Verhaard93719a92020-12-08 10:02:31 +0100307):
Tim Hall3b1578e2023-01-13 17:57:25 +0000308 assert isinstance(dtype, DataType)
309
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100310 # Tensor
311 const_tensor = Tensor(shape, dtype, name + "_0")
312 const_tensor.purpose = purpose
313 const_tensor.quantization = quantization
Tim Hall3b1578e2023-01-13 17:57:25 +0000314
315 # if the tensor datatype does not match that of the values then np.array() will perform a cast operation. this can
316 # result in undefined behaviour if casting from a numpy float to a numpy unsigned integer. therefore, we need to
317 # avoid this undefined behaviour by converting the numpy floats to python floats as these give the desired behaviour
318 # when casting to unsigned integers
319 if (
320 values is not None
321 and shape != [] # values are not a scalar
322 and isinstance(values[0], np.floating)
323 and dtype.type == BaseType.Unsigned
324 ):
325 values = [float(v) for v in values]
326
327 const_tensor.values = np.array(values, dtype=dtype.as_numpy_type())
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100328 # Operator
Louis Verhaardaee5d752020-09-30 09:01:52 +0200329 const_op = Operation(Op.Const, name)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100330 const_op.set_output_tensor(const_tensor)
patrik.gustavssoneeb85152020-12-21 17:10:40 +0000331 const_op.set_ifm_ofm_shapes()
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100332 return const_tensor
333
334
Jacob Bohlin1a666972020-09-11 10:04:15 +0200335# class that keeps track of all tensor addresses in the different memory types
336class TensorAddressMap:
Louis Verhaard93719a92020-12-08 10:02:31 +0100337 address_map: Dict = defaultdict(dict) # dict (tens.equivalence_id -> dict (mem_type -> address))
Jacob Bohlin1a666972020-09-11 10:04:15 +0200338
339 @classmethod
Louis Verhaard93719a92020-12-08 10:02:31 +0100340 def get_address_for_tens(cls, tens_id: UUID, mem_type: MemType) -> int:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200341 return cls.address_map[tens_id].get(mem_type)
342
343 @classmethod
Louis Verhaard93719a92020-12-08 10:02:31 +0100344 def set_address_for_tens(cls, tens_id: UUID, mem_type: MemType, address: int):
Jacob Bohlin1a666972020-09-11 10:04:15 +0200345 # Check previous address if there is one
346 previous_address = cls.address_map[tens_id].get(mem_type)
Louis Verhaard0b9c9a32020-09-15 14:05:38 +0200347 if address is not None and previous_address is not None:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200348 assert previous_address == address, "Two different addresses cannot be assigned to the same tensor."
349
350 # Set tensor's address for memory type
351 cls.address_map[tens_id][mem_type] = address
352
353
Louis Verhaard6c74c3b2020-12-17 13:54:09 +0100354@total_ordering
Tim Hall79d07d22020-04-27 18:20:16 +0100355class Tensor:
356 __slots__ = (
357 "shape",
Johan Alfvénb9f81592022-10-31 14:39:02 +0100358 "_original_shape",
Tim Hall79d07d22020-04-27 18:20:16 +0100359 "storage_shape",
360 "bandwidth_shape",
361 "dtype",
362 "name",
Fredrik Svedberg8d0f4892021-02-16 21:59:50 +0100363 "is_variable",
Tim Halld8339a72021-05-27 18:49:40 +0100364 "pre_buffer",
Tim Hall79d07d22020-04-27 18:20:16 +0100365 "ops",
366 "consumer_list",
367 "values",
Tim Hall79d07d22020-04-27 18:20:16 +0100368 "compressed_values",
Tim Hallf7e810a2020-06-25 15:04:31 +0100369 "compressed_values_substream_offsets",
Tim Hall79d07d22020-04-27 18:20:16 +0100370 "mem_area",
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200371 "mem_type",
Tim Hall79d07d22020-04-27 18:20:16 +0100372 "format",
373 "purpose",
374 "sub_purpose",
375 "alignment",
376 "weight_transpose_depthwise",
377 "storage_compression_scale",
378 "bandwidth_compression_scale",
379 "compression_scale_for_worst_weight_stream",
380 "weight_compression_scales",
381 "weight_compression_config",
Louis Verhaard9db529a2020-09-23 10:27:11 +0200382 "value_id",
Tim Hall79d07d22020-04-27 18:20:16 +0100383 "storage_rounding_quantum",
384 "brick_size",
Tim Hall79d07d22020-04-27 18:20:16 +0100385 "quantization",
386 "weight_compressed_offsets",
387 "element_size_bytes",
Tim Hall79d07d22020-04-27 18:20:16 +0100388 "block_traversal",
Tim Hall79d07d22020-04-27 18:20:16 +0100389 "equivalence_id",
Tim Halld8339a72021-05-27 18:49:40 +0100390 "src_tensor",
Patrik Gustavssonee99bb12021-04-08 09:04:00 +0200391 "needs_linear_format",
Johan Alfvén8d57aaa2022-02-04 11:19:17 +0100392 "ifm_write_protected",
Tim Hall79d07d22020-04-27 18:20:16 +0100393 )
394 AllocationQuantum = 16
395
Louis Verhaard93719a92020-12-08 10:02:31 +0100396 def __init__(self, shape: Shape, dtype: DataType, name: str):
Tim Hall79d07d22020-04-27 18:20:16 +0100397 self.shape = shape
Johan Alfvénb9f81592022-10-31 14:39:02 +0100398 self._original_shape = shape
Tim Hall79d07d22020-04-27 18:20:16 +0100399 self.storage_shape = shape
400 self.bandwidth_shape = shape
401 self.dtype = dtype
402 self.name = name
Fredrik Svedberg8d0f4892021-02-16 21:59:50 +0100403 self.is_variable = False
Tim Halld8339a72021-05-27 18:49:40 +0100404 self.pre_buffer = False
Louis Verhaard93719a92020-12-08 10:02:31 +0100405 self.equivalence_id: UUID = uuid.uuid4()
Tim Hall79d07d22020-04-27 18:20:16 +0100406
Louis Verhaard93719a92020-12-08 10:02:31 +0100407 self.ops: List[Operation] = []
408 self.consumer_list: List[Operation] = []
Tim Hall79d07d22020-04-27 18:20:16 +0100409
James Peet7519d502021-07-19 16:47:58 +0100410 self.values: Optional[np.ndarray] = None # elements are of type self.dtype
Louis Verhaard93719a92020-12-08 10:02:31 +0100411 self.compressed_values: Optional[np.ndarray] = None
412 self.compressed_values_substream_offsets: Optional[List] = None
413 self.mem_area: MemArea = MemArea.Unknown
414 self.mem_type: MemType = MemType.Unknown
415 self.format: TensorFormat = TensorFormat.Unknown
416 self.purpose: TensorPurpose = TensorPurpose.Unknown
417 self.sub_purpose: TensorSubPurpose = TensorSubPurpose.Standard
418 self.alignment: int = Tensor.AllocationQuantum
419 self.weight_transpose_depthwise: bool = False
Tim Hall79d07d22020-04-27 18:20:16 +0100420
Louis Verhaard93719a92020-12-08 10:02:31 +0100421 self.storage_compression_scale: float = 1.0
422 self.bandwidth_compression_scale: float = 1.0
423 self.compression_scale_for_worst_weight_stream: float = 1.0
424 self.weight_compression_scales: Optional[np.ndarray] = None
Louis Verhaard9db529a2020-09-23 10:27:11 +0200425 # if two tensors have the same weight_compression_config, then they have the same compressed values
Tim Hall79d07d22020-04-27 18:20:16 +0100426 self.weight_compression_config = None
Louis Verhaard9db529a2020-09-23 10:27:11 +0200427 # if two tensors have the same value_id, then they have the same values
Louis Verhaard93719a92020-12-08 10:02:31 +0100428 self.value_id: UUID = uuid.uuid4()
429 self.weight_compressed_offsets: List = []
430 self.storage_rounding_quantum: Tuple = (1, 1, 1, 1)
431 self.brick_size: Tuple = (1, 1, 1, 1)
432 self.element_size_bytes: int = 0
Tim Hall79d07d22020-04-27 18:20:16 +0100433
434 # quantization parameters
Louis Verhaard93719a92020-12-08 10:02:31 +0100435 self.quantization: Optional[QuantizationParameters] = None
436 self.block_traversal: TensorBlockTraversal = TensorBlockTraversal.Default
Tim Hall79d07d22020-04-27 18:20:16 +0100437
Patrik Gustavssonee99bb12021-04-08 09:04:00 +0200438 self.needs_linear_format = True
Johan Alfvén8d57aaa2022-02-04 11:19:17 +0100439 self.ifm_write_protected = False
Patrik Gustavsson458a2082020-08-13 13:41:05 +0200440
Tim Halld8339a72021-05-27 18:49:40 +0100441 # Reference to parent-tensor if this tensor is a clone
Jonas Ohlsson845e2322022-03-01 12:39:55 +0100442 self.src_tensor: Optional[Tensor] = None
Tim Halld8339a72021-05-27 18:49:40 +0100443
Jacob Bohlin1a666972020-09-11 10:04:15 +0200444 @property
Johan Alfvénb9f81592022-10-31 14:39:02 +0100445 def original_shape(self):
446 return self._original_shape
447
448 @property
Louis Verhaard93719a92020-12-08 10:02:31 +0100449 def address(self) -> int:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200450 return TensorAddressMap.get_address_for_tens(self.equivalence_id, self.mem_type)
451
452 @address.setter
Louis Verhaard93719a92020-12-08 10:02:31 +0100453 def address(self, address: int):
Jacob Bohlin1a666972020-09-11 10:04:15 +0200454 TensorAddressMap.set_address_for_tens(self.equivalence_id, self.mem_type, address)
455
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100456 @property
457 def is_standard_fm(self) -> bool:
458 return self.sub_purpose == TensorSubPurpose.Standard and self.purpose == TensorPurpose.FeatureMap
459
Johan Alfvén0f2e59f2022-10-21 11:21:38 +0200460 @property
461 def is_const(self) -> bool:
462 return self.ops != [] and self.ops[0].type == Op.Const
463
464 @property
465 def is_scalar(self) -> bool:
466 return self.shape == [] and self.elements() == 1
467
468 def is_broadcast(self, ofm) -> bool:
469 return self.shape != ofm.shape
470
Louis Verhaard93719a92020-12-08 10:02:31 +0100471 def element_size(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100472 if self.element_size_bytes == 0:
Diqing Zhonge3d18b02021-11-15 13:53:10 +0100473 return self.dtype.size_in_bits() // 8
Tim Hall79d07d22020-04-27 18:20:16 +0100474 return self.element_size_bytes
475
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100476 # Returns a copy, renamed to self.name + suffix
477 # The references to Operators will be empty when returned
478 # Depending on set_unique, the copy is shallow, or deep
479 # For set_unique==True, a new equivalence_id will be set
Louis Verhaard93719a92020-12-08 10:02:31 +0100480 def clone(self, suffix="_clone", set_unique: bool = False) -> "Tensor":
erik.andersson@arm.com42b94ed2021-02-11 14:02:08 +0100481 res = copy.copy(self)
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100482 if set_unique:
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100483 res.equivalence_id = uuid.uuid4()
erik.andersson@arm.com42b94ed2021-02-11 14:02:08 +0100484 res.storage_shape = list(self.storage_shape)
485 res.bandwidth_shape = list(self.bandwidth_shape)
486 if self.quantization is not None:
487 res.quantization = self.quantization.clone()
Tim Hall79d07d22020-04-27 18:20:16 +0100488
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100489 res.name = res.name + suffix
Tim Hall79d07d22020-04-27 18:20:16 +0100490 res.ops = []
491 res.consumer_list = []
Tim Hall79d07d22020-04-27 18:20:16 +0100492
Tim Hall79d07d22020-04-27 18:20:16 +0100493 return res
494
Louis Verhaard93719a92020-12-08 10:02:31 +0100495 def clone_into_fast_storage(self, arch) -> "Tensor":
Tim Hall79d07d22020-04-27 18:20:16 +0100496 res = self.clone(suffix="_fast_storage")
497 res.mem_area = arch.fast_storage_mem_area
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200498 res.mem_type = MemType.Scratch_fast
Tim Halld8339a72021-05-27 18:49:40 +0100499 res.src_tensor = self
Tim Hall79d07d22020-04-27 18:20:16 +0100500 return res
501
Tim Hall92cd33b2022-11-03 12:25:33 +0000502 def as_1D(self):
503 self.shape = [np.prod(self.shape)]
504 if self.values is not None:
505 self.values = self.values.reshape(self.shape)
506
507 def transpose(self, reorder):
508 self.shape = [self.shape[idx] for idx in reorder]
509 self._original_shape = [self._original_shape[idx] for idx in reorder]
510 if self.values is not None:
511 self.values = self.values.transpose(reorder)
512
Louis Verhaard93719a92020-12-08 10:02:31 +0100513 def copy_compressed_weight_info(self, src_tens: "Tensor"):
Louis Verhaard3c07c972020-05-07 08:12:58 +0200514 # Copies compressed values + all related weight compression info from the given tensor
Louis Verhaard9db529a2020-09-23 10:27:11 +0200515 self.equivalence_id = src_tens.equivalence_id
Louis Verhaard3c07c972020-05-07 08:12:58 +0200516 self.compressed_values = src_tens.compressed_values
Tim Hallf7e810a2020-06-25 15:04:31 +0100517 self.compressed_values_substream_offsets = src_tens.compressed_values_substream_offsets
Louis Verhaard3c07c972020-05-07 08:12:58 +0200518 self.storage_shape = src_tens.storage_shape
519 self.brick_size = src_tens.brick_size
520 self.weight_compression_scales = src_tens.weight_compression_scales
521 self.weight_compressed_offsets = src_tens.weight_compressed_offsets
522 self.weight_transpose_depthwise = src_tens.weight_transpose_depthwise
523 self.compression_scale_for_worst_weight_stream = src_tens.compression_scale_for_worst_weight_stream
524 self.storage_compression_scale = src_tens.storage_compression_scale
Diqing Zhong7e1d1d12020-10-30 15:10:46 +0100525 self.bandwidth_compression_scale = src_tens.bandwidth_compression_scale
Louis Verhaard3c07c972020-05-07 08:12:58 +0200526 self.block_traversal = src_tens.block_traversal
527 self.weight_compression_config = src_tens.weight_compression_config
Louis Verhaard9db529a2020-09-23 10:27:11 +0200528 self.value_id = src_tens.value_id
Louis Verhaard3c07c972020-05-07 08:12:58 +0200529
Louis Verhaard93719a92020-12-08 10:02:31 +0100530 def set_format(self, fmt: TensorFormat, arch):
Tim Hall79d07d22020-04-27 18:20:16 +0100531 self.format = fmt
532 shape_len = 0
533 try:
534 shape_len = len(self.shape)
535 except TypeError:
536 pass
537
Louis Verhaard0411edb2020-11-16 16:37:11 +0100538 if shape_len > 4:
539 return
Louis Verhaard04bd3e92021-08-19 16:36:32 +0200540 assert not (self.needs_linear_format and fmt == TensorFormat.NHCWB16)
Tim Hall79d07d22020-04-27 18:20:16 +0100541 self.storage_rounding_quantum = arch.storage_rounding_quantums[self.format]
Louis Verhaard93719a92020-12-08 10:02:31 +0100542 self.storage_rounding_quantum = tuple(self.storage_rounding_quantum[-shape_len:])
Tim Hall79d07d22020-04-27 18:20:16 +0100543 self.brick_size = arch.brick_sizes[self.format]
Louis Verhaard93719a92020-12-08 10:02:31 +0100544 self.brick_size = tuple(self.brick_size[-shape_len:])
Tim Hall79d07d22020-04-27 18:20:16 +0100545 if self.shape is None:
546 return
547
548 self.bandwidth_shape = shape_round_to_quantum(self.shape, self.brick_size)
549 self.storage_shape = shape_round_to_quantum(self.shape, self.storage_rounding_quantum)
550
551 if fmt == TensorFormat.WeightsCompressed:
552 compression_ratio = 5 / 8
553 self.storage_compression_scale = compression_ratio
554 self.bandwidth_compression_scale = compression_ratio
555 self.compression_scale_for_worst_weight_stream = compression_ratio
556
Louis Verhaard93719a92020-12-08 10:02:31 +0100557 def storage_elements(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100558 elems = shape_num_elements(self.storage_shape)
559 if elems is None:
560 return 0
561 return elems
562
Louis Verhaard93719a92020-12-08 10:02:31 +0100563 def elements(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100564 elems = shape_num_elements(self.shape)
565 if elems is None:
566 return 0
567 return elems
568
Louis Verhaard93719a92020-12-08 10:02:31 +0100569 def has_fully_defined_shape(self) -> bool:
Tim Hall79d07d22020-04-27 18:20:16 +0100570 return shape_fully_defined(self.shape)
571
Louis Verhaard93719a92020-12-08 10:02:31 +0100572 def storage_size(self, scale: float = 1.0) -> int:
Patrik Gustavsson90831bc2020-08-24 16:26:11 +0200573 raw_size = self.storage_elements() * self.element_size() * scale
Tim Hall79d07d22020-04-27 18:20:16 +0100574 if raw_size == 0:
575 raw_size = 1 # force it to take up space
576 rounded_size = numeric_util.round_up(numeric_util.round_up_to_int(raw_size), self.alignment)
577 return rounded_size
578
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100579 def storage_size_for_shape(self, op_storage_shape: Shape) -> int:
580 elems = shape_num_elements(op_storage_shape)
581 elems = elems if elems else 0
582 raw_size = elems * self.element_size()
583 if raw_size == 0:
584 raw_size = 1 # force it to take up space
585 rounded_size = numeric_util.round_up(numeric_util.round_up_to_int(raw_size), self.alignment)
586 return rounded_size
587
Louis Verhaard93719a92020-12-08 10:02:31 +0100588 def storage_shape_for_sub_purpose(
589 self, sub_purpose: TensorSubPurpose, param_a: Optional[int], param_b: Optional[int]
590 ) -> Shape:
Tim Hall79d07d22020-04-27 18:20:16 +0100591 if sub_purpose == TensorSubPurpose.DoubleBuffer:
Jacob Bohline843d332020-06-23 12:12:56 +0200592 shp = list(self.shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100593 assert len(shp) >= 2
Louis Verhaard93719a92020-12-08 10:02:31 +0100594 assert param_a is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100595 shp[-1] = min(shp[-1], param_a * 2)
Tim Hall79d07d22020-04-27 18:20:16 +0100596 else:
Jacob Bohlinfad72042021-08-24 21:51:41 +0200597 shp = full_shape(4, self.storage_shape, 1)
Jacob Bohline843d332020-06-23 12:12:56 +0200598 if sub_purpose == TensorSubPurpose.RollingBufferX:
599 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100600 assert param_a is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200601 shp[0] = 1
602 shp[2] = min(shp[2], param_a)
603 elif sub_purpose == TensorSubPurpose.RollingBufferY:
604 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100605 assert param_a is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200606 shp[0] = 1
607 shp[1] = min(shp[1], param_a)
608 elif sub_purpose == TensorSubPurpose.RollingBufferXY:
609 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100610 assert param_a is not None
611 assert param_b is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200612 shp[0] = 1
613 shp[2] = min(shp[2], param_a)
614 shp[1] = min(shp[1], param_b)
615 elif sub_purpose == TensorSubPurpose.Standard:
616 pass
617 else:
618 assert 0, "did not expect new sub purpose %s" % (sub_purpose,)
619
Tim Hall79d07d22020-04-27 18:20:16 +0100620 return shp
621
Louis Verhaard93719a92020-12-08 10:02:31 +0100622 def set_new_sub_purpose(self, sub_purpose: TensorSubPurpose, param_a=None, param_b=None):
Tim Hall79d07d22020-04-27 18:20:16 +0100623 self.storage_shape = self.storage_shape_for_sub_purpose(sub_purpose, param_a, param_b)
624 self.sub_purpose = sub_purpose
625 if sub_purpose == TensorSubPurpose.DoubleBuffer:
626 self.storage_compression_scale = self.compression_scale_for_worst_weight_stream
627
Louis Verhaard93719a92020-12-08 10:02:31 +0100628 def bandwidth(self) -> float:
Tim Hall79d07d22020-04-27 18:20:16 +0100629 elems = shape_num_elements(self.bandwidth_shape)
630 if elems is None:
631 return 0
632 return elems * self.element_size() * self.bandwidth_compression_scale
633
Louis Verhaard93719a92020-12-08 10:02:31 +0100634 def consumers(self) -> List[Operation]:
Tim Hall79d07d22020-04-27 18:20:16 +0100635 return self.consumer_list
636
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100637 def get_4D_storage_shape_for_shape(self, op_shape4D: Shape4D) -> Shape4D:
638 rounding_quantum = full_shape(4, list(self.storage_rounding_quantum), 1)
639 return Shape4D(shape_round_to_quantum(op_shape4D.as_list(), rounding_quantum))
640
Rickard Bolin17e53b52022-09-06 16:09:01 +0000641 def addresses_for_rolling_buffer(
642 self, start_coord: Shape, end_coord: Shape, strides: List[int], op_shape4D: Shape4D
643 ) -> Tuple:
Tim Hall79d07d22020-04-27 18:20:16 +0100644 # returns ( box_height0, box_height1, box_width, [address_tl, address_tr, address_bl, address_br] )
645
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100646 if self.storage_shape == []:
647 return (
648 1,
649 1,
650 1,
Rickard Bolin17e53b52022-09-06 16:09:01 +0000651 [self.address_for_coordinate(start_coord, strides, op_shape4D), 0, 0, 0],
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100652 )
Tim Hall79d07d22020-04-27 18:20:16 +0100653
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100654 if self.is_standard_fm:
655 storage_shape_4D = self.get_4D_storage_shape_for_shape(op_shape4D)
656 else:
657 storage_shape_4D = Shape4D(self.storage_shape)
658
659 crossing_y = numeric_util.round_up(start_coord[1] + 1, storage_shape_4D.height)
660 crossing_x = numeric_util.round_up(start_coord[2] + 1, storage_shape_4D.width)
Tim Hall79d07d22020-04-27 18:20:16 +0100661
662 crossing_y = min(crossing_y, end_coord[1])
663 crossing_x = min(crossing_x, end_coord[2])
664
665 box_height0 = crossing_y - start_coord[1]
666 box_width = crossing_x - start_coord[2]
667
Rickard Bolin9ae34552022-06-09 13:07:17 +0000668 addresses: List = [0] * 4
Rickard Bolin17e53b52022-09-06 16:09:01 +0000669 addresses[0] = self.address_for_coordinate(start_coord, strides, op_shape4D)
Tim Hall79d07d22020-04-27 18:20:16 +0100670
671 if end_coord[2] > crossing_x:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100672 addresses[1] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000673 [start_coord[0], start_coord[1], crossing_x, start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100674 )
Michael McGeagh528a56d2020-12-16 11:33:21 +0000675 raise UnsupportedFeatureError("Striping in vertical direction is not supported")
Tim Hall79d07d22020-04-27 18:20:16 +0100676 if end_coord[1] > crossing_y:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100677 addresses[2] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000678 [start_coord[0], crossing_y, start_coord[2], start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100679 )
Tim Hall79d07d22020-04-27 18:20:16 +0100680 if end_coord[1] > crossing_y and end_coord[2] > crossing_x:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100681 addresses[3] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000682 [start_coord[0], crossing_y, crossing_x, start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100683 )
Tim Hall79d07d22020-04-27 18:20:16 +0100684
685 return box_height0, box_height0, box_width, addresses
686
Rickard Bolin17e53b52022-09-06 16:09:01 +0000687 def get_strides(self, shape4D: Optional[Shape4D]) -> List[int]:
Tim Hall79d07d22020-04-27 18:20:16 +0100688
Rickard Bolin17e53b52022-09-06 16:09:01 +0000689 augmented_shape = self.get_augmented_shape(shape4D)
690 assert len(augmented_shape) == 5
Louis Verhaard93719a92020-12-08 10:02:31 +0100691 strides: List = [0] * len(augmented_shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100692 stride = self.element_size() * self.storage_compression_scale
693
694 if self.format != TensorFormat.NHCWB16:
Louis Verhaard93719a92020-12-08 10:02:31 +0100695 stride_order = [4, 1, 3, 2, 0]
Tim Hall79d07d22020-04-27 18:20:16 +0100696 for i in stride_order:
697 strides[i] = stride
698 stride *= augmented_shape[i]
699 else:
Tim Hall79d07d22020-04-27 18:20:16 +0100700 strides[4] = stride
Patrik Gustavsson2213e902020-05-05 17:49:35 +0200701 strides[3] = 16 * stride # STRIDE_X
Tim Hall79d07d22020-04-27 18:20:16 +0100702 strides[1] = strides[3] * augmented_shape[2] # STRIDE_C
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200703 strides[2] = augmented_shape[2] * augmented_shape[3] * stride # STRIDE_Y
Tim Hall79d07d22020-04-27 18:20:16 +0100704 strides[0] = strides[2] * augmented_shape[1] # STRIDE_N
705
Tim Hall79d07d22020-04-27 18:20:16 +0100706 return strides
707
Rickard Bolin17e53b52022-09-06 16:09:01 +0000708 def get_augmented_shape(self, shape4D: Optional[Shape4D] = None) -> Optional[Shape]:
709
710 if shape4D and self.is_standard_fm:
711 augmented_shape = self.get_4D_storage_shape_for_shape(shape4D).as_list()
712 else:
713 augmented_shape = full_shape(4, self.storage_shape, 1)
714
715 if self.format == TensorFormat.NHWC:
716 augmented_shape = [augmented_shape[0], augmented_shape[3]] + augmented_shape[1:3] + [1]
717
718 elif self.format == TensorFormat.NHCWB16:
719 augmented_shape = augmented_shape[0:4] + [1]
720
721 if augmented_shape[1] == 0:
722 augmented_shape[1] = 1
723
724 else:
725 assert self.format in (TensorFormat.Unknown, TensorFormat.WeightsCompressed)
726 return None
727
728 return augmented_shape
729
730 def get_augmented_coord(self, coord: Optional[Shape] = None) -> Optional[Shape]:
731 if coord is None:
732 coord = [0] * min(len(self.storage_shape), 4)
733
734 missing_len = 4 - len(coord)
735 augmented_coord = ([0] * missing_len) + coord
736
737 if self.format == TensorFormat.NHWC:
738 augmented_coord = [augmented_coord[0], augmented_coord[3]] + augmented_coord[1:3] + [0]
739
740 elif self.format == TensorFormat.NHCWB16:
741 channel_divisor = 16
742 augmented_coord = (
743 [augmented_coord[0], augmented_coord[3] // channel_divisor]
744 + augmented_coord[1:3]
745 + [augmented_coord[3] % channel_divisor]
746 )
747 else:
748 assert self.format in (TensorFormat.Unknown, TensorFormat.WeightsCompressed)
749 return None
750
751 return augmented_coord
752
Louis Verhaard93719a92020-12-08 10:02:31 +0100753 def find_npu_op(self) -> Optional[Operation]:
Tim Halld8339a72021-05-27 18:49:40 +0100754 # Returns the NPU operator that uses this tensor
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200755 for op in self.consumers():
Dwight Lidman940fdee2020-08-13 13:11:48 +0200756 if op.run_on_npu:
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200757 return op
Louis Verhaard93719a92020-12-08 10:02:31 +0100758 return None
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200759
Louis Verhaard93719a92020-12-08 10:02:31 +0100760 def compressed_stream_index_from_coord(self, coord: Shape) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100761 assert self.format == TensorFormat.WeightsCompressed
Louis Verhaard93719a92020-12-08 10:02:31 +0100762 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100763 assert len(self.compressed_values) > 0
764 assert len(self.compressed_values) + 1 == len(self.weight_compressed_offsets)
765
766 depth = coord[-1]
767 brick_depth = self.brick_size[-1]
768 # Clamp position at final element index
769 if depth > self.shape[-1]:
770 depth = self.shape[-1]
771
772 # Always round up to next boundary
Michael McGeagh8d3216f2020-08-10 11:35:57 +0100773 index = numeric_util.round_up_divide(depth, brick_depth)
Tim Hall79d07d22020-04-27 18:20:16 +0100774
775 # Check boundaries on all but last weight set (which may be shorter
776 # than the brick we divided it up into)
777 if index < len(self.weight_compressed_offsets) - 1:
778 # There are no half-way points in the weights
779 if (depth % brick_depth) != 0:
Michael McGeagh528a56d2020-12-16 11:33:21 +0000780 raise UnsupportedFeatureError("Offset into weights must be aligned to a brick")
Tim Hall79d07d22020-04-27 18:20:16 +0100781
782 return index
783
Louis Verhaard93719a92020-12-08 10:02:31 +0100784 def size_of_compressed_stream(self, index: int) -> int:
785 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100786 assert 0 <= index < len(self.compressed_values)
787 return len(self.compressed_values[index])
788
Louis Verhaard93719a92020-12-08 10:02:31 +0100789 def is_last_index_in_compressed_stream(self, index: int) -> bool:
790 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100791 assert 0 <= index < len(self.compressed_values)
792 return index == len(self.compressed_values) - 1
793
Rickard Bolin17e53b52022-09-06 16:09:01 +0000794 def address_for_coordinate(
795 self,
796 orig_coord: Shape,
797 strides: Optional[List[int]] = None,
798 op_shape4D: Optional[Shape4D] = None,
799 is_top_box: bool = False,
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100800 ) -> Optional[int]:
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000801
Tim Hall79d07d22020-04-27 18:20:16 +0100802 address_offset = 0
Tim Halld8339a72021-05-27 18:49:40 +0100803 assert self.purpose != TensorPurpose.Weights
Tim Hall79d07d22020-04-27 18:20:16 +0100804
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000805 # Strides may be passed as an argument, for example when creating feature maps as the strides may be modified
806 # by the "ofm_stride_multiplier" operation attribute. If not, they are calculated here.
807 if not strides:
808 strides = self.get_strides(op_shape4D)
809
810 coord = orig_coord
811 if is_top_box:
812 coord = [c - 1 for c in orig_coord]
813 address_offset += 1 * strides[-1] # one element
814
Tim Hall79d07d22020-04-27 18:20:16 +0100815 if self.sub_purpose == TensorSubPurpose.Standard:
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100816 shape = op_shape4D.as_list() if op_shape4D else self.shape
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000817 for _coord, _shape in zip(coord, shape):
818 assert _coord >= 0 and _coord < _shape
819
Tim Halld8339a72021-05-27 18:49:40 +0100820 if op_shape4D and self.is_standard_fm:
821 storage_shape = self.get_4D_storage_shape_for_shape(op_shape4D).as_list()
822 storage_size = self.storage_size_for_shape(storage_shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100823 else:
Tim Halld8339a72021-05-27 18:49:40 +0100824 storage_shape = self.storage_shape
825 coord = coord[-len(storage_shape) :]
826 storage_size = self.storage_size()
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100827
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000828 # Handle wraparound for partial buffers. Make sure to do this after subtracting top box
829 coord = [_coord % _shape for _coord, _shape in zip(coord, storage_shape)]
Tim Hall79d07d22020-04-27 18:20:16 +0100830
Rickard Bolin17e53b52022-09-06 16:09:01 +0000831 augmented_coord = self.get_augmented_coord(coord)
832 assert augmented_coord is not None
833
Tim Halld8339a72021-05-27 18:49:40 +0100834 address_offset += np.dot(augmented_coord, strides)
Tim Hall79d07d22020-04-27 18:20:16 +0100835
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000836 assert address_offset >= 0 and address_offset <= storage_size
Rickard Bolin17e53b52022-09-06 16:09:01 +0000837 return self.address + address_offset
Tim Hall79d07d22020-04-27 18:20:16 +0100838
Louis Verhaard93719a92020-12-08 10:02:31 +0100839 def is_allocated_in_tensor_arena(self, scratch_tensor_mem_area: MemArea) -> bool:
Michael McGeaghf3e3ad72020-12-02 12:39:03 +0000840 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 +0200841
Louis Verhaard93719a92020-12-08 10:02:31 +0100842 def equivalent(self, tens: "Tensor") -> bool:
Louis Verhaard0b8268a2020-08-05 16:11:29 +0200843 return self.equivalence_id == tens.equivalence_id
844
Louis Verhaard93719a92020-12-08 10:02:31 +0100845 def set_all_shapes(self, shape: Shape):
Michael McGeagh6a8d4242020-07-28 12:17:59 +0100846 self.shape = shape
847 self.storage_shape = shape
848 self.bandwidth_shape = shape
849
Louis Verhaard93719a92020-12-08 10:02:31 +0100850 def get_full_shape(self) -> Shape:
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100851 d = len(self.shape)
852 if d in (1, 3):
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100853 return full_shape(4, self.shape, 1)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100854 elif d == 2:
855 return [self.shape[0], 1, 1, self.shape[1]]
856 else:
Fredrik Svedberg835d8e12020-09-04 09:46:17 +0200857 return self.shape.copy()
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100858
Louis Verhaard93719a92020-12-08 10:02:31 +0100859 def is_quantized(self) -> bool:
Tim Hall93582962020-09-09 21:58:15 +0100860 # a tensor is quantized if it has an integral type and it contains valid quantization params
861
Tim Hall89567612020-10-27 11:57:57 +0000862 if not isinstance(self.quantization, QuantizationParameters):
Tim Hall93582962020-09-09 21:58:15 +0100863 return False
864
Tim Hall89567612020-10-27 11:57:57 +0000865 return (self.dtype.type & BaseType.Int) != 0 and self.quantization.is_valid()
Tim Hall93582962020-09-09 21:58:15 +0100866
James Peet7519d502021-07-19 16:47:58 +0100867 def get_scalar(self):
868 """
869 return: Unquantized or dequantized scalar value
870 rtype: self.dtype (if unquantized) or float (if dequantized)
871 """
872 assert self.values.size == 1, "get_scalar called on non-scalar tensor"
873 if self.is_quantized():
874 return self.quantization.dequantize(self.values).item(0)
875 else:
876 return self.values.item(0)
877
Ayaan Masooda2ec5aa2022-04-21 14:28:03 +0100878 def get_shape_as_2d(self, dimension_2_size: int) -> Optional[Shape4D]:
879
880 elms = self.elements()
881 dimension_1_size = elms // dimension_2_size
882 # Checks if the reduction works and shape is not 1D
883 is_reducible = dimension_1_size * dimension_2_size == elms and not (len(self.shape) == 1)
884
885 new_shape = None
886 if is_reducible:
887 new_shape = Shape4D([dimension_1_size, 1, 1, dimension_2_size])
888
889 return new_shape
890
Louis Verhaard6c74c3b2020-12-17 13:54:09 +0100891 def __lt__(self, other: "Tensor") -> bool:
892 return self.equivalence_id < other.equivalence_id
893
Tim Hall79d07d22020-04-27 18:20:16 +0100894 def __str__(self):
895 return "<nng.Tensor '%s' shape=%s dtype=%s>" % (self.name, self.shape, self.dtype)
896
897 __repr__ = __str__
Tim Hall93582962020-09-09 21:58:15 +0100898
Michael McGeagh528a56d2020-12-16 11:33:21 +0000899 def error(self, msg):
900 """
901 Raises a VelaError exception for errors encountered when parsing a Tensor
902
903 :param self: Tensor object that resulted in the error
904 :param msg: str object that contains a description of the specific error encountered
905 """
906
907 def _print_operators(ops):
908 lines = []
909 for idx, op in enumerate(ops):
910 op_type = getattr(op, "type", "Not an Operation")
911 op_id = getattr(op, "op_index", "-")
912 lines.append(f" {idx} = {op_type} ({op_id})")
913 return lines
914
915 lines = [f"Invalid {self.name} tensor. {msg}"]
916
917 lines += [" Driving operators:"]
918 lines += _print_operators(self.ops)
919
920 lines += [" Consuming operators:"]
921 lines += _print_operators(self.consumer_list)
922
923 raise VelaError("\n".join(lines))
924
Tim Hall93582962020-09-09 21:58:15 +0100925
Louis Verhaard93719a92020-12-08 10:02:31 +0100926def check_quantized_tens_scaling_equal(tens_a: Tensor, tens_b: Tensor) -> bool:
Tim Hall93582962020-09-09 21:58:15 +0100927 # checks that the scaling of two quantized tensors are equal
928
Tim Hall89567612020-10-27 11:57:57 +0000929 return tens_a.is_quantized() and tens_b.is_quantized() and tens_a.quantization.is_scaling_equal(tens_b.quantization)