summaryrefslogtreecommitdiff
path: root/compiler/ann-ref/src/ModelBuilder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/ann-ref/src/ModelBuilder.cpp')
-rw-r--r--compiler/ann-ref/src/ModelBuilder.cpp483
1 files changed, 483 insertions, 0 deletions
diff --git a/compiler/ann-ref/src/ModelBuilder.cpp b/compiler/ann-ref/src/ModelBuilder.cpp
new file mode 100644
index 000000000..1f966bd2e
--- /dev/null
+++ b/compiler/ann-ref/src/ModelBuilder.cpp
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ModelBuilder.h"
+
+#include "CompilationBuilder.h"
+#include "Validation.h"
+#include "Logging.h"
+#include "Assert.h"
+
+#include <string.h>
+#include <map>
+
+static inline void setFromIntList(std::vector<uint32_t> *vec, uint32_t count, const uint32_t *data)
+{
+ vec->resize(count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ (*vec)[i] = data[i];
+ }
+}
+
+// Returns the number of padding bytes needed to align data of the
+// specified length. It aligns object of length:
+// 2, 3 on a 2 byte boundary,
+// 4+ on a 4 byte boundary.
+// We may want to have different alignments for tensors.
+// TODO: This is arbitrary, more a proof of concept. We need
+// to determine what this should be.
+uint32_t alignBytesNeeded(uint32_t index, size_t length)
+{
+ uint32_t pattern;
+ if (length < 2)
+ {
+ pattern = 0; // No alignment necessary
+ }
+ else if (length < 4)
+ {
+ pattern = 1; // Align on 2-byte boundary
+ }
+ else
+ {
+ pattern = 3; // Align on 4-byte boundary
+ }
+ uint32_t extra = (~(index - 1)) & pattern;
+ return extra;
+}
+
+
+// The maximum number of operands and operations that a model may have.
+const uint32_t MAX_NUMBER_OF_OPERANDS = 0xFFFFFFFE;
+const uint32_t MAX_NUMBER_OF_OPERATIONS = 0xFFFFFFFE;
+
+bool ModelBuilder::badState(const char *name)
+{
+ if (mCompletedModel)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_" << name << " can't modify after model finished";
+ return true;
+ }
+ if (mInvalidModel)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_" << name << " can't modify an invalid model";
+ return true;
+ }
+ return false;
+}
+
+int ModelBuilder::addOperand(const ANeuralNetworksOperandType &type)
+{
+ if (badState("addOperand"))
+ {
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+
+ int n = validateOperandType(type, "ANeuralNetworksModel_addOperand", true);
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+ size_t idx = mOperands.size();
+ if (idx >= MAX_NUMBER_OF_OPERANDS)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_addOperand exceed max operands";
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ mOperands.resize(idx + 1);
+ auto &operand = mOperands[idx];
+ operand.type = static_cast<OperandType>(type.type);
+ setFromIntList(&operand.dimensions, type.dimensionCount, type.dimensions);
+ operand.numberOfConsumers = 0;
+ operand.scale = type.scale;
+ operand.zeroPoint = type.zeroPoint;
+ operand.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ operand.location = {.poolIndex = 0, .offset = 0, .length = 0};
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ModelBuilder::setOperandValue(uint32_t index, const void *buffer, size_t length)
+{
+ if (badState("setOperandValue"))
+ {
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+
+ VLOG(MODEL) << __func__ << " for operand " << index << " size " << length;
+ if (index >= operandCount())
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_setOperandValue setting operand " << index << " of "
+ << operandCount();
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ Operand &operand = mOperands[index];
+ if (buffer == nullptr)
+ {
+ if (length)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_setOperandValue buffer is nullptr but length is "
+ "not 0";
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ operand.lifetime = OperandLifeTime::NO_VALUE;
+ // The location is unused and is set to zeros.
+ operand.location = {.poolIndex = 0, .offset = 0, .length = 0};
+ }
+ else
+ {
+ if (length > 0xFFFFFFFF)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_setOperandValue value length of " << length
+ << " exceeds max size";
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ uint32_t valueLength = static_cast<uint32_t>(length);
+ uint32_t neededLength = sizeOfData(operand.type, operand.dimensions);
+ if (neededLength != valueLength)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_setOperandValue setting " << valueLength
+ << " bytes when needing " << neededLength;
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ if (valueLength <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES)
+ {
+ uint32_t existingSize = static_cast<uint32_t>(mSmallOperandValues.size());
+ uint32_t extraBytes = alignBytesNeeded(existingSize, valueLength);
+ mSmallOperandValues.resize(existingSize + extraBytes + valueLength);
+ operand.lifetime = OperandLifeTime::CONSTANT_COPY;
+ operand.location = {
+ .poolIndex = 0, .offset = existingSize + extraBytes, .length = neededLength};
+ memcpy(&mSmallOperandValues[operand.location.offset], buffer, valueLength);
+ VLOG(MODEL) << "Copied small value to offset " << operand.location.offset;
+ }
+ else
+ {
+ VLOG(MODEL) << "Saving large value";
+ operand.lifetime = OperandLifeTime::CONSTANT_REFERENCE;
+ // The values for poolIndex and offset will be set when the model is finished.
+ operand.location = {.poolIndex = 0, .offset = 0, .length = valueLength};
+ // We keep track of the buffers. We'll allocate the shared memory only
+ // once we know the total size, to avoid needless copies.
+ mLargeOperandValues.push_back(LargeValue{.operandIndex = index, .buffer = buffer});
+ }
+ }
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ModelBuilder::setOperandValueFromMemory(uint32_t index, const Memory *memory, uint32_t offset,
+ size_t length)
+{
+ VLOG(MODEL) << __func__ << " for operand " << index << " offset " << offset << " size " << length;
+ if (badState("setOperandValueFromMemory"))
+ {
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+
+ if (index >= operandCount())
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_setOperandValueFromMemory setting operand " << index
+ << " of " << operandCount();
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ Operand &operand = mOperands[index];
+ uint32_t neededLength = sizeOfData(operand.type, operand.dimensions);
+ if (neededLength != length)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_setOperandValueFromMemory setting " << length
+ << " bytes when needing " << neededLength;
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ if (!memory->validateSize(offset, length))
+ {
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ // TODO validate does not exceed length of memory
+ operand.lifetime = OperandLifeTime::CONSTANT_REFERENCE;
+ operand.location = {.poolIndex = mMemories.add(memory), .offset = offset, .length = neededLength};
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ModelBuilder::addOperation(OperationType type, uint32_t inputCount, const uint32_t *inputs,
+ uint32_t outputCount, const uint32_t *outputs)
+{
+
+ if (badState("addOperation"))
+ {
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+
+ if (!validateOperationType(type))
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_addOperation invalid operations type "
+ << static_cast<uint32_t>(type);
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ int n = validateOperandList(inputCount, inputs, operandCount(),
+ "ANeuralNetworksModel_addOperation inputs");
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+ n = validateOperandList(outputCount, outputs, operandCount(),
+ "ANeuralNetworksModel_addOperation outputs");
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+
+ uint32_t operationIndex = operationCount();
+ if (operationIndex >= MAX_NUMBER_OF_OPERATIONS)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_addOperation exceed max operations";
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+ mOperations.resize(operationIndex + 1);
+ auto &entry = mOperations[operationIndex];
+ entry.type = type;
+
+ setFromIntList(&entry.inputs, inputCount, inputs);
+ setFromIntList(&entry.outputs, outputCount, outputs);
+ for (uint32_t i : entry.inputs)
+ {
+ mOperands[i].numberOfConsumers++;
+ // TODO mOperands[i].consumers.push_back(operationIndex);
+ }
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ModelBuilder::identifyInputsAndOutputs(uint32_t inputCount, const uint32_t *inputs,
+ uint32_t outputCount, const uint32_t *outputs)
+{
+ if (badState("identifyInputsAndOutputs"))
+ {
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+
+ int n = validateOperandList(inputCount, inputs, operandCount(),
+ "ANeuralNetworksModel_identifyInputsAndOutputs inputs");
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+ n = validateOperandList(outputCount, outputs, operandCount(),
+ "ANeuralNetworksModel_identifyInputsAndOutputs outputs");
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+
+ // Makes a copy of the index list, validates the arguments, and changes
+ // the lifetime info of the corresponding operand.
+ auto setArguments = [&](std::vector<uint32_t> *indexVector, uint32_t indexCount,
+ const uint32_t *indexList, OperandLifeTime lifetime) -> bool {
+ indexVector->resize(indexCount);
+ for (uint32_t i = 0; i < indexCount; i++)
+ {
+ const uint32_t operandIndex = indexList[i];
+ if (operandIndex >= mOperands.size())
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_identifyInputsAndOutputs Can't set input or output "
+ "to be "
+ << operandIndex << " as this exceeds the numbe of operands " << mOperands.size();
+ return false;
+ }
+ (*indexVector)[i] = operandIndex;
+ Operand &operand = mOperands[operandIndex];
+ if (operand.lifetime != OperandLifeTime::TEMPORARY_VARIABLE)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_identifyInputsAndOutputs Can't set operand "
+ << operandIndex
+ << " to be an input or output. Check that it's not a constant or "
+ "already an input or output";
+ return false;
+ }
+ operand.lifetime = lifetime;
+ }
+ return true;
+ };
+
+ if (!setArguments(&mInputIndexes, inputCount, inputs, OperandLifeTime::MODEL_INPUT) ||
+ !setArguments(&mOutputIndexes, outputCount, outputs, OperandLifeTime::MODEL_OUTPUT))
+ {
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
+int ModelBuilder::createCompilation(CompilationBuilder **compilation)
+{
+ if (!mCompletedModel || mInvalidModel)
+ {
+ LOG(ERROR) << "ANeuralNetworksCompilation_create passed an unfinished model";
+ *compilation = nullptr;
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+ *compilation = new CompilationBuilder(this);
+ return (*compilation ? ANEURALNETWORKS_NO_ERROR : ANEURALNETWORKS_OUT_OF_MEMORY);
+}
+
+int ModelBuilder::finish()
+{
+ if (mCompletedModel)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_finish called more than once";
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+ if (mInvalidModel)
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_finish called on an invalid model";
+ return ANEURALNETWORKS_BAD_STATE;
+ }
+
+ int n = copyLargeValuesToMemory();
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+
+ Model modelForValidation;
+ publish(&modelForValidation);
+ if (!validateModel(modelForValidation))
+ {
+ LOG(ERROR) << "ANeuralNetworksModel_finish called on invalid model";
+ mInvalidModel = true;
+ return ANEURALNETWORKS_BAD_DATA;
+ }
+
+ // We sort the operations so that they will be in the appropriate
+ // order for a single-threaded, op at a time execution.
+ // TODO: we don't need this if we always run the partitioner.
+ sortIntoRunOrder();
+ mCompletedModel = true;
+ return ANEURALNETWORKS_NO_ERROR;
+}
+
+void ModelBuilder::sortIntoRunOrder()
+{
+ // Tracks the operations that can be executed.
+ std::vector<uint32_t> opsReadyToRun;
+ std::vector<Operation> runOrder;
+
+ // Tracks how many inputs are needed for each operation to be ready to run.
+ std::multimap<uint32_t, uint32_t> operandToOperations;
+ std::vector<uint32_t> unknownInputCount(operationCount());
+ for (uint32_t operationIndex = 0; operationIndex < operationCount(); operationIndex++)
+ {
+ uint32_t &count = unknownInputCount[operationIndex];
+ count = 0;
+ for (uint32_t operandIndex : mOperations[operationIndex].inputs)
+ {
+ auto lifetime = mOperands[operandIndex].lifetime;
+ if (lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ lifetime == OperandLifeTime::MODEL_OUTPUT)
+ {
+ count++;
+ operandToOperations.insert(std::pair<uint32_t, uint32_t>(operandIndex, operationIndex));
+ }
+ }
+ if (count == 0)
+ {
+ opsReadyToRun.push_back(operationIndex);
+ }
+ }
+
+ while (opsReadyToRun.size() > 0)
+ {
+ // Execute the next op
+ int opIndex = opsReadyToRun.back();
+ opsReadyToRun.pop_back();
+ const Operation &operation = mOperations[opIndex];
+
+ runOrder.push_back(mOperations[opIndex]);
+
+ // Mark all its outputs as known.
+ for (uint32_t operandIndex : operation.outputs)
+ {
+ auto range = operandToOperations.equal_range(operandIndex);
+ for (auto i = range.first; i != range.second; i++)
+ {
+ uint32_t &count = unknownInputCount[i->second];
+ if (--count == 0)
+ {
+ opsReadyToRun.push_back(i->second);
+ }
+ }
+ }
+ }
+ mOperations = runOrder;
+}
+
+void ModelBuilder::publish(Model *model) const
+{
+ model->operands = mOperands;
+ model->operations = mOperations;
+ model->inputIndexes = mInputIndexes;
+ model->outputIndexes = mOutputIndexes;
+ model->operandValues = mSmallOperandValues;
+
+ uint32_t count = mMemories.size();
+ model->pools.resize(count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ uint8_t *buffer;
+ mMemories[i]->getPointer(&buffer);
+ model->pools[i] = buffer;
+ }
+}
+
+int ModelBuilder::copyLargeValuesToMemory()
+{
+ if (!mLargeOperandValues.empty())
+ {
+ // Calculate the size of the shared memory needed for all the large values.
+ // Also sets the offset for each value within the memory.
+ size_t poolSize = 0;
+ for (LargeValue &l : mLargeOperandValues)
+ {
+ Operand &operand = mOperands[l.operandIndex];
+ ASSERT(operand.lifetime == OperandLifeTime::CONSTANT_REFERENCE);
+ poolSize += alignBytesNeeded(poolSize, operand.location.length);
+ operand.location.offset = poolSize;
+ poolSize += operand.location.length;
+ }
+
+ // Allocated the shared memory.
+ int n = mLargeValueMemory.create(poolSize);
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+ uint8_t *memoryPointer = nullptr;
+ n = mLargeValueMemory.getPointer(&memoryPointer);
+ if (n != ANEURALNETWORKS_NO_ERROR)
+ {
+ return n;
+ }
+ uint32_t poolIndex = mMemories.add(&mLargeValueMemory);
+ VLOG(MODEL) << "Allocated large value pool of size " << poolSize << " at index " << poolIndex;
+
+ // Copy the values to this memory.
+ for (LargeValue &l : mLargeOperandValues)
+ {
+ Operand &operand = mOperands[l.operandIndex];
+ operand.location.poolIndex = poolIndex;
+ memcpy(memoryPointer + operand.location.offset, l.buffer, operand.location.length);
+ }
+ }
+ return ANEURALNETWORKS_NO_ERROR;
+}