Adding New Experimental Op Files for Densify
Adding Densify operation .cpp and .h for converting sparse
tensor metadata into a dense operand. Adding
ANEURALNETWORKS_DENSIFY operation code. Creating
NeuralNetworksExperimentalFeatures.h for experimental
operation code. Modifying files to allow experimental
features.
Bug: 152069780
Test: mma
Change-Id: I18f46e6af904eca6848a3295def115374dd4caf1
diff --git a/common/Android.bp b/common/Android.bp
index b28f1fb..b02e500 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -36,6 +36,7 @@
"operations/Comparisons.cpp",
"operations/Concatenation.cpp",
"operations/Conv2D.cpp",
+ "operations/Densify.cpp",
"operations/DepthwiseConv2D.cpp",
"operations/Dequantize.cpp",
"operations/Elementwise.cpp",
@@ -154,8 +155,8 @@
],
}
-cc_library_static {
- name: "libneuralnetworks_common",
+cc_defaults {
+ name: "libneuralnetworks_common_defaults",
defaults: [
"neuralnetworks_defaults",
"neuralnetworks_operations",
@@ -283,6 +284,17 @@
],
}
+cc_library_static {
+ name: "libneuralnetworks_common",
+ defaults: ["libneuralnetworks_common_defaults"],
+}
+
+cc_library_static {
+ name: "libneuralnetworks_common_experimental",
+ defaults: ["libneuralnetworks_common_defaults"],
+ cflags: ["-DNN_EXPERIMENTAL_FEATURE"],
+}
+
cc_defaults {
name: "neuralnetworks_cl_defaults",
host_supported: false,
diff --git a/common/OperationResolver.cpp b/common/OperationResolver.cpp
index e6792b2..5f5a9b2 100644
--- a/common/OperationResolver.cpp
+++ b/common/OperationResolver.cpp
@@ -97,6 +97,9 @@
const OperationRegistration* register_TRANSPOSE_CONV_2D();
const OperationRegistration* register_UNIDIRECTIONAL_SEQUENCE_LSTM();
const OperationRegistration* register_UNIDIRECTIONAL_SEQUENCE_RNN();
+#ifdef NN_EXPERIMENTAL_FEATURE
+const OperationRegistration* register_DENSIFY();
+#endif // NN_EXPERIMENTAL_FEATURE
BuiltinOperationResolver::BuiltinOperationResolver() {
registerOperation(register_ABS());
@@ -172,21 +175,38 @@
registerOperation(register_TRANSPOSE_CONV_2D());
registerOperation(register_UNIDIRECTIONAL_SEQUENCE_LSTM());
registerOperation(register_UNIDIRECTIONAL_SEQUENCE_RNN());
+#ifdef NN_EXPERIMENTAL_FEATURE
+ registerOperation(register_DENSIFY());
+#endif // NN_EXPERIMENTAL_FEATURE
}
const OperationRegistration* BuiltinOperationResolver::findOperation(
OperationType operationType) const {
auto index = static_cast<int32_t>(operationType);
- if (index < 0 || index >= kNumberOfOperationTypes) {
- return nullptr;
+ if (index >= 0 && index < kNumberOfOperationTypes) {
+ return mRegistrations[index];
}
- return mRegistrations[index];
+#ifdef NN_EXPERIMENTAL_FEATURE
+ if (index >= kStartOfExperimentalOperations &&
+ index < kStartOfExperimentalOperations + kNumberOfExperimentalOperationTypes) {
+ return mExperimentalRegistrations[index - kStartOfExperimentalOperations];
+ }
+#endif // NN_EXPERIMENTAL_FEATURE
+ return nullptr;
}
void BuiltinOperationResolver::registerOperation(
const OperationRegistration* operationRegistration) {
CHECK(operationRegistration != nullptr);
auto index = static_cast<int32_t>(operationRegistration->type);
+#ifdef NN_EXPERIMENTAL_FEATURE
+ if (index >= kStartOfExperimentalOperations) {
+ CHECK_LT(index, kStartOfExperimentalOperations + kNumberOfExperimentalOperationTypes);
+ CHECK(mExperimentalRegistrations[index - kStartOfExperimentalOperations] == nullptr);
+ mExperimentalRegistrations[index - kStartOfExperimentalOperations] = operationRegistration;
+ return;
+ }
+#endif // NN_EXPERIMENTAL_FEATURE
CHECK_LE(0, index);
CHECK_LT(index, kNumberOfOperationTypes);
CHECK(mRegistrations[index] == nullptr);
diff --git a/common/TypeUtils.cpp b/common/TypeUtils.cpp
index ce2cd51..2108c68 100644
--- a/common/TypeUtils.cpp
+++ b/common/TypeUtils.cpp
@@ -555,6 +555,10 @@
return os << "RANK";
case OperationType::OEM_OPERATION:
return os << "OEM_OPERATION";
+#ifdef NN_EXPERIMENTAL_FEATURE
+ case OperationType::DENSIFY:
+ return os << "DENSIFY";
+#endif // NN_EXPERIMENTAL_FEATURE
}
if (isExtension(operationType)) {
return os << "Extension OperationType " << underlyingType(operationType);
@@ -894,6 +898,10 @@
return os << "ANDROID_S";
case Version::CURRENT_RUNTIME:
return os << "CURRENT_RUNTIME";
+#ifdef NN_EXPERIMENTAL_FEATURE
+ case Version::EXPERIMENTAL:
+ return os << "EXPERIMENTAL";
+#endif // NN_EXPERIMENTAL_FEATURE
}
return os << "Version{" << underlyingType(version) << "}";
}
diff --git a/common/include/LegacyUtils.h b/common/include/LegacyUtils.h
index 97d9bff..5c705a5 100644
--- a/common/include/LegacyUtils.h
+++ b/common/include/LegacyUtils.h
@@ -42,6 +42,11 @@
// The number of operation types (OperationCode) defined in NeuralNetworks.h.
const int kNumberOfOperationTypes = 102;
+
+#ifdef NN_EXPERIMENTAL_FEATURE
+const int kNumberOfExperimentalOperationTypes = 1;
+#endif // NN_EXPERIMENTAL_FEATURE
+
static_assert(kNumberOfOperationTypes == BuiltinOperationResolver::kNumberOfOperationTypes);
// The number of execution preferences defined in NeuralNetworks.h.
diff --git a/common/include/OperationResolver.h b/common/include/OperationResolver.h
index 04719b9..d88a244 100644
--- a/common/include/OperationResolver.h
+++ b/common/include/OperationResolver.h
@@ -93,12 +93,27 @@
// The number of operation types (OperationCode) defined in NeuralNetworks.h.
static constexpr int kNumberOfOperationTypes = 102;
+#ifdef NN_EXPERIMENTAL_FEATURE
+ // The number of experimental operation types (ANeuralNetworksExperimentalOperationCode) defined
+ // in NeuralNetworksExperimentalFeatures.h.
+ static constexpr int kNumberOfExperimentalOperationTypes = 1;
+
+ // The starting value of experimental operation types (ANeuralNetworksExperimentalOperationCode)
+ // defined in NeuralNetworksExperimentalFeatures.h.
+ static constexpr int kStartOfExperimentalOperations = 20000;
+#endif // NN_EXPERIMENTAL_FEATURE
+
private:
BuiltinOperationResolver();
void registerOperation(const OperationRegistration* operationRegistration);
const OperationRegistration* mRegistrations[kNumberOfOperationTypes] = {};
+
+#ifdef NN_EXPERIMENTAL_FEATURE
+ const OperationRegistration* mExperimentalRegistrations[kNumberOfExperimentalOperationTypes] =
+ {};
+#endif // NN_EXPERIMENTAL_FEATURE
};
// NN_REGISTER_OPERATION creates OperationRegistration for consumption by
diff --git a/common/include/nnapi/Types.h b/common/include/nnapi/Types.h
index f8124f4..e61fcce 100644
--- a/common/include/nnapi/Types.h
+++ b/common/include/nnapi/Types.h
@@ -979,7 +979,17 @@
// Returns status, timingLaunched, timingFenced
using ExecuteFencedInfoCallback = std::function<GeneralResult<std::pair<Timing, Timing>>()>;
-enum class Version { ANDROID_OC_MR1, ANDROID_P, ANDROID_Q, ANDROID_R, ANDROID_S, CURRENT_RUNTIME };
+enum class Version {
+ ANDROID_OC_MR1,
+ ANDROID_P,
+ ANDROID_Q,
+ ANDROID_R,
+ ANDROID_S,
+ CURRENT_RUNTIME,
+#ifdef NN_EXPERIMENTAL_FEATURE
+ EXPERIMENTAL,
+#endif // NN_EXPERIMENTAL_FEATURE
+};
// Describes the memory preference of an operand.
struct MemoryPreference {
diff --git a/common/operations/Densify.cpp b/common/operations/Densify.cpp
new file mode 100644
index 0000000..ce831ed
--- /dev/null
+++ b/common/operations/Densify.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifdef NN_EXPERIMENTAL_FEATURE
+
+#include "Densify.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <numeric>
+#include <vector>
+
+#include "OperationResolver.h"
+#include "OperationsUtils.h"
+#include "Tracing.h"
+#include "nnapi/OperandTypes.h"
+#include "nnapi/TypeUtils.h"
+#include "nnapi/Validation.h"
+
+#define LOG_TAG "Operations"
+
+namespace android {
+namespace nn {
+namespace densify_op {
+
+constexpr uint32_t kMinNumInputs = 5;
+constexpr uint32_t kInputTensor = 0;
+constexpr uint32_t kInputTravOrder = 1;
+constexpr uint32_t kInputBlockMap = 2;
+constexpr uint32_t kInputDimFormat = 3;
+constexpr uint32_t kInputDimensions = 4;
+constexpr uint32_t kInputArrSeg = 5;
+constexpr uint32_t kInputArrIdx = 6;
+constexpr uint32_t kNumOutputs = 1;
+constexpr uint32_t kOutputTensor = 0;
+constexpr int32_t DENSE = 0;
+constexpr int32_t SPARSE_CSR = 1;
+
+uint64_t getFlattenedIndex(const std::vector<int32_t>& indices, const std::vector<uint32_t>& shape,
+ const int origRank) {
+ uint64_t index = 0;
+ int subElems = 1;
+ // origRank = size of destDims
+ for (int i = origRank - 1; i >= 0; i--) {
+ index += uint64_t(indices[i] * subElems);
+ subElems *= shape[i];
+ }
+ return index;
+}
+
+template <typename T>
+void populate(const T* srcData, std::vector<int32_t>* indices, int32_t level, int32_t prevIdx,
+ T* destData, const std::vector<uint32_t>& destDims,
+ const std::vector<int32_t>& dimFormat, const int32_t* traversalOrder,
+ const std::vector<int32_t>& blockSize, const int32_t* blockMap,
+ const std::vector<std::vector<int32_t>>& dimMetadata, const int origRank) {
+ if (level == (*indices).size()) { // level == size of traversal order
+ std::vector<int> origIdx(origRank);
+ int i = 0;
+ // Calculating origIdx using dense tensor dimensions
+ for (; i < origIdx.size(); i++) {
+ int origDim = traversalOrder[i];
+ origIdx[origDim] = (*indices)[i];
+ }
+ // Modifying origIdx using block dimensions
+ for (; i < (*indices).size(); i++) {
+ const int blockIdx = traversalOrder[i] - origRank;
+ const int origDim = blockMap[blockIdx];
+ origIdx[origDim] = origIdx[origDim] * blockSize[blockIdx] + (*indices)[i];
+ }
+ // Writing srcData to destData
+ destData[getFlattenedIndex(origIdx, destDims, origRank)] = srcData[prevIdx];
+ return;
+ }
+ const int metadataIdx = 2 * level;
+ if (dimFormat[level] == DENSE) { // DENSE dimension format
+ const int shapeOfLevel = dimMetadata[metadataIdx].front();
+ for (int i = 0; i < shapeOfLevel; i++) {
+ (*indices)[level] = i;
+ populate(srcData, indices, level + 1, prevIdx * shapeOfLevel + i, destData, destDims,
+ dimFormat, traversalOrder, blockSize, blockMap, dimMetadata, origRank);
+ }
+ } else { // SPARSE_CSR dimension format
+ const auto& arraySegments = dimMetadata[metadataIdx];
+ const auto& arrayIndices = dimMetadata[metadataIdx + 1];
+ for (int i = arraySegments[prevIdx]; i < arraySegments[prevIdx + 1]; i++) {
+ (*indices)[level] = arrayIndices[i];
+ populate(srcData, indices, level + 1, i, destData, destDims, dimFormat, traversalOrder,
+ blockSize, blockMap, dimMetadata, origRank);
+ }
+ }
+}
+
+template <typename T>
+std::vector<T> arrToVector(const T* arr, uint32_t size) {
+ return arr == nullptr ? std::vector<T>() : std::vector<T>(arr, arr + size);
+}
+
+template <typename T>
+inline bool densify(IOperationExecutionContext* context) {
+ // Getting all inputs
+ std::vector<Shape> inputShapes;
+ const uint32_t inputCount = context->getNumInputs();
+ inputShapes.reserve(inputCount);
+ const T* srcData = context->getInputBuffer<T>(kInputTensor);
+ inputShapes.push_back(context->getInputShape(kInputTensor));
+ const int32_t* traversalOrder = context->getInputBuffer<int32_t>(kInputTravOrder);
+ inputShapes.push_back(context->getInputShape(kInputTravOrder));
+ const int32_t* blockMap = context->getInputBuffer<int32_t>(kInputBlockMap);
+ inputShapes.push_back(context->getInputShape(kInputBlockMap));
+ const int32_t* dimFormatPtr = context->getInputBuffer<int32_t>(kInputDimFormat);
+ inputShapes.push_back(context->getInputShape(kInputDimFormat));
+ const int32_t* dimensionsPtr = context->getInputBuffer<int32_t>(kInputDimensions);
+ inputShapes.push_back(context->getInputShape(kInputDimensions));
+
+ std::vector<const int32_t*> dimMetadataPtrs;
+ for (uint32_t i = kInputArrSeg; i < inputCount; i++) {
+ inputShapes.push_back(context->getInputShape(i));
+ const int32_t* metadata = context->getInputBuffer<int32_t>(i);
+ dimMetadataPtrs.push_back(metadata);
+ }
+ Shape destShape = context->getOutputShape(kOutputTensor);
+
+ // Organizing dimFormat, dimensions, dimMetadata into vectors
+ std::vector<int32_t> dimFormat(
+ inputShapes[kInputDimFormat].dimensions.front()); // size of dimFormatPtr
+ std::vector<int32_t> dimensions(dimFormat.size());
+ std::vector<std::vector<int32_t>> dimMetadata(2 * dimFormat.size());
+ for (size_t i = 0; i < dimFormat.size(); i++) {
+ dimFormat[i] = dimFormatPtr[i];
+ dimensions[i] = dimensionsPtr[i];
+ if (dimFormat[i] == 0) {
+ dimMetadata[i * 2] = {dimensions[i]};
+ } else {
+ dimMetadata[i * 2] = // array segments
+ arrToVector(dimMetadataPtrs[i * 2],
+ inputShapes[i * 2 + kInputArrSeg].dimensions.front());
+ dimMetadata[i * 2 + 1] = // array indices
+ arrToVector(dimMetadataPtrs[i * 2 + 1],
+ inputShapes[i * 2 + kInputArrIdx].dimensions.front());
+ }
+ }
+
+ // Creating blockSize vector
+ const int origRank = destShape.dimensions.size();
+ std::vector<int32_t> blockSize(
+ inputShapes[kInputBlockMap].dimensions.front()); // size of block map
+ for (int i = 0; i < inputShapes[kInputBlockMap].dimensions.front(); i++) {
+ const int32_t origDim = traversalOrder[origRank + i];
+ blockSize[i] = dimensions[origDim];
+ }
+
+ // Calculating the number of output entries
+ const size_t denseTotal =
+ std::accumulate(destShape.dimensions.begin(), destShape.dimensions.end(),
+ static_cast<size_t>(1), std::multiplies<>{});
+ T zeroPoint = T();
+ if (const OperandType type = inputShapes.front().type;
+ type == OperandType::TENSOR_QUANT8_ASYMM ||
+ type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
+ type == OperandType::TENSOR_QUANT16_ASYMM) {
+ zeroPoint = static_cast<T>(inputShapes.front().offset);
+ }
+
+ T* destData = context->getOutputBuffer<T>(kOutputTensor);
+ for (int32_t i = 0; i < denseTotal; i++) {
+ destData[i] = zeroPoint;
+ }
+
+ std::vector<int32_t> indices(
+ inputShapes[kInputTravOrder].dimensions.front()); // size of traversal order
+ populate(srcData, &indices, 0, 0, destData, destShape.dimensions, dimFormat, traversalOrder,
+ blockSize, blockMap, dimMetadata, origRank);
+ return true;
+}
+
+Result<Version> validate(const IOperationValidationContext* context) {
+ // Checking number of inputs and outputs
+ const uint32_t inputCount = context->getNumInputs();
+ NN_RET_CHECK_GE(inputCount, kMinNumInputs);
+ NN_RET_CHECK_EQ(inputCount,
+ kMinNumInputs + context->getInputShape(kInputTravOrder).dimensions.front() * 2);
+ NN_RET_CHECK_EQ(context->getNumOutputs(), kNumOutputs);
+ NN_RET_CHECK_EQ(context->getInputShape(kInputTensor).dimensions.size(), 1);
+ for (uint32_t i = 1; i < inputCount; i++) {
+ NN_RET_CHECK_EQ(context->getInputShape(i).dimensions.size(), 1);
+ NN_RET_CHECK_EQ(context->getInputType(i), OperandType::TENSOR_INT32);
+ }
+ return Version::EXPERIMENTAL;
+}
+
+bool prepare(IOperationExecutionContext* context) {
+ // Setting OutputShape
+ Shape destShape = context->getInputShape(kInputTensor);
+
+ const int32_t* traversalOrder = context->getInputBuffer<int32_t>(kInputTravOrder);
+ const int32_t* blockMap = context->getInputBuffer<int32_t>(kInputBlockMap);
+ const int32_t* dimensions = context->getInputBuffer<int32_t>(kInputDimensions);
+ Shape dimensionsShape = context->getInputShape(kInputDimensions);
+ Shape blockMapShape = context->getInputShape(kInputBlockMap);
+ const uint32_t origRank = dimensionsShape.dimensions.front() - blockMapShape.dimensions.front();
+ std::vector<uint32_t> destDims(origRank);
+
+ int i = 0;
+ for (; i < destDims.size(); i++) {
+ const int32_t origDim = traversalOrder[i];
+ destDims[origDim] = dimensions[i];
+ }
+ for (; i < dimensionsShape.dimensions.front(); i++) {
+ const int32_t traversalIdx = traversalOrder[i] - origRank;
+ const int32_t origDim = blockMap[traversalIdx];
+ destDims[origDim] *= dimensions[i];
+ }
+ destShape.dimensions = destDims;
+ return context->setOutputShape(kOutputTensor, destShape);
+}
+
+bool execute(IOperationExecutionContext* context) {
+ switch (context->getInputType(kInputTensor)) {
+ case OperandType::TENSOR_BOOL8:
+ return densify<bool8>(context);
+ case OperandType::TENSOR_FLOAT32:
+ return densify<float>(context);
+ case OperandType::TENSOR_FLOAT16:
+ return densify<_Float16>(context);
+ case OperandType::TENSOR_INT32:
+ return densify<int32_t>(context);
+ case OperandType::TENSOR_QUANT8_ASYMM:
+ return densify<uint8_t>(context);
+ case OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ case OperandType::TENSOR_QUANT8_SYMM:
+ return densify<int8_t>(context);
+ case OperandType::TENSOR_QUANT16_SYMM:
+ return densify<int16_t>(context);
+ case OperandType::TENSOR_QUANT16_ASYMM:
+ return densify<uint16_t>(context);
+ default:
+ return false;
+ }
+}
+
+} // namespace densify_op
+
+NN_REGISTER_OPERATION(DENSIFY, "DENSIFY", densify_op::validate, densify_op::prepare,
+ densify_op::execute, .allowOmittedOperand = true);
+
+} // namespace nn
+} // namespace android
+
+#endif // NN_EXPERIMENTAL_FEATURE
\ No newline at end of file
diff --git a/common/operations/Densify.h b/common/operations/Densify.h
new file mode 100644
index 0000000..8ffed34
--- /dev/null
+++ b/common/operations/Densify.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 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_COMMON_OPERATIONS_DENSIFY_H
+#define ANDROID_FRAMEWORKS_ML_NN_COMMON_OPERATIONS_DENSIFY_H
+
+#include <vector>
+
+#include "LegacyUtils.h"
+#include "OperationResolver.h"
+#include "OperationsUtils.h"
+
+namespace android {
+namespace nn {
+namespace densify_op {
+
+/**
+ * getFlattenedIndex:
+ * Gets the index of destData where indices points to. Uses shape and origRank
+ * for calculations.
+ */
+uint64_t getFlattenedIndex(const std::vector<int32_t>& indices, const std::vector<uint32_t>& shape,
+ const int origRank);
+
+/**
+ * populate (Recursive Function):
+ * Used to populate the destData with elements from srcData one value at a time.
+ * Inputs:
+ * * srcData = input data of non-zero values.
+ * * indices = used to determine the index in destData where we write srcData to. Uses block
+ * dimension.
+ * * level = used to keep track of recursion level. Each recursive instance exits when level == size
+ * of traversal order.
+ * * prevIdx = used to keep placement in array segments and srcData.
+ * * destData = dense output data. Input being written to.
+ * * denseShape = shape of the output tensor. Used to calculate the flattened idx.
+ * * dimFormat = dimension format for each entry in traversal order. The format is either DENSE
+ * (dimFormat[i] == 0) or SPARSE_CSR (dimFormat[i] == 1). Format is significant to determine how
+ * recursive iterations will occur and what metadata is stored in dimMetadata.
+ * * traversalOrder = contains n+k elements. The first n elements are a permutation of the dense
+ * tensor shape. The last k elements are a permutation of the block dimensions. Used to determine
+ * order of traversal paths.
+ * * blockSize = dense size of blocks. The last k elements of dimensions.
+ * * blockMap = Used to determine how the block dimension maps to the original tensor dimension.
+ * * dimMetadata = metadata varies depending on dimFormat values. If format is DENSE,
+ * dimMetadata[i*2][0] is the total number of elements in the dense tensor on the ith traversal
+ * path, and recursive iterations are through a standard for loop from 0 to dimMetadata[i*2][0].
+ * If format is SPARSE_CSR, dimMetadata[i*2] is a vector of array segments and
+ * dimMetadata[i*2+1] is a vector of array indices. The next recursive iterations will be
+ * looping through the array segments vector (since array segments are the same as row pointers in
+ * CSR format, the ith entry should never be greater than the ith+1 entry) and modifying the input
+ * indices with elements from the array indices vector.
+ * * origRank = the size of denseShape. Used for calculating flattened index of indices.
+ */
+template <typename T>
+void populate(const T* srcData, std::vector<int32_t>* indices, int32_t level, int32_t prevIdx,
+ T* destData, const std::vector<uint32_t>& denseShape,
+ const std::vector<int32_t>& dimFormat, const int32_t* traversalOrder,
+ const std::vector<int32_t>& blockSize, const int32_t* blockMap,
+ const std::vector<std::vector<int32_t>>& dimMetadata, const int origRank);
+
+/**
+ * arrToVector:
+ * Converts a T array into an T vector.
+ */
+template <typename T>
+std::vector<T> arrToVector(const T* arr, uint32_t size);
+
+/**
+ * densify:
+ * Core of execution function. Prepares inputs for Populate function.
+ */
+template <typename T>
+inline bool densify(IOperationExecutionContext* context);
+
+Result<Version> validate(const IOperationValidationContext* context);
+
+bool prepare(IOperationExecutionContext* context);
+
+bool execute(IOperationExecutionContext* context);
+
+} // namespace densify_op
+} // namespace nn
+} // namespace android
+
+#endif // ANDROID_FRAMEWORKS_ML_NN_COMMON_OPERATIONS_DENSIFY_H
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 0933560..125f354 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -198,6 +198,19 @@
apex_available: ["//apex_available:platform"],
}
+// Required for tests (b/147158681)
+cc_library_static {
+ name: "libneuralnetworks_static_experimental",
+ defaults: [
+ "libneuralnetworks_defaults",
+ "neuralnetworks_defaults",
+ ],
+ exclude_static_libs: ["libneuralnetworks_common"],
+ static_libs: ["libneuralnetworks_common_experimental"],
+ cflags: ["-DNN_EXPERIMENTAL_FEATURE"],
+ apex_available: ["//apex_available:platform"],
+}
+
cc_library_static {
name: "libneuralnetworks_cl",
defaults: [
diff --git a/runtime/Manager.cpp b/runtime/Manager.cpp
index b459ab3..f472363 100644
--- a/runtime/Manager.cpp
+++ b/runtime/Manager.cpp
@@ -256,6 +256,9 @@
case Version::ANDROID_S:
return ANEURALNETWORKS_FEATURE_LEVEL_5;
case Version::CURRENT_RUNTIME:
+#ifdef NN_EXPERIMENTAL_FEATURE
+ case Version::EXPERIMENTAL:
+#endif // NN_EXPERIMENTAL_FEATURE
break;
}
LOG(FATAL) << "Unsupported driver feature level: " << featureLevel;
diff --git a/runtime/ModelBuilder.cpp b/runtime/ModelBuilder.cpp
index fa20c10..5ab182e 100644
--- a/runtime/ModelBuilder.cpp
+++ b/runtime/ModelBuilder.cpp
@@ -373,7 +373,16 @@
}
if (!isExtension(operationType)) {
- if (!validCode(kNumberOfOperationTypes, kNumberOfOperationTypesOEM, type)) {
+ bool allowExperimental = false;
+#ifdef NN_EXPERIMENTAL_FEATURE
+ if (type >= BuiltinOperationResolver::kStartOfExperimentalOperations &&
+ type < BuiltinOperationResolver::kStartOfExperimentalOperations +
+ BuiltinOperationResolver::kNumberOfExperimentalOperationTypes) {
+ allowExperimental = true;
+ }
+#endif // NN_EXPERIMENTAL_FEATURE
+ if (!validCode(kNumberOfOperationTypes, kNumberOfOperationTypesOEM, type) &&
+ !allowExperimental) {
LOG(ERROR) << "ANeuralNetworksModel_addOperation invalid operation type " << type;
return ANEURALNETWORKS_BAD_DATA;
}
diff --git a/runtime/include/NeuralNetworksExperimentalFeatures.h b/runtime/include/NeuralNetworksExperimentalFeatures.h
new file mode 100644
index 0000000..035db6c
--- /dev/null
+++ b/runtime/include/NeuralNetworksExperimentalFeatures.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/**
+ * @addtogroup NeuralNetworks
+ * @{
+ */
+
+/**
+ * @file NeuralNetworksExperimentalFeatures.h
+ */
+
+#ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_NEURAL_NETWORKS_EXPERIMENTAL_FEATURES_H
+#define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_NEURAL_NETWORKS_EXPERIMENTAL_FEATURES_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ * This file is part of Android's set of stable system headers
+ * exposed by the Android NDK (Native Development Kit).
+ *
+ * Third-party source AND binary code relies on the definitions
+ * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Operation types for experimental features.
+ *
+ * The type of an operation in a model.
+ */
+typedef enum {
+ /**
+ * Expands a representation of a sparse tensor to a dense tensor.
+ *
+ * To encode a conceptual n-dimensional dense tensor with dims [D0, ..., Dn-1], potentially with
+ * a k-dimensional block (0 <= k <= n) with dims [Dn, ..., Dn+k-1], the format specifies:
+ * * 1: In what order to traverse these dimensions. For example, to store a 2-D matrix in row
+ * major order, the traversal order would be [D0, D1], whereas to store it in column major
+ * order, the traversal order would be [D1, D0]. If the 2-D matrix has a 2-D inner block,
+ * the traversal order could be [D0, D1, D2, D3].
+ * * 2: How each block dimension in [Dn, ..., Dn+k-1] maps to the original tensor dimension in
+ * [D0, ..., Dn-1].
+ * * 3: In the traversal order defined above, the format (dense vs. sparse) and index metadata
+ * for each dimension. For a dense dimension, this is just the size of that dimension. For
+ * a sparse dimension, it's the same as the compressed index defined in the Compressed
+ * Sparse Row (CSR) format.
+ * (http://scipy-lectures.org/advanced/scipy_sparse/csr_matrix.html)
+ *
+ * The number of inputs to this operation is determined by the number of dimensions (including
+ * the block dimensions) of the sparsity parameters. Currently, the only formats supported are
+ * DENSE and SPARSE_CSR, but additional sparsity formats may be added in later versions of this
+ * operation.
+ *
+ * Supported tensor {@link OperandCode}:
+ * * {@link ANEURALNETWORKS_TENSOR_FLOAT16}
+ * * {@link ANEURALNETWORKS_TENSOR_FLOAT32}
+ * * {@link ANEURALNETWORKS_TENSOR_QUANT8_SYMM}
+ * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}
+ * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED}
+ * * {@link ANEURALNETWORKS_TENSOR_BOOL8}
+ * * {@link ANEURALNETWORKS_TENSOR_INT32}
+ * * {@link ANEURALNETWORKS_TENSOR_QUANT16_SYMM}
+ * * {@link ANEURALNETWORKS_TENSOR_QUANT16_ASYMM}
+ *
+ *
+ * Reference:
+ * * This implementation is a modification of the TACO format.
+ * http://tensor-compiler.org/kjolstad-oopsla17-tensor-compiler.pdf
+ *
+ * Inputs:
+ * * 0: A 1-D tensor representing the compressed sparse tensor data of a conceptual
+ * n-dimensional tensor.
+ * * 1: A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor defining the traversal order for
+ * reading the non-zero blocks. For an n-dimensional tensor with dimensions [D0, D1, …,
+ * Dn-1]: if block sparse with a k-dimensional block (0 < k <= n), the traversal order has
+ * n+k elements. The first n elements are still a permutation of [D0, …, Dn-1]. The last k
+ * elements are a permutation of [Dn, …, Dn+k-1], defining how to traverse a block
+ * internally. If not block sparse, the traversal order is just a permutation of [D0, …,
+ * Dn-1].
+ * * 2: An optional 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor defining the block map. For
+ * a block sparse n-dimensional tensor with a k-dimensional block (0 < k <= n), it stores
+ * how a block dimension [Dn, …, Dn+k-1] maps to the original tensor dimension in [D0, …,
+ * Dn-1]. For i, j where 0 <= i < j < k, blockMap[i] < blockMap[j]. If not block sparse,
+ * this is null.
+ * * 3: A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor with n+k elements defining the format
+ * of each dimension in the traversal order (listed above). The format is either DENSE
+ * (where DENSE = 0) or SPARSE_CSR (where SPARSE_CSR = 1). DENSE means that each coordinate
+ * in this dimension is stored implicitly. SPARSE_CSR means only the coordinates with
+ * non-zero elements are stored.
+ * * 4: A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor with n+k elements defining the size of
+ * each dimension or block. The product of all these sizes totals the number of elements in
+ * the dense tensor. First n elements represent the sparse tensor’s shape, and the last k
+ * elements represent the block’s shape.
+ * * 5 ~ (5 + 2 * (n+k)): An optional pair of {@link ANEURALNETWORKS_TENSOR_INT32} tensors which
+ * together specify the sparse indices along that dimension. The first pair of arguments
+ * corresponds to D0, the second to D1, and so on until Dn+k-1. If the dimension is DENSE,
+ * both arguments in the pair are null and the dimension is implicitly specified by the
+ * corresponding element in Input 4. If the dimension is SPARSE_CSR, then we use the pair
+ * of array segments and array indices to encode that dimension:
+ * * * +0: An optional list of n+k input 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensors,
+ * defining the array segments. The array segments represent how to segment the indices
+ * array, each segment corresponds to one element in the previous dimension. Array
+ * segments are interspersed with array indices (listed below), so this input could be
+ * input (5, 5 + 2, …, 5 + 2*(n+k-1)). For i, j where 0 =< i < j, arraySegments[i] <=
+ * arraySegments[j]. Used if the dimension is SPARSE_CSR, omitted if the dimension is
+ * DENSE.
+ * * * +1: An optional list of n+k input 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensors,
+ * defining the array indices. The array indices represent the index of the non-zero
+ * elements within this dimension (as those in the CSR matrix format, where the first
+ * array is row pointers and the second array is column indices). Array indices are
+ * interspersed with array segments (listed above), so this input could be input (6, 6 +
+ * 2, …, 6 + 2*(n+k-1)). Used if the dimension is SPARSE_CSR, omitted if the dimension
+ * is DENSE.
+ *
+ * Outputs:
+ * * 0: An n-D dense tensor. The output tensor has the same {@link OperandCode} as input 0.
+ */
+ ANEURALNETWORKS_DENSIFY = 20000,
+} ANeuralNetworksExperimentalOperationCode;
+
+__END_DECLS
+
+#endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_NEURAL_NETWORKS_EXPERIMENTAL_FEATURES_H
+
+/** @} */
diff --git a/tools/api/Types.t b/tools/api/Types.t
index 4a8df12..5a2b96f 100644
--- a/tools/api/Types.t
+++ b/tools/api/Types.t
@@ -235,7 +235,17 @@
// Returns status, timingLaunched, timingFenced
using ExecuteFencedInfoCallback = std::function<GeneralResult<std::pair<Timing, Timing>>()>;
-enum class Version { ANDROID_OC_MR1, ANDROID_P, ANDROID_Q, ANDROID_R, ANDROID_S, CURRENT_RUNTIME };
+enum class Version {
+ ANDROID_OC_MR1,
+ ANDROID_P,
+ ANDROID_Q,
+ ANDROID_R,
+ ANDROID_S,
+ CURRENT_RUNTIME,
+#ifdef NN_EXPERIMENTAL_FEATURE
+ EXPERIMENTAL,
+#endif // NN_EXPERIMENTAL_FEATURE
+};
// Describes the memory preference of an operand.
struct MemoryPreference {