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