358 lines
15 KiB
C++
358 lines
15 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#define LOG_TAG "ModelBuilder"
|
|
|
|
#include "ModelBuilder.h"
|
|
|
|
#include "CompilationBuilder.h"
|
|
#include "Utils.h"
|
|
|
|
#include <map>
|
|
#include <utility>
|
|
|
|
namespace android {
|
|
namespace nn {
|
|
|
|
// 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;
|
|
|
|
int ModelBuilder::addOperand(const ANeuralNetworksOperandType& type) {
|
|
if (mCompletedModel) {
|
|
LOG(ERROR) << "ANeuralNetworksModel_addOperand can't modify after model finished";
|
|
return ANEURALNETWORKS_BAD_DATA;
|
|
}
|
|
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) {
|
|
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::copyLargeValuesToSharedMemory() {
|
|
VLOG(MODEL) << __func__ << " has " << mLargeOperandValues.size() << " values.";
|
|
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];
|
|
nnAssert(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;
|
|
}
|
|
|
|
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 (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;
|
|
}
|
|
// 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(ANeuralNetworksOperationType type, uint32_t inputCount,
|
|
const uint32_t* inputs, uint32_t outputCount,
|
|
const uint32_t* outputs) {
|
|
if (mCompletedModel) {
|
|
LOG(ERROR) << "ANeuralNetworksModel_addOperation can't modify after model finished";
|
|
return ANEURALNETWORKS_BAD_DATA;
|
|
}
|
|
if (!validCode(kNumberOfOperationTypes, kNumberOfOperationTypesOEM, type)) {
|
|
LOG(ERROR) << "ANeuralNetworksModel_addOperation invalid operations type " << 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 = static_cast<OperationType>(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 (mCompletedModel) {
|
|
LOG(ERROR) << "ANeuralNetworksModel_identifyInputsAndOutputs can't modify after model finished";
|
|
return ANEURALNETWORKS_BAD_DATA;
|
|
}
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
int n = copyLargeValuesToSharedMemory();
|
|
if (n != ANEURALNETWORKS_NO_ERROR) {
|
|
return n;
|
|
}
|
|
|
|
// 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::setHidlModel(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++) {
|
|
model->pools[i] = mMemories[i]->getHidlMemory();
|
|
}
|
|
}
|
|
|
|
} // namespace nn
|
|
} // namespace android
|