168 lines
6.6 KiB
C++
168 lines
6.6 KiB
C++
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "SVDF.h"
|
|
|
|
#include "CpuExecutor.h"
|
|
#include "HalInterfaces.h"
|
|
|
|
namespace android {
|
|
namespace nn {
|
|
|
|
namespace {
|
|
|
|
// TODO: Implement this using circular buffer instead.
|
|
// This is here temporarily only to show the logic.
|
|
void svdf_right_shift_state(const float* state_in, int state_len, float shift_value,
|
|
float* state_out) {
|
|
for (int i = 0; i < state_len - 1; i++) {
|
|
state_out[i] = state_in[i + 1];
|
|
}
|
|
state_out[state_len - 1] = shift_value;
|
|
}
|
|
|
|
int32_t getInt32ScalarData(RunTimeOperandInfo& info) {
|
|
int32_t * data = reinterpret_cast<int32_t*>(info.buffer);
|
|
return data[0];
|
|
}
|
|
|
|
}
|
|
|
|
SVDF::SVDF(const Operation& operation,
|
|
std::vector<RunTimeOperandInfo>& operands) {
|
|
input_ = GetInput(operation, operands, kInputTensor);
|
|
weights_feature_ = GetInput(operation, operands, kWeightsFeatureTensor);
|
|
weights_time_ = GetInput(operation, operands, kWeightsTimeTensor);
|
|
bias_ = GetInput(operation, operands, kBiasTensor);
|
|
state_in_ = GetInput(operation, operands, kStateInTensor);
|
|
|
|
params_.rank_ = getInt32ScalarData(*GetInput(operation, operands, kRankParam));
|
|
params_.activation_ = static_cast<ActivationFn>(getInt32ScalarData(
|
|
*GetInput(operation, operands, kActivationParam)));
|
|
|
|
state_out_ = GetOutput(operation, operands, kStateOutTensor);
|
|
output_ = GetOutput(operation, operands, kOutputTensor);
|
|
}
|
|
|
|
bool SVDF::Prepare(const Operation &operation,
|
|
std::vector<RunTimeOperandInfo> &operands,
|
|
Shape *stateShape,
|
|
Shape *outputShape) {
|
|
// Check we have all the inputs and outputs we need.
|
|
const int num_inputs = NumInputsWithValues(operation, operands);
|
|
NN_CHECK(num_inputs == 6 || num_inputs == 7);
|
|
NN_CHECK_EQ(NumOutputs(operation), 2);
|
|
|
|
const RunTimeOperandInfo *input =
|
|
GetInput(operation, operands, SVDF::kInputTensor);
|
|
const RunTimeOperandInfo *weights_feature =
|
|
GetInput(operation, operands, SVDF::kWeightsFeatureTensor);
|
|
const RunTimeOperandInfo *weights_time =
|
|
GetInput(operation, operands, SVDF::kWeightsTimeTensor);
|
|
|
|
// Check all the parameters of tensor match within themselves and match the
|
|
// input configuration.
|
|
const uint32_t batch_size = SizeOfDimension(input, 0);
|
|
const uint32_t num_units = SizeOfDimension(weights_feature, 0);
|
|
const uint32_t memory_size = SizeOfDimension(weights_time, 1);
|
|
NN_CHECK_EQ(SizeOfDimension(input, 1), SizeOfDimension(weights_feature, 1));
|
|
NN_CHECK_EQ(SizeOfDimension(weights_time, 0), num_units);
|
|
|
|
const RunTimeOperandInfo *bias =
|
|
GetInput(operation, operands, kBiasTensor);
|
|
if (!IsNullInput(bias)) {
|
|
NN_CHECK_EQ(SizeOfDimension(bias, 0), num_units);
|
|
}
|
|
|
|
// Resize state.
|
|
const Shape &inputShape = input->shape();
|
|
stateShape->type = inputShape.type;
|
|
stateShape->dimensions = { batch_size, memory_size * num_units };
|
|
stateShape->offset = inputShape.offset;
|
|
stateShape->scale = inputShape.scale;
|
|
|
|
// Resize output.
|
|
outputShape->type = inputShape.type;
|
|
outputShape->dimensions = { batch_size, num_units };
|
|
outputShape->offset = inputShape.offset;
|
|
outputShape->scale = inputShape.scale;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SVDF::Eval() {
|
|
const int batch_size = input_->shape().dimensions[0];
|
|
const int input_size = input_->shape().dimensions[1];
|
|
const int num_units = weights_feature_->shape().dimensions[0];
|
|
const int memory_size = weights_time_->shape().dimensions[1];
|
|
const int weights_feature_stride = weights_feature_->shape().dimensions[1];
|
|
const int weights_time_stride = weights_time_->shape().dimensions[1];
|
|
|
|
// Initialize weights_feature and weights_time pointers.
|
|
const float* weights_feature_ptr = reinterpret_cast<float *>(weights_feature_->buffer);
|
|
const float* weights_time_ptr = reinterpret_cast<float *>(weights_time_->buffer);
|
|
|
|
// For each batch
|
|
for (int b = 0; b < batch_size; b++) {
|
|
// Initialize the pointer to input, output and bias.
|
|
const float* input_ptr_batch = reinterpret_cast<float *>(input_->buffer) + b * input_size;
|
|
float* output_ptr_batch = reinterpret_cast<float*>(output_->buffer) + b * num_units;
|
|
const float* state_in_ptr_batch = reinterpret_cast<const float*>(state_in_->buffer) + b * (memory_size - 1) * num_units;
|
|
float* state_out_ptr_batch = reinterpret_cast<float*>(state_out_->buffer) + b * (memory_size - 1) * num_units;
|
|
|
|
// For each unit
|
|
for (int c = 0; c < num_units; c++) {
|
|
float activation = 0.0;
|
|
|
|
// tf.nn.conv1d(inputs, weights_feature, feature_dim, "VALID")
|
|
for (int j = 0; j < input_size; j++) {
|
|
activation += input_ptr_batch[j] * weights_feature_ptr[j];
|
|
}
|
|
|
|
// Initialize state pointer for unit 'c'.
|
|
const float* state_in_ptr = state_in_ptr_batch + c * (memory_size - 1);
|
|
float* state_out_ptr = state_out_ptr_batch + c * (memory_size - 1);
|
|
|
|
// Apply bias if bias tensor exists.
|
|
output_ptr_batch[c] = bias_->buffer ? reinterpret_cast<float *>(bias_->buffer)[c] : 0.f;
|
|
|
|
// output = tf.matmul(state, weights_time)
|
|
output_ptr_batch[c] += weights_time_ptr[memory_size - 1] * activation;
|
|
for (int j = 0; j < memory_size - 1; j++) {
|
|
output_ptr_batch[c] += weights_time_ptr[j] * state_in_ptr[j];
|
|
}
|
|
|
|
// Apply activation.
|
|
output_ptr_batch[c] =
|
|
(ActivationFunctor(params_.activation_))(output_ptr_batch[c]);
|
|
|
|
// Right shift the state and concatenate with activation.
|
|
svdf_right_shift_state(state_in_ptr, memory_size - 1, activation,
|
|
state_out_ptr);
|
|
|
|
// Update weight pointers.
|
|
weights_feature_ptr += weights_feature_stride;
|
|
weights_time_ptr += weights_time_stride;
|
|
}
|
|
// Reset weight pointers for next batch.
|
|
weights_feature_ptr = reinterpret_cast<float*>(weights_feature_->buffer);
|
|
weights_time_ptr = reinterpret_cast<float*>(weights_time_->buffer);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace nn
|
|
} // namespace android
|