1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
18
19 #include "HexagonModel.h"
20 #include <numeric>
21 #include <unordered_set>
22 #include "HexagonOperations.h"
23
24 namespace android {
25 namespace hardware {
26 namespace neuralnetworks {
27 namespace V1_0 {
28 namespace implementation {
29 namespace hexagon {
30
getOperandsInfo(const NeuralnetworksModel & model,const std::vector<RunTimePoolInfo> & pools)31 static std::vector<OperandInfo> getOperandsInfo(const NeuralnetworksModel& model,
32 const std::vector<RunTimePoolInfo>& pools) {
33 std::vector<OperandInfo> info(model.operands.size());
34 for (size_t i = 0; i < model.operands.size(); ++i) {
35 const Operand& operand = model.operands[i];
36 info[i] = {
37 .type = operand.type,
38 .dimensions = operand.dimensions,
39 .scale = operand.scale,
40 .zeroPoint = operand.zeroPoint,
41 .lifetime = operand.lifetime,
42 .buffer = const_cast<uint8_t*>(getData(operand, model.operandValues, pools)),
43 .length = operand.location.length,
44 };
45 }
46 return info;
47 }
48
Model(const NeuralnetworksModel & model)49 Model::Model(const NeuralnetworksModel& model) : mGraphId(0), mNodeCount(0), mCompiled(false) {
50 mPools = mapPools(model.pools);
51 mOperands = getOperandsInfo(model, mPools);
52 std::for_each(mPools.begin(), mPools.end(), [](RunTimePoolInfo& mem) { mem.update(); });
53
54 mOperations = model.operations;
55 mInputs = model.inputIndexes;
56 mOutputs = model.outputIndexes;
57 }
58
Model(Model && other)59 Model::Model(Model&& other) {
60 *this = std::move(other);
61 }
62
operator =(Model && other)63 Model& Model::operator=(Model&& other) {
64 if (this != &other) {
65 mNodeCount = other.mNodeCount;
66 mGraphId = other.mGraphId;
67 mCompiled = other.mCompiled;
68 mOperands = std::move(other.mOperands);
69 mOperations = std::move(other.mOperations);
70 mInputs = std::move(other.mInputs);
71 mOutputs = std::move(other.mOutputs);
72 mPools = std::move(other.mPools);
73 other.mNodeCount = 0;
74 other.mGraphId = {};
75 other.mCompiled = false;
76 }
77 return *this;
78 }
79
~Model()80 Model::~Model() {
81 clearModel();
82 }
83
getLog()84 std::string Model::getLog() {
85 char buffer[16 * 1024];
86 int err = hexagon::Controller::getInstance().getlog(
87 mGraphId, reinterpret_cast<uint8_t*>(buffer), sizeof(buffer));
88 HEXAGON_SOFT_ASSERT_EQ(0, err, "failed getLog");
89 return buffer;
90 }
91
getGraph()92 std::string Model::getGraph() {
93 char buffer[16 * 1024];
94 int err = hexagon::Controller::getInstance().snpprint(
95 mGraphId, reinterpret_cast<uint8_t*>(buffer), sizeof(buffer));
96 HEXAGON_SOFT_ASSERT_EQ(0, err, "failed getGraph");
97 return buffer;
98 }
99
getNextNode()100 uint32_t Model::getNextNode() {
101 return ++mNodeCount;
102 }
103
getPointer(uint32_t operand)104 const int32_t* Model::getPointer(uint32_t operand) {
105 return reinterpret_cast<const int32_t*>(mOperands[operand].buffer);
106 }
107
getShape(uint32_t operand)108 Shape Model::getShape(uint32_t operand) {
109 return {
110 .type = mOperands[operand].type,
111 .dimensions = mOperands[operand].dimensions,
112 .scale = mOperands[operand].scale,
113 .offset = mOperands[operand].zeroPoint,
114 };
115 }
116
setShape(uint32_t operand,const Shape & shape)117 bool Model::setShape(uint32_t operand, const Shape& shape) {
118 const hexagon_nn_output& output = mOperands[operand].hexagon_output;
119 HEXAGON_SOFT_ASSERT_EQ(output, hexagon_nn_output{}, "Output has already been set");
120 // mOperands[operand].type = shape.type;
121 mOperands[operand].dimensions = shape.dimensions;
122 // mOperands[operand].scale = shape.scale;
123 // mOperands[operand].zeroPoint = shape.offset;
124 return true;
125 }
126
isConstant(uint32_t operand)127 bool Model::isConstant(uint32_t operand) {
128 OperandLifeTime lifetime = mOperands[operand].lifetime;
129 return lifetime == OperandLifeTime::CONSTANT_COPY ||
130 lifetime == OperandLifeTime::CONSTANT_REFERENCE;
131 }
132
createTensorInternal(uint32_t B,uint32_t H,uint32_t W,uint32_t D,const uint8_t * ptr,size_t size)133 hexagon_nn_input Model::createTensorInternal(uint32_t B, uint32_t H, uint32_t W, uint32_t D,
134 const uint8_t* ptr, size_t size) {
135 uint32_t node = getNextNode();
136 bool success = hexagon::Controller::getInstance().append_const_node(mGraphId, node, B, H, W, D,
137 ptr, size) == 0;
138 HEXAGON_SOFT_ASSERT(success, "Failed to create tensor");
139 return {.src_id = node, .output_idx = 0};
140 }
141
createShape(uint32_t B,uint32_t H,uint32_t W,uint32_t D)142 hexagon_nn_input Model::createShape(uint32_t B, uint32_t H, uint32_t W, uint32_t D) {
143 uint32_t dump = 0;
144 return createTensorInternal(B, H, W, D, reinterpret_cast<uint8_t*>(&dump), sizeof(dump));
145 }
146
addOperand(uint32_t operandIndex)147 hexagon_nn_input Model::addOperand(uint32_t operandIndex) {
148 const OperandInfo& operand = mOperands[operandIndex];
149 std::vector<uint32_t> dims = getAlignedDimensions(operand.dimensions, 4);
150 HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Rank must be at most 4");
151 hexagon_nn_input result =
152 createTensorInternal(dims[0], dims[1], dims[2], dims[3], operand.buffer, operand.length);
153 HEXAGON_SOFT_ASSERT_NE(hexagon_nn_input{}, result, "Failed to add operand");
154 return result;
155 }
156
getTensor(uint32_t operand)157 const hexagon_nn_input& Model::getTensor(uint32_t operand) {
158 hexagon_nn_input& tensor = mOperands[operand].hexagon_input;
159 if (tensor == hexagon_nn_input{}) {
160 tensor = addOperand(operand);
161 }
162 return tensor;
163 }
164
getQuantizationMin(uint32_t operand)165 const hexagon_nn_input& Model::getQuantizationMin(uint32_t operand) {
166 OperandInfo& operandInfo = mOperands[operand];
167 if (operandInfo.hexagon_input_min == hexagon_nn_input{}) {
168 float real_value =
169 operandInfo.type == OperandType::TENSOR_QUANT8_ASYMM
170 ? (std::numeric_limits<uint8_t>::min() - operandInfo.zeroPoint) * operandInfo.scale
171 : std::numeric_limits<uint32_t>::min() * operandInfo.scale;
172 operandInfo.hexagon_input_min = createValues<float>({real_value});
173 }
174 return operandInfo.hexagon_input_min;
175 }
176
getQuantizationMax(uint32_t operand)177 const hexagon_nn_input& Model::getQuantizationMax(uint32_t operand) {
178 OperandInfo& operandInfo = mOperands[operand];
179 if (operandInfo.hexagon_input_max == hexagon_nn_input{}) {
180 float real_value =
181 operandInfo.type == OperandType::TENSOR_QUANT8_ASYMM
182 ? (std::numeric_limits<uint8_t>::max() - operandInfo.zeroPoint) * operandInfo.scale
183 : std::numeric_limits<uint32_t>::max() * operandInfo.scale;
184 operandInfo.hexagon_input_max = createValues<float>({real_value});
185 }
186 return operandInfo.hexagon_input_max;
187 }
188
getPadding(uint32_t operand)189 hexagon_nn_padding_type Model::getPadding(uint32_t operand) {
190 const int32_t padding = getScalar<int32_t>(operand);
191 return hexagon::getPadding(padding);
192 }
193
createQuantizationValue(uint32_t operand,int32_t quant_value)194 hexagon_nn_input Model::createQuantizationValue(uint32_t operand, int32_t quant_value) {
195 OperandInfo& operandInfo = mOperands[operand];
196 float real_value = (quant_value - operandInfo.zeroPoint) * operandInfo.scale;
197 return createValues<float>({real_value});
198 }
199
createConvFilterTensor(uint32_t operand)200 hexagon_nn_input Model::createConvFilterTensor(uint32_t operand) {
201 OperandInfo& operandInfo = mOperands[operand];
202 std::vector<uint32_t> dims = getAlignedDimensions(mOperands[operand].dimensions, 4);
203 HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Need at most 4 dimensions");
204 // NHWC --> HWCN
205 if (getShape(operand).type == OperandType::TENSOR_FLOAT32) {
206 std::vector<float> transposed =
207 transpose<float>(dims[0], dims[1] * dims[2] * dims[3],
208 reinterpret_cast<const float*>(operandInfo.buffer));
209 return createTensorInternal(dims[1], dims[2], dims[3], dims[0],
210 reinterpret_cast<const uint8_t*>(transposed.data()),
211 operandInfo.length);
212 } else {
213 std::vector<uint8_t> transposed =
214 transpose<uint8_t>(dims[0], dims[1] * dims[2] * dims[3],
215 reinterpret_cast<const uint8_t*>(operandInfo.buffer));
216 return createTensorInternal(dims[1], dims[2], dims[3], dims[0],
217 reinterpret_cast<const uint8_t*>(transposed.data()),
218 operandInfo.length);
219 }
220 }
221
createDepthwiseFilterTensor(uint32_t operand,int32_t depth_multiplier)222 hexagon_nn_input Model::createDepthwiseFilterTensor(uint32_t operand, int32_t depth_multiplier) {
223 OperandInfo& operandInfo = mOperands[operand];
224 std::vector<uint32_t> dims = getAlignedDimensions(mOperands[operand].dimensions, 4);
225 HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Need at most 4 dimensions");
226 // NHWC --> HWCN
227 return createTensorInternal(dims[1], dims[2], dims[3] / depth_multiplier,
228 dims[0] * depth_multiplier, operandInfo.buffer, operandInfo.length);
229 }
230
createFullyConnectedWeightTensor(uint32_t operand)231 hexagon_nn_input Model::createFullyConnectedWeightTensor(uint32_t operand) {
232 OperandInfo& operandInfo = mOperands[operand];
233 std::vector<uint32_t> dims = getAlignedDimensions(mOperands[operand].dimensions, 4);
234 HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Need at most 4 dimensions");
235 // WC --> CW
236 uint32_t num_units = dims[0] * dims[1] * dims[2];
237 uint32_t input_size = dims[3];
238 if (getShape(operand).type == OperandType::TENSOR_FLOAT32) {
239 std::vector<float> transposed = transpose<float>(
240 num_units, input_size, reinterpret_cast<const float*>(operandInfo.buffer));
241 return createTensorInternal(1, 1, input_size, num_units,
242 reinterpret_cast<const uint8_t*>(transposed.data()),
243 operandInfo.length);
244 } else {
245 std::vector<uint8_t> transposed = transpose<uint8_t>(
246 num_units, input_size, reinterpret_cast<const uint8_t*>(operandInfo.buffer));
247 return createTensorInternal(1, 1, input_size, num_units,
248 reinterpret_cast<const uint8_t*>(transposed.data()),
249 operandInfo.length);
250 }
251 }
252
getFloatActivation(uint32_t operand)253 op_type Model::getFloatActivation(uint32_t operand) {
254 return getFloatActivationFunction(getScalar<FusedActivationFunc>(operand));
255 }
256
getQuantizedActivation(uint32_t operand)257 op_type Model::getQuantizedActivation(uint32_t operand) {
258 return getQuantizedActivationFunction(getScalar<FusedActivationFunc>(operand));
259 }
260
verifyOperationInputs(const std::vector<hexagon_nn_input> & inputs)261 static bool verifyOperationInputs(const std::vector<hexagon_nn_input>& inputs) {
262 for (const hexagon_nn_input& input : inputs) {
263 if (input == hexagon_nn_input{}) {
264 return false;
265 }
266 }
267 return true;
268 }
269
verifyOperationOutputs(const std::vector<hexagon_nn_output> & outputs)270 static bool verifyOperationOutputs(const std::vector<hexagon_nn_output>& outputs) {
271 for (const hexagon_nn_output& output : outputs) {
272 if (output == hexagon_nn_output{}) {
273 return false;
274 }
275 }
276 return true;
277 }
278
addOperationInternal(op_type op,hexagon_nn_padding_type pad,const std::vector<hexagon_nn_input> & inputs,const std::vector<hexagon_nn_output> & outputs)279 uint32_t Model::addOperationInternal(op_type op, hexagon_nn_padding_type pad,
280 const std::vector<hexagon_nn_input>& inputs,
281 const std::vector<hexagon_nn_output>& outputs) {
282 HEXAGON_SOFT_ASSERT(verifyOperationInputs(inputs),
283 "error adding operation: one or more inputs is invalid");
284 HEXAGON_SOFT_ASSERT(verifyOperationOutputs(outputs),
285 "error adding operation: one or more outputs is invalid");
286 uint32_t node = getNextNode();
287 return hexagon::Controller::getInstance().append_node(mGraphId, node, op, pad, inputs.data(),
288 inputs.size(), outputs.data(),
289 outputs.size()) == 0
290 ? node
291 : 0;
292 }
293
getHexagonOutputs(const std::vector<uint32_t> & operands)294 std::vector<hexagon_nn_output> Model::getHexagonOutputs(const std::vector<uint32_t>& operands) {
295 std::vector<hexagon_nn_output> outputs;
296 for (uint32_t index : operands) {
297 const OperandInfo& operand = mOperands[index];
298 outputs.push_back(make_hexagon_nn_output(operand.dimensions, getSize(operand.type)));
299 if (operand.type == OperandType::TENSOR_QUANT8_ASYMM) {
300 outputs.push_back(make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float)));
301 outputs.push_back(make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float)));
302 }
303 }
304 return outputs;
305 }
306
registerHexagonInputs(const std::vector<uint32_t> & operands,uint32_t node)307 bool Model::registerHexagonInputs(const std::vector<uint32_t>& operands, uint32_t node) {
308 uint32_t idx = 0;
309 for (uint32_t i = 0; i < static_cast<uint32_t>(operands.size()); ++i) {
310 OperandInfo& operand = mOperands[operands[i]];
311 HEXAGON_SOFT_ASSERT_EQ(operand.hexagon_input, hexagon_nn_input{},
312 "Error: operation output has already been registered");
313 operand.hexagon_input = {.src_id = node, .output_idx = idx++};
314 if (operand.type == OperandType::TENSOR_QUANT8_ASYMM) {
315 operand.hexagon_input_min = {.src_id = node, .output_idx = idx++};
316 operand.hexagon_input_max = {.src_id = node, .output_idx = idx++};
317 }
318 }
319 return true;
320 }
321
addBasicOperation(op_type op,hexagon_nn_padding_type pad,const std::vector<hexagon_nn_input> & inputs,const std::vector<uint32_t> & outputs)322 bool Model::addBasicOperation(op_type op, hexagon_nn_padding_type pad,
323 const std::vector<hexagon_nn_input>& inputs,
324 const std::vector<uint32_t>& outputs) {
325 std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
326 uint32_t node = addOperationInternal(op, pad, inputs, outs);
327 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
328 return registerHexagonInputs(outputs, node);
329 }
330
setupActivationArgs(op_type op)331 std::vector<hexagon_nn_input> Model::setupActivationArgs(op_type op) {
332 switch (op) {
333 case OP_Nop:
334 return {};
335 case OP_Relu_f:
336 FALLTHROUGH_INTENDED;
337 case OP_QuantizedRelu_8:
338 return {};
339 case OP_ReluX_f:
340 FALLTHROUGH_INTENDED;
341 case OP_QuantizedReluX_8:
342 return {createValues<float>({6.0f})};
343 case OP_Clamp_f:
344 FALLTHROUGH_INTENDED;
345 case OP_QuantizedClamp_8:
346 return {createValues<float>({-1.0f}), createValues<float>({1.0f})};
347 default:
348 HEXAGON_SOFT_ASSERT(false, "Unknown activation symbol " << op);
349 }
350 }
351
addFloatOperationWithActivation(op_type op,hexagon_nn_padding_type pad,op_type activation,const std::vector<hexagon_nn_input> & inputs,const std::vector<uint32_t> & outputs)352 bool Model::addFloatOperationWithActivation(op_type op, hexagon_nn_padding_type pad,
353 op_type activation,
354 const std::vector<hexagon_nn_input>& inputs,
355 const std::vector<uint32_t>& outputs) {
356 std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
357 std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
358
359 uint32_t node = addOperationInternal(op, pad, inputs, outs);
360 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
361
362 std::vector<hexagon_nn_input> buffer_in = {{.src_id = node, .output_idx = 0}};
363 buffer_in.insert(buffer_in.end(), actArgs.begin(), actArgs.end());
364 node = addOperationInternal(activation, NN_PAD_NA, buffer_in, outs);
365 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
366
367 return registerHexagonInputs(outputs, node);
368 }
369
addQuant8OperationWithActivation(op_type op,hexagon_nn_padding_type pad,op_type activation,const std::vector<hexagon_nn_input> & inputs,const std::vector<uint32_t> & outputs)370 bool Model::addQuant8OperationWithActivation(op_type op, hexagon_nn_padding_type pad,
371 op_type activation,
372 const std::vector<hexagon_nn_input>& inputs,
373 const std::vector<uint32_t>& outputs) {
374 std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
375 std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
376
377 uint32_t node = addOperationInternal(op, pad, inputs, outs);
378 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
379
380 std::vector<hexagon_nn_input> buffer_in = {{.src_id = node, .output_idx = 0},
381 {.src_id = node, .output_idx = 1},
382 {.src_id = node, .output_idx = 2}};
383 buffer_in.insert(buffer_in.end(), actArgs.begin(), actArgs.end());
384 node = addOperationInternal(activation, NN_PAD_NA, buffer_in, outs);
385 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
386
387 return registerHexagonInputs(outputs, node);
388 }
389
addFusedFloatOperation(op_type op,hexagon_nn_padding_type pad,const hexagon_nn_input & bias,op_type activation,const std::vector<hexagon_nn_input> & inputs,const std::vector<uint32_t> & outputs)390 bool Model::addFusedFloatOperation(op_type op, hexagon_nn_padding_type pad,
391 const hexagon_nn_input& bias, op_type activation,
392 const std::vector<hexagon_nn_input>& inputs,
393 const std::vector<uint32_t>& outputs) {
394 HEXAGON_SOFT_ASSERT_EQ(1, outputs.size(), "addFusedFloatOperation requires 1 output");
395 std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
396 std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
397 uint32_t node;
398
399 node = addOperationInternal(op, pad, inputs, outs);
400 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
401
402 if (bias != hexagon_nn_input{}) {
403 const hexagon_nn_input buffer1_in = {.src_id = node, .output_idx = 0};
404 node = addOperationInternal(OP_BiasAdd_f, NN_PAD_NA, {buffer1_in, bias}, outs);
405 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding bias operation");
406 }
407
408 if (activation != OP_Nop) {
409 std::vector<hexagon_nn_input> buffer2_in = {{.src_id = node, .output_idx = 0}};
410 buffer2_in.insert(buffer2_in.end(), actArgs.begin(), actArgs.end());
411 node = addOperationInternal(activation, NN_PAD_NA, buffer2_in, outs);
412 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
413 }
414
415 return registerHexagonInputs(outputs, node);
416 }
417
addFusedQuant8Operation(op_type op,hexagon_nn_padding_type pad,const std::vector<hexagon_nn_input> & bias,op_type activation,const std::vector<hexagon_nn_input> & inputs,const std::vector<uint32_t> & outputs)418 bool Model::addFusedQuant8Operation(op_type op, hexagon_nn_padding_type pad,
419 const std::vector<hexagon_nn_input>& bias, op_type activation,
420 const std::vector<hexagon_nn_input>& inputs,
421 const std::vector<uint32_t>& outputs) {
422 HEXAGON_SOFT_ASSERT_EQ(1, outputs.size(), "addFusedQuant8Operation requires 1 output");
423 std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
424 const hexagon_nn_input& new_min = getQuantizationMin(outputs[0]);
425 const hexagon_nn_input& new_max = getQuantizationMax(outputs[0]);
426 uint32_t node;
427
428 hexagon_nn_output tensor_out8 =
429 make_hexagon_nn_output(mOperands[outputs[0]].dimensions, sizeof(uint8_t));
430 hexagon_nn_output tensor_out32 =
431 make_hexagon_nn_output(mOperands[outputs[0]].dimensions, sizeof(int32_t));
432 hexagon_nn_output scalar_out32 = make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float));
433
434 std::vector<hexagon_nn_output> out8 = {tensor_out8, scalar_out32, scalar_out32};
435 std::vector<hexagon_nn_output> out32 = {tensor_out32, scalar_out32, scalar_out32};
436
437 // base operation
438 node = addOperationInternal(op, pad, inputs, out32);
439 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
440 hexagon_nn_input previous = {.src_id = node, .output_idx = 0};
441 hexagon_nn_input previous_min = {.src_id = node, .output_idx = 1};
442 hexagon_nn_input previous_max = {.src_id = node, .output_idx = 2};
443
444 // add bias
445 if (bias.size() == 3) {
446 node = addOperationInternal(
447 OP_QuantizedBiasAdd_32p32to32, NN_PAD_NA,
448 {previous, bias[0], previous_min, previous_max, bias[1], bias[2]}, out32);
449 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding bias operation");
450 previous.src_id = node;
451 previous_min.src_id = node;
452 previous_max.src_id = node;
453 }
454
455 // requantize
456 node = addOperationInternal(OP_Requantize_32to8, NN_PAD_NA,
457 {previous, previous_min, previous_max, new_min, new_max}, out8);
458 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding requantize operation");
459 previous.src_id = node;
460 previous_min.src_id = node;
461 previous_max.src_id = node;
462
463 // activation
464 if (activation != OP_Nop) {
465 std::vector<hexagon_nn_input> buffer = {previous, previous_min, previous_max};
466 buffer.insert(buffer.end(), actArgs.begin(), actArgs.end());
467 node = addOperationInternal(activation, NN_PAD_NA, buffer, out8);
468 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
469 }
470
471 return registerHexagonInputs(outputs, node);
472 }
473
verifyOperations()474 bool Model::verifyOperations() {
475 std::vector<bool> supported = supportedOperations();
476 return std::all_of(supported.begin(), supported.end(), [](bool valid) { return valid; });
477 }
478
verifyOperands()479 bool Model::verifyOperands() {
480 for (const OperandInfo& operand : mOperands) {
481 HEXAGON_SOFT_ASSERT_GE(4ul, operand.dimensions.size(),
482 "Operand must have at most 4 dimensions");
483 for (uint32_t dim : operand.dimensions) {
484 HEXAGON_SOFT_ASSERT_NE(0, dim, "At least one operand with unknown dimension");
485 }
486 }
487 return true;
488 }
489
addInputs()490 bool Model::addInputs() {
491 // prepare OP_INPUT's outputs
492 std::vector<hexagon_nn_output> outs;
493 for (size_t i = 0; i < mInputs.size(); ++i) {
494 OperandInfo& operand = mOperands[mInputs[i]];
495 outs.push_back(make_hexagon_nn_output(operand.dimensions, getSize(operand.type)));
496 }
497
498 // add single input node for entire graph
499 uint32_t node = addOperationInternal(OP_INPUT, NN_PAD_NA, {}, outs);
500 HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding input operation");
501
502 // update operand information
503 for (size_t i = 0; i < mInputs.size(); ++i) {
504 OperandInfo& operand = mOperands[mInputs[i]];
505 operand.hexagon_input = {.src_id = node, .output_idx = static_cast<uint32_t>(i)};
506 }
507
508 return true;
509 }
510
addOperations()511 bool Model::addOperations() {
512 for (const Operation& operation : mOperations) {
513 OperationType operationType = operation.type;
514
515 // For now, the operation type is always the same as its first operand
516 // parameter. If this changes in the future, this line of code will need
517 // to be updated.
518 OperandType operandType = mOperands[operation.inputs[0]].type;
519
520 OperationTuple opTuple = std::make_pair(operationType, operandType);
521 HEXAGON_SOFT_ASSERT(
522 getOperationPrepareTable().find(opTuple) != getOperationPrepareTable().end(),
523 "Operation not found");
524 bool success =
525 getOperationPrepareTable()[opTuple](operation.inputs, operation.outputs, this);
526 HEXAGON_SOFT_ASSERT(success, "error adding operation");
527 }
528 return true;
529 }
530
addOutputs()531 bool Model::addOutputs() {
532 // prepare OP_OUTPUT's inputs
533 std::vector<hexagon_nn_input> ins;
534 for (size_t out : mOutputs) {
535 OperandInfo& operand = mOperands[out];
536 HEXAGON_SOFT_ASSERT_NE(operand.hexagon_input, hexagon_nn_input{},
537 "output operand has not been registered");
538
539 if (operand.type == OperandType::TENSOR_QUANT8_ASYMM) {
540 // Adjust quantized range of outputs
541 uint32_t dequant = addOperationInternal(
542 OP_Dequantize, NN_PAD_NA,
543 {operand.hexagon_input, operand.hexagon_input_min, operand.hexagon_input_max},
544 {make_hexagon_nn_output(operand.dimensions, sizeof(float))});
545 uint32_t quant =
546 addOperationInternal(OP_Quantize, NN_PAD_NA,
547 {{.src_id = dequant, .output_idx = 0},
548 createQuantizationValue(out, 0),
549 createQuantizationValue(out, 255)},
550 {make_hexagon_nn_output(operand.dimensions, sizeof(uint8_t)),
551 make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float)),
552 make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float))});
553 ins.push_back({.src_id = quant, .output_idx = 0});
554 } else {
555 ins.push_back(operand.hexagon_input);
556 }
557 }
558
559 // add single output node for entire graph
560 bool success = addBasicOperation(OP_OUTPUT, NN_PAD_NA, ins, {});
561 HEXAGON_SOFT_ASSERT(success, "Error adding output operation");
562
563 return true;
564 }
565
clearModel()566 void Model::clearModel() {
567 mCompiled = false;
568 for (OperandInfo& operand : mOperands) {
569 operand.hexagon_input = {};
570 operand.hexagon_input_min = {};
571 operand.hexagon_input_max = {};
572 operand.hexagon_output = {};
573 }
574 if (mGraphId != hexagon_nn_nn_id{}) {
575 hexagon::Controller::getInstance().teardown(mGraphId);
576 }
577 }
578
supportedOperations()579 std::vector<bool> Model::supportedOperations() {
580 std::vector<bool> supported(mOperations.size());
581 for (size_t i = 0; i < supported.size(); ++i) {
582 const Operation& operation = mOperations[i];
583 OperationType operationType = operation.type;
584
585 // For now, the operation type is always the same as its first operand
586 // parameter. If this changes in the future, this line of code will need
587 // to be updated.
588 OperandType operandType = mOperands[operation.inputs[0]].type;
589
590 OperationTuple opTuple = std::make_pair(operationType, operandType);
591
592 auto entry = getOperationCheckTable().find(opTuple);
593 if (entry != getOperationCheckTable().end()) {
594 supported[i] = entry->second(operation.inputs, operation.outputs, this);
595 } else {
596 supported[i] = false;
597 }
598 }
599 return supported;
600 }
601
prepare()602 bool Model::prepare() {
603 if (!verifyOperations() || !verifyOperands()) {
604 return false;
605 }
606
607 int err = hexagon::Controller::getInstance().init(&mGraphId);
608 HEXAGON_SOFT_ASSERT_EQ(0, err, "Hexagon could not allocate new graph");
609 HEXAGON_SOFT_ASSERT_NE(0, mGraphId, "Hexagon could not allocate new graph");
610 hexagon::Controller::getInstance().set_debug_level(mGraphId, 0);
611
612 if (!addInputs() || !addOperations() || !addOutputs()) {
613 clearModel();
614 LOG(ERROR) << "Something went wrong. Clearing the model and aborting.";
615 return false;
616 }
617
618 err = hexagon::Controller::getInstance().prepare(mGraphId);
619
620 LOG(INFO) << "PrepareModel was " << (err == 0 ? "SUCCESSFUL" : "UNSUCCESSFUL");
621
622 return err == 0;
623 }
624
convertToTensordef(const OperandInfo & operand)625 static hexagon_nn_tensordef convertToTensordef(const OperandInfo& operand) {
626 std::vector<uint32_t> dimensions = getAlignedDimensions(operand.dimensions, 4);
627 return {
628 .batches = dimensions[0],
629 .height = dimensions[1],
630 .width = dimensions[2],
631 .depth = dimensions[3],
632 .data = operand.buffer,
633 .dataLen = static_cast<int32_t>(operand.length),
634 .data_valid_len = operand.length, // unused?
635 .unused = 0,
636 };
637 }
638
getSize(const OperandInfo & operand)639 static uint32_t getSize(const OperandInfo& operand) {
640 return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(),
641 getSize(operand.type), std::multiplies<>{});
642 }
643
getUpdatedOperand(const RequestArgument & inputOutput,const std::vector<RunTimePoolInfo> & pools,const OperandInfo & oldInfo)644 static OperandInfo getUpdatedOperand(const RequestArgument& inputOutput,
645 const std::vector<RunTimePoolInfo>& pools,
646 const OperandInfo& oldInfo) {
647 OperandInfo newInfo = oldInfo;
648
649 const RunTimePoolInfo& pool = pools[inputOutput.location.poolIndex];
650 uint32_t offset = inputOutput.location.offset;
651
652 if (inputOutput.dimensions.size() > 0) {
653 newInfo.dimensions = inputOutput.dimensions;
654 }
655
656 newInfo.buffer = pool.getBuffer() + offset;
657 newInfo.length = getSize(newInfo);
658
659 return newInfo;
660 }
661
execute(const Request & request)662 bool Model::execute(const Request& request) {
663 std::vector<RunTimePoolInfo> pools = mapPools(request.pools);
664
665 // prepare inputs
666 std::vector<hexagon_nn_tensordef> inputs;
667 for (size_t i = 0; i < request.inputs.size(); ++i) {
668 const OperandInfo& oldInfo = mOperands[mInputs[i]];
669 OperandInfo newInfo = getUpdatedOperand(request.inputs[i], pools, oldInfo);
670 inputs.push_back(convertToTensordef(newInfo));
671 }
672
673 // prepare outputs
674 std::vector<hexagon_nn_tensordef> outputs;
675 for (size_t i = 0; i < request.outputs.size(); ++i) {
676 const OperandInfo& oldInfo = mOperands[mOutputs[i]];
677 OperandInfo newInfo = getUpdatedOperand(request.outputs[i], pools, oldInfo);
678 outputs.push_back(convertToTensordef(newInfo));
679 }
680
681 // execute model
682 int err = hexagon::Controller::getInstance().execute_new(mGraphId, inputs.data(), inputs.size(),
683 outputs.data(), outputs.size());
684
685 std::for_each(pools.begin(), pools.end(), [](RunTimePoolInfo& pool) { pool.update(); });
686
687 LOG(INFO) << "EXECUTION WAS " << (err == 0 ? "SUCCESSFUL" : "UNSUCCESSFUL");
688
689 return err == 0;
690 }
691
692 } // namespace hexagon
693 } // namespace implementation
694 } // namespace V1_0
695 } // namespace neuralnetworks
696 } // namespace hardware
697 } // namespace android
698