blob: 899b1bedf209ee3371e8f41706d52dc40f97fd7c [file] [log] [blame]
Rickard Bolinbc6ee582022-11-04 08:24:29 +00001# SPDX-FileCopyrightText: Copyright 2020-2022 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,
303 dtype: DataType,
304 values: np.ndarray,
305 value_dtype: np.dtype = None,
306 purpose: TensorPurpose = TensorPurpose.Unknown,
307 quantization: QuantizationParameters = None,
308):
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100309 # Tensor
310 const_tensor = Tensor(shape, dtype, name + "_0")
311 const_tensor.purpose = purpose
312 const_tensor.quantization = quantization
313 const_tensor.values = np.array(values, dtype=value_dtype)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100314 # Operator
Louis Verhaardaee5d752020-09-30 09:01:52 +0200315 const_op = Operation(Op.Const, name)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100316 const_op.set_output_tensor(const_tensor)
patrik.gustavssoneeb85152020-12-21 17:10:40 +0000317 const_op.set_ifm_ofm_shapes()
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100318 return const_tensor
319
320
Jacob Bohlin1a666972020-09-11 10:04:15 +0200321# class that keeps track of all tensor addresses in the different memory types
322class TensorAddressMap:
Louis Verhaard93719a92020-12-08 10:02:31 +0100323 address_map: Dict = defaultdict(dict) # dict (tens.equivalence_id -> dict (mem_type -> address))
Jacob Bohlin1a666972020-09-11 10:04:15 +0200324
325 @classmethod
Louis Verhaard93719a92020-12-08 10:02:31 +0100326 def get_address_for_tens(cls, tens_id: UUID, mem_type: MemType) -> int:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200327 return cls.address_map[tens_id].get(mem_type)
328
329 @classmethod
Louis Verhaard93719a92020-12-08 10:02:31 +0100330 def set_address_for_tens(cls, tens_id: UUID, mem_type: MemType, address: int):
Jacob Bohlin1a666972020-09-11 10:04:15 +0200331 # Check previous address if there is one
332 previous_address = cls.address_map[tens_id].get(mem_type)
Louis Verhaard0b9c9a32020-09-15 14:05:38 +0200333 if address is not None and previous_address is not None:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200334 assert previous_address == address, "Two different addresses cannot be assigned to the same tensor."
335
336 # Set tensor's address for memory type
337 cls.address_map[tens_id][mem_type] = address
338
339
Louis Verhaard6c74c3b2020-12-17 13:54:09 +0100340@total_ordering
Tim Hall79d07d22020-04-27 18:20:16 +0100341class Tensor:
342 __slots__ = (
343 "shape",
Johan Alfvénb9f81592022-10-31 14:39:02 +0100344 "_original_shape",
Tim Hall79d07d22020-04-27 18:20:16 +0100345 "storage_shape",
346 "bandwidth_shape",
347 "dtype",
348 "name",
Fredrik Svedberg8d0f4892021-02-16 21:59:50 +0100349 "is_variable",
Tim Halld8339a72021-05-27 18:49:40 +0100350 "pre_buffer",
Tim Hall79d07d22020-04-27 18:20:16 +0100351 "ops",
352 "consumer_list",
353 "values",
Tim Hall79d07d22020-04-27 18:20:16 +0100354 "compressed_values",
Tim Hallf7e810a2020-06-25 15:04:31 +0100355 "compressed_values_substream_offsets",
Tim Hall79d07d22020-04-27 18:20:16 +0100356 "mem_area",
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200357 "mem_type",
Tim Hall79d07d22020-04-27 18:20:16 +0100358 "format",
359 "purpose",
360 "sub_purpose",
361 "alignment",
362 "weight_transpose_depthwise",
363 "storage_compression_scale",
364 "bandwidth_compression_scale",
365 "compression_scale_for_worst_weight_stream",
366 "weight_compression_scales",
367 "weight_compression_config",
Louis Verhaard9db529a2020-09-23 10:27:11 +0200368 "value_id",
Tim Hall79d07d22020-04-27 18:20:16 +0100369 "storage_rounding_quantum",
370 "brick_size",
Tim Hall79d07d22020-04-27 18:20:16 +0100371 "quantization",
372 "weight_compressed_offsets",
373 "element_size_bytes",
Tim Hall79d07d22020-04-27 18:20:16 +0100374 "block_traversal",
Tim Hall79d07d22020-04-27 18:20:16 +0100375 "equivalence_id",
Tim Halld8339a72021-05-27 18:49:40 +0100376 "src_tensor",
Patrik Gustavssonee99bb12021-04-08 09:04:00 +0200377 "needs_linear_format",
Johan Alfvén8d57aaa2022-02-04 11:19:17 +0100378 "ifm_write_protected",
Tim Hall79d07d22020-04-27 18:20:16 +0100379 )
380 AllocationQuantum = 16
381
Louis Verhaard93719a92020-12-08 10:02:31 +0100382 def __init__(self, shape: Shape, dtype: DataType, name: str):
Tim Hall79d07d22020-04-27 18:20:16 +0100383 self.shape = shape
Johan Alfvénb9f81592022-10-31 14:39:02 +0100384 self._original_shape = shape
Tim Hall79d07d22020-04-27 18:20:16 +0100385 self.storage_shape = shape
386 self.bandwidth_shape = shape
387 self.dtype = dtype
388 self.name = name
Fredrik Svedberg8d0f4892021-02-16 21:59:50 +0100389 self.is_variable = False
Tim Halld8339a72021-05-27 18:49:40 +0100390 self.pre_buffer = False
Louis Verhaard93719a92020-12-08 10:02:31 +0100391 self.equivalence_id: UUID = uuid.uuid4()
Tim Hall79d07d22020-04-27 18:20:16 +0100392
Louis Verhaard93719a92020-12-08 10:02:31 +0100393 self.ops: List[Operation] = []
394 self.consumer_list: List[Operation] = []
Tim Hall79d07d22020-04-27 18:20:16 +0100395
James Peet7519d502021-07-19 16:47:58 +0100396 self.values: Optional[np.ndarray] = None # elements are of type self.dtype
Louis Verhaard93719a92020-12-08 10:02:31 +0100397 self.compressed_values: Optional[np.ndarray] = None
398 self.compressed_values_substream_offsets: Optional[List] = None
399 self.mem_area: MemArea = MemArea.Unknown
400 self.mem_type: MemType = MemType.Unknown
401 self.format: TensorFormat = TensorFormat.Unknown
402 self.purpose: TensorPurpose = TensorPurpose.Unknown
403 self.sub_purpose: TensorSubPurpose = TensorSubPurpose.Standard
404 self.alignment: int = Tensor.AllocationQuantum
405 self.weight_transpose_depthwise: bool = False
Tim Hall79d07d22020-04-27 18:20:16 +0100406
Louis Verhaard93719a92020-12-08 10:02:31 +0100407 self.storage_compression_scale: float = 1.0
408 self.bandwidth_compression_scale: float = 1.0
409 self.compression_scale_for_worst_weight_stream: float = 1.0
410 self.weight_compression_scales: Optional[np.ndarray] = None
Louis Verhaard9db529a2020-09-23 10:27:11 +0200411 # if two tensors have the same weight_compression_config, then they have the same compressed values
Tim Hall79d07d22020-04-27 18:20:16 +0100412 self.weight_compression_config = None
Louis Verhaard9db529a2020-09-23 10:27:11 +0200413 # if two tensors have the same value_id, then they have the same values
Louis Verhaard93719a92020-12-08 10:02:31 +0100414 self.value_id: UUID = uuid.uuid4()
415 self.weight_compressed_offsets: List = []
416 self.storage_rounding_quantum: Tuple = (1, 1, 1, 1)
417 self.brick_size: Tuple = (1, 1, 1, 1)
418 self.element_size_bytes: int = 0
Tim Hall79d07d22020-04-27 18:20:16 +0100419
420 # quantization parameters
Louis Verhaard93719a92020-12-08 10:02:31 +0100421 self.quantization: Optional[QuantizationParameters] = None
422 self.block_traversal: TensorBlockTraversal = TensorBlockTraversal.Default
Tim Hall79d07d22020-04-27 18:20:16 +0100423
Patrik Gustavssonee99bb12021-04-08 09:04:00 +0200424 self.needs_linear_format = True
Johan Alfvén8d57aaa2022-02-04 11:19:17 +0100425 self.ifm_write_protected = False
Patrik Gustavsson458a2082020-08-13 13:41:05 +0200426
Tim Halld8339a72021-05-27 18:49:40 +0100427 # Reference to parent-tensor if this tensor is a clone
Jonas Ohlsson845e2322022-03-01 12:39:55 +0100428 self.src_tensor: Optional[Tensor] = None
Tim Halld8339a72021-05-27 18:49:40 +0100429
Jacob Bohlin1a666972020-09-11 10:04:15 +0200430 @property
Johan Alfvénb9f81592022-10-31 14:39:02 +0100431 def original_shape(self):
432 return self._original_shape
433
434 @property
Louis Verhaard93719a92020-12-08 10:02:31 +0100435 def address(self) -> int:
Jacob Bohlin1a666972020-09-11 10:04:15 +0200436 return TensorAddressMap.get_address_for_tens(self.equivalence_id, self.mem_type)
437
438 @address.setter
Louis Verhaard93719a92020-12-08 10:02:31 +0100439 def address(self, address: int):
Jacob Bohlin1a666972020-09-11 10:04:15 +0200440 TensorAddressMap.set_address_for_tens(self.equivalence_id, self.mem_type, address)
441
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100442 @property
443 def is_standard_fm(self) -> bool:
444 return self.sub_purpose == TensorSubPurpose.Standard and self.purpose == TensorPurpose.FeatureMap
445
Johan Alfvén0f2e59f2022-10-21 11:21:38 +0200446 @property
447 def is_const(self) -> bool:
448 return self.ops != [] and self.ops[0].type == Op.Const
449
450 @property
451 def is_scalar(self) -> bool:
452 return self.shape == [] and self.elements() == 1
453
454 def is_broadcast(self, ofm) -> bool:
455 return self.shape != ofm.shape
456
Louis Verhaard93719a92020-12-08 10:02:31 +0100457 def element_size(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100458 if self.element_size_bytes == 0:
Diqing Zhonge3d18b02021-11-15 13:53:10 +0100459 return self.dtype.size_in_bits() // 8
Tim Hall79d07d22020-04-27 18:20:16 +0100460 return self.element_size_bytes
461
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100462 # Returns a copy, renamed to self.name + suffix
463 # The references to Operators will be empty when returned
464 # Depending on set_unique, the copy is shallow, or deep
465 # For set_unique==True, a new equivalence_id will be set
Louis Verhaard93719a92020-12-08 10:02:31 +0100466 def clone(self, suffix="_clone", set_unique: bool = False) -> "Tensor":
erik.andersson@arm.com42b94ed2021-02-11 14:02:08 +0100467 res = copy.copy(self)
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100468 if set_unique:
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100469 res.equivalence_id = uuid.uuid4()
erik.andersson@arm.com42b94ed2021-02-11 14:02:08 +0100470 res.storage_shape = list(self.storage_shape)
471 res.bandwidth_shape = list(self.bandwidth_shape)
472 if self.quantization is not None:
473 res.quantization = self.quantization.clone()
Tim Hall79d07d22020-04-27 18:20:16 +0100474
Patrik Gustavsson6ae0e422020-11-04 12:43:50 +0100475 res.name = res.name + suffix
Tim Hall79d07d22020-04-27 18:20:16 +0100476 res.ops = []
477 res.consumer_list = []
Tim Hall79d07d22020-04-27 18:20:16 +0100478
Tim Hall79d07d22020-04-27 18:20:16 +0100479 return res
480
Louis Verhaard93719a92020-12-08 10:02:31 +0100481 def clone_into_fast_storage(self, arch) -> "Tensor":
Tim Hall79d07d22020-04-27 18:20:16 +0100482 res = self.clone(suffix="_fast_storage")
483 res.mem_area = arch.fast_storage_mem_area
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200484 res.mem_type = MemType.Scratch_fast
Tim Halld8339a72021-05-27 18:49:40 +0100485 res.src_tensor = self
Tim Hall79d07d22020-04-27 18:20:16 +0100486 return res
487
Tim Hall92cd33b2022-11-03 12:25:33 +0000488 def as_1D(self):
489 self.shape = [np.prod(self.shape)]
490 if self.values is not None:
491 self.values = self.values.reshape(self.shape)
492
493 def transpose(self, reorder):
494 self.shape = [self.shape[idx] for idx in reorder]
495 self._original_shape = [self._original_shape[idx] for idx in reorder]
496 if self.values is not None:
497 self.values = self.values.transpose(reorder)
498
Louis Verhaard93719a92020-12-08 10:02:31 +0100499 def copy_compressed_weight_info(self, src_tens: "Tensor"):
Louis Verhaard3c07c972020-05-07 08:12:58 +0200500 # Copies compressed values + all related weight compression info from the given tensor
Louis Verhaard9db529a2020-09-23 10:27:11 +0200501 self.equivalence_id = src_tens.equivalence_id
Louis Verhaard3c07c972020-05-07 08:12:58 +0200502 self.compressed_values = src_tens.compressed_values
Tim Hallf7e810a2020-06-25 15:04:31 +0100503 self.compressed_values_substream_offsets = src_tens.compressed_values_substream_offsets
Louis Verhaard3c07c972020-05-07 08:12:58 +0200504 self.storage_shape = src_tens.storage_shape
505 self.brick_size = src_tens.brick_size
506 self.weight_compression_scales = src_tens.weight_compression_scales
507 self.weight_compressed_offsets = src_tens.weight_compressed_offsets
508 self.weight_transpose_depthwise = src_tens.weight_transpose_depthwise
509 self.compression_scale_for_worst_weight_stream = src_tens.compression_scale_for_worst_weight_stream
510 self.storage_compression_scale = src_tens.storage_compression_scale
Diqing Zhong7e1d1d12020-10-30 15:10:46 +0100511 self.bandwidth_compression_scale = src_tens.bandwidth_compression_scale
Louis Verhaard3c07c972020-05-07 08:12:58 +0200512 self.block_traversal = src_tens.block_traversal
513 self.weight_compression_config = src_tens.weight_compression_config
Louis Verhaard9db529a2020-09-23 10:27:11 +0200514 self.value_id = src_tens.value_id
Louis Verhaard3c07c972020-05-07 08:12:58 +0200515
Louis Verhaard93719a92020-12-08 10:02:31 +0100516 def set_format(self, fmt: TensorFormat, arch):
Tim Hall79d07d22020-04-27 18:20:16 +0100517 self.format = fmt
518 shape_len = 0
519 try:
520 shape_len = len(self.shape)
521 except TypeError:
522 pass
523
Louis Verhaard0411edb2020-11-16 16:37:11 +0100524 if shape_len > 4:
525 return
Louis Verhaard04bd3e92021-08-19 16:36:32 +0200526 assert not (self.needs_linear_format and fmt == TensorFormat.NHCWB16)
Tim Hall79d07d22020-04-27 18:20:16 +0100527 self.storage_rounding_quantum = arch.storage_rounding_quantums[self.format]
Louis Verhaard93719a92020-12-08 10:02:31 +0100528 self.storage_rounding_quantum = tuple(self.storage_rounding_quantum[-shape_len:])
Tim Hall79d07d22020-04-27 18:20:16 +0100529 self.brick_size = arch.brick_sizes[self.format]
Louis Verhaard93719a92020-12-08 10:02:31 +0100530 self.brick_size = tuple(self.brick_size[-shape_len:])
Tim Hall79d07d22020-04-27 18:20:16 +0100531 if self.shape is None:
532 return
533
534 self.bandwidth_shape = shape_round_to_quantum(self.shape, self.brick_size)
535 self.storage_shape = shape_round_to_quantum(self.shape, self.storage_rounding_quantum)
536
537 if fmt == TensorFormat.WeightsCompressed:
538 compression_ratio = 5 / 8
539 self.storage_compression_scale = compression_ratio
540 self.bandwidth_compression_scale = compression_ratio
541 self.compression_scale_for_worst_weight_stream = compression_ratio
542
Louis Verhaard93719a92020-12-08 10:02:31 +0100543 def storage_elements(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100544 elems = shape_num_elements(self.storage_shape)
545 if elems is None:
546 return 0
547 return elems
548
Louis Verhaard93719a92020-12-08 10:02:31 +0100549 def elements(self) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100550 elems = shape_num_elements(self.shape)
551 if elems is None:
552 return 0
553 return elems
554
Louis Verhaard93719a92020-12-08 10:02:31 +0100555 def has_fully_defined_shape(self) -> bool:
Tim Hall79d07d22020-04-27 18:20:16 +0100556 return shape_fully_defined(self.shape)
557
Louis Verhaard93719a92020-12-08 10:02:31 +0100558 def storage_size(self, scale: float = 1.0) -> int:
Patrik Gustavsson90831bc2020-08-24 16:26:11 +0200559 raw_size = self.storage_elements() * self.element_size() * scale
Tim Hall79d07d22020-04-27 18:20:16 +0100560 if raw_size == 0:
561 raw_size = 1 # force it to take up space
562 rounded_size = numeric_util.round_up(numeric_util.round_up_to_int(raw_size), self.alignment)
563 return rounded_size
564
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100565 def storage_size_for_shape(self, op_storage_shape: Shape) -> int:
566 elems = shape_num_elements(op_storage_shape)
567 elems = elems if elems else 0
568 raw_size = elems * self.element_size()
569 if raw_size == 0:
570 raw_size = 1 # force it to take up space
571 rounded_size = numeric_util.round_up(numeric_util.round_up_to_int(raw_size), self.alignment)
572 return rounded_size
573
Louis Verhaard93719a92020-12-08 10:02:31 +0100574 def storage_shape_for_sub_purpose(
575 self, sub_purpose: TensorSubPurpose, param_a: Optional[int], param_b: Optional[int]
576 ) -> Shape:
Tim Hall79d07d22020-04-27 18:20:16 +0100577 if sub_purpose == TensorSubPurpose.DoubleBuffer:
Jacob Bohline843d332020-06-23 12:12:56 +0200578 shp = list(self.shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100579 assert len(shp) >= 2
Louis Verhaard93719a92020-12-08 10:02:31 +0100580 assert param_a is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100581 shp[-1] = min(shp[-1], param_a * 2)
Tim Hall79d07d22020-04-27 18:20:16 +0100582 else:
Jacob Bohlinfad72042021-08-24 21:51:41 +0200583 shp = full_shape(4, self.storage_shape, 1)
Jacob Bohline843d332020-06-23 12:12:56 +0200584 if sub_purpose == TensorSubPurpose.RollingBufferX:
585 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100586 assert param_a is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200587 shp[0] = 1
588 shp[2] = min(shp[2], param_a)
589 elif sub_purpose == TensorSubPurpose.RollingBufferY:
590 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100591 assert param_a is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200592 shp[0] = 1
593 shp[1] = min(shp[1], param_a)
594 elif sub_purpose == TensorSubPurpose.RollingBufferXY:
595 assert len(shp) == 4
Louis Verhaard93719a92020-12-08 10:02:31 +0100596 assert param_a is not None
597 assert param_b is not None
Jacob Bohline843d332020-06-23 12:12:56 +0200598 shp[0] = 1
599 shp[2] = min(shp[2], param_a)
600 shp[1] = min(shp[1], param_b)
601 elif sub_purpose == TensorSubPurpose.Standard:
602 pass
603 else:
604 assert 0, "did not expect new sub purpose %s" % (sub_purpose,)
605
Tim Hall79d07d22020-04-27 18:20:16 +0100606 return shp
607
Louis Verhaard93719a92020-12-08 10:02:31 +0100608 def set_new_sub_purpose(self, sub_purpose: TensorSubPurpose, param_a=None, param_b=None):
Tim Hall79d07d22020-04-27 18:20:16 +0100609 self.storage_shape = self.storage_shape_for_sub_purpose(sub_purpose, param_a, param_b)
610 self.sub_purpose = sub_purpose
611 if sub_purpose == TensorSubPurpose.DoubleBuffer:
612 self.storage_compression_scale = self.compression_scale_for_worst_weight_stream
613
Louis Verhaard93719a92020-12-08 10:02:31 +0100614 def bandwidth(self) -> float:
Tim Hall79d07d22020-04-27 18:20:16 +0100615 elems = shape_num_elements(self.bandwidth_shape)
616 if elems is None:
617 return 0
618 return elems * self.element_size() * self.bandwidth_compression_scale
619
Louis Verhaard93719a92020-12-08 10:02:31 +0100620 def consumers(self) -> List[Operation]:
Tim Hall79d07d22020-04-27 18:20:16 +0100621 return self.consumer_list
622
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100623 def get_4D_storage_shape_for_shape(self, op_shape4D: Shape4D) -> Shape4D:
624 rounding_quantum = full_shape(4, list(self.storage_rounding_quantum), 1)
625 return Shape4D(shape_round_to_quantum(op_shape4D.as_list(), rounding_quantum))
626
Rickard Bolin17e53b52022-09-06 16:09:01 +0000627 def addresses_for_rolling_buffer(
628 self, start_coord: Shape, end_coord: Shape, strides: List[int], op_shape4D: Shape4D
629 ) -> Tuple:
Tim Hall79d07d22020-04-27 18:20:16 +0100630 # returns ( box_height0, box_height1, box_width, [address_tl, address_tr, address_bl, address_br] )
631
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100632 if self.storage_shape == []:
633 return (
634 1,
635 1,
636 1,
Rickard Bolin17e53b52022-09-06 16:09:01 +0000637 [self.address_for_coordinate(start_coord, strides, op_shape4D), 0, 0, 0],
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100638 )
Tim Hall79d07d22020-04-27 18:20:16 +0100639
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100640 if self.is_standard_fm:
641 storage_shape_4D = self.get_4D_storage_shape_for_shape(op_shape4D)
642 else:
643 storage_shape_4D = Shape4D(self.storage_shape)
644
645 crossing_y = numeric_util.round_up(start_coord[1] + 1, storage_shape_4D.height)
646 crossing_x = numeric_util.round_up(start_coord[2] + 1, storage_shape_4D.width)
Tim Hall79d07d22020-04-27 18:20:16 +0100647
648 crossing_y = min(crossing_y, end_coord[1])
649 crossing_x = min(crossing_x, end_coord[2])
650
651 box_height0 = crossing_y - start_coord[1]
652 box_width = crossing_x - start_coord[2]
653
Rickard Bolin9ae34552022-06-09 13:07:17 +0000654 addresses: List = [0] * 4
Rickard Bolin17e53b52022-09-06 16:09:01 +0000655 addresses[0] = self.address_for_coordinate(start_coord, strides, op_shape4D)
Tim Hall79d07d22020-04-27 18:20:16 +0100656
657 if end_coord[2] > crossing_x:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100658 addresses[1] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000659 [start_coord[0], start_coord[1], crossing_x, start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100660 )
Michael McGeagh528a56d2020-12-16 11:33:21 +0000661 raise UnsupportedFeatureError("Striping in vertical direction is not supported")
Tim Hall79d07d22020-04-27 18:20:16 +0100662 if end_coord[1] > crossing_y:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100663 addresses[2] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000664 [start_coord[0], crossing_y, start_coord[2], start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100665 )
Tim Hall79d07d22020-04-27 18:20:16 +0100666 if end_coord[1] > crossing_y and end_coord[2] > crossing_x:
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100667 addresses[3] = self.address_for_coordinate(
Rickard Bolin17e53b52022-09-06 16:09:01 +0000668 [start_coord[0], crossing_y, crossing_x, start_coord[3]], strides, op_shape4D
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100669 )
Tim Hall79d07d22020-04-27 18:20:16 +0100670
671 return box_height0, box_height0, box_width, addresses
672
Rickard Bolin17e53b52022-09-06 16:09:01 +0000673 def get_strides(self, shape4D: Optional[Shape4D]) -> List[int]:
Tim Hall79d07d22020-04-27 18:20:16 +0100674
Rickard Bolin17e53b52022-09-06 16:09:01 +0000675 augmented_shape = self.get_augmented_shape(shape4D)
676 assert len(augmented_shape) == 5
Louis Verhaard93719a92020-12-08 10:02:31 +0100677 strides: List = [0] * len(augmented_shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100678 stride = self.element_size() * self.storage_compression_scale
679
680 if self.format != TensorFormat.NHCWB16:
Louis Verhaard93719a92020-12-08 10:02:31 +0100681 stride_order = [4, 1, 3, 2, 0]
Tim Hall79d07d22020-04-27 18:20:16 +0100682 for i in stride_order:
683 strides[i] = stride
684 stride *= augmented_shape[i]
685 else:
Tim Hall79d07d22020-04-27 18:20:16 +0100686 strides[4] = stride
Patrik Gustavsson2213e902020-05-05 17:49:35 +0200687 strides[3] = 16 * stride # STRIDE_X
Tim Hall79d07d22020-04-27 18:20:16 +0100688 strides[1] = strides[3] * augmented_shape[2] # STRIDE_C
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200689 strides[2] = augmented_shape[2] * augmented_shape[3] * stride # STRIDE_Y
Tim Hall79d07d22020-04-27 18:20:16 +0100690 strides[0] = strides[2] * augmented_shape[1] # STRIDE_N
691
Tim Hall79d07d22020-04-27 18:20:16 +0100692 return strides
693
Rickard Bolin17e53b52022-09-06 16:09:01 +0000694 def get_augmented_shape(self, shape4D: Optional[Shape4D] = None) -> Optional[Shape]:
695
696 if shape4D and self.is_standard_fm:
697 augmented_shape = self.get_4D_storage_shape_for_shape(shape4D).as_list()
698 else:
699 augmented_shape = full_shape(4, self.storage_shape, 1)
700
701 if self.format == TensorFormat.NHWC:
702 augmented_shape = [augmented_shape[0], augmented_shape[3]] + augmented_shape[1:3] + [1]
703
704 elif self.format == TensorFormat.NHCWB16:
705 augmented_shape = augmented_shape[0:4] + [1]
706
707 if augmented_shape[1] == 0:
708 augmented_shape[1] = 1
709
710 else:
711 assert self.format in (TensorFormat.Unknown, TensorFormat.WeightsCompressed)
712 return None
713
714 return augmented_shape
715
716 def get_augmented_coord(self, coord: Optional[Shape] = None) -> Optional[Shape]:
717 if coord is None:
718 coord = [0] * min(len(self.storage_shape), 4)
719
720 missing_len = 4 - len(coord)
721 augmented_coord = ([0] * missing_len) + coord
722
723 if self.format == TensorFormat.NHWC:
724 augmented_coord = [augmented_coord[0], augmented_coord[3]] + augmented_coord[1:3] + [0]
725
726 elif self.format == TensorFormat.NHCWB16:
727 channel_divisor = 16
728 augmented_coord = (
729 [augmented_coord[0], augmented_coord[3] // channel_divisor]
730 + augmented_coord[1:3]
731 + [augmented_coord[3] % channel_divisor]
732 )
733 else:
734 assert self.format in (TensorFormat.Unknown, TensorFormat.WeightsCompressed)
735 return None
736
737 return augmented_coord
738
Louis Verhaard93719a92020-12-08 10:02:31 +0100739 def find_npu_op(self) -> Optional[Operation]:
Tim Halld8339a72021-05-27 18:49:40 +0100740 # Returns the NPU operator that uses this tensor
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200741 for op in self.consumers():
Dwight Lidman940fdee2020-08-13 13:11:48 +0200742 if op.run_on_npu:
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200743 return op
Louis Verhaard93719a92020-12-08 10:02:31 +0100744 return None
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200745
Louis Verhaard93719a92020-12-08 10:02:31 +0100746 def compressed_stream_index_from_coord(self, coord: Shape) -> int:
Tim Hall79d07d22020-04-27 18:20:16 +0100747 assert self.format == TensorFormat.WeightsCompressed
Louis Verhaard93719a92020-12-08 10:02:31 +0100748 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100749 assert len(self.compressed_values) > 0
750 assert len(self.compressed_values) + 1 == len(self.weight_compressed_offsets)
751
752 depth = coord[-1]
753 brick_depth = self.brick_size[-1]
754 # Clamp position at final element index
755 if depth > self.shape[-1]:
756 depth = self.shape[-1]
757
758 # Always round up to next boundary
Michael McGeagh8d3216f2020-08-10 11:35:57 +0100759 index = numeric_util.round_up_divide(depth, brick_depth)
Tim Hall79d07d22020-04-27 18:20:16 +0100760
761 # Check boundaries on all but last weight set (which may be shorter
762 # than the brick we divided it up into)
763 if index < len(self.weight_compressed_offsets) - 1:
764 # There are no half-way points in the weights
765 if (depth % brick_depth) != 0:
Michael McGeagh528a56d2020-12-16 11:33:21 +0000766 raise UnsupportedFeatureError("Offset into weights must be aligned to a brick")
Tim Hall79d07d22020-04-27 18:20:16 +0100767
768 return index
769
Louis Verhaard93719a92020-12-08 10:02:31 +0100770 def size_of_compressed_stream(self, index: int) -> int:
771 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100772 assert 0 <= index < len(self.compressed_values)
773 return len(self.compressed_values[index])
774
Louis Verhaard93719a92020-12-08 10:02:31 +0100775 def is_last_index_in_compressed_stream(self, index: int) -> bool:
776 assert self.compressed_values is not None
Tim Hall79d07d22020-04-27 18:20:16 +0100777 assert 0 <= index < len(self.compressed_values)
778 return index == len(self.compressed_values) - 1
779
Rickard Bolin17e53b52022-09-06 16:09:01 +0000780 def address_for_coordinate(
781 self,
782 orig_coord: Shape,
783 strides: Optional[List[int]] = None,
784 op_shape4D: Optional[Shape4D] = None,
785 is_top_box: bool = False,
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100786 ) -> Optional[int]:
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000787
Tim Hall79d07d22020-04-27 18:20:16 +0100788 address_offset = 0
Tim Halld8339a72021-05-27 18:49:40 +0100789 assert self.purpose != TensorPurpose.Weights
Tim Hall79d07d22020-04-27 18:20:16 +0100790
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000791 # Strides may be passed as an argument, for example when creating feature maps as the strides may be modified
792 # by the "ofm_stride_multiplier" operation attribute. If not, they are calculated here.
793 if not strides:
794 strides = self.get_strides(op_shape4D)
795
796 coord = orig_coord
797 if is_top_box:
798 coord = [c - 1 for c in orig_coord]
799 address_offset += 1 * strides[-1] # one element
800
Tim Hall79d07d22020-04-27 18:20:16 +0100801 if self.sub_purpose == TensorSubPurpose.Standard:
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100802 shape = op_shape4D.as_list() if op_shape4D else self.shape
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000803 for _coord, _shape in zip(coord, shape):
804 assert _coord >= 0 and _coord < _shape
805
Tim Halld8339a72021-05-27 18:49:40 +0100806 if op_shape4D and self.is_standard_fm:
807 storage_shape = self.get_4D_storage_shape_for_shape(op_shape4D).as_list()
808 storage_size = self.storage_size_for_shape(storage_shape)
Tim Hall79d07d22020-04-27 18:20:16 +0100809 else:
Tim Halld8339a72021-05-27 18:49:40 +0100810 storage_shape = self.storage_shape
811 coord = coord[-len(storage_shape) :]
812 storage_size = self.storage_size()
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100813
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000814 # Handle wraparound for partial buffers. Make sure to do this after subtracting top box
815 coord = [_coord % _shape for _coord, _shape in zip(coord, storage_shape)]
Tim Hall79d07d22020-04-27 18:20:16 +0100816
Rickard Bolin17e53b52022-09-06 16:09:01 +0000817 augmented_coord = self.get_augmented_coord(coord)
818 assert augmented_coord is not None
819
Tim Halld8339a72021-05-27 18:49:40 +0100820 address_offset += np.dot(augmented_coord, strides)
Tim Hall79d07d22020-04-27 18:20:16 +0100821
Rickard Bolinfd0a3382022-09-21 08:24:51 +0000822 assert address_offset >= 0 and address_offset <= storage_size
Rickard Bolin17e53b52022-09-06 16:09:01 +0000823 return self.address + address_offset
Tim Hall79d07d22020-04-27 18:20:16 +0100824
Louis Verhaard93719a92020-12-08 10:02:31 +0100825 def is_allocated_in_tensor_arena(self, scratch_tensor_mem_area: MemArea) -> bool:
Michael McGeaghf3e3ad72020-12-02 12:39:03 +0000826 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 +0200827
Louis Verhaard93719a92020-12-08 10:02:31 +0100828 def equivalent(self, tens: "Tensor") -> bool:
Louis Verhaard0b8268a2020-08-05 16:11:29 +0200829 return self.equivalence_id == tens.equivalence_id
830
Louis Verhaard93719a92020-12-08 10:02:31 +0100831 def set_all_shapes(self, shape: Shape):
Michael McGeagh6a8d4242020-07-28 12:17:59 +0100832 self.shape = shape
833 self.storage_shape = shape
834 self.bandwidth_shape = shape
835
Louis Verhaard93719a92020-12-08 10:02:31 +0100836 def get_full_shape(self) -> Shape:
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100837 d = len(self.shape)
838 if d in (1, 3):
Patrik Gustavsson2349d422020-12-01 16:02:29 +0100839 return full_shape(4, self.shape, 1)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100840 elif d == 2:
841 return [self.shape[0], 1, 1, self.shape[1]]
842 else:
Fredrik Svedberg835d8e12020-09-04 09:46:17 +0200843 return self.shape.copy()
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100844
Louis Verhaard93719a92020-12-08 10:02:31 +0100845 def is_quantized(self) -> bool:
Tim Hall93582962020-09-09 21:58:15 +0100846 # a tensor is quantized if it has an integral type and it contains valid quantization params
847
Tim Hall89567612020-10-27 11:57:57 +0000848 if not isinstance(self.quantization, QuantizationParameters):
Tim Hall93582962020-09-09 21:58:15 +0100849 return False
850
Tim Hall89567612020-10-27 11:57:57 +0000851 return (self.dtype.type & BaseType.Int) != 0 and self.quantization.is_valid()
Tim Hall93582962020-09-09 21:58:15 +0100852
James Peet7519d502021-07-19 16:47:58 +0100853 def get_scalar(self):
854 """
855 return: Unquantized or dequantized scalar value
856 rtype: self.dtype (if unquantized) or float (if dequantized)
857 """
858 assert self.values.size == 1, "get_scalar called on non-scalar tensor"
859 if self.is_quantized():
860 return self.quantization.dequantize(self.values).item(0)
861 else:
862 return self.values.item(0)
863
Ayaan Masooda2ec5aa2022-04-21 14:28:03 +0100864 def get_shape_as_2d(self, dimension_2_size: int) -> Optional[Shape4D]:
865
866 elms = self.elements()
867 dimension_1_size = elms // dimension_2_size
868 # Checks if the reduction works and shape is not 1D
869 is_reducible = dimension_1_size * dimension_2_size == elms and not (len(self.shape) == 1)
870
871 new_shape = None
872 if is_reducible:
873 new_shape = Shape4D([dimension_1_size, 1, 1, dimension_2_size])
874
875 return new_shape
876
Louis Verhaard6c74c3b2020-12-17 13:54:09 +0100877 def __lt__(self, other: "Tensor") -> bool:
878 return self.equivalence_id < other.equivalence_id
879
Tim Hall79d07d22020-04-27 18:20:16 +0100880 def __str__(self):
881 return "<nng.Tensor '%s' shape=%s dtype=%s>" % (self.name, self.shape, self.dtype)
882
883 __repr__ = __str__
Tim Hall93582962020-09-09 21:58:15 +0100884
Michael McGeagh528a56d2020-12-16 11:33:21 +0000885 def error(self, msg):
886 """
887 Raises a VelaError exception for errors encountered when parsing a Tensor
888
889 :param self: Tensor object that resulted in the error
890 :param msg: str object that contains a description of the specific error encountered
891 """
892
893 def _print_operators(ops):
894 lines = []
895 for idx, op in enumerate(ops):
896 op_type = getattr(op, "type", "Not an Operation")
897 op_id = getattr(op, "op_index", "-")
898 lines.append(f" {idx} = {op_type} ({op_id})")
899 return lines
900
901 lines = [f"Invalid {self.name} tensor. {msg}"]
902
903 lines += [" Driving operators:"]
904 lines += _print_operators(self.ops)
905
906 lines += [" Consuming operators:"]
907 lines += _print_operators(self.consumer_list)
908
909 raise VelaError("\n".join(lines))
910
Tim Hall93582962020-09-09 21:58:15 +0100911
Louis Verhaard93719a92020-12-08 10:02:31 +0100912def check_quantized_tens_scaling_equal(tens_a: Tensor, tens_b: Tensor) -> bool:
Tim Hall93582962020-09-09 21:58:15 +0100913 # checks that the scaling of two quantized tensors are equal
914
Tim Hall89567612020-10-27 11:57:57 +0000915 return tens_a.is_quantized() and tens_b.is_quantized() and tens_a.quantization.is_scaling_equal(tens_b.quantization)