You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

364 lines
14 KiB

/*
* 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.
*/
#ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_MEMORY_H
#define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_MEMORY_H
#include <CpuExecutor.h>
#include <LegacyUtils.h>
#include <android-base/macros.h>
#include <android-base/scopeguard.h>
#include <nnapi/IBuffer.h>
#include <nnapi/IBurst.h>
#include <nnapi/SharedMemory.h>
#include <nnapi/Validation.h>
#include <sys/mman.h>
#include <algorithm>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include "NeuralNetworks.h"
namespace android {
namespace nn {
class CompilationBuilder;
class Device;
class ModelBuilder;
class RuntimePreparedModel;
// A utility template class to accumulate multiple objects and assign each
// a distinct index number, starting with 0.
//
// The user of this class is responsible for avoiding concurrent calls
// to this class from multiple threads.
template <typename ObjectType>
class ObjectTracker {
public:
// Adds the object, if it does not already exists. Returns its index.
// The objects should survive the tracker.
uint32_t add(const ObjectType* object) {
VLOG(MEMORY) << __func__ << "(" << SHOW_IF_DEBUG(object) << ")";
// See if we already have this object. If so, return its index.
auto i = mKnown.find(object);
if (i != mKnown.end()) {
return i->second;
}
VLOG(MEMORY) << "It's new";
// It's a new one. Save it an assign an index to it.
size_t next = mKnown.size();
uint32_t idx = static_cast<uint32_t>(next);
mKnown[object] = idx;
mObjects.push_back(object);
return idx;
}
// Returns the number of objects contained.
uint32_t size() const { return mObjects.size(); }
// Returns the ith object.
const ObjectType* operator[](size_t i) const {
CHECK(i < size());
return mObjects[i];
}
// Iteration
auto begin() { return mObjects.begin(); }
auto end() { return mObjects.end(); }
auto begin() const { return mObjects.begin(); }
auto end() const { return mObjects.end(); }
const std::vector<const ObjectType*>& getObjects() const { return mObjects; }
private:
// The vector of object pointers we are building.
std::vector<const ObjectType*> mObjects;
// A faster way to see if we already have an object than doing find().
std::unordered_map<const ObjectType*, uint32_t> mKnown;
};
using CompilationRole = std::tuple<const CompilationBuilder*, IOType, uint32_t>;
struct MemoryDescriptor {
std::vector<uint32_t> dimensions;
ObjectTracker<RuntimePreparedModel> preparedModels;
std::vector<BufferRole> inputRoles, outputRoles;
};
class MemoryValidatorBase {
DISALLOW_COPY_AND_ASSIGN(MemoryValidatorBase);
public:
MemoryValidatorBase() = default;
virtual ~MemoryValidatorBase() = default;
// Validate the memory usage and size information when passed in
// ANeuralNetworks{Model,Compilation}_set*FromMemory.
//
// This method only validates the arguments against the memory. It does not validate the
// correctness of the arguments themselves. E.g. it does not validate if the index is out of
// range.
//
// Usages:
// - ANeuralNetworksModel_setOperandValueFromMemory:
// validate(nullptr, IOType::INPUT, operandIndex, nullptr, offset, length)
//
// - ANeuralNetworksExecution_setInputFromMemory:
// validate(compilation, IOType::INPUT, inputIndex, type, offset, length)
//
// - ANeuralNetworksExecution_setOutputFromMemory:
// validate(compilation, IOType::OUTPUT, outputIndex, type, offset, length)
//
virtual bool validate(const CompilationBuilder* compilation, IOType ioType, uint32_t index,
const ANeuralNetworksOperandType* type, uint32_t offset,
uint32_t length) const = 0;
// Validate the memory dimensional information at the beginning of a computation.
virtual bool validateInputDimensions(const std::vector<uint32_t>&) const { return true; }
// The validation metadata for this memory.
struct Metadata {
// The byte size of the memory when it is transformed to a closely packed layout.
// Set to 0 if unknown (e.g. non-BLOB mode AHWB or device memory with dynamic shape).
uint32_t logicalSize;
// The dimensions of the memory. Set to empty if undefined.
std::vector<uint32_t> dimensions;
// The data type, scale, zero point, and extra parameters of the target operand.
// Other fields will be ignored, including dimensions, lifetime, location, etc.
// Set to std::nullopt if undefined.
std::optional<Operand> operand;
};
virtual Metadata getMetadata() const = 0;
// Try update the memory metadata with the provided metadata. Return false if incompatible.
virtual bool updateMetadata(const Metadata& metadata) = 0;
// Whether the memory is created with unknown dimensions or rank.
virtual bool createdWithUnknownShape() const { return false; }
virtual void setInitialized(bool) {}
virtual bool isInitialized() const { return true; }
};
int copyIBufferToMemory(const SharedBuffer& src, const SharedMemory& dst);
int copyMemoryToIBuffer(const SharedMemory& src, const SharedBuffer& dst,
const std::vector<uint32_t>& dimensions);
// Represents a memory region.
class RuntimeMemory {
// Disallow copy and assign to prevent slicing
DISALLOW_COPY_AND_ASSIGN(RuntimeMemory);
public:
virtual ~RuntimeMemory() = default;
Request::MemoryPool getMemoryPool() const;
const SharedMemory& getMemory() const { return kMemory; }
const SharedBuffer& getIBuffer() const { return kBuffer; }
virtual uint32_t getSize() const { return nn::getSize(getMemory()); }
virtual std::optional<RunTimePoolInfo> getRunTimePoolInfo() const;
MemoryValidatorBase& getValidator() const {
CHECK(mValidator != nullptr);
return *mValidator;
}
void setValidator(std::unique_ptr<MemoryValidatorBase> validator) {
mValidator = std::move(validator);
}
// This function binds `cacheHold` to the memory object, holding it for as long as the Memory
// object is alive. This keeps the cache present while the Memory object is alive. If
// `cacheHold` is null, this function is a no-op.
void hold(const IBurst::OptionalCacheHold& cacheHold) const;
static int copy(const RuntimeMemory& src, const RuntimeMemory& dst);
protected:
explicit RuntimeMemory(SharedMemory memory);
RuntimeMemory(SharedMemory memory, std::unique_ptr<MemoryValidatorBase> validator);
explicit RuntimeMemory(SharedBuffer buffer);
// The canonical representation for this memory. We will use one of the
// following values when communicating with the drivers.
const SharedMemory kMemory = std::make_shared<const Memory>();
const SharedBuffer kBuffer;
std::unique_ptr<MemoryValidatorBase> mValidator;
private:
mutable std::mutex mMutex;
// This set contains `CacheHold` objects, holding it for as long as the Memory object is alive.
// This keeps the cache present while the Memory object is alive.
mutable std::set<IBurst::OptionalCacheHold> mHold;
mutable std::optional<RunTimePoolInfo> mCachedRunTimePoolInfo;
mutable bool mHasCachedRunTimePoolInfo = false;
};
class MemoryBuilder {
DISALLOW_COPY_AND_ASSIGN(MemoryBuilder);
public:
MemoryBuilder() = default;
int addRole(const CompilationBuilder& compilation, IOType ioType, uint32_t index, float freq);
int setDimensions(const std::vector<uint32_t>& dimensions);
int finish();
std::pair<int, std::unique_ptr<RuntimeMemory>> allocate() const;
private:
bool badState(const char* name) const;
// The memory descriptor that the MemoryBuilder is building.
MemoryDescriptor mDesc;
// The roles that have been specified via addRole.
// This is to check whether a new role has been seen before or not.
std::set<CompilationRole> mRoles;
// Keep track of the data type, scale, zero point, and extra parameters of the target operand.
// Other fields will be ignored, including dimensions, lifetime, location, etc.
// It is std::nullopt if no usage has been specified yet.
std::optional<Operand> mOperand;
// Once the descriptor has been finished, we should not allow further modifications.
bool mFinished = false;
// The following fields are only valid when finished.
// The chosen device to allocate the memory. Set to nullptr if there are multiple devices.
const Device* mAllocator = nullptr;
// Whether BLOB mode AHWB is supported on all of the relevant devices of the roles.
bool mSupportsAhwb = false;
// If set to true, allocate() will fallback to Ashmem or AHardwareBuffer if the memory
// allocation fails on the chosen device, or if there is no device chosen.
bool mShouldFallback = true;
};
class MemoryAshmem : public RuntimeMemory {
public:
// Creates a memory object containing a new android shared memory ("ashmem")
// object of the size specified in bytes. Because this ashmem region can be
// shared with and accessed by one or more driver processes, MemoryAshmem
// has shared ownership over the ashmem region.
//
// On success, returns ANEURALNETWORKS_NO_ERROR and a memory object.
// On error, returns the appropriate NNAPI error code and nullptr.
static std::pair<int, std::unique_ptr<MemoryAshmem>> create(uint32_t size);
// Get a pointer to the ashmem region of memory. The returned pointer is
// valid for the lifetime of the MemoryAshmem object. This call always
// returns non-null because it was validated during MemoryAshmem::create.
uint8_t* getPointer() const;
std::optional<RunTimePoolInfo> getRunTimePoolInfo() const override {
return RunTimePoolInfo::createFromExistingBuffer(getPointer(), nn::getSize(kMemory));
}
// prefer using MemoryAshmem::create
MemoryAshmem(SharedMemory memory, Mapping mapped);
private:
const Mapping kMapping;
};
class MemoryFd : public RuntimeMemory {
public:
// Create a memory object based on input size, prot, and fd. This function
// duplicates the provided fd, and owns the duplicate.
//
// On success, returns ANEURALNETWORKS_NO_ERROR and a memory object.
// On error, returns the appropriate NNAPI error code and nullptr.
static std::pair<int, std::unique_ptr<MemoryFd>> create(size_t size, int prot, int fd,
size_t offset);
// prefer using MemoryFd::create
explicit MemoryFd(SharedMemory memory);
};
class MemoryAHWB : public RuntimeMemory {
public:
// Create a memory object to keep track of (but not take ownership of) the
// provided AHardwareBuffer handle.
//
// On success, returns ANEURALNETWORKS_NO_ERROR and a memory object.
// On error, returns the appropriate NNAPI error code and nullptr.
static std::pair<int, std::unique_ptr<MemoryAHWB>> create(const AHardwareBuffer& ahwb);
// prefer using MemoryAHWB::create
MemoryAHWB(SharedMemory memory, std::unique_ptr<MemoryValidatorBase> validator)
: RuntimeMemory(std::move(memory), std::move(validator)) {}
};
class MemoryRuntimeAHWB : public RuntimeMemory {
public:
// Create a memory object containing a new BLOB-mode AHardwareBuffer memory
// object of the size specified in bytes. The created memory is managed and
// owned by the NNAPI runtime.
//
// On success, returns ANEURALNETWORKS_NO_ERROR and a memory object.
// On error, returns the appropriate NNAPI error code and nullptr.
static std::pair<int, std::unique_ptr<MemoryRuntimeAHWB>> create(uint32_t size);
// Get a pointer to the content of the memory. The returned pointer is
// valid for the lifetime of the MemoryRuntimeAHWB object. This call always
// returns non-null because it was validated during MemoryRuntimeAHWB::create.
uint8_t* getPointer() const;
std::optional<RunTimePoolInfo> getRunTimePoolInfo() const override {
return RunTimePoolInfo::createFromExistingBuffer(getPointer(), nn::getSize(kMemory));
}
// prefer using MemoryRuntimeAHWB::create
MemoryRuntimeAHWB(SharedMemory memory, Mapping mapping);
private:
const Mapping kMapping;
};
class MemoryFromDevice : public RuntimeMemory {
public:
// Create a memory object to keep track of a driver-allocated device memory.
// The memory is recognized by the driver via a token.
//
// On success, returns ANEURALNETWORKS_NO_ERROR and a memory object.
// On error, returns the appropriate NNAPI error code and nullptr.
static std::pair<int, std::unique_ptr<MemoryFromDevice>> create(SharedBuffer buffer);
// prefer using MemoryFromDevice::create
explicit MemoryFromDevice(SharedBuffer buffer);
};
using MemoryTracker = ObjectTracker<RuntimeMemory>;
} // namespace nn
} // namespace android
#endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_MEMORY_H