"git@developer.sourcefind.cn:orangecat/ollama.git" did not exist on "f24741ff39d0c090b5ee515263da9eacdba24c9d"
Commit edea2b67 authored by Terry Koo's avatar Terry Koo
Browse files

Remove runtime because reasons.

parent a4bb31d0
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/math/sgemvv.h"
#include <chrono>
#include <random>
#include "dragnn/core/test/generic.h"
#include "dragnn/runtime/math/arithmetic.h"
#include "dragnn/runtime/math/transformations.h"
#include "dragnn/runtime/math/types.h"
#include "dragnn/runtime/test/helpers.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
void naive_sgemv(const MutableMatrix<float> &matrix, const float *v,
const float *b, float *y) {
for (int row = 0; row < matrix.num_rows(); row++) {
y[row] = b[row];
for (int col = 0; col < matrix.num_columns(); col++) {
y[row] += matrix.row(row)[col] * v[col];
}
}
}
// Everything except floats require copying.
template <class ElementType>
constexpr bool RequiresCopy();
template <>
constexpr bool RequiresCopy<TruncatedFloat16>() {
return true;
}
#if defined(__F16C__)
template <>
constexpr bool RequiresCopy<IeeeFloat16>() {
return true;
}
#endif
template <>
constexpr bool RequiresCopy<float>() {
return false;
}
template <class ElementType>
void ConvertRow(Vector<float> input, MutableVector<ElementType> output);
template <>
void ConvertRow<float>(Vector<float> input, MutableVector<float> output) {}
template <>
void ConvertRow<TruncatedFloat16>(Vector<float> input,
MutableVector<TruncatedFloat16> output) {
CHECK_EQ(input.size() % 16, 0);
CHECK_EQ(input.size(), output.size());
for (int i = 0; i < input.size(); ++i) {
int i_permuted = (i / 16) * 16 + FastUnpackPermutation(i % 16);
output[i] = TruncatedFloat16::DebugFromFloat(input[i_permuted]);
}
}
#if defined(__F16C__)
template <>
void ConvertRow<IeeeFloat16>(Vector<float> input,
MutableVector<IeeeFloat16> output) {
CHECK_EQ(input.size() % 16, 0);
CHECK_EQ(input.size(), output.size());
for (int i = 0; i < input.size(); ++i) {
output[i] = IeeeFloat16::DebugFromFloat(input[i]);
}
}
#endif
// Converts a matrix to SGEMV. If the element type is not float, copies the
// matrix and then converts it.
template <int sse_batch_size, typename ElementType = float>
SgemvMatrix<sse_batch_size, ElementType> ConvertToSgemv(
const Matrix<float> &matrix, UniqueMatrix<ElementType> *sgemv_storage) {
MutableBlockedMatrix<ElementType, BlockedMatrixFormat::kRowBlockedColumnMajor>
blocked;
TF_EXPECT_OK(blocked.Reset(sgemv_storage->area(), matrix.num_rows(),
matrix.num_columns()));
// TODO(googleuser): Clean this up when we can use C++17's `if constexpr`
// ... then we will not have to introduce this raw pointer, which is either
// an actual new variable or alias to `sgemv_storage`.
UniqueMatrix<float> *uncompressed;
if (RequiresCopy<ElementType>()) {
uncompressed = new UniqueMatrix<float>((*sgemv_storage)->num_rows(),
(*sgemv_storage)->num_columns());
} else {
// NOTE: Because we don't have C++17's `if constexpr`, we need to add a
// reinterpret_cast, so this code can compile when ElementType != float.
uncompressed = reinterpret_cast<UniqueMatrix<float> *>(sgemv_storage);
}
// Copy to the uncompressed matrix. If ElementType == float, this is just
// the output, otherwise it's the temporary array.
MutableBlockedMatrix<float, BlockedMatrixFormat::kRowBlockedColumnMajor>
uncompressed_matrix;
TF_EXPECT_OK(uncompressed_matrix.Reset(
uncompressed->area(), matrix.num_rows(), matrix.num_columns()));
TF_EXPECT_OK(CopyMatrix(matrix, &uncompressed_matrix));
if (RequiresCopy<ElementType>()) {
for (int i = 0; i < blocked.num_vectors(); ++i) {
ConvertRow<ElementType>(Vector<float>(uncompressed_matrix.vector(i)),
blocked.vector(i));
}
delete uncompressed;
}
SgemvMatrix<sse_batch_size, ElementType> sgemv_matrix;
TF_EXPECT_OK(sgemv_matrix.Initialize(blocked.AsConst()));
return sgemv_matrix;
}
void InitRandomVector(MutableVector<float> vector) {
// clock() is updated less frequently than a cycle counter, so keep around the
// RNG just in case we initialize some vectors in less than a clock tick.
static std::mt19937 *rng = new std::mt19937(clock());
std::normal_distribution<float> distribution(0, 1);
for (int i = 0; i < vector.size(); i++) {
vector[i] = distribution(*rng);
}
}
void InitRandomMatrix(MutableMatrix<float> matrix) {
// See InitRandomVector comment.
static std::mt19937 *rng = new std::mt19937(clock());
std::normal_distribution<float> distribution(0, 1);
GenerateMatrix(
matrix.num_rows(), matrix.num_columns(),
[&distribution](int row, int col) { return distribution(*rng); },
&matrix);
}
TEST(SgemvvTest, MatmulNoBias) {
constexpr int sse_batch_size = 32;
constexpr int num_rows = 32;
constexpr int num_columns = 15;
constexpr int output_size = 8;
constexpr int sgemv_views = num_rows * num_columns / sse_batch_size;
static_assert(num_rows * num_columns % sse_batch_size == 0,
"Bad matrix size");
ASSERT_EQ(output_size % 8, 0) << "Output size must still be 32-byte aligned.";
UniqueMatrix<float> matrix(num_rows, num_columns);
UniqueMatrix<float> sgemv_storage(sgemv_views, sse_batch_size);
UniqueVector<float> input_vector(num_columns);
UniqueVector<float> output(num_rows);
UniqueVector<float> expected(num_rows);
// Random initialization for all variables/values.
InitRandomMatrix(*matrix);
InitRandomVector(*output);
InitRandomVector(*expected);
InitRandomVector(*input_vector);
// Layout SGEMV matrix.
SgemvMatrix<sse_batch_size> sgemv_matrix =
ConvertToSgemv<sse_batch_size>(Matrix<float>(*matrix), &sgemv_storage);
// SGEMV multiplication.
SgemvInputBatch<1> inputs = {{input_vector->data()}, {nullptr}};
SgemvOutputBatch<1> outputs = {{output->data()}};
sgemv_matrix.MaskedMatrixMultiVectorProductNoInitial(inputs, output_size,
&outputs);
// Naive algorithm.
MultiplyMatrixAndVector<float>(Matrix<float>(*matrix),
Vector<float>(*input_vector), *expected);
// Check that results are equal.
for (int i = 0; i < output_size; i++) {
EXPECT_NEAR(output->data()[i], expected->data()[i], 1e-5);
}
}
TEST(SgemvvTest, ErrorsWithBadMultiple) {
// Pick num_rows which is (32-byte) alignable, but not a multiple of
// sse_batch_size (32 floats). These should return errors.
for (int num_rows = 8; num_rows < 32; num_rows += 8) {
// Layout blocked matrix.
UniqueMatrix<float> sgemv_storage(1, num_rows);
MutableBlockedMatrix<float, BlockedMatrixFormat::kRowBlockedColumnMajor>
blocked;
TF_EXPECT_OK(blocked.Reset(sgemv_storage.area(), num_rows, 1));
// Initialize SgemvvMatrix.
SgemvMatrix<32> matrix;
EXPECT_THAT(matrix.Initialize(blocked.AsConst()),
test::IsErrorWithSubstr("must be equal to sse_batch_size"));
}
}
template <typename ElementType>
string TypenameString();
template <>
string TypenameString<float>() {
return "float32";
}
template <>
string TypenameString<TruncatedFloat16>() {
return "bfloat16";
}
#if defined(__F16C__)
template <>
string TypenameString<IeeeFloat16>() {
return "float16";
}
#endif
template <typename ElementType>
float ToleranceAt128();
template <>
float ToleranceAt128<float>() {
return 1e-5;
}
template <>
float ToleranceAt128<TruncatedFloat16>() {
return 1;
}
#if defined(__F16C__)
template <>
float ToleranceAt128<IeeeFloat16>() {
return 1e-1;
}
#endif
template <int sse_batch_size, int num_rows, int num_cols, typename ElementType>
void RunPerformanceTest(int output_size) {
constexpr int sgemv_views = num_rows * num_cols / sse_batch_size;
static_assert(num_rows * num_cols % sse_batch_size == 0, "Bad matrix size");
ASSERT_EQ(output_size % 8, 0) << "Output size must still be 32-byte aligned.";
UniqueMatrix<float> matrix(num_rows, num_cols);
UniqueMatrix<ElementType> sgemv_storage(sgemv_views, sse_batch_size);
UniqueVector<float> initial_1(num_rows);
UniqueVector<float> initial_2(num_rows);
UniqueVector<float> vector_1(num_cols);
UniqueVector<float> vector_2(num_cols);
UniqueVector<float> output_1(num_rows);
UniqueVector<float> output_2(num_rows);
UniqueVector<float> expected_output_1(num_rows);
UniqueVector<float> expected_output_2(num_rows);
UniqueVector<float> untouched_output_1(num_rows);
UniqueVector<float> untouched_output_2(num_rows);
// Random initialization for all variables/values.
InitRandomMatrix(*matrix);
InitRandomVector(*initial_1);
InitRandomVector(*initial_2);
InitRandomVector(*output_1);
InitRandomVector(*output_2);
InitRandomVector(*expected_output_1);
InitRandomVector(*expected_output_2);
InitRandomVector(*vector_1);
InitRandomVector(*vector_2);
for (int i = 0; i < num_rows; i++) {
(*untouched_output_1)[i] = (*output_1)[i];
(*untouched_output_2)[i] = (*output_2)[i];
}
// Layout SGEMV matrix.
SgemvMatrix<sse_batch_size, ElementType> sgemv_matrix =
ConvertToSgemv<sse_batch_size, ElementType>(Matrix<float>(*matrix),
&sgemv_storage);
naive_sgemv(*matrix, vector_1->data(), initial_1->data(),
expected_output_1->data());
naive_sgemv(*matrix, vector_2->data(), initial_2->data(),
expected_output_2->data());
double raw_flops_per_iteration = 2.0 * 2.0 * num_rows * num_cols;
const uint64 iterations =
static_cast<uint64>(std::round(4e9 / raw_flops_per_iteration));
auto start_time = std::chrono::system_clock::now();
SgemvInputBatch<2> inputs = {
{vector_1->data(), vector_2->data()},
{initial_1->data(), initial_2->data()},
};
SgemvOutputBatch<2> outputs = {{output_1->data(), output_2->data()}};
if (num_rows == output_size) {
for (int iter = 0; iter < iterations; iter++) {
sgemv_matrix.template MatrixMultiVectorProduct<2, 0, 0>(inputs, &outputs);
}
} else {
for (int iter = 0; iter < iterations; iter++) {
sgemv_matrix.template MaskedMatrixMultiVectorProduct<2>(
inputs, output_size, &outputs);
}
}
auto end_time = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end_time - start_time;
double elapsed = elapsed_seconds.count();
// Each MatrixVectorVectorProduct does 2 Matrix-vector ops, and each op does a
// multiply and an add (2 floating-point operations) for each entry in the
// matrix.
string raw_gflops = "";
if (num_rows != output_size) {
raw_gflops = ::tensorflow::strings::StrCat(
", ", raw_flops_per_iteration * iterations / 1e9 / elapsed, " raw");
}
VLOG(0) << " ElementType " << TypenameString<ElementType>() << " GFLOPS: "
<< (2.0 * 2.0 * output_size * num_cols * iterations) / 1e9 / elapsed
<< " effective" << raw_gflops;
const float tolerance =
ToleranceAt128<ElementType>() * (num_rows / 128.0) + 1e-5;
for (int i = 0; i < output_size; i++) {
EXPECT_NEAR(output_1->data()[i], expected_output_1->data()[i], tolerance);
EXPECT_NEAR(output_2->data()[i], expected_output_2->data()[i], tolerance);
}
// Check that any non-output items are untouched.
for (int i = output_size; i < num_rows; i++) {
EXPECT_EQ((*output_1)[i], (*untouched_output_1)[i]);
EXPECT_EQ((*output_2)[i], (*untouched_output_2)[i]);
}
}
TEST(SgemvvTest, PerformanceAndAccuracyTest) {
// Benchmarking is hard. Sometimes results vary between test runs, or are just
// unreliable. This could be in part from CPU frequency scaling, and also how
// favorably the memory allocator places data (coherence, etc.).
constexpr int kNumBatches = 3;
VLOG(0) << "64x64 32-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<32, 64, 64, float>(64);
#if defined(__F16C__)
RunPerformanceTest<32, 64, 64, IeeeFloat16>(64);
#endif
}
VLOG(0) << "128x128 32-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<32, 128, 128, float>(128);
}
VLOG(0) << "256x256 32-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<32, 256, 256, float>(256);
#if defined(__F16C__)
RunPerformanceTest<32, 256, 256, IeeeFloat16>(256);
#endif
RunPerformanceTest<32, 256, 256, TruncatedFloat16>(256);
}
VLOG(0) << "96x96 48-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<48, 96, 96, float>(96);
}
VLOG(0) << "48x96 48-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<48, 48, 96, float>(48);
}
VLOG(0) << "40x96 48-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<48, 48, 96, float>(40);
}
// These larger matrices are about the same amount of computation as one
// 96-dimensional LSTM cell (without output softmax).
VLOG(0) << "480x96 48-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<48, 480, 96, float>(480);
#if defined(__F16C__)
RunPerformanceTest<48, 480, 96, IeeeFloat16>(480);
#endif
RunPerformanceTest<48, 480, 96, TruncatedFloat16>(480);
}
VLOG(0) << "472x96 48-batch-size test";
for (int batch = 0; batch < kNumBatches; ++batch) {
RunPerformanceTest<48, 480, 96, float>(472);
#if defined(__F16C__)
RunPerformanceTest<48, 480, 96, IeeeFloat16>(472);
#endif
RunPerformanceTest<48, 480, 96, TruncatedFloat16>(472);
}
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// =============================================================================
// Utility functions that can transform different matrix types. This includes
// non-trivial transposes, and converting vectors/etc. to the matrix types. This
// library should NOT be used for any performance-critical work, and should NOT
// be included at all in the mobile runtime.
#ifndef DRAGNN_RUNTIME_MATH_TRANSFORMATIONS_H_
#define DRAGNN_RUNTIME_MATH_TRANSFORMATIONS_H_
#include "dragnn/runtime/math/types.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace internal {
// Puts a format-agnostic API on matrix-like data types. This is convenient, but
// has the downside of potential confusing compiler errors (when a
// specialization does not exist), and isn't suitable for optimizations like
// blocked transformations.
template <class T>
T *GetMatrixElement(int row, int col, MatrixImpl<T> *matrix) {
return &matrix->row(row)[col];
}
template <class T>
const T &GetMatrixElement(int row, int col, const MatrixImpl<T> &matrix) {
return matrix.row(row)[col];
}
template <class T>
T *GetMatrixElement(
int row, int col,
BlockedMatrixImpl<T, BlockedMatrixFormat::kRowBlockedColumnMajor> *matrix) {
int sub_matrix_idx = row / matrix->block_size();
int vector_idx = sub_matrix_idx * matrix->num_columns() + col;
int element_idx = row % matrix->block_size();
return &matrix->vector(vector_idx)[element_idx];
}
template <class T>
const T &GetMatrixElement(
int row, int col,
const BlockedMatrixImpl<T, BlockedMatrixFormat::kRowBlockedColumnMajor>
&matrix) {
int sub_matrix_idx = row / matrix.block_size();
int vector_idx = sub_matrix_idx * matrix.num_columns() + col;
int element_idx = row % matrix.block_size();
return matrix.vector(vector_idx)[element_idx];
}
template <class T>
T *GetMatrixElement(
int row, int col,
BlockedMatrixImpl<T, BlockedMatrixFormat::kColumnBlockedRowMajor> *matrix) {
int sub_matrix_idx = col / matrix->block_size();
int vector_idx = sub_matrix_idx * matrix->num_rows() + row;
int element_idx = col % matrix->block_size();
return &matrix->vector(vector_idx)[element_idx];
}
template <class T>
const T &GetMatrixElement(
int row, int col,
const BlockedMatrixImpl<T, BlockedMatrixFormat::kColumnBlockedRowMajor>
&matrix) {
int sub_matrix_idx = col / matrix.block_size();
int vector_idx = sub_matrix_idx * matrix.num_rows() + row;
int element_idx = col % matrix.block_size();
return matrix.vector(vector_idx)[element_idx];
}
} // namespace internal
// Generates values for a matrix, by calling a provided function on each
// row/column index. Thanks to the magic of templating, the function call should
// be inlined and not cause too much overhead being "called" on each index.
template <class Function, class OutputMatrix>
void GenerateMatrix(int num_rows, int num_columns, const Function &get_value,
OutputMatrix *output_matrix) {
for (size_t row = 0; row < num_rows; ++row) {
for (size_t column = 0; column < num_columns; ++column) {
*(GetMatrixElement(row, column, output_matrix)) = get_value(row, column);
}
}
}
// Copies the first |num_rows| rows and |num_columns| columns of input_matrix to
// output_matrix.
template <class InputMatrix, class OutputMatrix>
void CopyMatrixPrefix(const InputMatrix &input_matrix, int num_rows,
int num_columns, OutputMatrix *output_matrix) {
const auto &get_value = [input_matrix](int row, int column) {
return GetMatrixElement(row, column, input_matrix);
};
GenerateMatrix(num_rows, num_columns, get_value, output_matrix);
}
// Copies matrices. The matrices can be of different types, but must have the
// same dimensions.
template <class InputMatrix, class OutputMatrix>
tensorflow::Status CopyMatrix(const InputMatrix &input_matrix,
OutputMatrix *output_matrix) {
if (input_matrix.num_rows() != output_matrix->num_rows()) {
return tensorflow::errors::InvalidArgument(
"Input matrix num_rows (", input_matrix.num_rows(),
") != output matrix num_rows (", output_matrix->num_rows(), ")");
}
if (input_matrix.num_columns() != output_matrix->num_columns()) {
return tensorflow::errors::InvalidArgument(
"Input matrix num_columns (", input_matrix.num_columns(),
") != output matrix num_columns (", output_matrix->num_columns(), ")");
}
CopyMatrixPrefix(input_matrix, input_matrix.num_rows(),
input_matrix.num_columns(), output_matrix);
return tensorflow::Status::OK();
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_MATH_TRANSFORMATIONS_H_
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/math/transformations.h"
#include "dragnn/runtime/test/helpers.h"
#include <gmock/gmock.h>
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Generates a matrix where each value is of the form `aa.bb`, where `aa` is the
// column index and `bb` is the row index.
TEST(TransformationsTest, GenerateRowColIdxMatrix) {
UniqueMatrix<float> row_col_matrix(5, 5);
GenerateMatrix(
5, 5,
[](int row, int col) { return static_cast<float>(row) + (col / 100.0f); },
row_col_matrix.get());
ExpectMatrix(Matrix<float>(*row_col_matrix),
{{0.0f, 0.01f, 0.02f, 0.03f, 0.04f},
{1.0f, 1.01f, 1.02f, 1.03f, 1.04f},
{2.0f, 2.01f, 2.02f, 2.03f, 2.04f},
{3.0f, 3.01f, 3.02f, 3.03f, 3.04f},
{4.0f, 4.01f, 4.02f, 4.03f, 4.04f}});
}
TEST(TransformationsTest, CopiesMatrix) {
UniqueMatrix<float> a({{1, 2}}), b({{3, 4}});
TF_EXPECT_OK(CopyMatrix(*a, b.get()));
EXPECT_EQ(b->row(0)[0], 1);
EXPECT_EQ(b->row(0)[1], 2);
}
TEST(TransformationsTest, CopiesRowBlockedMatrix) {
UniqueMatrix<double> source({{1, 2, 3}, //
{4, 5, 6}, //
{7, 8, 9}, //
{10, 11, 12}, //
{13, 14, 15}, //
{16, 17, 18}, //
{19, 20, 21}, //
{22, 23, 24}});
UniqueMatrix<double> dst_mem(6, 4);
MutableBlockedMatrix<double, BlockedMatrixFormat::kRowBlockedColumnMajor>
blocked;
TF_EXPECT_OK(blocked.Reset(dst_mem.area(), 8, 3));
TF_EXPECT_OK(CopyMatrix(*source, &blocked));
ExpectMatrix(Matrix<double>(*dst_mem), {{1, 4, 7, 10}, //
{2, 5, 8, 11}, //
{3, 6, 9, 12}, //
{13, 16, 19, 22}, //
{14, 17, 20, 23}, //
{15, 18, 21, 24}});
}
// This test is the same as the above, except everything is transposed.
TEST(TransformationsTest, CopiesColumnBlockedMatrix) {
UniqueMatrix<double> source( //
{{1, 4, 7, 10, 13, 16, 19, 22}, //
{2, 5, 8, 11, 14, 17, 20, 23}, //
{3, 6, 9, 12, 15, 18, 21, 24}});
UniqueMatrix<double> dst_mem(6, 4);
MutableBlockedMatrix<double> blocked;
TF_EXPECT_OK(blocked.Reset(dst_mem.area(), 3, 8));
TF_EXPECT_OK(CopyMatrix(*source, &blocked));
ExpectMatrix(Matrix<double>(*dst_mem), {{1, 4, 7, 10}, //
{2, 5, 8, 11}, //
{3, 6, 9, 12}, //
{13, 16, 19, 22}, //
{14, 17, 20, 23}, //
{15, 18, 21, 24}});
}
} // namespace
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// =============================================================================
// Mathematical types.
#ifndef DRAGNN_RUNTIME_MATH_TYPES_H_
#define DRAGNN_RUNTIME_MATH_TYPES_H_
#include <stddef.h>
#include <limits>
#include "dragnn/runtime/alignment.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/platform/logging.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Blocked matrix formats, for fast inference routines.
enum class BlockedMatrixFormat {
// Represents a row-blocked block-column-major matrix. In other words, first
// split a matrix M into
//
// [ M_1
// ...
// M_m ]
//
// sub-matrices, where each M_i is a `block_size x n` sub-matrix. Then each
// M_i is formatted in column-major order, and the sub-matrices' data is
// concatenated together.
kRowBlockedColumnMajor,
// Represents a column-blocked block-row-major matrix. This is the
// transpose of the above. A matrix M is split into
//
// [ M_1 ... M_n ]
//
// sub-matrices, where each M_i is a `m x block_size` sub-matrix. Then each
// M_i is formatted in row-major order, and the sub-matrices' data is
// concatenated together.
kColumnBlockedRowMajor,
};
namespace internal {
// An aligned vector of values. Do not use this class directly, instead use
// (Mutable)Vector below.
template <class T>
class VectorImpl {
public:
static_assert(IsAlignable<T>(), "T must be alignable");
// Creates an empty vector.
VectorImpl() = default;
// Points this at the |view|, which must be evenly divisible into Ts.
template <class Byte>
explicit VectorImpl(AlignedViewImpl<Byte> view);
// Points this at a prefix of the |view| containing |size| Ts. The |view|
// must span at least |size| * sizeof(T) bytes.
template <class Byte>
VectorImpl(AlignedViewImpl<Byte> view, size_t size);
// Points this at the same values as |that|, possibly reinterpreting type.
template <class U>
explicit VectorImpl(VectorImpl<U> that);
template <class U>
VectorImpl &operator=(VectorImpl<U> that);
// Enables range-based for loops.
T *begin() const { return data(); }
T *end() const { return begin() + size(); }
// Accessors.
T *data() const { return data_; }
size_t size() const { return size_; }
bool empty() const { return size() == 0; }
T &operator[](size_t index) const;
// Gets a sub-vector starting at |start| with |size| elements.
VectorImpl<T> Subsequence(size_t start, size_t size) const;
private:
template <class U>
friend class MatrixImpl;
template <class U, BlockedMatrixFormat format>
friend class BlockedMatrixImpl;
// Points this at [|data|,|data|+|size|), bypassing alignment checks.
VectorImpl(T *data, size_t size);
// Pointer to the start of the vector.
T *data_ = nullptr;
// Number of values in the vector.
size_t size_ = 0;
};
// Returns the format corresponding to the transpose of the |format|.
constexpr BlockedMatrixFormat TransposeBlockedMatrixFormat(
BlockedMatrixFormat format);
// A row-major matrix where each row or column is aligned. Do not use this
// class directly, instead use (Mutable)Matrix below.
template <class T>
class MatrixImpl {
public:
static_assert(IsAlignable<T>(), "T must be alignable");
// Creates an empty matrix.
MatrixImpl() = default;
// Points each row of this matrix at the corresponding sub-view of the |area|.
// Each view in the |area| must be evenly divisible into Ts.
template <class Byte>
explicit MatrixImpl(AlignedAreaImpl<Byte> area);
// Creates a matrix from a single vector. Assumes that the vector's stride is
// the minimum alignment padding.
explicit MatrixImpl(VectorImpl<T> single_vector);
// Points this at the same values as |that|.
template <class U>
explicit MatrixImpl(MatrixImpl<U> that);
template <class U>
MatrixImpl &operator=(MatrixImpl<U> that);
// Accessors.
T *data() const { return data_; }
size_t num_rows() const { return num_rows_; }
size_t num_columns() const { return num_columns_; }
size_t row_stride() const { return row_stride_; }
VectorImpl<T> row(size_t index) const;
private:
template <class U>
friend class MatrixImpl;
// Pointer to the start of the matrix.
T *data_ = nullptr;
// Number of rows and columns in the matrix.
size_t num_rows_ = 0;
size_t num_columns_ = 0;
// Distance between the starts of consecutive rows.
size_t row_stride_ = 0;
};
// Blocked matrix representation. See BlockedMatrixFormat for details.
template <class T, BlockedMatrixFormat format>
class BlockedMatrixImpl {
public:
static_assert(IsAlignable<T>(), "T must be alignable");
// These aliases allow templated code to reach back in and get template
// parameters, like std::vector<T>::iterator::value aliases.
using ElementType = T;
static constexpr bool IsRowBlocked() {
return format == BlockedMatrixFormat::kRowBlockedColumnMajor;
}
// Creates an empty matrix.
BlockedMatrixImpl() = default;
// Creates a copy of this matrix, using the same values (underlying area), but
// possibly re-interpreting the type. The new type U must be the same size,
// and `T *` must be implictly convertible to `U *` (usually just adding
// "const" qualifiers, but theoretically it could be a superclass).
template <class U>
explicit BlockedMatrixImpl(BlockedMatrixImpl<U, format> that);
template <class U>
BlockedMatrixImpl &operator=(BlockedMatrixImpl<U, format> that);
// Creates a new view that's const-qualified, in particular converting
// MutableBlockedMatrix to BlockedMatrix.
BlockedMatrixImpl<const T, format> AsConst() const {
return BlockedMatrixImpl<const T, format>(*this);
}
// Initializes the matrix. Raises errors if the matrix dimensions are
// incompatible with the underlying area, namely if the number of views in
// `area` do not cover the whole matrix, and also if the matrix cannot be
// blocked according to (template parameter) `format`.
//
// Further, because this class is used for (delicate / specialized) optimized
// inference routines, it is also required that no padding is present, i.e.
// that the block size is divisible by kAlignmentBytes (currently 32).
template <class Byte>
tensorflow::Status Reset(AlignedAreaImpl<Byte> area, size_t num_rows,
size_t num_columns);
// Returns the transpose of this.
BlockedMatrixImpl<T, TransposeBlockedMatrixFormat(format)> Transpose() const;
// Accessors.
size_t num_rows() const { return num_rows_; }
size_t num_columns() const { return num_columns_; }
size_t block_size() const { return block_size_; }
size_t num_vectors() const { return num_vectors_; }
VectorImpl<T> vector(size_t index) const;
private:
template <class U, BlockedMatrixFormat other_format>
friend class BlockedMatrixImpl;
// This is the same as calling Reset(), except the area is not checked.
template <class Byte>
explicit BlockedMatrixImpl(AlignedAreaImpl<Byte> area, int num_rows,
int num_columns);
// Pointer to the start of the matrix.
T *data_ = nullptr;
// Number of rows and columns in the matrix. Unlike MatrixImpl, there is no
// API for directly accessing rows and columns, but it's necessary for any
// algorithm (e.g. matrix-vector multiplication) to know the logical shape.
size_t num_rows_ = 0;
size_t num_columns_ = 0;
size_t block_size_ = 0; // in T's
size_t num_vectors_ = 0; // = num_rows * num_columns / block_size
};
} // namespace internal
// Public aliases; use these.
template <class T>
using Vector = internal::VectorImpl<const T>;
template <class T>
using Matrix = internal::MatrixImpl<const T>;
template <class T, BlockedMatrixFormat format =
BlockedMatrixFormat::kColumnBlockedRowMajor>
using BlockedMatrix = internal::BlockedMatrixImpl<const T, format>;
template <class T>
using MutableVector = internal::VectorImpl<T>;
template <class T>
using MutableMatrix = internal::MatrixImpl<T>;
template <class T, BlockedMatrixFormat format =
BlockedMatrixFormat::kColumnBlockedRowMajor>
using MutableBlockedMatrix = internal::BlockedMatrixImpl<T, format>;
// Implementation details below.
namespace internal {
template <class T>
template <class Byte>
VectorImpl<T>::VectorImpl(AlignedViewImpl<Byte> view)
: data_(reinterpret_cast<T *>(view.data())),
size_(view.size() / sizeof(T)) {
DCHECK_EQ(view.size() % sizeof(T), 0);
}
template <class T>
template <class Byte>
VectorImpl<T>::VectorImpl(AlignedViewImpl<Byte> view, size_t size)
: data_(reinterpret_cast<T *>(view.data())), size_(size) {
DCHECK_LE(size * sizeof(T), view.size());
}
template <class T>
template <class U>
VectorImpl<T>::VectorImpl(VectorImpl<U> that)
: data_(that.data()), size_(that.size()) {
static_assert(sizeof(T) == sizeof(U), "T and U must be the same size");
}
template <class T>
template <class U>
VectorImpl<T> &VectorImpl<T>::operator=(VectorImpl<U> that) {
static_assert(sizeof(T) == sizeof(U), "T and U must be the same size");
data_ = that.data();
size_ = that.size();
return *this;
}
template <class T>
T &VectorImpl<T>::operator[](size_t index) const {
DCHECK_LT(index, size());
return data_[index];
}
template <class T>
VectorImpl<T>::VectorImpl(T *data, size_t size) : data_(data), size_(size) {
TF_DCHECK_OK(OkIfAligned(data));
}
template <class T>
VectorImpl<T> VectorImpl<T>::Subsequence(size_t start, size_t size) const {
DCHECK_LE(start + size, size_);
return VectorImpl<T>(&data_[start], size);
}
constexpr BlockedMatrixFormat TransposeBlockedMatrixFormat(
BlockedMatrixFormat format) {
return format == BlockedMatrixFormat::kRowBlockedColumnMajor
? BlockedMatrixFormat::kColumnBlockedRowMajor
: BlockedMatrixFormat::kRowBlockedColumnMajor;
}
template <class T>
MatrixImpl<T>::MatrixImpl(VectorImpl<T> single_vector)
: data_(single_vector.data()),
num_rows_(1),
num_columns_(single_vector.size()),
row_stride_(PadToAlignment(single_vector.size() * sizeof(T)) /
sizeof(T)) {}
template <class T>
template <class Byte>
MatrixImpl<T>::MatrixImpl(AlignedAreaImpl<Byte> area)
: data_(reinterpret_cast<T *>(area.data())),
num_rows_(area.num_views()),
num_columns_(area.view_size() / sizeof(T)),
row_stride_(area.view_stride() / sizeof(T)) {
DCHECK_EQ(area.view_size() % sizeof(T), 0);
DCHECK_EQ(area.view_stride() % sizeof(T), 0);
}
template <class T>
template <class U>
MatrixImpl<T>::MatrixImpl(MatrixImpl<U> that)
: data_(that.data_),
num_rows_(that.num_rows()),
num_columns_(that.num_columns()),
row_stride_(that.row_stride_) {
static_assert(sizeof(T) == sizeof(U), "T and U must be the same size");
}
template <class T>
template <class U>
MatrixImpl<T> &MatrixImpl<T>::operator=(MatrixImpl<U> that) {
static_assert(sizeof(T) == sizeof(U), "T and U must be the same size");
data_ = that.data_;
num_rows_ = that.num_rows();
num_columns_ = that.num_columns();
row_stride_ = that.row_stride_;
return *this;
}
template <class T>
VectorImpl<T> MatrixImpl<T>::row(size_t index) const {
DCHECK_LT(index, num_rows());
// Note that |row_stride_|, not |num_columns_|, determines the start of the
// row. The former is aligned and may stride over a wider span than normal
// when this is a "slice" of a larger matrix.
return VectorImpl<T>(data_ + row_stride_ * index, num_columns());
}
template <class T, BlockedMatrixFormat format>
template <class U>
BlockedMatrixImpl<T, format>::BlockedMatrixImpl(
BlockedMatrixImpl<U, format> that)
: data_(that.data_),
num_rows_(that.num_rows()),
num_columns_(that.num_columns()),
block_size_(that.block_size()),
num_vectors_(that.num_vectors()) {
static_assert(sizeof(T) == sizeof(U), "T and U must be the same size");
}
template <class T, BlockedMatrixFormat format>
template <class U>
BlockedMatrixImpl<T, format> &BlockedMatrixImpl<T, format>::operator=(
BlockedMatrixImpl<U, format> that) {
static_assert(sizeof(T) == sizeof(U), "T and U must be the same size");
data_ = that.data_;
num_rows_ = that.num_rows();
num_columns_ = that.num_columns();
block_size_ = that.block_size();
num_vectors_ = that.num_vectors();
return *this;
}
template <class T, BlockedMatrixFormat format>
template <class Byte>
tensorflow::Status BlockedMatrixImpl<T, format>::Reset(
AlignedAreaImpl<Byte> area, size_t num_rows, size_t num_columns) {
data_ = reinterpret_cast<T *>(area.view(0).data());
num_rows_ = num_rows;
num_columns_ = num_columns;
block_size_ = area.view_size() / sizeof(T);
num_vectors_ = num_rows * num_columns / block_size_;
if (area.view_stride() != area.view_size()) {
return tensorflow::errors::InvalidArgument(
"Padding is not supported for blocked matrix formats. Underlying area "
"has size ",
area.view_size(), " which is padded to stride ", area.view_stride(),
".");
}
if (area.view_size() % sizeof(T) != 0) {
return tensorflow::errors::InvalidArgument(
"View size ", area.view_size(),
" is not a multiple of the templated type's size, ", sizeof(T));
}
if (num_vectors_ != area.num_views()) {
return tensorflow::errors::InvalidArgument("Area has ", area.num_views(),
" views, but should have ",
num_vectors_);
}
// The block dimension must divide rows or columns evenly.
size_t divided_dimension = IsRowBlocked() ? num_rows : num_columns;
if (divided_dimension % block_size_ != 0) {
return tensorflow::errors::InvalidArgument(
IsRowBlocked() ? "row" : "column",
"-blocked matrix has major dimension ", divided_dimension,
" which is not divisible by the block size, ", block_size_);
}
return tensorflow::Status::OK();
}
template <class T, BlockedMatrixFormat format>
VectorImpl<T> BlockedMatrixImpl<T, format>::vector(size_t index) const {
DCHECK_LT(index, num_vectors_);
return VectorImpl<T>(data_ + block_size_ * index, block_size_);
}
template <class T, BlockedMatrixFormat format>
BlockedMatrixImpl<T, TransposeBlockedMatrixFormat(format)>
BlockedMatrixImpl<T, format>::Transpose() const {
BlockedMatrixImpl<T, TransposeBlockedMatrixFormat(format)> result;
result.data_ = data_;
result.num_columns_ = num_rows_;
result.num_rows_ = num_columns_;
result.block_size_ = block_size_;
result.num_vectors_ = num_vectors_;
return result;
}
} // namespace internal
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_MATH_TYPES_H_
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/math/types.h"
#include <stddef.h>
#include <string.h>
#include <set>
#include "dragnn/core/test/generic.h"
#include "dragnn/runtime/alignment.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Creates a pointer that is be invalid. This is useful for creating proxy areas
// for testing, whose real data should never be accessed. We manually tested
// that if this pointer is dereferenced, a segmentation fault will be thrown.
char *InvalidAlignedPointer() {
return reinterpret_cast<char *>(3 * internal::kAlignmentBytes);
}
// Expects that two pointers point to the same address.
void ExpectSameAddress(const void *ptr1, const void *ptr2) {
EXPECT_EQ(ptr1, ptr2);
}
template <class A, class B>
bool StructsEqual(const A &a, const B &b) {
static_assert(sizeof(A) == sizeof(B),
"StructsEqual must be given structs of the same size.");
return memcmp(&a, &b, sizeof(A)) == 0;
}
// Tests that (Mutable)Vector is empty by default.
TEST(VectorTest, EmptyByDefault) {
const Vector<int> vector1;
EXPECT_EQ(vector1.size(), 0);
EXPECT_TRUE(vector1.empty());
const MutableVector<int> vector2;
EXPECT_EQ(vector2.size(), 0);
EXPECT_TRUE(vector2.empty());
}
// Tests that (Mutable)Vector can be initialized from a view.
TEST(VectorTest, ConstructFromView) {
MutableAlignedView view;
char *ptr = InvalidAlignedPointer();
TF_ASSERT_OK(view.Reset(ptr, 10 * sizeof(int)));
const Vector<int> vector1(view);
ExpectSameAddress(vector1.data(), ptr);
EXPECT_EQ(vector1.size(), 10);
EXPECT_FALSE(vector1.empty());
const MutableVector<int> vector2(view);
ExpectSameAddress(vector2.data(), ptr);
EXPECT_EQ(vector2.size(), 10);
EXPECT_FALSE(vector2.empty());
}
// Tests that (Mutable)Vector can be initialized from a prefix of a view.
TEST(VectorTest, ConstructFromViewPrefix) {
MutableAlignedView view;
char *ptr = InvalidAlignedPointer();
TF_ASSERT_OK(view.Reset(ptr, 10 * sizeof(int)));
// Use a prefix of 3 of the 10 available ints in the |view|.
const Vector<int> vector1(view, 3);
ExpectSameAddress(vector1.data(), ptr);
EXPECT_EQ(vector1.size(), 3);
EXPECT_FALSE(vector1.empty());
// Use a prefix of 5 of the 10 available ints in the |view|.
const MutableVector<int> vector2(view, 5);
ExpectSameAddress(vector2.data(), ptr);
EXPECT_EQ(vector2.size(), 5);
EXPECT_FALSE(vector2.empty());
}
// Tests that (Mutable)Vector supports copy-construction and assignment with
// shallow-copy semantics, and reinterprets from T* to const T*.
TEST(VectorTest, CopyAndAssign) {
MutableAlignedView view;
char *ptr = InvalidAlignedPointer();
TF_ASSERT_OK(view.Reset(ptr, 10 * sizeof(int)));
const MutableVector<int> vector1(view);
// Copy-construct from another vector.
MutableVector<int> vector2(vector1);
ExpectSameAddress(vector2.data(), ptr);
EXPECT_EQ(vector2.size(), 10);
EXPECT_FALSE(vector2.empty());
// Assign from an empty vector, effectively clearing it.
vector2 = MutableVector<int>();
EXPECT_EQ(vector2.size(), 0);
EXPECT_TRUE(vector2.empty());
// Assign from the original vector.
vector2 = vector1;
ExpectSameAddress(vector2.data(), ptr);
EXPECT_EQ(vector2.size(), 10);
EXPECT_FALSE(vector2.empty());
// Copy-construct from another vector. Note that this reinterprets type.
Vector<int> vector3(vector1);
ExpectSameAddress(vector3.data(), ptr);
EXPECT_EQ(vector3.size(), 10);
EXPECT_FALSE(vector3.empty());
// Assign from an empty vector, effectively clearing it.
vector3 = Vector<int>();
EXPECT_EQ(vector3.size(), 0);
EXPECT_TRUE(vector3.empty());
// Assign from another vector. Note that this reinterprets type.
vector3 = vector2;
ExpectSameAddress(vector3.data(), ptr);
EXPECT_EQ(vector3.size(), 10);
EXPECT_FALSE(vector3.empty());
}
// Tests that (Mutable)Vector supports access via operator[].
TEST(VectorTest, Subscript) {
UniqueAlignedArray array;
array.Reset(10 * sizeof(float));
// Write into a mutable vector.
const MutableVector<float> mutable_vector(array.view());
ASSERT_EQ(mutable_vector.size(), 10);
for (int i = 0; i < 10; ++i) mutable_vector[i] = i;
// Read from a const vector that points at the same values.
const Vector<float> const_vector(array.view());
ASSERT_EQ(const_vector.size(), 10);
for (int i = 0; i < 10; ++i) EXPECT_EQ(const_vector[i], i);
}
// Tests the subsequence operator.
TEST(VectorTest, Subsequence) {
// Debug checks will fail if either of the constructed vectors is not aligned.
constexpr int numAlignedFloats = internal::kAlignmentBytes / sizeof(float);
UniqueAlignedArray array;
array.Reset(2 * numAlignedFloats * sizeof(float));
// Write into a mutable vector.
const MutableVector<float> mutable_vector(array.view());
for (int i = 0; i < 2 * numAlignedFloats; ++i) mutable_vector[i] = i;
// Subscript beginning.
Vector<float> first_half(mutable_vector.Subsequence(0, numAlignedFloats));
ASSERT_EQ(first_half.size(), numAlignedFloats);
for (int i = 0; i < numAlignedFloats; ++i) {
EXPECT_EQ(first_half[i], i);
}
// Subscript end.
Vector<float> second_half(
mutable_vector.Subsequence(numAlignedFloats, numAlignedFloats));
ASSERT_EQ(second_half.size(), numAlignedFloats);
for (int i = 0; i < numAlignedFloats; ++i) {
EXPECT_EQ(second_half[i], i + numAlignedFloats);
}
}
// Tests that (Mutable)Vector supports access via range-based for loops.
TEST(VectorTest, RangeBasedFor) {
UniqueAlignedArray array;
array.Reset(10 * sizeof(float));
// Write into a mutable vector.
const MutableVector<float> mutable_vector(array.view());
ASSERT_EQ(mutable_vector.size(), 10);
float counter = 0.0;
for (float &value : mutable_vector) value = counter++;
// Read from a const vector that points at the same values.
const Vector<float> const_vector(array.view());
ASSERT_EQ(const_vector.size(), 10);
counter = 0.0;
for (const float &value : const_vector) EXPECT_EQ(value, counter++);
}
// Tests that (Mutable)Matrix is empty by default.
TEST(MatrixTest, EmptyByDefault) {
const Matrix<int> matrix1;
EXPECT_EQ(matrix1.num_rows(), 0);
EXPECT_EQ(matrix1.num_columns(), 0);
EXPECT_EQ(matrix1.row_stride(), 0);
const MutableMatrix<int> matrix2;
EXPECT_EQ(matrix2.num_rows(), 0);
EXPECT_EQ(matrix2.num_columns(), 0);
EXPECT_EQ(matrix2.row_stride(), 0);
}
// Tests that (Mutable)Matrix can be constructed from an area.
TEST(MatrixTest, ConstructFromArea) {
MutableAlignedView view;
char *ptr = InvalidAlignedPointer();
const size_t kNumRows = 11;
const size_t kNumColumns = 13;
const size_t kRowBytes = kNumColumns * sizeof(int);
const size_t kRowStride = PadToAlignment(kRowBytes) / sizeof(int);
const size_t bytes = ComputeAlignedAreaSize(kNumRows, kRowBytes);
TF_ASSERT_OK(view.Reset(ptr, bytes));
MutableAlignedArea area;
TF_ASSERT_OK(area.Reset(view, kNumRows, kRowBytes));
const Matrix<int> matrix1(area);
EXPECT_EQ(matrix1.num_rows(), kNumRows);
EXPECT_EQ(matrix1.num_columns(), kNumColumns);
EXPECT_EQ(matrix1.row_stride(), kRowStride);
ExpectSameAddress(matrix1.row(0).data(), ptr);
ExpectSameAddress(matrix1.data(), ptr);
const MutableMatrix<int> matrix2(area);
EXPECT_EQ(matrix2.num_rows(), kNumRows);
EXPECT_EQ(matrix2.num_columns(), kNumColumns);
EXPECT_EQ(matrix2.row_stride(), kRowStride);
ExpectSameAddress(matrix2.row(0).data(), ptr);
ExpectSameAddress(matrix2.data(), ptr);
}
// Tests that (Mutable)Matrix supports copy-construction and assignment with
// shallow-copy semantics, and reinterprets from T* to const T*.
TEST(MatrixTest, CopyAndAssign) {
MutableAlignedView view;
char *ptr = InvalidAlignedPointer();
const size_t kNumRows = 11;
const size_t kNumColumns = 13;
const size_t kRowBytes = kNumColumns * sizeof(int);
const size_t kRowStride = PadToAlignment(kRowBytes) / sizeof(int);
const size_t bytes = ComputeAlignedAreaSize(kNumRows, kRowBytes);
TF_ASSERT_OK(view.Reset(ptr, bytes));
MutableAlignedArea area;
TF_ASSERT_OK(area.Reset(view, kNumRows, kRowBytes));
const MutableMatrix<int> matrix1(area);
EXPECT_EQ(matrix1.num_rows(), kNumRows);
EXPECT_EQ(matrix1.num_columns(), kNumColumns);
EXPECT_EQ(matrix1.row_stride(), kRowStride);
ExpectSameAddress(matrix1.row(0).data(), ptr);
ExpectSameAddress(matrix1.data(), ptr);
// Copy-construct from another matrix.
MutableMatrix<int> matrix2(matrix1);
EXPECT_EQ(matrix2.num_rows(), kNumRows);
EXPECT_EQ(matrix2.num_columns(), kNumColumns);
EXPECT_EQ(matrix2.row_stride(), kRowStride);
ExpectSameAddress(matrix2.row(0).data(), ptr);
ExpectSameAddress(matrix2.data(), ptr);
// Assign from an empty matrix, effectively clearing it.
matrix2 = MutableMatrix<int>();
EXPECT_EQ(matrix2.num_rows(), 0);
EXPECT_EQ(matrix2.num_columns(), 0);
EXPECT_EQ(matrix2.row_stride(), 0);
// Assign from the original matrix.
matrix2 = matrix1;
EXPECT_EQ(matrix2.num_rows(), kNumRows);
EXPECT_EQ(matrix2.num_columns(), kNumColumns);
EXPECT_EQ(matrix2.row_stride(), kRowStride);
ExpectSameAddress(matrix2.row(0).data(), ptr);
ExpectSameAddress(matrix2.data(), ptr);
// Copy-construct from another matrix. Note that this reinterprets type.
Matrix<int> matrix3(matrix2);
EXPECT_EQ(matrix3.num_rows(), kNumRows);
EXPECT_EQ(matrix3.num_columns(), kNumColumns);
EXPECT_EQ(matrix3.row_stride(), kRowStride);
ExpectSameAddress(matrix3.row(0).data(), ptr);
ExpectSameAddress(matrix3.data(), ptr);
// Assign from an empty matrix, effectively clearing it.
matrix3 = Matrix<int>();
EXPECT_EQ(matrix3.num_rows(), 0);
EXPECT_EQ(matrix3.num_columns(), 0);
EXPECT_EQ(matrix3.row_stride(), 0);
// Assign from the original matrix. Note that this reinterprets type.
matrix3 = matrix1;
EXPECT_EQ(matrix3.num_rows(), kNumRows);
EXPECT_EQ(matrix3.num_columns(), kNumColumns);
EXPECT_EQ(matrix3.row_stride(), kRowStride);
ExpectSameAddress(matrix3.row(0).data(), ptr);
ExpectSameAddress(matrix3.data(), ptr);
}
// Tests that (Mutable)Matrix supports row access.
TEST(MatrixTest, Rows) {
const size_t kNumRows = 11;
const size_t kNumColumns = 13;
const size_t bytes =
ComputeAlignedAreaSize(kNumRows, kNumColumns * sizeof(float));
UniqueAlignedArray array;
array.Reset(bytes);
MutableAlignedArea area;
TF_ASSERT_OK(area.Reset(array.view(), kNumRows, kNumColumns * sizeof(float)));
// Write to a mutable matrix.
const MutableMatrix<float> mutable_matrix(area);
ASSERT_EQ(mutable_matrix.num_rows(), kNumRows);
ASSERT_EQ(mutable_matrix.num_columns(), kNumColumns);
for (size_t row = 0; row < kNumRows; ++row) {
for (size_t column = 0; column < kNumColumns; ++column) {
mutable_matrix.row(row)[column] = row * 1000.0 + column;
}
}
// Read from a const matrix that points at the same values.
const Matrix<float> const_matrix(area);
ASSERT_EQ(const_matrix.num_rows(), kNumRows);
ASSERT_EQ(const_matrix.num_columns(), kNumColumns);
for (size_t row = 0; row < kNumRows; ++row) {
for (size_t column = 0; column < kNumColumns; ++column) {
EXPECT_EQ(const_matrix.row(row)[column], row * 1000.0 + column);
}
}
}
TEST(MatrixTest, MatrixFromVector) {
for (int cols = 0; cols < 100; ++cols) {
MutableAlignedView view;
char *ptr = InvalidAlignedPointer();
TF_ASSERT_OK(view.Reset(ptr, cols * sizeof(int)));
const MutableVector<int> vector(view);
const MutableMatrix<int> matrix(vector);
ASSERT_EQ(matrix.row(0).data(), vector.data());
ExpectSameAddress(matrix.data(), vector.data());
ASSERT_EQ(matrix.num_rows(), 1);
ASSERT_EQ(matrix.num_columns(), vector.size());
}
}
template <class MatrixType>
class BlockedMatrixTest : public ::testing::Test {};
typedef ::testing::Types<
BlockedMatrix<float, BlockedMatrixFormat::kRowBlockedColumnMajor>,
BlockedMatrix<float, BlockedMatrixFormat::kColumnBlockedRowMajor>,
BlockedMatrix<int64, BlockedMatrixFormat::kRowBlockedColumnMajor>,
BlockedMatrix<int64, BlockedMatrixFormat::kColumnBlockedRowMajor>>
BlockedRowAndColumnTypes;
TYPED_TEST_CASE(BlockedMatrixTest, BlockedRowAndColumnTypes);
TYPED_TEST(BlockedMatrixTest, PaddingNotAllowed) {
MutableAlignedView view;
MutableAlignedArea area;
constexpr size_t kNumRows = 10;
constexpr size_t kNumColumns = 10;
constexpr size_t kBlockSize = 5;
constexpr size_t kNumViews = (kNumRows * kNumColumns) / kBlockSize;
constexpr size_t kBlockSizeBytes =
kBlockSize * sizeof(typename TypeParam::ElementType);
const size_t bytes = ComputeAlignedAreaSize(kNumViews, kBlockSizeBytes);
TF_ASSERT_OK(view.Reset(InvalidAlignedPointer(), bytes));
TF_ASSERT_OK(area.Reset(view, kNumViews, kBlockSizeBytes));
// 5 is usually relatively prime to the alignment size, but you may have to
// update this test if kAlignmentBytes changes.
ASSERT_NE(PadToAlignment(kBlockSizeBytes), kBlockSizeBytes);
TypeParam matrix;
EXPECT_THAT(matrix.Reset(area, kNumRows, kNumColumns),
test::IsErrorWithSubstr(
"Padding is not supported for blocked matrix formats."));
}
// Tests accessors, and the size of matrices after allocation.
TYPED_TEST(BlockedMatrixTest, Accessors) {
MutableAlignedView view;
MutableAlignedArea area;
char *ptr = InvalidAlignedPointer();
constexpr size_t kNumRows = 48;
constexpr size_t kNumColumns = 24;
constexpr size_t kBlockSize = 8;
constexpr size_t kNumViews = (kNumRows * kNumColumns) / kBlockSize;
constexpr size_t kBlockSizeBytes =
kBlockSize * sizeof(typename TypeParam::ElementType);
const size_t bytes = ComputeAlignedAreaSize(kNumViews, kBlockSizeBytes);
TF_ASSERT_OK(view.Reset(ptr, bytes));
TF_ASSERT_OK(area.Reset(view, kNumViews, kBlockSizeBytes));
TypeParam matrix;
// If the view size is wrong, it should fail.
EXPECT_THAT(
matrix.Reset(area, kNumRows + 1, kNumColumns),
test::IsErrorWithSubstr("Area has 144 views, but should have 147"));
// If the blocking scheme cannot divide the matrix evenly, an error should
// be raised. The choice of 12 and 96 is a bit non-trivial: they are numbers
// that conveniently result in the correct area (so other errors won't be
// raised), but an incompatible division of the vectors.
if (TypeParam::IsRowBlocked()) {
EXPECT_THAT(
matrix.Reset(area, 12, 96),
test::IsErrorWithSubstr("row-blocked matrix has major dimension 12 "
"which is not divisible by the block "
"size, 8"));
} else {
EXPECT_THAT(
matrix.Reset(area, 96, 12),
test::IsErrorWithSubstr("column-blocked matrix has major dimension "
"12 which is not divisible by the block "
"size, 8"));
}
TF_EXPECT_OK(matrix.Reset(area, kNumRows, kNumColumns));
EXPECT_EQ(matrix.vector(0).data(),
reinterpret_cast<typename TypeParam::ElementType *>(ptr));
EXPECT_EQ(matrix.num_rows(), kNumRows);
EXPECT_EQ(matrix.num_columns(), kNumColumns);
EXPECT_EQ(matrix.block_size(), kBlockSize);
EXPECT_EQ(matrix.num_vectors(), kNumViews);
}
TYPED_TEST(BlockedMatrixTest, CopyCastTranspose) {
MutableAlignedView view;
MutableAlignedArea area;
constexpr size_t kNumRows = 48;
constexpr size_t kNumColumns = 24;
constexpr size_t kBlockSize = 8;
constexpr size_t kNumViews = (kNumRows * kNumColumns) / kBlockSize;
constexpr size_t kBlockSizeBytes =
kBlockSize * sizeof(typename TypeParam::ElementType);
const size_t bytes = ComputeAlignedAreaSize(kNumViews, kBlockSizeBytes);
TF_ASSERT_OK(view.Reset(InvalidAlignedPointer(), bytes));
TF_ASSERT_OK(area.Reset(view, kNumViews, kBlockSizeBytes));
TypeParam matrix;
TF_EXPECT_OK(matrix.Reset(area, kNumRows, kNumColumns));
// Test both copying and casting to const.
TypeParam matrix_copy = matrix;
auto readonly = matrix.AsConst();
EXPECT_TRUE(StructsEqual(matrix, matrix_copy));
EXPECT_TRUE(StructsEqual(matrix, readonly));
for (int i = 0; i < kNumViews; ++i) {
EXPECT_EQ(matrix.vector(i).data(), matrix_copy.vector(i).data());
EXPECT_EQ(matrix.vector(i).data(), readonly.vector(i).data());
}
// Transpose matrix.
auto transposed = matrix.Transpose();
auto readonly_transposed = readonly.Transpose();
EXPECT_FALSE(StructsEqual(matrix, transposed));
EXPECT_FALSE(StructsEqual(readonly, readonly_transposed));
EXPECT_TRUE(StructsEqual(transposed, readonly_transposed));
}
} // namespace
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/mmap.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <utility>
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/gtl/cleanup.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/logging.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
int UniqueAlignedMmap::Syscalls::Open(const string &path) const {
return open(path.c_str(), O_RDONLY);
}
int UniqueAlignedMmap::Syscalls::Close(int file_descriptor) const {
return close(file_descriptor);
}
void *UniqueAlignedMmap::Syscalls::Mmap(int file_descriptor,
size_t size) const {
return mmap(nullptr, size, PROT_READ, MAP_SHARED, file_descriptor, 0);
}
int UniqueAlignedMmap::Syscalls::Munmap(void *data, size_t size) const {
return munmap(data, size);
}
UniqueAlignedMmap::UniqueAlignedMmap(std::unique_ptr<Syscalls> syscalls)
: syscalls_(std::move(syscalls)) {}
UniqueAlignedMmap::UniqueAlignedMmap(UniqueAlignedMmap &&that)
: syscalls_(std::move(that.syscalls_)) {
view_ = that.view_;
path_ = that.path_;
that.view_ = MutableAlignedView();
that.path_.clear();
}
UniqueAlignedMmap &UniqueAlignedMmap::operator=(UniqueAlignedMmap &&that) {
syscalls_ = std::move(that.syscalls_);
view_ = that.view_;
path_ = that.path_;
that.view_ = MutableAlignedView();
that.path_.clear();
return *this;
}
UniqueAlignedMmap::~UniqueAlignedMmap() {
UnmapIfNonEmpty(view_.data(), view_.size(), path_);
}
tensorflow::Status UniqueAlignedMmap::Reset(const string &path) {
uint64 size = 0;
TF_RETURN_IF_ERROR(tensorflow::Env::Default()->GetFileSize(path, &size));
// Since mmap() cannot map 0 bytes, we skip the call on empty files. This is
// OK because UnmapIfNonEmpty() also skips munmap() on an empty region.
if (size == 0) {
view_ = MutableAlignedView();
path_ = path;
return tensorflow::Status::OK();
}
const int file_descriptor = syscalls_->Open(path);
if (file_descriptor == -1) {
// TODO(googleuser): Use strerror_r() to export the system error message.
return tensorflow::errors::Unknown("Failed to open '", path, "'");
}
// In case we error out.
auto ensure_closed = tensorflow::gtl::MakeCleanup([&] {
if (syscalls_->Close(file_descriptor) != 0) {
LOG(ERROR) << "Failed to close '" << path << "'";
}
});
void *mmap_result = syscalls_->Mmap(file_descriptor, size);
if (mmap_result == MAP_FAILED) {
return tensorflow::errors::Unknown("Failed to mmap '", path, "'");
}
// In case we error out.
auto ensure_unmapped = tensorflow::gtl::MakeCleanup(
[&] { UnmapIfNonEmpty(mmap_result, size, path); });
// Since mmap() increments the refcount of the |file_descriptor|, it must be
// closed to prevent a leak.
ensure_closed.release(); // going to close it manually
if (syscalls_->Close(file_descriptor) != 0) {
return tensorflow::errors::Unknown("Failed to close '", path, "'");
}
// Most implementations of mmap() place the mapped region on a page boundary,
// which is plenty of alignment. Since this is so unlikely to fail, we don't
// try to recover if the address is misaligned. A potential recovery method
// is to allocate a UniqueAlignedArray and read the file normally.
MutableAlignedView data;
TF_RETURN_IF_ERROR(data.Reset(reinterpret_cast<char *>(mmap_result), size));
// Success; make modifications.
view_ = data;
path_ = path;
ensure_unmapped.release(); // this has taken ownership of the mapped file
return tensorflow::Status::OK();
}
void UniqueAlignedMmap::UnmapIfNonEmpty(void *data, size_t size,
const string &path) const {
if (size == 0) return;
if (syscalls_->Munmap(data, size) != 0) {
LOG(ERROR) << "Failed to munmap() file '" << path << "'";
}
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// =============================================================================
// Utils for establishing and managing memory-mapped files.
#ifndef DRAGNN_RUNTIME_MMAP_H_
#define DRAGNN_RUNTIME_MMAP_H_
#include <stddef.h>
#include <memory>
#include <string>
#include "dragnn/runtime/alignment.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// A uniquely-owned aligned memory-mapped file. This has virtual methods only
// for mocking in tests; do not derive from class.
class UniqueAlignedMmap {
public:
// A mockable wrapper around the system calls used by this class.
class Syscalls {
public:
virtual ~Syscalls() = default;
// Each method below forwards to the similarly-named syscall. Some methods
// have been simplified by omitting arguments that are never varied.
virtual int Open(const string &path) const;
virtual int Close(int file_descriptor) const;
virtual void *Mmap(int file_descriptor, size_t size) const;
virtual int Munmap(void *data, size_t size) const;
};
// Creates an empty, unmapped memory region.
UniqueAlignedMmap() = default;
// FOR TESTS ONLY. As above, but injects the |syscalls|.
explicit UniqueAlignedMmap(std::unique_ptr<Syscalls> syscalls);
// Supports movement only.
UniqueAlignedMmap(UniqueAlignedMmap &&that);
UniqueAlignedMmap &operator=(UniqueAlignedMmap &&that);
UniqueAlignedMmap(const UniqueAlignedMmap &that) = delete;
UniqueAlignedMmap &operator=(const UniqueAlignedMmap &that) = delete;
// Unmaps the current memory-mapped file, if any.
~UniqueAlignedMmap();
// Resets this to a memory-mapping of the |path|. On error, returns non-OK
// and modifies nothing.
tensorflow::Status Reset(const string &path);
// Returns the mapped memory region.
AlignedView view() const { return AlignedView(view_); }
private:
// Unmaps [|data|,|data|+|size|), if non-empty. Uses the |path| for error
// logging. Does not return a status because none of the call sites could
// pass it along; they'd log it anyways.
void UnmapIfNonEmpty(void *data, size_t size, const string &path) const;
// The system calls used to perform the memory-mapping.
std::unique_ptr<Syscalls> syscalls_{new Syscalls()};
// The current memory-mapped file, or empty if unmapped. Mutable to satisfy
// munmap(), which requires a non-const pointer---contents are not modified.
MutableAlignedView view_;
// The path to the current memory-mapped file, if any, for debug logging.
string path_;
};
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_MMAP_H_
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/mmap_array_variable_store.h"
#include <utility>
#include "tensorflow/core/lib/core/errors.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
tensorflow::Status MmapArrayVariableStore::Reset(
const ArrayVariableStoreSpec &spec, const string &path) {
UniqueAlignedMmap data;
TF_RETURN_IF_ERROR(data.Reset(path));
TF_RETURN_IF_ERROR(ArrayVariableStore::Reset(spec, data.view()));
// Success; make modifications.
data_ = std::move(data);
return tensorflow::Status::OK();
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 DRAGNN_RUNTIME_MMAP_ARRAY_VARIABLE_STORE_H_
#define DRAGNN_RUNTIME_MMAP_ARRAY_VARIABLE_STORE_H_
#include <string>
#include "dragnn/protos/runtime.pb.h"
#include "dragnn/runtime/array_variable_store.h"
#include "dragnn/runtime/mmap.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// An ArrayVariableStore subclass that maps file content into memory.
class MmapArrayVariableStore : public ArrayVariableStore {
public:
// Creates an uninitialized store.
MmapArrayVariableStore() = default;
// Resets this to represent the variables defined by the |spec|, mapping the
// byte array from the |path|. On error, returns non-OK and modifies nothing.
tensorflow::Status Reset(const ArrayVariableStoreSpec &spec,
const string &path);
private:
// The memory-mapped file containing the variables.
UniqueAlignedMmap data_;
};
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_MMAP_ARRAY_VARIABLE_STORE_H_
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/mmap.h"
#include <stddef.h>
#include <sys/mman.h>
#include <string>
#include <utility>
#include "dragnn/core/test/generic.h"
#include "syntaxnet/base.h"
#include <gmock/gmock.h>
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
using ::testing::Return;
// A mockable set of system calls.
class MockSyscalls : public UniqueAlignedMmap::Syscalls {
public:
MOCK_CONST_METHOD1(Open, int(const string &path));
MOCK_CONST_METHOD1(Close, int(int file_descriptor));
MOCK_CONST_METHOD2(Mmap, void *(int file_descriptor, size_t size));
MOCK_CONST_METHOD2(Munmap, int(void *, size_t size));
};
class UniqueAlignedMmapTest : public ::testing::Test {
protected:
const string kInvalidFile = "/some/invalid/path";
const string kEmptyFile = tensorflow::io::JoinPath(
test::GetTestDataPrefix(), "dragnn/runtime/testdata/empty_file");
const string kTenBytes = tensorflow::io::JoinPath(
test::GetTestDataPrefix(), "dragnn/runtime/testdata/ten_bytes");
std::unique_ptr<MockSyscalls> syscalls_{new MockSyscalls()};
};
// Tests that the mapped region is empty by default.
TEST_F(UniqueAlignedMmapTest, EmptyByDefault) {
UniqueAlignedMmap data;
EXPECT_TRUE(data.view().empty());
}
// Tests that an empty file can be mapped.
TEST_F(UniqueAlignedMmapTest, EmptyFile) {
UniqueAlignedMmap data;
TF_ASSERT_OK(data.Reset(kEmptyFile));
EXPECT_TRUE(data.view().empty());
}
// Tests that a non-empty file can be mapped.
TEST_F(UniqueAlignedMmapTest, TenBytes) {
UniqueAlignedMmap data;
TF_ASSERT_OK(data.Reset(kTenBytes));
ASSERT_EQ(data.view().size(), 10);
EXPECT_STREQ(data.view().data(), "0123456789");
}
// Tests that the mapped files can be move-constructed and move-assigned.
TEST_F(UniqueAlignedMmapTest, Movement) {
UniqueAlignedMmap data1;
TF_ASSERT_OK(data1.Reset(kTenBytes));
UniqueAlignedMmap data2(std::move(data1));
ASSERT_EQ(data2.view().size(), 10);
EXPECT_STREQ(data2.view().data(), "0123456789");
UniqueAlignedMmap data3;
data3 = std::move(data2);
ASSERT_EQ(data3.view().size(), 10);
EXPECT_STREQ(data3.view().data(), "0123456789");
}
// Tests that the mapping fails if the file is invalid.
TEST_F(UniqueAlignedMmapTest, InvalidFile) {
UniqueAlignedMmap data;
EXPECT_FALSE(data.Reset(kInvalidFile).ok());
}
// Tests that the mapping fails if the file cannot be open()ed.
TEST_F(UniqueAlignedMmapTest, FailToOpen) {
EXPECT_CALL(*syscalls_, Open(kTenBytes)).WillOnce(Return(-1));
UniqueAlignedMmap data(std::move(syscalls_));
EXPECT_THAT(data.Reset(kTenBytes), test::IsErrorWithSubstr("Failed to open"));
}
// Tests that the mapping fails if the file cannot be mmap()ed.
TEST_F(UniqueAlignedMmapTest, FailToMmap) {
const int kFileDescriptor = 5;
EXPECT_CALL(*syscalls_, Open(kTenBytes)).WillOnce(Return(kFileDescriptor));
EXPECT_CALL(*syscalls_, Mmap(kFileDescriptor, 10))
.WillOnce(Return(MAP_FAILED));
EXPECT_CALL(*syscalls_, Close(kFileDescriptor)).WillOnce(Return(0));
UniqueAlignedMmap data(std::move(syscalls_));
EXPECT_THAT(data.Reset(kTenBytes), test::IsErrorWithSubstr("Failed to mmap"));
}
// As above, but also fails to close.
TEST_F(UniqueAlignedMmapTest, FailToMmapAndClose) {
const int kFileDescriptor = 5;
EXPECT_CALL(*syscalls_, Open(kTenBytes)).WillOnce(Return(kFileDescriptor));
EXPECT_CALL(*syscalls_, Mmap(kFileDescriptor, 10))
.WillOnce(Return(MAP_FAILED));
EXPECT_CALL(*syscalls_, Close(kFileDescriptor)).WillOnce(Return(-1));
UniqueAlignedMmap data(std::move(syscalls_));
EXPECT_THAT(data.Reset(kTenBytes), test::IsErrorWithSubstr("Failed to mmap"));
}
// Tests that the mapping fails if the file cannot be close()ed.
TEST_F(UniqueAlignedMmapTest, FailToClose) {
const int kFileDescriptor = 5;
EXPECT_CALL(*syscalls_, Open(kTenBytes)).WillOnce(Return(kFileDescriptor));
EXPECT_CALL(*syscalls_, Mmap(kFileDescriptor, 10)).WillOnce(Return(nullptr));
EXPECT_CALL(*syscalls_, Close(kFileDescriptor)).WillOnce(Return(-1));
EXPECT_CALL(*syscalls_, Munmap(nullptr, 10)).WillOnce(Return(0));
UniqueAlignedMmap data(std::move(syscalls_));
EXPECT_THAT(data.Reset(kTenBytes),
test::IsErrorWithSubstr("Failed to close"));
}
// As above, but also fails to munmap().
TEST_F(UniqueAlignedMmapTest, FailToCloseAndMunmap) {
const int kFileDescriptor = 5;
EXPECT_CALL(*syscalls_, Open(kTenBytes)).WillOnce(Return(kFileDescriptor));
EXPECT_CALL(*syscalls_, Mmap(kFileDescriptor, 10)).WillOnce(Return(nullptr));
EXPECT_CALL(*syscalls_, Close(kFileDescriptor)).WillOnce(Return(-1));
EXPECT_CALL(*syscalls_, Munmap(nullptr, 10)).WillOnce(Return(-1));
UniqueAlignedMmap data(std::move(syscalls_));
EXPECT_THAT(data.Reset(kTenBytes),
test::IsErrorWithSubstr("Failed to close"));
}
// Tests that the mapping fails if the mapped region is misaligned.
TEST_F(UniqueAlignedMmapTest, Misaligned) {
char *ptr = nullptr;
++ptr;
const int kFileDescriptor = 5;
EXPECT_CALL(*syscalls_, Open(kTenBytes)).WillOnce(Return(kFileDescriptor));
EXPECT_CALL(*syscalls_, Mmap(kFileDescriptor, 10)).WillOnce(Return(ptr));
EXPECT_CALL(*syscalls_, Close(kFileDescriptor)).WillOnce(Return(0));
EXPECT_CALL(*syscalls_, Munmap(ptr, 10)).WillOnce(Return(0));
UniqueAlignedMmap data(std::move(syscalls_));
EXPECT_THAT(data.Reset(kTenBytes),
test::IsErrorWithSubstr("Pointer fails alignment requirement"));
}
} // namespace
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/mst_solver_component_base.h"
#include <stddef.h>
#include "dragnn/runtime/attributes.h"
#include "dragnn/runtime/math/types.h"
#include "dragnn/runtime/network_unit.h"
#include "tensorflow/core/lib/core/errors.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Attributes used by the MST solver.
struct MstSolverAttributes : public Attributes {
// Whether to solve for a spanning forest instead of a spanning tree.
Optional<bool> forest{"forest", false, this};
// Training-only attributes, ignored in the runtime.
Ignored loss{"loss", this};
};
} // namespace
MstSolverComponentBase::MstSolverComponentBase(const string &builder_name,
const string &backend_name)
: builder_name_(builder_name), backend_name_(backend_name) {}
bool MstSolverComponentBase::Supports(
const ComponentSpec &component_spec,
const string &normalized_builder_name) const {
const string network_unit = NetworkUnit::GetClassName(component_spec);
return (normalized_builder_name == "BulkAnnotatorComponent" ||
normalized_builder_name == builder_name_) &&
(component_spec.backend().registered_name() == "StatelessComponent" ||
component_spec.backend().registered_name() == backend_name_) &&
component_spec.transition_system().registered_name() == "heads" &&
network_unit == "MstSolverNetwork" &&
component_spec.fixed_feature_size() == 0 &&
component_spec.linked_feature_size() == 1;
}
tensorflow::Status MstSolverComponentBase::Initialize(
const ComponentSpec &component_spec, VariableStore *variable_store,
NetworkStateManager *network_state_manager,
ExtensionManager *extension_manager) {
MstSolverAttributes attributes;
TF_RETURN_IF_ERROR(
attributes.Reset(component_spec.network_unit().parameters()));
forest_ = attributes.forest();
const LinkedFeatureChannel &link = component_spec.linked_feature(0);
size_t dimension = 0;
TF_RETURN_IF_ERROR(network_state_manager->LookupLayer(
link.source_component(), link.source_layer(), &dimension,
&adjacency_handle_));
if (dimension != 1) {
return tensorflow::errors::InvalidArgument(
"Adjacency matrix has dimension ", dimension, " but expected 1");
}
extension_manager->GetShared(&heads_handle_);
extension_manager->GetShared(&solver_handle_);
return tensorflow::Status::OK();
}
tensorflow::Status MstSolverComponentBase::ComputeHeads(
SessionState *session_state,
tensorflow::gtl::ArraySlice<Index> *heads) const {
Matrix<float> adjacency(
session_state->network_states.GetLayer(adjacency_handle_));
const size_t num_nodes = adjacency.num_rows();
Solver &solver = session_state->extensions.Get(solver_handle_);
TF_RETURN_IF_ERROR(solver.Init(forest_, num_nodes));
for (size_t target = 0; target < num_nodes; ++target) {
Vector<float> source_scores = adjacency.row(target);
for (size_t source = 0; source < num_nodes; ++source) {
if (source == target) {
solver.AddRoot(source, source_scores[source]);
} else {
solver.AddArc(source, target, source_scores[source]);
}
}
}
std::vector<Index> &argmax = session_state->extensions.Get(heads_handle_);
argmax.resize(num_nodes);
TF_RETURN_IF_ERROR(solver.Solve(&argmax));
*heads = argmax;
return tensorflow::Status::OK();
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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 DRAGNN_RUNTIME_MST_SOLVER_COMPONENT_BASE_H_
#define DRAGNN_RUNTIME_MST_SOLVER_COMPONENT_BASE_H_
#include <vector>
#include "dragnn/core/compute_session.h"
#include "dragnn/mst/mst_solver.h"
#include "dragnn/protos/spec.pb.h"
#include "dragnn/runtime/component.h"
#include "dragnn/runtime/extensions.h"
#include "dragnn/runtime/network_states.h"
#include "dragnn/runtime/session_state.h"
#include "dragnn/runtime/variable_store.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Base class for MST parsing components, which select heads jointly by finding
// the maximum spanning tree of the input tokens.
//
// This base class only computes the selected heads, while subclasses apply the
// heads to the annotations in the ComputeSession.
class MstSolverComponentBase : public Component {
public:
// NB: This definition of Index should match the MstSolver TF op wrappers.
using Index = uint16;
// Partially implements Component.
bool Supports(const ComponentSpec &component_spec,
const string &normalized_builder_name) const override;
tensorflow::Status Initialize(const ComponentSpec &component_spec,
VariableStore *variable_store,
NetworkStateManager *network_state_manager,
ExtensionManager *extension_manager) override;
bool PreferredTo(const Component &other) const override { return false; }
protected:
// Creates a component that supports the |builder_name| and |backend_name|.
MstSolverComponentBase(const string &builder_name,
const string &backend_name);
// Points |heads| at the list of heads computed from the |session_state|,
// where a self-loop indicates a root. Returns non-OK on error.
tensorflow::Status ComputeHeads(
SessionState *session_state,
tensorflow::gtl::ArraySlice<Index> *heads) const;
private:
using Solver = MstSolver<Index, float>;
// Names of the supported component builder and backend.
const string builder_name_;
const string backend_name_;
// Whether to solve for a spanning forest instead of a spanning tree.
bool forest_ = false;
// Directed adjacency matrix input.
PairwiseLayerHandle<float> adjacency_handle_;
// List of selected head indices.
SharedExtensionHandle<std::vector<Index>> heads_handle_;
// Reusable MST solver.
SharedExtensionHandle<Solver> solver_handle_;
};
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_MST_SOLVER_COMPONENT_BASE_H_
// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/mst_solver_component_base.h"
#include <stddef.h>
#include <string>
#include <vector>
#include "dragnn/core/compute_session.h"
#include "dragnn/core/input_batch_cache.h"
#include "dragnn/core/test/generic.h"
#include "dragnn/protos/spec.pb.h"
#include "dragnn/protos/trace.pb.h"
#include "dragnn/runtime/network_states.h"
#include "dragnn/runtime/session_state.h"
#include "dragnn/runtime/test/network_test_base.h"
#include "dragnn/runtime/variable_store.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
constexpr size_t kNumSteps = 12;
constexpr size_t kRootIndex = 7; // the root and head of all other tokens
constexpr char kTestBuilder[] = "TestBuilder";
constexpr char kTestBackend[] = "TestBackend";
constexpr char kPreviousComponentName[] = "previous_component";
constexpr char kAdjacencyLayerName[] = "adjacency_layer";
constexpr char kBadDimLayerName[] = "bad_layer";
// A subclass for tests.
class BasicMstSolverComponent : public MstSolverComponentBase {
public:
BasicMstSolverComponent()
: MstSolverComponentBase(kTestBuilder, kTestBackend) {}
// Implements Component. These methods are never called, but must be defined
// so the class is not abstract.
tensorflow::Status Evaluate(
SessionState *session_state, ComputeSession *compute_session,
ComponentTrace *component_trace) const override {
return tensorflow::Status::OK();
}
// Publicizes the base class's method.
using MstSolverComponentBase::ComputeHeads;
};
// Returns a ComponentSpec that works with the head selection component.
ComponentSpec MakeGoodSpec() {
ComponentSpec component_spec;
component_spec.mutable_component_builder()->set_registered_name(kTestBuilder);
component_spec.mutable_backend()->set_registered_name(kTestBackend);
component_spec.mutable_transition_system()->set_registered_name("heads");
component_spec.mutable_network_unit()->set_registered_name(
"some.path.to.MstSolverNetwork");
LinkedFeatureChannel *link = component_spec.add_linked_feature();
link->set_source_component(kPreviousComponentName);
link->set_source_layer(kAdjacencyLayerName);
return component_spec;
}
class MstSolverComponentBaseTest : public NetworkTestBase {
protected:
// Initializes a head selection component from the |component_spec| and sets
// |heads| to the extracted head indices. Returs non-OK on error.
tensorflow::Status Run(const ComponentSpec &component_spec,
std::vector<int> *heads) {
AddComponent(kPreviousComponentName);
AddPairwiseLayer(kAdjacencyLayerName, 1);
AddPairwiseLayer(kBadDimLayerName, 2);
BasicMstSolverComponent component;
TF_RETURN_IF_ERROR(component.Initialize(component_spec, &variable_store_,
&network_state_manager_,
&extension_manager_));
network_states_.Reset(&network_state_manager_);
StartComponent(kNumSteps);
// Fill the |kRootIndex|'th column of the adjacency matrix with higher
// scores, so all tokens select it as head. The |kRootIndex|'th token
// itself is a self-loop, so it becomes a root.
MutableMatrix<float> adjacency =
GetPairwiseLayer(kPreviousComponentName, kAdjacencyLayerName);
for (size_t target = 0; target < kNumSteps; ++target) {
for (size_t source = 0; source < kNumSteps; ++source) {
adjacency.row(target)[source] = source == kRootIndex ? 1.0 : 0.0;
}
}
session_state_.extensions.Reset(&extension_manager_);
tensorflow::gtl::ArraySlice<MstSolverComponentBase::Index> argmax;
TF_RETURN_IF_ERROR(component.ComputeHeads(&session_state_, &argmax));
heads->assign(argmax.begin(), argmax.end());
return tensorflow::Status::OK();
}
};
// Tests that the expected heads are produced for a good spec.
TEST_F(MstSolverComponentBaseTest, RunsGoodSpec) {
std::vector<int> heads;
TF_ASSERT_OK(Run(MakeGoodSpec(), &heads));
const std::vector<int> expected_heads(kNumSteps, kRootIndex);
EXPECT_EQ(heads, expected_heads);
}
// Tests that a layer with the wrong dimension is rejected
TEST_F(MstSolverComponentBaseTest, WrongDimension) {
ComponentSpec component_spec = MakeGoodSpec();
component_spec.mutable_linked_feature(0)->set_source_layer(kBadDimLayerName);
std::vector<int> heads;
EXPECT_THAT(Run(component_spec, &heads),
test::IsErrorWithSubstr(
"Adjacency matrix has dimension 2 but expected 1"));
}
// Tests that the component is always dis-preferred.
TEST_F(MstSolverComponentBaseTest, NotPreferred) {
BasicMstSolverComponent component;
EXPECT_FALSE(component.PreferredTo(component));
}
// Tests that the good spec is supported.
TEST_F(MstSolverComponentBaseTest, SupportsGoodSpec) {
ComponentSpec component_spec = MakeGoodSpec();
BasicMstSolverComponent component;
EXPECT_TRUE(component.Supports(component_spec, kTestBuilder));
}
// Tests that various bad specs are rejected.
TEST_F(MstSolverComponentBaseTest, RejectsBadSpecs) {
ComponentSpec component_spec = MakeGoodSpec();
BasicMstSolverComponent component;
EXPECT_FALSE(component.Supports(component_spec, "bad"));
component_spec = MakeGoodSpec();
component_spec.mutable_backend()->set_registered_name("bad");
EXPECT_FALSE(component.Supports(component_spec, kTestBuilder));
component_spec = MakeGoodSpec();
component_spec.mutable_transition_system()->set_registered_name("bad");
EXPECT_FALSE(component.Supports(component_spec, kTestBuilder));
component_spec = MakeGoodSpec();
component_spec.mutable_network_unit()->set_registered_name("bad");
EXPECT_FALSE(component.Supports(component_spec, kTestBuilder));
component_spec = MakeGoodSpec();
component_spec.add_fixed_feature();
EXPECT_FALSE(component.Supports(component_spec, kTestBuilder));
component_spec = MakeGoodSpec();
component_spec.add_linked_feature();
EXPECT_FALSE(component.Supports(component_spec, kTestBuilder));
component_spec = MakeGoodSpec();
component_spec.clear_linked_feature();
EXPECT_FALSE(component.Supports(component_spec, kTestBuilder));
}
} // namespace
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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.
# =============================================================================
"""Build extension rules for handling multiple target architectures."""
# Build configs for specific CPU architectures. Each entry specified
# additional copts and tags.
# TODO(googleuser): Figure out a workaround for the shift
# instructions, and look for any other unsupported instructions.
MULTIARCH_CONFIGS = {
"generic": {
"copts": [],
"tags": [],
},
"avx": {
"copts": [
"-msse4.2",
],
"tags": [],
},
"avx2fma": {
"copts": [
"-msse4.2",
"-mavx",
"-mavx2",
"-mfma",
],
"tags": [
"local",
"manual",
],
},
}
# List of targets which are built for multiple architectures. These
# dependencies in dragnn_cc_* build rules are replaced with one with the
# appropriate suffix, e.g. _multiarch_generic
MULTIARCH_TARGETS = [
"//dragnn/runtime:biaffine_digraph_component",
"//dragnn/runtime:bulk_dynamic_component",
"//dragnn/runtime:bulk_feed_forward_network",
"//dragnn/runtime:bulk_lstm_network",
"//dragnn/runtime:feed_forward_network",
"//dragnn/runtime:feed_forward_network_kernel",
"//dragnn/runtime:feed_forward_network_layer",
"//dragnn/runtime:fixed_embeddings",
"//dragnn/runtime:linked_embeddings",
"//dragnn/runtime:lstm_network",
"//dragnn/runtime:lstm_network_kernel",
"//dragnn/runtime:network_unit_base",
"//dragnn/runtime:sequence_bulk_dynamic_component",
"//dragnn/runtime:sequence_features",
"//dragnn/runtime:sequence_links",
"//dragnn/runtime:sequence_model",
"//dragnn/runtime/lstm_cell:cell_function",
"//dragnn/runtime/lstm_cell:test_helpers",
"//dragnn/runtime/myelin:myelin_dynamic_component",
"//dragnn/runtime/myelin:myelin_dynamic_component_base",
"//dragnn/runtime/myelin:sequence_myelin_dynamic_component",
"//dragnn/runtime/xla:sequence_xla_dynamic_component_mixin",
"//dragnn/runtime/xla:testdata_simple_component_library",
"//dragnn/runtime/xla:xla_aot_dynamic_component",
"//dragnn/runtime/xla:xla_dynamic_component",
"//dragnn/runtime/xla:xla_dynamic_component_base",
]
def multiarch_name(target_name, arch_name):
"""Generates the multiarch version of |target_name| given |arch_name|."""
return target_name + '_multiarch_' + arch_name
def _is_multiarch(target):
"""Returns true if |target| is designated as a multiarch target."""
return (target in MULTIARCH_TARGETS or
('//' + native.package_name() + target) in MULTIARCH_TARGETS)
def _dragnn_cc_multiarch_target(native_rule = None,
name = '',
target_arch = None,
target_suffix = '',
copts = [],
deps = [],
tags = [],
opts_self = False,
deps_transformer = None,
**kwargs):
"""Generates a target for multiple architectures.
Using the |native_rule| (e.g. cc_library) to create a set of targets for
all CPU architectures listed in MULTIARCH_CONFIGS, with added suffixes
that designate the architecture.
When |target_arch| is set, then only that single target is generated,
and the name of the target is unchanged (no suffix is added).
When |opts_self| is true, then the 'copts' entry in MULTIARCH_CONFIGS
is additionally used to build this target.
The 'tags' entry in MULTIARCH_CONFIGS are included in the build tags.
Args:
native_rule: The build rule used for all generated targets
name: The original name of the build rule (without any suffix).
target_arch: When set, only this architecture is targeted.
target_suffix: Additional suffix to add after the architecture.
copts: The original compilation options for this target.
deps: The original dependencies for this target.
tags: The original build tags for this target.
opts_self: When true, additional copts are included.
deps_transformer: When set, a function to apply to the multiarch deps.
**kwargs: Additional args passed along to the build rule.
"""
# Determine set of target architectures based on |target_arch|.
if target_arch:
if target_arch in MULTIARCH_CONFIGS:
arch_items = [(target_arch, MULTIARCH_CONFIGS[target_arch])]
else:
fail('Unknown target_arch value: ' + target_arch)
else:
arch_items = MULTIARCH_CONFIGS.items()
# There is one target for each architecture in |arch_items|.
for arch, arch_config in arch_items:
# Transform the multi-arch deps.
multiarch_deps = [multiarch_name(dep, arch) if _is_multiarch(dep) else dep
for dep in deps]
if deps_transformer:
multiarch_deps = deps_transformer(multiarch_deps)
native_rule(
name = (name if target_arch else multiarch_name(name, arch)) + target_suffix,
copts = copts + arch_config['copts'] if opts_self else copts,
deps = multiarch_deps,
tags = tags + arch_config['tags'],
**kwargs)
def _dragnn_cc_multiarch_test_target(name = None,
target_arch = None,
**kwargs):
"""Test target wrapper which puts arch name before '_test'."""
test_suffix = '_test'
has_test_suffix = name.endswith(test_suffix)
# Keeps _test at the end of the target name.
test_name = name[:-len(test_suffix)] if has_test_suffix else name
target_suffix = test_suffix if has_test_suffix else ''
_dragnn_cc_multiarch_target(native_rule = native.cc_test,
name = test_name,
target_arch = target_arch,
target_suffix = target_suffix,
**kwargs)
# When |target_arch| is set, the resulting test is named |name|. Otherwise,
# tests with arch-specific names are generated, and for convenience we add a
# test_suite named |name| that runs the generic version of the test.
if not target_arch:
native.test_suite(
name = name,
tests = [multiarch_name(test_name, 'generic') + target_suffix])
def dragnn_cc_multiarch_library(**kwargs):
"""Similar to cc_library, but creates multiple architecture targets."""
_dragnn_cc_multiarch_target(native_rule = native.cc_library,
**kwargs)
def dragnn_cc_multiarch_test(**kwargs):
"""Similar to cc_test, but creates multiple architecture targets."""
_dragnn_cc_multiarch_test_target(**kwargs)
def dragnn_cc_multiarch_binary(**kwargs):
"""Similar to cc_binary, but creates multiple architecture targets."""
_dragnn_cc_multiarch_target(native_rule = native.cc_binary,
**kwargs)
def dragnn_cc_library(target_arch = 'generic', **kwargs):
"""Similar to cc_library, but targets one specific architecture."""
_dragnn_cc_multiarch_target(native_rule = native.cc_library,
target_arch = target_arch,
**kwargs)
def dragnn_cc_test(target_arch = 'generic', **kwargs):
"""Similar to cc_test, but targets one specific architecture."""
_dragnn_cc_multiarch_test_target(target_arch = target_arch,
**kwargs)
def dragnn_cc_binary(target_arch = 'generic', **kwargs):
"""Similar to cc_binary, but targets one specific architecture."""
_dragnn_cc_multiarch_target(native_rule = native.cc_binary,
target_arch = target_arch,
**kwargs)
package(default_visibility = ["//visibility:public"])
load(
":build_defs.bzl",
"dragnn_myelin_cc_library",
"dragnn_myelin_cc_test",
"dragnn_myelin_cc_multiarch_library",
"dragnn_myelin_cc_multiarch_test",
)
test_suite(name = "all_tests")
filegroup(
name = "test_myelination_output",
srcs = glob(["testdata/myelination_output/**"]),
)
cc_library(
name = "attr_value_utils",
srcs = ["attr_value_utils.cc"],
hdrs = ["attr_value_utils.h"],
deps = [
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:framework_headers_lib",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:protos_all_cc",
],
)
cc_test(
name = "attr_value_utils_test",
size = "small",
srcs = ["attr_value_utils_test.cc"],
deps = [
":attr_value_utils",
"//dragnn/core/test:generic",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
],
)
dragnn_myelin_cc_library(
name = "myelin_cell_converter",
srcs = ["myelin_cell_converter.cc"],
hdrs = ["myelin_cell_converter.h"],
deps = [
":attr_value_utils",
"//dragnn/protos:export_proto_cc",
"//dragnn/runtime:trained_model",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:framework_headers_lib",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:protos_all_cc",
],
)
dragnn_myelin_cc_test(
name = "myelin_cell_converter_test",
size = "small",
timeout = "moderate",
srcs = ["myelin_cell_converter_test.cc"],
data = ["//dragnn/runtime:test_rnn_tagger"],
deps = [
":myelin_cell_converter",
":myelin_spec_utils",
"//dragnn/components/syntaxnet:syntaxnet_component",
"//dragnn/core/test:generic",
"//dragnn/runtime:alignment",
"//dragnn/runtime:trained_model",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
"@sling//sling/myelin:compute",
"@sling//sling/myelin:flow",
"@sling//sling/myelin:graph",
],
)
dragnn_myelin_cc_library(
name = "myelin_library",
srcs = ["myelin_library.cc"],
hdrs = ["myelin_library.h"],
deps = [
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@sling//sling/myelin:flow",
],
)
dragnn_myelin_cc_test(
name = "myelin_library_test",
size = "small",
srcs = ["myelin_library_test.cc"],
deps = [
":myelin_library",
"//syntaxnet:test_main",
"@org_tensorflow//tensorflow/core:test",
],
)
dragnn_myelin_cc_library(
name = "myelin_spec_utils",
srcs = ["myelin_spec_utils.cc"],
hdrs = ["myelin_spec_utils.h"],
deps = [
":myelin_library",
"//dragnn/protos:spec_proto_cc",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@sling//sling/base",
"@sling//sling/file",
"@sling//sling/myelin:compute",
"@sling//sling/myelin:flow",
"@sling//sling/myelin/kernel:tensorflow",
],
)
dragnn_myelin_cc_test(
name = "myelin_spec_utils_test",
size = "small",
srcs = ["myelin_spec_utils_test.cc"],
deps = [
":myelin_spec_utils",
"//dragnn/core/test:generic",
"//dragnn/protos:spec_proto_cc",
"//syntaxnet:base",
"//syntaxnet:test_main",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
"@sling//sling/file",
"@sling//sling/file:posix",
"@sling//sling/myelin:compute",
"@sling//sling/myelin:flow",
],
)
dragnn_myelin_cc_library(
name = "myelin_tracing",
srcs = ["myelin_tracing.cc"],
hdrs = ["myelin_tracing.h"],
deps = [
"//dragnn/protos:cell_trace_proto_cc",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@sling//sling/myelin:compute",
],
)
dragnn_myelin_cc_test(
name = "myelin_tracing_test",
size = "small",
srcs = ["myelin_tracing_test.cc"],
deps = [
":myelin_spec_utils",
":myelin_tracing",
"//dragnn/core/test:generic",
"//dragnn/protos:cell_trace_proto_cc",
"//dragnn/runtime/test:helpers",
"//syntaxnet:base",
"//syntaxnet:test_main",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
"@sling//sling/myelin:compute",
"@sling//sling/myelin:flow",
],
)
dragnn_myelin_cc_multiarch_library(
name = "myelin_dynamic_component_base",
srcs = ["myelin_dynamic_component_base.cc"],
hdrs = ["myelin_dynamic_component_base.h"],
deps = [
":myelin_spec_utils",
":myelin_tracing",
"//dragnn/protos:cell_trace_proto_cc",
"//dragnn/protos:spec_proto_cc",
"//dragnn/protos:trace_proto_cc",
"//dragnn/runtime:alignment",
"//dragnn/runtime:component",
"//dragnn/runtime:extensions",
"//dragnn/runtime:fixed_embeddings",
"//dragnn/runtime:linked_embeddings",
"//dragnn/runtime:network_states",
"//dragnn/runtime:session_state",
"//dragnn/runtime:transition_system_traits",
"//dragnn/runtime:variable_store",
"//dragnn/runtime/math:types",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@sling//sling/myelin:compute",
"@sling//sling/myelin:flow",
],
)
dragnn_myelin_cc_multiarch_library(
name = "myelin_dynamic_component",
srcs = ["myelin_dynamic_component.cc"],
deps = [
":myelin_dynamic_component_base",
"//dragnn/core:compute_session",
"//dragnn/protos:spec_proto_cc",
"//dragnn/protos:trace_proto_cc",
"//dragnn/runtime:component",
"//dragnn/runtime:fixed_embeddings",
"//dragnn/runtime:linked_embeddings",
"//dragnn/runtime:network_states",
"//dragnn/runtime:session_state",
"//dragnn/runtime/math:types",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@sling//sling/myelin:compute",
],
alwayslink = 1,
)
dragnn_myelin_cc_multiarch_test(
name = "myelin_dynamic_component_test",
size = "small",
srcs = ["myelin_dynamic_component_test.cc"],
deps = [
":myelin_dynamic_component",
":myelin_spec_utils",
"//dragnn/core/test:generic",
"//dragnn/protos:cell_trace_proto_cc",
"//dragnn/protos:spec_proto_cc",
"//dragnn/protos:trace_proto_cc",
"//dragnn/runtime:component",
"//dragnn/runtime:extensions",
"//dragnn/runtime/test:network_test_base",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
"@sling//sling/file",
"@sling//sling/file:posix",
"@sling//sling/myelin:flow",
],
)
dragnn_myelin_cc_library(
name = "myelination",
srcs = ["myelination.cc"],
hdrs = ["myelination.h"],
deps = [
":myelin_cell_converter",
":myelin_spec_utils",
"//dragnn/protos:spec_proto_cc",
"//dragnn/runtime:component",
"//dragnn/runtime:trained_model",
"//syntaxnet:base",
"//syntaxnet:registry",
"@org_tensorflow//tensorflow/core:lib",
],
)
dragnn_myelin_cc_test(
name = "myelination_test",
size = "small",
timeout = "moderate",
srcs = ["myelination_test.cc"],
data = [
":test_myelination_output",
"//dragnn/runtime:test_rnn_tagger",
],
deps = [
":myelin_spec_utils",
":myelination",
"//dragnn/components/syntaxnet:syntaxnet_component",
"//dragnn/core/test:generic",
"//dragnn/protos:spec_proto_cc",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
],
)
dragnn_myelin_cc_multiarch_library(
name = "sequence_myelin_dynamic_component",
srcs = ["sequence_myelin_dynamic_component.cc"],
deps = [
":myelin_dynamic_component_base",
"//dragnn/core:compute_session",
"//dragnn/protos:spec_proto_cc",
"//dragnn/protos:trace_proto_cc",
"//dragnn/runtime:component",
"//dragnn/runtime:extensions",
"//dragnn/runtime:network_states",
"//dragnn/runtime:sequence_features",
"//dragnn/runtime:sequence_links",
"//dragnn/runtime:sequence_model",
"//dragnn/runtime:session_state",
"//dragnn/runtime:variable_store",
"//dragnn/runtime/math:types",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@sling//sling/myelin:compute",
],
alwayslink = 1,
)
dragnn_myelin_cc_multiarch_test(
name = "sequence_myelin_dynamic_component_test",
size = "small",
srcs = ["sequence_myelin_dynamic_component_test.cc"],
deps = [
":myelin_spec_utils",
":sequence_myelin_dynamic_component",
"//dragnn/core:compute_session",
"//dragnn/core:input_batch_cache",
"//dragnn/core/test:generic",
"//dragnn/protos:spec_proto_cc",
"//dragnn/protos:trace_proto_cc",
"//dragnn/runtime:component",
"//dragnn/runtime:extensions",
"//dragnn/runtime:network_states",
"//dragnn/runtime:sequence_backend",
"//dragnn/runtime:sequence_extractor",
"//dragnn/runtime:sequence_linker",
"//dragnn/runtime:sequence_predictor",
"//dragnn/runtime:session_state",
"//dragnn/runtime:variable_store",
"//dragnn/runtime/math:types",
"//dragnn/runtime/test:network_test_base",
"//syntaxnet:base",
"@org_tensorflow//tensorflow/core:lib",
"@org_tensorflow//tensorflow/core:test",
"@sling//sling/file",
"@sling//sling/file:posix",
"@sling//sling/myelin:flow",
],
)
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// =============================================================================
// Implementation note: This file contains branched versions of functions from
// tensorflow/core/framework/attr_value_util.cc. These functions are branched
// to prevent changes in their behavior from impacting the Myelin conversion.
#include "dragnn/runtime/myelin/attr_value_utils.h"
#include <algorithm>
#include <vector>
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/tensor_shape.pb.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/platform/protobuf.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
using ::tensorflow::AttrValue;
using ::tensorflow::NameAttrList;
using ::tensorflow::PartialTensorShape;
using ::tensorflow::StringPiece;
using ::tensorflow::Tensor;
using ::tensorflow::TensorProto;
using ::tensorflow::TensorShape;
namespace strings = ::tensorflow::strings;
namespace str_util = ::tensorflow::str_util;
string SummarizeString(const string &str) {
string escaped = str_util::CEscape(str);
// If the string is long, replace the middle with ellipses.
constexpr int kMaxStringSummarySize = 80;
if (escaped.size() >= kMaxStringSummarySize) {
StringPiece prefix(escaped);
StringPiece suffix = prefix;
prefix.remove_suffix(escaped.size() - 10);
suffix.remove_prefix(escaped.size() - 10);
return strings::StrCat("\"", prefix, "...", suffix, "\"");
} else {
return strings::StrCat("\"", escaped, "\"");
}
}
string SummarizeTensor(const TensorProto &tensor_proto) {
Tensor t;
if (!t.FromProto(tensor_proto)) return "<Invalid TensorProto>";
return t.DebugString();
}
string SummarizeFunc(const NameAttrList &func) {
std::vector<string> entries;
for (auto p : func.attr()) {
entries.push_back(
strings::StrCat(p.first, "=", AttrValueToString(p.second)));
}
std::sort(entries.begin(), entries.end());
return strings::StrCat(func.name(), "[", str_util::Join(entries, ", "), "]");
}
} // namespace
string AttrValueToString(const AttrValue &attr_value) {
switch (attr_value.value_case()) {
case AttrValue::kS:
return SummarizeString(attr_value.s());
case AttrValue::kI:
return strings::StrCat(attr_value.i());
case AttrValue::kF:
return strings::StrCat(attr_value.f());
case AttrValue::kB:
return attr_value.b() ? "true" : "false";
case AttrValue::kType:
return DataType_Name(attr_value.type());
case AttrValue::kShape:
return PartialTensorShape::DebugString(attr_value.shape());
case AttrValue::kTensor:
return SummarizeTensor(attr_value.tensor());
case AttrValue::kList: {
std::vector<string> pieces;
if (attr_value.list().s_size() > 0) {
for (int i = 0; i < attr_value.list().s_size(); ++i) {
pieces.push_back(SummarizeString(attr_value.list().s(i)));
}
} else if (attr_value.list().i_size() > 0) {
for (int i = 0; i < attr_value.list().i_size(); ++i) {
pieces.push_back(strings::StrCat(attr_value.list().i(i)));
}
} else if (attr_value.list().f_size() > 0) {
for (int i = 0; i < attr_value.list().f_size(); ++i) {
pieces.push_back(strings::StrCat(attr_value.list().f(i)));
}
} else if (attr_value.list().b_size() > 0) {
for (int i = 0; i < attr_value.list().b_size(); ++i) {
pieces.push_back(attr_value.list().b(i) ? "true" : "false");
}
} else if (attr_value.list().type_size() > 0) {
for (int i = 0; i < attr_value.list().type_size(); ++i) {
pieces.push_back(DataType_Name(attr_value.list().type(i)));
}
} else if (attr_value.list().shape_size() > 0) {
for (int i = 0; i < attr_value.list().shape_size(); ++i) {
pieces.push_back(
TensorShape::DebugString(attr_value.list().shape(i)));
}
} else if (attr_value.list().tensor_size() > 0) {
for (int i = 0; i < attr_value.list().tensor_size(); ++i) {
pieces.push_back(SummarizeTensor(attr_value.list().tensor(i)));
}
} else if (attr_value.list().func_size() > 0) {
for (int i = 0; i < attr_value.list().func_size(); ++i) {
pieces.push_back(SummarizeFunc(attr_value.list().func(i)));
}
}
return strings::StrCat("[", str_util::Join(pieces, ", "), "]");
}
case AttrValue::kFunc: {
return SummarizeFunc(attr_value.func());
}
case AttrValue::kPlaceholder:
return strings::StrCat("$", attr_value.placeholder());
case AttrValue::VALUE_NOT_SET:
return "<Unknown AttrValue type>";
}
return "<Unknown AttrValue type>"; // Prevent missing return warning
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// =============================================================================
// Utils for working with tensorflow.AttrValue protos.
#ifndef DRAGNN_RUNTIME_MYELIN_ATTR_VALUE_UTILS_H_
#define DRAGNN_RUNTIME_MYELIN_ATTR_VALUE_UTILS_H_
#include <string>
#include "syntaxnet/base.h"
#include "tensorflow/core/framework/attr_value.pb.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Returns a string representation of the |attr_value|. This is similar to
// tensorflow::SummarizeAttrValue(), but never elides or abbreviates.
string AttrValueToString(const tensorflow::AttrValue &attr_value);
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_MYELIN_ATTR_VALUE_UTILS_H_
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// =============================================================================
// NB: These tests don't assert on dtypes, shapes, or tensors, because those are
// just calls to TF library functions. (I.e., don't test someone else's API).
#include "dragnn/runtime/myelin/attr_value_utils.h"
#include <string>
#include "dragnn/core/test/generic.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/lib/strings/numbers.h"
#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Tests that singular attributes are stringified correctly.
TEST(AttrValueToStringTest, Singular) {
{
tensorflow::AttrValue attr_value;
attr_value.set_s("foo");
EXPECT_EQ(AttrValueToString(attr_value), "\"foo\"");
}
{
tensorflow::AttrValue attr_value;
attr_value.set_i(123);
EXPECT_EQ(AttrValueToString(attr_value), "123");
}
{
tensorflow::AttrValue attr_value;
attr_value.set_f(-1.5);
EXPECT_EQ(AttrValueToString(attr_value), "-1.5");
}
{
tensorflow::AttrValue attr_value;
attr_value.set_b(false);
EXPECT_EQ(AttrValueToString(attr_value), "false");
attr_value.set_b(true);
EXPECT_EQ(AttrValueToString(attr_value), "true");
}
}
// Tests that list attributes are stringified correctly.
TEST(AttrValueToStringTest, List) {
{
tensorflow::AttrValue attr_value;
attr_value.mutable_list()->add_s("foo");
attr_value.mutable_list()->add_s("bar");
attr_value.mutable_list()->add_s("baz");
EXPECT_EQ(AttrValueToString(attr_value), "[\"foo\", \"bar\", \"baz\"]");
}
{
tensorflow::AttrValue attr_value;
attr_value.mutable_list()->add_i(123);
attr_value.mutable_list()->add_i(-45);
attr_value.mutable_list()->add_i(6789);
EXPECT_EQ(AttrValueToString(attr_value), "[123, -45, 6789]");
}
{
tensorflow::AttrValue attr_value;
attr_value.mutable_list()->add_f(-1.5);
attr_value.mutable_list()->add_f(0.25);
attr_value.mutable_list()->add_f(3.5);
EXPECT_EQ(AttrValueToString(attr_value), "[-1.5, 0.25, 3.5]");
}
{
tensorflow::AttrValue attr_value;
attr_value.mutable_list()->add_b(false);
attr_value.mutable_list()->add_b(true);
attr_value.mutable_list()->add_b(false);
EXPECT_EQ(AttrValueToString(attr_value), "[false, true, false]");
}
}
} // namespace
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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.
# =============================================================================
"""Build rules that restrict Myelin to supported environments.
As of this writing, SLING requires Linux and x86-64:
https://github.com/google/sling/blob/master/README.md#building
The technique used here is to replace the hdrs, srcs, and deps with appropriate
empty content when building in an unsupported environment.
"""
load(
"//dragnn/runtime:multiarch.bzl",
"dragnn_cc_multiarch_library",
"dragnn_cc_multiarch_test",
)
def _if_supported(consequent, alternative=[]):
"""Returns the |consequent| iff the build environment supports Myelin."""
return select({
"@org_tensorflow//tensorflow:linux_x86_64": consequent,
"//conditions:default": alternative,
})
def _if_supported_test_deps(deps):
"""Like _if_supported, but returns appropriate fallback deps for a test."""
return _if_supported(deps, ["//syntaxnet:test_main"])
def dragnn_myelin_cc_library(hdrs=[], srcs=[], deps=[], **kwargs):
"""Like cc_library, but reduces to a NOP in unsupported environments."""
native.cc_library(
hdrs = _if_supported(hdrs),
srcs = _if_supported(srcs),
deps = _if_supported(deps),
**kwargs)
def dragnn_myelin_cc_test(srcs=[], deps=[], **kwargs):
"""Like cc_test, but reduces to a NOP in unsupported environments."""
native.cc_test(
srcs = _if_supported(srcs),
deps = _if_supported_test_deps(deps),
**kwargs)
# Implementation note: Bazel select()s are not resolved at the time that build
# rules are evaluated. If we pass _if_supported(deps) into the multi-arch build
# rules (like we do for the native rules above), then the multi-arch rules break
# when they attempt to iterate over the deps---at that point, the deps are an
# unresolved select() that can't be iterated. To get around this, we delay the
# select() by passing _if_supported into the multi-arch rule, which will apply
# it just before passing the deps to the native rule.
def dragnn_myelin_cc_multiarch_library(hdrs=[], srcs=[], **kwargs):
"""Multi-arch version of dragnn_myelin_cc_library."""
dragnn_cc_multiarch_library(
hdrs = _if_supported(hdrs),
srcs = _if_supported(srcs),
deps_transformer = _if_supported,
**kwargs)
def dragnn_myelin_cc_multiarch_test(srcs=[], **kwargs):
"""Multi-arch version of dragnn_myelin_cc_test."""
dragnn_cc_multiarch_test(
srcs = _if_supported(srcs, []),
deps_transformer = _if_supported_test_deps,
**kwargs)
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 "dragnn/runtime/myelin/myelin_cell_converter.h"
#include <stddef.h>
#include <algorithm>
#include <limits>
#include "dragnn/runtime/myelin/attr_value_utils.h"
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/node_def_util.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor.pb.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/tensor_shape.pb.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/strings/numbers.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/cpu_info.h"
#include "tensorflow/core/platform/logging.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Returns true if the |tensor_name| denotes a control dependency.
bool IsControlDependency(const string &tensor_name) {
return tensor_name[0] == '^';
}
// Returns true if the |node| is a TF variable.
bool IsVariableNode(const tensorflow::NodeDef &node) {
return node.op() == "VariableV2";
}
// Returns true if the |node| is a tf.constant().
bool IsConstantNode(const tensorflow::NodeDef &node) {
return node.op() == "Const";
}
// Returns true if the |node| is a tf.placeholder().
bool IsPlaceholderNode(const tensorflow::NodeDef &node) {
return node.op() == "Placeholder";
}
// Sets |max_value| to |value| if it is lesser.
void UpdateMax(uint32 value, uint32 *max_value) {
*max_value = std::max(*max_value, value);
}
// Loads the |tensor| from the constant |node|. On error, returns non-OK.
tensorflow::Status GetConstantTensor(const tensorflow::NodeDef &node,
tensorflow::Tensor *tensor) {
DCHECK(IsConstantNode(node));
return tensorflow::GetNodeAttr(node, "value", tensor);
}
// Loads the |shape| from the placeholder |node|. On error, returns non-OK.
tensorflow::Status GetPlaceholderShape(const tensorflow::NodeDef &node,
tensorflow::TensorShape *shape) {
DCHECK(IsPlaceholderNode(node));
return tensorflow::GetNodeAttr(node, "shape", shape);
}
// Returns the dtype string associated with the |node|, or an empty string if it
// cannot be inferred.
string GetDType(const tensorflow::NodeDef &node) {
tensorflow::DataType dtype;
tensorflow::Status status = tensorflow::GetNodeAttr(node, "T", &dtype);
if (!status.ok()) status = tensorflow::GetNodeAttr(node, "dtype", &dtype);
if (status.ok()) return tensorflow::DataTypeString(dtype);
return string();
}
// Modifies the |dtype| into a reference type.
void MarkAsReferenceDType(string *dtype) {
DCHECK_NE((*dtype)[0], '&');
*dtype = tensorflow::strings::StrCat("&", *dtype);
}
// Loads the CellSubgraphSpec for the component named |component_name| from the
// |trained_model| into the |spec|. On error, returns non-OK.
tensorflow::Status LoadCellSubgraphSpec(const string &component_name,
const TrainedModel &trained_model,
CellSubgraphSpec *spec) {
const string tensor_name =
tensorflow::strings::StrCat(component_name, "/EXPORT/CellSubgraphSpec");
tensorflow::Tensor tensor;
TF_RETURN_IF_ERROR(trained_model.EvaluateTensor(tensor_name, &tensor));
if (!spec->ParseFromString(tensor.scalar<string>()())) {
return tensorflow::errors::InvalidArgument(
"Failed to parse CellSubgraphSpec for component ", component_name);
}
VLOG(1) << tensor_name << " = \n" << spec->DebugString();
return tensorflow::Status::OK();
}
} // namespace
// Writer for incrementally building a Flow file.
// https://github.com/google/sling/tree/master/myelin#flow-file-format
class MyelinCellConverter::Writer {
public:
// TODO(googleuser): Add templated Write() methods and coerce typed data into
// little-endian format, so this doesn't need to run on a little-endian CPU.
static_assert(tensorflow::port::kLittleEndian,
"Flow files must be written in little-endian format");
// Creates a writer that overwrites |flow|.
explicit Writer(string *flow) : flow_(CHECK_NOTNULL(flow)) {
flow_->clear();
Write("flow", 4); // magic number
WriteInt32(4); // version
}
// Appends [|data|,|data|+|size|) to the Flow file.
void Write(const void *data, size_t size) {
flow_->append(reinterpret_cast<const char *>(data), size);
}
// Appends the |value| to the Flow file.
void WriteInt32(int32 value) { Write(&value, sizeof(int32)); }
void WriteUint64(uint64 value) { Write(&value, sizeof(uint64)); }
// Writes the |str| to the Flow file as a length-prefixed string.
void WriteString(const string &str) {
DCHECK_LE(str.size(), std::numeric_limits<int32>::max());
WriteInt32(str.size());
Write(str.data(), str.size());
}
private:
// Flow file content.
string *const flow_;
};
tensorflow::Status MyelinCellConverter::Convert(
const string &component_name, const TrainedModel &trained_model,
string *flow) {
return MyelinCellConverter().ConvertImpl(component_name, trained_model, flow);
}
tensorflow::Status MyelinCellConverter::ConvertImpl(
const string &component_name, const TrainedModel &trained_model,
string *flow) {
component_name_ = component_name;
trained_model_ = &trained_model;
CellSubgraphSpec spec;
TF_RETURN_IF_ERROR(
LoadCellSubgraphSpec(component_name_, *trained_model_, &spec));
TF_RETURN_IF_ERROR(BuildInputsAndOutputs(spec));
TF_RETURN_IF_ERROR(BuildOperations());
Writer writer(flow);
TF_RETURN_IF_ERROR(WriteVariables(&writer));
WriteOperations(&writer);
WriteFunctions(&writer);
WriteConnectors(&writer);
WriteBlobs(&writer);
return tensorflow::Status::OK();
}
tensorflow::Status MyelinCellConverter::BuildInputsAndOutputs(
const CellSubgraphSpec &spec) {
std::set<string> unique_input_names;
for (const CellSubgraphSpec::Input &input : spec.input()) {
if (!unique_input_names.insert(input.name()).second) {
return tensorflow::errors::InvalidArgument(
"Duplicate input name { ", input.ShortDebugString(), " }");
}
TensorId tensor_id;
TF_RETURN_IF_ERROR(ParseTensorId(input.tensor(), &tensor_id));
if (inputs_.find(tensor_id) != inputs_.end()) {
return tensorflow::errors::InvalidArgument(
"Duplicate input variable { ", input.ShortDebugString(),
" }; currently has name '", inputs_[tensor_id], "'");
}
inputs_[tensor_id] = input.name();
}
std::set<string> unique_output_names;
for (const CellSubgraphSpec::Output &output : spec.output()) {
if (!unique_output_names.insert(output.name()).second) {
return tensorflow::errors::InvalidArgument(
"Duplicate output name { ", output.ShortDebugString(), " }");
}
TensorId tensor_id;
TF_RETURN_IF_ERROR(ParseTensorId(output.tensor(), &tensor_id));
outputs_[tensor_id].insert(output.name());
}
// Check that recurrent inputs match the name of an output.
for (const CellSubgraphSpec::Input &input : spec.input()) {
if (input.type() != CellSubgraphSpec::Input::TYPE_RECURRENT) continue;
if (unique_output_names.find(input.name()) == unique_output_names.end()) {
return tensorflow::errors::InvalidArgument(
"Recurrent input does not match any output { ",
input.ShortDebugString(), " }");
}
}
return tensorflow::Status::OK();
}
tensorflow::Status MyelinCellConverter::BuildOperations() {
// Extract sets of input and output node names.
std::set<string> input_node_names;
std::set<string> output_node_names;
for (const auto &it : inputs_) input_node_names.insert(it.first.first);
for (const auto &it : outputs_) output_node_names.insert(it.first.first);
// Set of nodes that have already been visited by the DFS.
std::set<string> visited;
// DFS backwards from output nodes to input nodes and collect operations.
std::vector<string> stack(output_node_names.begin(), output_node_names.end());
while (!stack.empty()) {
const string name = stack.back();
stack.pop_back();
if (!visited.insert(name).second) continue; // already visited; skip
const tensorflow::NodeDef *node = nullptr;
TF_RETURN_IF_ERROR(trained_model_->LookupNode(name, &node));
Operation &operation = operations_[name];
if (operation.node != nullptr && operation.node != node) {
return tensorflow::errors::Internal("Inconsistent nodes for operation ",
name, " (", operation.node->name(),
" vs ", node->name());
}
operation.node = node;
// Function inputs bound the search; don't expand them.
if (input_node_names.find(name) != input_node_names.end()) continue;
// Expand (non-control) inputs.
for (const string &input_name : node->input()) {
if (IsControlDependency(input_name)) continue;
VLOG(1) << name << " has input " << input_name;
TensorId tensor_id;
TF_RETURN_IF_ERROR(ParseTensorId(input_name, &tensor_id));
stack.push_back(tensor_id.first);
// Add the input tensor and register the output index on the input op.
operation.inputs.push_back(AsVariableName(tensor_id));
UpdateMax(tensor_id.second + 1,
&operations_[tensor_id.first].num_outputs);
}
}
// Register output indices for the |outputs_|; the DFS does not cover these.
for (const auto &it : outputs_) {
const TensorId &tensor_id = it.first;
UpdateMax(tensor_id.second + 1, &operations_[tensor_id.first].num_outputs);
}
// Sanity check: All operations must have nodes and outputs.
for (const auto &it : operations_) {
const Operation &operation = it.second;
DCHECK(operation.node != nullptr);
DCHECK_GT(operation.num_outputs, 0);
}
return tensorflow::Status::OK();
}
tensorflow::Status MyelinCellConverter::WriteVariables(Writer *writer) const {
int num_variables = 0;
for (const auto &it : operations_) num_variables += it.second.num_outputs;
writer->WriteInt32(num_variables);
for (const auto &it : operations_) {
const Operation &operation = it.second;
for (uint32 output = 0; output < operation.num_outputs; ++output) {
TF_RETURN_IF_ERROR(WriteVariable(*operation.node, output, writer));
}
}
return tensorflow::Status::OK();
}
tensorflow::Status MyelinCellConverter::WriteVariable(
const tensorflow::NodeDef &node, uint32 output_index,
Writer *writer) const {
const TensorId tensor_id(node.name(), output_index);
const string name = AsVariableName(tensor_id);
const std::set<string> aliases = GetAliases(tensor_id);
// Only cell inputs and outputs have aliases.
const bool is_cell_input_or_output = !aliases.empty();
// Treat cell inputs and outputs as references, so they can be pointed at
// pieces of memory managed by the DRAGNN runtime.
string dtype = GetDType(node);
if (is_cell_input_or_output) MarkAsReferenceDType(&dtype);
// Extract variable data and shape, if available. Myelin treats a 0-element
// shape (e.g., [0], [1, 0, 2]) as undefined and will infer shapes for such
// variables, so we ensure that the shape is undefined unless explicitly set.
tensorflow::Tensor tensor;
tensorflow::TensorShape shape({0}); // undefined by default
if (IsConstantNode(node)) {
TF_RETURN_IF_ERROR(GetConstantTensor(node, &tensor));
shape = tensor.shape();
} else if (IsVariableNode(node)) {
TF_RETURN_IF_ERROR(trained_model_->EvaluateTensor(name, &tensor));
shape = tensor.shape();
} else if (IsPlaceholderNode(node)) {
TF_RETURN_IF_ERROR(GetPlaceholderShape(node, &shape));
}
const tensorflow::StringPiece data = tensor.tensor_data();
writer->WriteString(name);
writer->WriteInt32(aliases.size());
for (const string &alias : aliases) writer->WriteString(alias);
writer->WriteString(dtype);
writer->WriteInt32(shape.dims());
for (int i = 0; i < shape.dims(); ++i) writer->WriteInt32(shape.dim_size(i));
writer->WriteUint64(data.size());
writer->Write(data.data(), data.size());
return tensorflow::Status::OK();
}
std::set<string> MyelinCellConverter::GetAliases(
const TensorId &tensor_id) const {
std::set<string> aliases;
const auto input_it = inputs_.find(tensor_id);
if (input_it != inputs_.end()) {
const string &name = input_it->second;
aliases.insert(tensorflow::strings::StrCat("INPUT/", name));
}
const auto output_it = outputs_.find(tensor_id);
if (output_it != outputs_.end()) {
for (const string &name : output_it->second) {
aliases.insert(tensorflow::strings::StrCat("OUTPUT/", name));
}
}
return aliases;
}
void MyelinCellConverter::WriteOperations(Writer *writer) const {
writer->WriteInt32(operations_.size());
for (const auto &it : operations_) {
const Operation &operation = it.second;
WriteOperation(operation, writer);
}
}
void MyelinCellConverter::WriteOperation(const Operation &operation,
Writer *writer) const {
const string &name = operation.node->name();
const string &type = operation.node->op();
// Create one output per possible output index, in order.
std::vector<string> outputs;
for (uint32 output = 0; output < operation.num_outputs; ++output) {
outputs.push_back(AsVariableName(TensorId(name, output)));
}
// Copy the attrs to a sorted map for deterministic ordering.
std::map<string, tensorflow::AttrValue> attrs(operation.node->attr().begin(),
operation.node->attr().end());
writer->WriteString(name);
writer->WriteString(type);
writer->WriteInt32(operation.inputs.size());
for (const string &input : operation.inputs) writer->WriteString(input);
writer->WriteInt32(outputs.size());
for (const string &output : outputs) writer->WriteString(output);
writer->WriteInt32(attrs.size());
for (const auto &it : attrs) {
writer->WriteString(it.first);
writer->WriteString(AttrValueToString(it.second));
}
}
void MyelinCellConverter::WriteFunctions(Writer *writer) const {
writer->WriteInt32(1);
writer->WriteString(component_name_);
writer->WriteInt32(operations_.size());
for (const auto &it : operations_) writer->WriteString(it.first);
}
void MyelinCellConverter::WriteConnectors(Writer *writer) const {
writer->WriteInt32(0);
}
void MyelinCellConverter::WriteBlobs(Writer *writer) const {
writer->WriteInt32(0);
}
tensorflow::Status MyelinCellConverter::ParseTensorId(const string &tensor_name,
TensorId *tensor_id) {
if (IsControlDependency(tensor_name)) {
return tensorflow::errors::InvalidArgument(
"Cannot parse tensor ID from control dependency '", tensor_name, "'");
}
const auto colon_index = tensor_name.rfind(':');
// NB: If |colon_index| is string::npos, takes the whole string as desired.
tensor_id->first = tensor_name.substr(0, colon_index);
if (colon_index == string::npos) { // no colon; assume 0
tensor_id->second = 0;
} else {
const string output_str = tensor_name.substr(colon_index + 1);
if (!tensorflow::strings::safe_strtou32(output_str, &tensor_id->second)) {
return tensorflow::errors::InvalidArgument("Malformed tensor name ",
tensor_name);
}
}
return tensorflow::Status::OK();
}
string MyelinCellConverter::AsVariableName(const TensorId &tensor_id) {
if (tensor_id.second == 0) return tensor_id.first;
return tensorflow::strings::StrCat(tensor_id.first, ":", tensor_id.second);
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment