blob: f094ae3c7a9290b3c07ef43e8413e849d8a8a932 [file] [log] [blame]
/*
* 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.
*/
// Classes used to plan how to execute a model across multiple devices.
#ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_EXECUTION_PLAN_H
#define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_EXECUTION_PLAN_H
#include <LegacyUtils.h>
#include <TokenHasher.h>
#include <android-base/logging.h>
#include <nnapi/IBurst.h>
#include <nnapi/Types.h>
#include <algorithm>
#include <chrono>
#include <functional>
#include <map>
#include <memory>
#include <ostream>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#include "Memory.h"
#include "ModelArgumentInfo.h"
#include "ModelBuilder.h"
#include "NeuralNetworks.h"
namespace android {
namespace nn {
class BurstBuilder;
class CompilationBuilder;
class Device;
class ExecutionBuilder;
class ExecutionPlan;
class RuntimeMemory;
class RuntimePreparedModel;
class StepExecutor;
struct ConstantReferenceLocation;
struct CacheInfo;
// NNAPI Control Flow allows referring to an NNAPI model inside another NNAPI
// model using OperandType::SUBGRAPH. For example, an IF operation within a
// model mey refer to two other models corresponding to then and else branches.
//
// The partitioning process transforms this nested representation into a list
// of LogicalSteps.
//
// The following terms are used:
// - The main model is the top-level model being compiled (not referenced by any
// OperandType::SUBGRAPH operand within the compilation).
// - A referenced model is a non-top-level model being compiled (referenced by
// at least one OperandType::SUBGRAPH operand within the set of models being
// compiled).
// - A source model is either the main model or a referenced model.
// - A step model is a model excerpted from a source model during the
// partitioning process.
// - A partition is a LogicalStep representing at least one operation of a
// source model. In particular, ExecutionStep represents a step model, IfStep
// represents an IF operation, WhileStep represents a WHILE operation.
// A GotoStep is not a partition.
// - A partition boundary operand is a source model operand that is an input or
// output of a partition. For ExecutionStep, the inputs and outputs of the
// step model are boundary operands; for IfStep and WhileStep, the inputs and
// outputs of the corresponding operation are boundary operands.
// - A partition boundary static temporary is a partition boundary
// operand which is of lifetime TEMPORARY_VARIABLE in the source model and
// whose dimensions are fully specified.
// - A partition boundary dynamic temporary is a partition boundary
// operand which is of lifetime TEMPORARY_VARIABLE in the source model and
// whose dimensions are not fully specified.
// - A main execution is the execution of a main model.
//
// Referenced models can be sources of partition boundary operands. For example,
// this happens when a referenced model is partitioned into one or more
// LogicalSteps.
//
// (model index, operand index within model)
typedef std::pair<uint32_t, uint32_t> SourceOperandIndex;
// A collection of source models.
class SourceModels {
public:
uint32_t addModel(const ModelBuilder* model) {
uint32_t modelIndex = mModels.size();
mModels.push_back(model);
return modelIndex;
}
const ModelBuilder* getModel(uint32_t index) const { return mModels[index]; }
uint32_t size() const { return mModels.size(); }
private:
std::vector<const ModelBuilder*> mModels;
};
// Represents all partition boundary dynamic temporaries for a particular main
// execution.
//
// Usage pattern:
// - declare() every partition boundary dynamic temporary.
// - endDeclarations(). After this point, lookup() is permitted.
// - Before executing an ExecutionStep, call allocate().
// - After executing an ExecutionStep, call redeclare() for every partition
// boundary dynamic temporary for which we've learned or guessed more about
// the dimensions or length.
//
// Each partition boundary temporary has a location assigned by allocate() for
// its defining step (see declare() and allocate()). That location remains
// valid until redeclare() increases the length of some temporary in its defining
// step or allocate() is called again for its defining step.
class DynamicTemporaries {
DISALLOW_COPY_AND_ASSIGN(DynamicTemporaries);
public:
DynamicTemporaries() = default;
DynamicTemporaries(DynamicTemporaries&&) = default;
DynamicTemporaries& operator=(DynamicTemporaries&&) = default;
// Declare a dynamic temporary. stepIndex is the step that defines the
// temporary (i.e., in which the temporary appears as an operation output
// operand). initialDimensions and initialLength indicate what we know or
// (in the case of length) guess about those properties.
void declare(SourceOperandIndex sourceOperandIndex, uint32_t stepIndex,
const Dimensions& initialDimensions, uint32_t initialLength, uint32_t alignment,
uint32_t padding);
// Indicate that we've finished declaring all dynamic temporaries.
void endDeclarations() {
CHECK(!mDeclared);
mDeclared = true;
}
// Redeclare a dynamic temporary, indicating what we've learned about it.
// This may invalidate the location of temporaries defined by its step.
// Returns true if dimensions or length changed, false otherwise.
bool redeclare(SourceOperandIndex sourceOperandIndex, const Dimensions& newDimensions,
uint32_t newLength);
// Ensure that all dynamic temporaries defined by the specified step have
// locations. The return value is a ResultCode (e.g.,
// ANEURALNETWORKS_NO_ERROR).
//
// Even if dynamic temporaries have already been allocated for this step,
// this call may reallocate them. A reallocation is not guaranteed to
// preserve location (LocationAndShape.memory, LocationAndShape.offset) or
// contents of temporaries.
int allocate(uint32_t stepIndex);
// Do the dynamic temporaries defined by this step have valid allocations?
// (Will be true if there are no dynamic temporaries defined by this step.)
bool allocated(uint32_t stepIndex) const;
// Dump information to VLOG(EXECUTION).
void vlogDump(const char* context = nullptr) const;
// If the specified operand is a dynamic temporary, return location and
// shape information; otherwise, return std::nullopt.
//
// If temporary exists but does not have a valid allocation, then:
// - If mustBeAllocated == true, then trigger a failed CHECK().
// - If mustBeAllocated == false, then memory == nullptr and offset == ~0.
struct LocationAndShape {
const RuntimeMemory* memory;
uint32_t offset;
const Dimensions* dimensions;
uint32_t paddedLength;
};
std::optional<LocationAndShape> lookup(SourceOperandIndex sourceOperandIndex,
bool mustBeAllocated = true) const;
// Have any dynamic temporaries been declared?
bool empty() const { return mSourceOperandToTemporary.empty(); }
private:
// The same as LocationAndShape, except that:
// - the base of the location is represented not by memory but by defining stepIndex
// - it additionally contains information about the preferred alignment and padding
struct InternalLocationAndShape {
uint32_t stepIndex;
uint32_t offset;
Dimensions dimensions;
uint32_t paddedLength;
uint32_t alignment;
uint32_t padding;
};
std::map<SourceOperandIndex, InternalLocationAndShape> mSourceOperandToTemporary;
// Every dynamic temporary defined at a given stepIndex.
std::map<uint32_t, std::vector<SourceOperandIndex>> mStepIndexToSourceOperandIndexes;
std::map<uint32_t, std::unique_ptr<MemoryAshmem>> mStepIndexToMemory;
// For a given defining stepIndex, we consider either all its dynamic
// temporaries to be allocated (have valid locations) or none of them to be.
std::set<uint32_t> mAllocatedStepIndexes;
// Has endDeclarations() been called?
bool mDeclared = false;
};
// The location of a static temporary.
struct StaticTemporaryLocation {
// The offset relative to ExecutionPlan::Controller::mTemporaries during execution.
uint32_t offset;
uint32_t paddedLength;
};
// An excerpt of a source model to be run by a specific device.
class ExecutionStep {
public:
typedef std::vector<std::pair<uint32_t, uint32_t>> RemapVectorType;
typedef std::set<std::pair<uint32_t, uint32_t>> StepModelOutputSetType;
enum OperandKind { INPUT, OUTPUT };
ExecutionStep(ExecutionPlan* plan, uint32_t stepIndex, uint32_t sourceModelIndex,
std::shared_ptr<Device> device);
int addOperation(int operationIndex);
int addOperand(uint32_t sourceOperandIndex, uint32_t* stepOperandIndex, OperandKind kind);
// Each container entry is of the form (source model operand index, step model operand index)
const RemapVectorType& getStepModelInputs() const { return mStepModelInputs; }
const RemapVectorType& getStepModelOutputs() const { return mStepModelOutputs; }
const RemapVectorType& getModelInputs() const { return mModelInputs; }
const RemapVectorType& getModelOutputs() const { return mModelOutputs; }
const RemapVectorType& getTempsAsStepModelInputs() const { return mTempsAsStepModelInputs; }
const StepModelOutputSetType& getTempsAsStepModelOutputs() const {
return mTempsAsStepModelOutputs;
}
const RemapVectorType& getOutputsAsStepModelInputs() const { return mOutputsAsStepModelInputs; }
const std::vector<uint32_t>& getInputIndexStepModelToMainModel() const {
return mInputIndexStepModelToMainModel;
}
const std::vector<uint32_t>& getOutputIndexStepModelToMainModel() const {
return mOutputIndexStepModelToMainModel;
}
const std::vector<uint32_t>& getOutputsAsStepModelInputsIndexToMainModel() const {
return mOutputsAsStepModelInputsIndexToMainModel;
}
const std::set<uint32_t>& getModelOutputsThatAreDownstreamInputs() const {
return mModelOutputsThatAreDownstreamInputs;
}
uint32_t getIndex() const { return mIndex; }
uint32_t getSourceModelIndex() const { return mSourceModelIndex; }
void declareModelOutputIsDownstreamInput(uint32_t mainModelOutputIndex);
void recordTempAsStepModelOutput(uint32_t stepOperandIndex);
// If this step has a step model output of unknown size, sets
// *hasOutputOfUnknownSize to true; otherwise, leaves it
// unchanged.
int finishStepModel(const ModelBuilder* mainModel, bool* hasOutputOfUnknownSize,
int32_t executionPreference, int32_t priority);
const ModelBuilder* getStepModel() const { return &mStepModel; }
std::shared_ptr<Device> getDevice() const { return mDevice; }
// only available after calling finishStepModel()
std::shared_ptr<RuntimePreparedModel> getPreparedStepModel() const {
return mPreparedStepModel;
}
// Map inputs and outputs from ExecutionBuilder to StepExecutor.
//
// This method only reads map entries for which the first element of
// SourceOperandIndex is mSourceModelIndex.
//
// mainModelOutputShapes may be nullptr if the only main model outputs that are
// inputs of this step are of fully specified shape.
void mapInputsAndOutputs(
std::shared_ptr<StepExecutor> stepExecutor,
const std::vector<OutputShape>* mainModelOutputShapes,
const RuntimeMemory* temporaryMemory, // for static temporaries
const std::map<SourceOperandIndex, StaticTemporaryLocation>&
sourceOperandToLocationOfTemporary, // for static temporaries
const DynamicTemporaries& dynamicTemporaries,
const std::map<SourceOperandIndex, uint32_t>& sourceOperandToInputIndex,
const std::map<SourceOperandIndex, uint32_t>& sourceOperandToOutputIndex,
const std::map<SourceOperandIndex, ConstantReferenceLocation>&
sourceOperandToConstantReference) const;
bool hasNoInputsOrNoOutputs() const {
return mStepModelInputs.empty() || mStepModelOutputs.empty();
}
void dump() const;
// For test only, get the transformed cache token.
const uint8_t* forTest_getCacheToken() const { return mToken.getCacheToken(); }
private:
void logStepModel() const;
const ModelBuilder* getSourceModel() const;
// TODO: Some of the data is working state information that
// shouldn't be needed after we've constructed but not executed
// the step.
ExecutionPlan* mPlan;
uint32_t mIndex; // index of step within plan
uint32_t mSourceModelIndex;
ModelBuilder mStepModel; // An excerpt of a source model to be run by one device.
std::shared_ptr<Device> mDevice;
std::shared_ptr<RuntimePreparedModel> mPreparedStepModel;
// All inputs of this step model:
// (source model operand index, step model operand index)
//
// Depending on whether the source operand is an input or output of the main
// model, the memory should be mapped using
// ExecutionPlan::CompoundBody::mSourceOperandToInputIndex,
// ExecutionPlan::Controller::mSourceOperandToLocationOfTemporary, or
// ExecutionPlan::Controller::mDynamicTemporaries, or
// ExecutionPlan::CompoundBody::mSourceOperandToOutputIndex.
RemapVectorType mStepModelInputs;
// All outputs of this step model:
// (source model operand index, step model operand index)
//
// Depending on whether the source operand is an output of the main model,
// the memory should be mapped using
// ExecutionPlan::CompoundBody::mSourceOperandToOutputIndex,
// ExecutionPlan::Controller::mSourceOperandToLocationOfTemporary, or
// ExecutionPlan::Controller::mDynamicTemporaries.
//
// mOutputIndexStepModelToMainModel and declareModelOutputIsDownstreamInput()
// rely on mModelOutputs being a prefix of mStepModelOutputs.
RemapVectorType mStepModelOutputs;
// Inputs of main model that are also inputs of this step model:
// (main model operand index, step model operand index)
RemapVectorType mModelInputs;
// Outputs of main model that are also outputs of this step model:
// (main model operand index, step model operand index)
RemapVectorType mModelOutputs;
// Temporaries of source model that are inputs of this step model:
// (source model operand index, step model operand index)
RemapVectorType mTempsAsStepModelInputs;
// Temporaries of source model that are outputs of this step model:
// (source model operand index, step model operand index)
StepModelOutputSetType mTempsAsStepModelOutputs;
// Outputs of main model that are inputs of this step model:
// (main model operand index, step model operand index)
RemapVectorType mOutputsAsStepModelInputs;
// Converts operand indexes from the source model to the step model.
std::unordered_map<uint32_t, uint32_t> mOperandMap;
// Converts input indexes from the step model to the main model
// (these are input indexes, not operand indexes). This vector
// only describes inputs of the step model that are also inputs of
// the main model -- that is, mModelInputs but not mTempsAsStepModelInputs.
std::vector<uint32_t> mInputIndexStepModelToMainModel;
// Converts output indexes from the step model to the main model
// (these are output indexes, not operand indexes). This vector
// only describes outputs of the step model that are also outputs of
// the main model -- that is, mModelOutputs but not
// mTempsAsStepModelOutputs.
std::vector<uint32_t> mOutputIndexStepModelToMainModel;
// Converts indexes into mOutputsAsStepModelInputs to indexes into
// main model outputs (these are input and output indexes, not
// operand indexes). To be specific, if the main model outputs
// are mainModelOutputs,
//
// mOutputsAsStepModelInputsIndexToMainModel.size() ==
// mOutputsAsStepModelInputs.size()
//
// and when (0 <= i < mOutputsAsStepModelInputs.size()),
//
// mainModelOutputs[mOutputsAsStepModelInputsIndexToMainModel[i]] ==
// mOutputsAsStepModelInputs[i].first
std::vector<uint32_t> mOutputsAsStepModelInputsIndexToMainModel;
// Step model output indexes (not operand indexes) that are outputs of the
// main model used as inputs to some other partition.
std::set<uint32_t> mModelOutputsThatAreDownstreamInputs;
// The compilation caching token.
TokenHasher mToken;
};
// An IF operation to be run on the ExecutionPlan::next() interpreter. The
// branch models might run on devices. See LogicalStep.
//
// Execution plan structure:
// Index Step
// i if then=(i + 1) else=(j + 1)
// ... (then model steps)
// j goto k
// ... (else model steps)
// k (steps after the IF)
struct IfStep {
// The index of this step.
size_t index = ~size_t(0);
// The index of the first step of the "then" branch.
size_t thenStepIndex = ~size_t(0);
// The index of the first step of the "else" branch.
size_t elseStepIndex = ~size_t(0);
// The boolean condition input of the IF operation. The value of this
// operand determines the branch of the IF operation to be executed.
SourceOperandIndex conditionOperandIndex = {~uint32_t(0), ~uint32_t(0)};
// Input operands of the IF operation to be passed to a branch model.
std::vector<SourceOperandIndex> outerInputOperands;
// Output operands of the IF operation.
std::vector<SourceOperandIndex> outerOutputOperands;
// Input operands of the "then" branch model.
std::vector<SourceOperandIndex> thenBranchInputOperands;
// Output operands of the "then" branch model.
std::vector<SourceOperandIndex> thenBranchOutputOperands;
// Input operands of the "else" branch model.
std::vector<SourceOperandIndex> elseBranchInputOperands;
// Output operands of the "else" branch model.
std::vector<SourceOperandIndex> elseBranchOutputOperands;
};
// A WHILE operation to be run on the ExecutionPlan::next() interpreter. The
// condition and body models might run other devices. See LogicalStep.
//
// Execution plan structure:
// Index Step
// i while cond=(i + 1) body=(j + 1) exit=(k + 1)
// ... (cond model steps)
// j goto i
// ... (body model steps)
// k goto i
// ... (steps after the WHILE)
//
// Note that WhileStep has WhileState associated with it.
struct WhileStep {
// The index of this step.
size_t index = ~size_t(0);
// The index of the first step of the condition model.
size_t condStepIndex = ~size_t(0);
// The index of the first step of the body model.
size_t bodyStepIndex = ~size_t(0);
// The index of the first step after the loop.
size_t exitStepIndex = ~size_t(0);
// Input operands of the WHILE operation to be passed to the condition and
// body models.
std::vector<SourceOperandIndex> outerInputOperands;
// Output operands of the WHILE operation.
std::vector<SourceOperandIndex> outerOutputOperands;
// Input operands of the condition model.
std::vector<SourceOperandIndex> condInputOperands;
// Output operand of the condition model. The value of this operand
// determines whether to continue execution or exit the loop.
SourceOperandIndex condOutputOperand = {~uint32_t(0), ~uint32_t(0)};
// Input operands of the body model.
std::vector<SourceOperandIndex> bodyInputOperands;
// Output operands of the body model.
std::vector<SourceOperandIndex> bodyOutputOperands;
};
// A helper step. See LogicalStep.
struct GotoStep {
// The index of this step.
size_t index = ~size_t(0);
// The index of the step to go to.
size_t gotoStepIndex = ~size_t(0);
};
// One of ExecutionStep, IfStep, WhileStep, or GotoStep.
//
// When ExecutionPlan::next() is called, it interprets logical steps until it
// encounters an ExecutionStep ("interpreted execution").
// - For an IfStep, it decides which branch to take and proceeds to the
// corresponding step.
// - For a WhileStep, it decides whether to execute the condition or body (based
// on WhileState), or exit the loop (based on the condition model output), and
// proceeds to the corresponding step.
// - For a GotoStep, it proceeds to the indicated step unconditionally.
class LogicalStep {
public:
template <typename... Args>
explicit LogicalStep(Args&&... args) : mStep(std::forward<Args>(args)...) {}
bool isExecution() const { return std::holds_alternative<ExecutionStep>(mStep); }
bool isIf() const { return std::holds_alternative<IfStep>(mStep); }
bool isWhile() const { return std::holds_alternative<WhileStep>(mStep); }
bool isGoto() const { return std::holds_alternative<GotoStep>(mStep); }
// Returns a non-null pointer or crashes.
ExecutionStep* executionStep() { return &std::get<ExecutionStep>(mStep); }
IfStep* ifStep() { return &std::get<IfStep>(mStep); }
WhileStep* whileStep() { return &std::get<WhileStep>(mStep); }
GotoStep* gotoStep() { return &std::get<GotoStep>(mStep); }
// Returns a non-null pointer or crashes.
const ExecutionStep* executionStep() const { return &std::get<ExecutionStep>(mStep); }
const IfStep* ifStep() const { return &std::get<IfStep>(mStep); }
const WhileStep* whileStep() const { return &std::get<WhileStep>(mStep); }
const GotoStep* gotoStep() const { return &std::get<GotoStep>(mStep); }
// May return nullptr.
ExecutionStep* tryExecutionStep() { return std::get_if<ExecutionStep>(&mStep); }
IfStep* tryIfStep() { return std::get_if<IfStep>(&mStep); }
WhileStep* tryWhileStep() { return std::get_if<WhileStep>(&mStep); }
GotoStep* tryGotoStep() { return std::get_if<GotoStep>(&mStep); }
// May return nullptr.
const ExecutionStep* tryExecutionStep() const { return std::get_if<ExecutionStep>(&mStep); }
const IfStep* tryIfStep() const { return std::get_if<IfStep>(&mStep); }
const WhileStep* tryWhileStep() const { return std::get_if<WhileStep>(&mStep); }
const GotoStep* tryGotoStep() const { return std::get_if<GotoStep>(&mStep); }
void dump() const;
private:
std::variant<ExecutionStep, IfStep, WhileStep, GotoStep> mStep;
};
std::ostream& operator<<(std::ostream& os, const IfStep& step);
std::ostream& operator<<(std::ostream& os, const WhileStep& step);
std::ostream& operator<<(std::ostream& os, const GotoStep& step);
// Describes the state of WhileStep.
struct WhileState {
// A pseudo iteration number indicating the loop is not being executed.
static constexpr uint64_t kOutsideLoop = ~uint64_t(0);
// Whether we need to evaluate the condition or body next.
enum Stage { EVALUATE_CONDITION, EVALUATE_BODY } stage = EVALUATE_CONDITION;
// Current iteration number. Must be set to kOutsideLoop when exiting the
// loop.
uint64_t iteration = kOutsideLoop;
// Time point when the loop started executing.
TimePoint startTime;
};
struct ConstantCopyLocation {
const uint8_t* buffer;
uint32_t length;
};
struct ConstantReferenceLocation {
const RuntimeMemory* memory;
uint32_t offset;
uint32_t length;
};
// A tuple of {execution_step_index, io_type, io_index} specifying an input/output role of an
// ExecutionStep.
using StepRole = std::tuple<uint32_t, IOType, uint32_t>;
// A callback function that takes the prepared_model, io_type, and io_index of a step role.
using StepRoleCallback = std::function<void(const RuntimePreparedModel*, IOType, uint32_t)>;
class ExecutionPlan {
public:
ExecutionPlan(const ExecutionPlan&) = delete;
ExecutionPlan& operator=(const ExecutionPlan&) = delete;
ExecutionPlan() {}
~ExecutionPlan() { delete mBody; }
// Controller is part of the interface to a mechanism for performing a
// main execution in N steps.
//
// The value of N may not be known beforehand if the model contains WHILE
// loops. See LogicalStep.
//
// Usage pattern:
// - Instantiate Controller with ExecutionPlan::makeController().
// - Call ExecutionPlan::next() on Controller N+1 times. The first N times,
// *executor is set to point to a new StepExecutor corresponding
// to that step. The N+1st time, *executor is set to nullptr,
// signifying there are no more steps.
// - If ExecutionPlan::next() returns anything other than ANEURALNETWORKS_NO_ERROR,
// a problem has occurred.
class Controller {
friend class ExecutionPlan;
private:
Controller(const Controller&) = delete;
Controller& operator=(const Controller&) = delete;
static const size_t kBadStepIndex = ~size_t(0);
// A constructor for mState == COMPOUND.
Controller(const ExecutionPlan* plan, ExecutionBuilder* executionBuilder,
const BurstBuilder* burstBuilder,
// static temporaries
uint32_t totalSizeOfTemporaries,
std::map<SourceOperandIndex, StaticTemporaryLocation>
sourceOperandToLocationOfTemporary,
std::map<SourceOperandIndex, StaticTemporaryLocation>
sourceOperandToLocationOfTemporary2,
std::map<SourceOperandIndex, uint32_t> sourceOperandToInputIndex,
std::map<SourceOperandIndex, uint32_t> sourceOperandToOutputIndex,
const std::map<SourceOperandIndex, ConstantCopyLocation>&
sourceOperandToConstantCopy,
std::map<SourceOperandIndex, ConstantReferenceLocation>
sourceOperandToConstantReference,
DynamicTemporaries dynamicTemporaries);
// Sets the location of innerOperand to be the same as the location of outerOperand.
void setInput(const SourceOperandIndex& outerOperand,
const SourceOperandIndex& innerOperand);
void setOutput(const SourceOperandIndex& outerOperand,
const SourceOperandIndex& innerOperand);
// Wait for mLastStepSyncFd to signal.
// No-op if mLastStepSyncFd is -1 which the mLastStepSyncFd is initialized to.
// mLastStepSyncFd will also be set to -1 when the most recently processed step
// does not generate a sync fence.
int waitForLastStepSyncFence() const;
[[maybe_unused]] const ExecutionPlan* mPlan;
ExecutionBuilder* mExecutionBuilder;
const BurstBuilder* mBurstBuilder;
// Map from source operand index to an offset into mTemporaries used
// to represent that operand as an inter-partition input or output.
//
// The four maps
// - mSourceOperandToLocationOfTemporary
// - mSourceOperandToInputIndex
// - mSourceOperandToOutputIndex
// - mSourceOperandToConstantReference
// are initialized from similarly named fields of ExecutionPlan::CompoundBody.
//
// A particular key appears in at most one map at any given time. This
// restriction does not apply to mSourceOperandToLocationOfTemporary2.
//
// The maps are modified during the execution of IfStep and WhileStep.
// See ExecutionPlan::nextCompound().
std::map<SourceOperandIndex, StaticTemporaryLocation> mSourceOperandToLocationOfTemporary;
// Map from source operand index to an additional offset into
// mTemporaries used for double buffering of WHILE loop output operands.
std::map<SourceOperandIndex, StaticTemporaryLocation> mSourceOperandToLocationOfTemporary2;
// Map from source operand index to an input index of the main model.
std::map<SourceOperandIndex, uint32_t> mSourceOperandToInputIndex;
// Map from source operand index to an output index of the main model.
std::map<SourceOperandIndex, uint32_t> mSourceOperandToOutputIndex;
// Map from source operand index to a constant reference location.
// Used for WHILE loop operand initializers that are constant references.
std::map<SourceOperandIndex, ConstantReferenceLocation> mSourceOperandToConstantReference;
// static temporaries
std::unique_ptr<MemoryAshmem> mTemporaries;
DynamicTemporaries mDynamicTemporaries;
// Index of the next step to be processed by ExecutionPlan::next().
size_t mNextStepIndex;
// The value to reset mNextStepIndex to for partial CPU fallback.
size_t mFallbackNextStepIndex;
// Map from WhileStep index to the associated WhileState.
std::unordered_map<size_t, WhileState> mWhileState;
// The sync fence fd of the last step.
int mLastStepSyncFd;
};
std::vector<SharedBurst> makeBursts() const;
// Only legal to call when mState == COMPOUND.
std::shared_ptr<Controller> makeController(ExecutionBuilder* executionBuilder,
const BurstBuilder* burstBuilder) const;
// Sets up a new StepExecutor and burstController (if applicable) if there
// is a step to execute. See ExecutionPlan::Controller.
// Handles control flow. See LogicalStep.
// burstController is nullptr if we are not to do burst execution.
// mainModelOutputShapes may be nullptr if the only main model outputs that are step model
// inputs are of fully specified shape.
// syncFdOfLastStep is the sync fence fd generated by the most recently processed step.
// Only legal to call when mState == COMPOUND.
int next(std::shared_ptr<Controller> controller, std::shared_ptr<StepExecutor>* executor,
SharedBurst* burstController, const std::vector<OutputShape>* mainModelOutputShapes,
int syncFdOfLastStep = -1) const;
// Create the same executor as the last one created by next().
int fallback(std::shared_ptr<Controller> controller, std::shared_ptr<StepExecutor>* executor,
SharedBurst* burstController,
const std::vector<OutputShape>* mainModelOutputShapes) const;
// Only legal to call when mState == SIMPLE.
// See the constructor of StepExecutor for the semantics of "reusable".
std::shared_ptr<StepExecutor> makeStepExecutor(bool reusable,
ExecutionBuilder* executionBuilder) const;
ExecutionStep* createNewExecutionStep(uint32_t sourceModelIndex,
const std::shared_ptr<Device> device);
IfStep* createNewIfStep();
WhileStep* createNewWhileStep();
GotoStep* createNewGotoStep();
// Only legal to call when mState == COMPOUND.
size_t getNextStepIndex() const { return compound()->mSteps.size(); }
void becomeSingleStep(const std::shared_ptr<Device> device, const ModelBuilder* model);
// simulateFailureResultCode == ANEURALNETWORKS_NO_ERROR means behave normally.
int finish(int32_t executionPreference, int32_t priority, const OptionalTimePoint& deadline,
int simulateFailureResultCode);
void recordOutputDef(SourceOperandIndex sourceOperandIndex, uint32_t stepIndex);
void recordTemporaryDef(SourceOperandIndex sourceOperandIndex, uint32_t stepIndex);
void dump() const;
void reset();
bool isValid() const { return mState != EMPTY && mBody != nullptr && mBody->mSuccessfulFinish; }
bool isSimple() const { return mState == SIMPLE; }
bool isCompound() const { return mState == COMPOUND; }
bool isSimpleCpu() const;
void setCaching(const CacheInfo* cacheInfo, const uint8_t* token) {
mCacheInfo = cacheInfo;
mToken = token;
}
const CacheInfo* getCacheInfo() const { return mCacheInfo; }
const uint8_t* getCacheToken() const { return mToken; }
// The caller is responsible for making sure the index is within range.
void forEachStepRoleOfInput(uint32_t index, const StepRoleCallback& callback) const {
CHECK(mBody != nullptr);
mBody->forEachStepRoleOfInput(index, callback);
}
void forEachStepRoleOfOutput(uint32_t index, const StepRoleCallback& callback) const {
CHECK(mBody != nullptr);
mBody->forEachStepRoleOfOutput(index, callback);
}
// "type" specifies input or output, and "index" is the main model input or output index.
// The caller is responsible for making sure the index is within range.
MemoryPreference getMemoryPreference(IOType type, uint32_t index) const;
SourceModels& getSourceModels() { return mSourceModels; }
const SourceModels& getSourceModels() const { return mSourceModels; }
// "index" is the main model input or output index.
// The caller is responsible for making sure the index is within range.
SourceOperandIndex getInputSourceOperand(uint32_t index) const;
SourceOperandIndex getOutputSourceOperand(uint32_t index) const;
bool hasDynamicTemporaries() const;
// These functions are solely intended for use by unit tests of
// the partitioning algorithm.
enum class Kind {
ERROR,
EMPTY,
SIMPLE,
COMPOUND
}; // See operator<< defined outside this class
Kind forTest_getKind() const;
std::shared_ptr<const Device> forTest_simpleGetDevice() const;
const std::vector<std::shared_ptr<LogicalStep>>& forTest_compoundGetSteps() const;
void forTest_compoundForEachStepRoleOfSourceOperand(SourceOperandIndex index,
const StepRoleCallback& callback) const {
compound()->forEachStepRoleOfSourceOperand(index, callback);
}
// The "flat" in the name signifies that this method requires that the
// model not contain any control flow operations.
std::set<uint32_t> forTest_flatGetDynamicTemporaries() const;
const uint8_t* forTest_simpleGetCacheToken() const;
bool forTest_hasStepModelWithNoInputsOrNoOutputs() const;
private:
// Becomes a new COMPOUND step if mState == EMPTY, otherwise does nothing.
// Illegal to call for when mState == SIMPLE.
void becomeCompoundIfEmpty();
const Operand& getSourceOperand(const std::pair<uint32_t, uint32_t>& sourceOperandIndex) const {
return getSourceModels()
.getModel(sourceOperandIndex.first)
->getOperand(sourceOperandIndex.second);
}
class Buffer {
public:
Buffer(void* pointer, uint32_t size);
Buffer(RunTimePoolInfo info, uint32_t offset);
void* getPointer() const;
uint32_t getSize() const;
void flush() const;
private:
RunTimePoolInfo mInfo;
uint32_t mOffset;
};
// Returns the buffer associated with a partition boundary operand.
std::optional<Buffer> getBuffer(std::shared_ptr<Controller> controller,
SourceOperandIndex operandIndex) const;
std::optional<Buffer> getBufferFromModelArgumentInfo(
const ModelArgumentInfo& info, const ExecutionBuilder* executionBuilder) const;
// Reads the value of a partition boundary boolean condition operand.
int readConditionValue(std::shared_ptr<Controller> controller, SourceOperandIndex operandIndex,
bool* value) const;
// Handles control flow. See LogicalStep.
int nextCompound(std::shared_ptr<Controller> controller,
std::shared_ptr<StepExecutor>* executor, SharedBurst* burstController,
const std::vector<OutputShape>* mainModelOutputShapes) const;
int nextCompound(const ExecutionStep* step, std::shared_ptr<Controller> controller,
std::shared_ptr<StepExecutor>* executor, SharedBurst* burstController,
const std::vector<OutputShape>* mainModelOutputShapes) const;
int nextCompound(const IfStep* step, std::shared_ptr<Controller> controller,
std::shared_ptr<StepExecutor>* executor, SharedBurst* burstController,
const std::vector<OutputShape>* mainModelOutputShapes) const;
int nextCompound(const WhileStep* step, std::shared_ptr<Controller> controller,
std::shared_ptr<StepExecutor>* executor, SharedBurst* burstController,
const std::vector<OutputShape>* mainModelOutputShapes) const;
int nextCompound(const GotoStep* step, std::shared_ptr<Controller> controller,
std::shared_ptr<StepExecutor>* executor, SharedBurst* burstController,
const std::vector<OutputShape>* mainModelOutputShapes) const;
struct Body {
virtual ~Body() {}
virtual void dump() const = 0;
virtual int finish(const SourceModels* sourceModels, int32_t executionPreference,
int32_t priority, const OptionalTimePoint& deadline,
int simulateFailureResultCode) = 0;
virtual bool hasDynamicTemporaries() const = 0;
virtual bool hasStepModelWithNoInputsOrNoOutputs() const = 0;
virtual void forEachStepRoleOfInput(uint32_t index,
const StepRoleCallback& callback) const = 0;
virtual void forEachStepRoleOfOutput(uint32_t index,
const StepRoleCallback& callback) const = 0;
bool mSuccessfulFinish = false;
};
struct SimpleBody : Body {
SimpleBody(std::shared_ptr<Device> device, const ModelBuilder* model,
const CacheInfo* cacheInfo, const uint8_t* token)
: mDevice(device), mModel(model), mCacheInfo(cacheInfo), mToken(token) {}
void dump() const override;
int finish(const SourceModels* sourceModels, int32_t executionPreference, int32_t priority,
const OptionalTimePoint& deadline, int simulateFailureResultCode) override;
bool hasDynamicTemporaries() const override { return false; }
bool hasStepModelWithNoInputsOrNoOutputs() const override { return false; }
void forEachStepRoleOfInput(uint32_t index,
const StepRoleCallback& callback) const override;
void forEachStepRoleOfOutput(uint32_t index,
const StepRoleCallback& callback) const override;
std::shared_ptr<Device> mDevice;
const ModelBuilder* mModel;
std::shared_ptr<RuntimePreparedModel> mPreparedModel;
const CacheInfo* mCacheInfo;
TokenHasher mToken;
};
struct CompoundBody : Body {
CompoundBody(const ExecutionPlan* plan) : mPlan(plan) { CHECK(plan != nullptr); }
void dump() const override;
int finish(const SourceModels* sourceModels, int32_t executionPreference, int32_t priority,
const OptionalTimePoint& deadline, int simulateFailureResultCode) override;
bool hasDynamicTemporaries() const override { return mHasDynamicTemporaries; }
bool hasStepModelWithNoInputsOrNoOutputs() const override;
void forEachStepRoleOfInput(uint32_t index,
const StepRoleCallback& callback) const override;
void forEachStepRoleOfOutput(uint32_t index,
const StepRoleCallback& callback) const override;
// Supported for any legal source operand index. For a source operand that doesn't have a
// step role, the callback will not be invoked at all.
void forEachStepRoleOfSourceOperand(const SourceOperandIndex& index,
const StepRoleCallback& callback) const;
// Supported for any legal source operand index.
MemoryPreference getMemoryPreferenceOfSourceOperand(const SourceOperandIndex& index) const;
// TODO: Some of the data is working state information that
// shouldn't be needed after we've constructed but not
// executed the plan.
std::vector<std::shared_ptr<LogicalStep>> mSteps;
// Map from source operand index to defining ExecutionStep index.
// Used for all (and only) SUBGRAPH_OUTPUTs that are defined by
// ExecutionSteps. Those defined by IfSteps and WhileSteps are not in
// the map.
std::map<SourceOperandIndex, uint32_t> mOutputToDefiningExecutionStep;
// Map from source operand index to defining ExecutionStep index.
// Used for all (and only) TEMPORARY_VARIABLEs that are defined by
// ExecutionSteps. Those defined by IfSteps and WhileSteps are not in
// the map.
std::map<SourceOperandIndex, uint32_t> mTemporaryToDefiningExecutionStep;
// Map from source operand index to input index of the main model.
// This map only contains SUBGRAPH_INPUTs of the main model and is used
// to initialize ExecutionPlan::Controller::mSourceOperandToInputIndex;
std::map<SourceOperandIndex, uint32_t> mSourceOperandToInputIndex;
// Map from source operand index to output index of the main model.
// This map only contains SUBGRAPH_OUTPUTs of the main model and is used
// to initialize ExecutionPlan::Controller::mSourceOperandToOutputIndex;
std::map<SourceOperandIndex, uint32_t> mSourceOperandToOutputIndex;
// Map from source operand index to location of a CONSTANT_COPY or
// POINTER operand.
// This map only contains constant partition boundary IF and WHILE
// operands and is used to create a ExecutionPlan::Controller.
std::map<SourceOperandIndex, ConstantCopyLocation> mSourceOperandToBoundaryConstantCopy;
// Map from source operand index to location of a CONSTANT_REFERENCE
// operand. This map only contains constant partition boundary IF and
// WHILE operands and is used to initialize
// ExecutionPlan::Controller::mSourceOperandToConstantReference.
std::map<SourceOperandIndex, ConstantReferenceLocation>
mSourceOperandToBoundaryConstantReference;
// Map from source operand index of a boundary operand to the step roles that its memory
// may be used for.
// This map only contains partition boundary operands that have ExecutionStep roles, that
// is, SUBGRAPH_INPUTs, SUBGRAPH_OUTPUTs, and partition boundary static and dynamic
// temporaries. If a partition boundary operand is not found in the map, then the operand
// does not have any ExecutionStep role (this may happen with interpreted control flow).
std::map<SourceOperandIndex, std::set<StepRole>> mSourceOperandToStepRoles;
bool mHasDynamicTemporaries = false;
private:
void findTempsAsStepModelOutputs();
void findModelOutputsThatAreDownstreamInputs();
// Constant values that are inputs to IF and WHILE operations and lie on
// a partition boundary ("control flow boundary constants") require
// special treatment. We need to be able to dynamically associate those
// values with the corresponding SUBGRAPH_INPUT operands in a referenced
// model.
//
// For CONSTANT_COPY and POINTER boundary operands, we copy those to
// temporary memory and treat them similarly to TEMPORARY_VARIABLE
// operands in Controller.
//
// For CONSTANT_REFERENCE boundary operands, we keep track of them in
// ExecutionPlan::Controller::mSourceOperandToConstantReference.
//
// Note that for IF inputs and input-only WHILE inputs that are boundary
// constants, we could embed those inside the referenced model, but we
// currently don't do so. See b/148216514.
void findControlFlowBoundaryConstants(const SourceModels* sourceModels);
// This method will set mSourceOperandToStepRoles.
void findMemoryStepRoles();
const ExecutionPlan* mPlan;
};
enum { EMPTY, SIMPLE, COMPOUND } mState = EMPTY;
Body* mBody = nullptr;
SimpleBody* simple() {
CHECK(mState == SIMPLE);
CHECK(mBody != nullptr);
return static_cast<SimpleBody*>(mBody);
}
const SimpleBody* simple() const {
CHECK(mState == SIMPLE);
CHECK(mBody != nullptr);
return static_cast<const SimpleBody*>(mBody);
}
CompoundBody* compound() {
CHECK(mState == COMPOUND);
CHECK(mBody != nullptr);
return static_cast<CompoundBody*>(mBody);
}
const CompoundBody* compound() const {
CHECK(mState == COMPOUND);
CHECK(mBody != nullptr);
return static_cast<const CompoundBody*>(mBody);
}
void forEachDynamicTemporary(const std::function<void(SourceOperandIndex, const Operand&,
uint32_t definingStepIndex)>&) const;
// Pointers to compilation caching information in CompilationBuilder.
const CacheInfo* mCacheInfo = nullptr;
const uint8_t* mToken = nullptr;
SourceModels mSourceModels;
};
inline std::ostream& operator<<(std::ostream& out, ExecutionPlan::Kind kind) {
const int intKind = static_cast<int>(kind);
if (kind < ExecutionPlan::Kind::ERROR || kind > ExecutionPlan::Kind::COMPOUND) {
return out << "<UNK(" << intKind << ")>";
}
static const char* name[] = {"ERROR", "EMPTY", "SIMPLE", "COMPOUND"};
return out << name[intKind];
}
} // namespace nn
} // namespace android
#endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_EXECUTION_PLAN_H