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 "[email protected]"
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