Commit edea2b67 authored by Terry Koo's avatar Terry Koo
Browse files

Remove runtime because reasons.

parent a4bb31d0
This diff is collapsed.
// 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.
// =============================================================================
// Definitions of activation functions for neural netowrks.
#ifndef DRAGNN_RUNTIME_ACTIVATION_FUNCTIONS_H_
#define DRAGNN_RUNTIME_ACTIVATION_FUNCTIONS_H_
#include "dragnn/runtime/math/arithmetic.h"
#include "dragnn/runtime/math/types.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Possible types of activation functions.
//
// TODO(googleuser): If many activation functions are added, or if functions start
// using configuration parameters (e.g., leakiness of a leaky ReLU), then switch
// to a registered class.
enum class ActivationFunction {
kIdentity, // pass-through, useful for classification logits
kRelu, // ReLU; i.e., max(0,x)
};
// Applies the |activation_function| to the |values|.
template <class T>
void ApplyActivationFunction(ActivationFunction activation_function,
MutableVector<T> values);
// Implementation details below.
template <class T>
void ApplyActivationFunction(ActivationFunction activation_function,
MutableVector<T> values) {
switch (activation_function) {
case ActivationFunction::kIdentity:
break;
case ActivationFunction::kRelu:
MaxElements(T(), values);
break;
}
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_ACTIVATION_FUNCTIONS_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/activation_functions.h"
#include "dragnn/runtime/math/types.h"
#include "dragnn/runtime/test/helpers.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Tests that kIdentity is a pass-through.
TEST(ActivationFunctionsTest, ApplyIdentity) {
UniqueVector<float> values({1.25f, -1.5f, 0.0f, 0.0625f, -0.03125});
ApplyActivationFunction(ActivationFunction::kIdentity, *values);
EXPECT_EQ((*values)[0], 1.25);
EXPECT_EQ((*values)[1], -1.5);
EXPECT_EQ((*values)[2], 0.0);
EXPECT_EQ((*values)[3], 0.0625);
EXPECT_EQ((*values)[4], -0.03125);
}
// Tests that kRelu clips to zero.
TEST(ActivationFunctionsTest, ApplyRelu) {
UniqueVector<float> values({1.25f, -1.5f, 0.0f, 0.0625f, -0.03125});
ApplyActivationFunction(ActivationFunction::kRelu, *values);
EXPECT_EQ((*values)[0], 1.25);
EXPECT_EQ((*values)[1], 0.0); // clipped
EXPECT_EQ((*values)[2], 0.0); // boundary
EXPECT_EQ((*values)[3], 0.0625);
EXPECT_EQ((*values)[4], 0.0); // clipped
}
} // 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.
// =============================================================================
// Utils for working with aligned memory blocks. The DRAGNN runtime requires
// aligned memory for use in vectorized math. Do not rely on any particular
// value of the alignment requirement, because it will vary over time and in
// different build configurations.
#ifndef DRAGNN_RUNTIME_ALIGNMENT_H_
#define DRAGNN_RUNTIME_ALIGNMENT_H_
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <memory>
#include <type_traits>
#include <vector>
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/platform/logging.h"
// This is a type that has some private methods (so non-POD), but is known to be
// trivially-deconstructable. Ergo we add some special handling so
// IsAlignable<bfloat16> returns true.
namespace tensorflow {
struct bfloat16;
}
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Returns true if |T| can be used in an aligned memory block.
template <class T>
constexpr bool IsAlignable();
// Returns OK iff the |pointer| satisfies the alignment requirement.
tensorflow::Status OkIfAligned(const void *pointer);
// Returns the next alignment boundary at or after the |byte_offset|.
size_t PadToAlignment(size_t byte_offset);
// As above, but for pointers.
template <class T>
T *PadToAlignment(T *pointer);
// Returns the number of bytes required to store a sequence of |num_arrays|
// aligned arrays of |array_size| bytes, including alignment padding. See
// (Mutable)AlignedArea below.
size_t ComputeAlignedAreaSize(size_t num_arrays, size_t array_size);
// Returns the number of bytes required to store a sequence of byte arrays of
// the given |sizes|, including alignment padding after each array.
size_t ComputeTotalBytesWithAlignmentPadding(const std::vector<size_t> &sizes);
// Forward-declared for friendship below.
class Operands;
class UniqueAlignedArray;
enum class BlockedMatrixFormat;
namespace internal {
// A non-owning view of an aligned byte array. Templated so const and mutable
// versions can share implementation. Do not use this class directly, instead
// use (Mutable)AlignedView below.
template <class Byte>
class AlignedViewImpl {
public:
static_assert(sizeof(Byte) == 1, "Byte must be byte-sized");
// Creates an empty view.
AlignedViewImpl() = default;
// Points this at the same bytes as |that|, possibly reinterpreting type.
template <class OtherByte>
explicit AlignedViewImpl(AlignedViewImpl<OtherByte> that);
template <class OtherByte>
AlignedViewImpl &operator=(AlignedViewImpl<OtherByte> that);
// Points this at [|data|,|data|+|size|). On error, returns non-OK and
// modifies nothing.
tensorflow::Status Reset(Byte *data, size_t size);
// Splits this into a list of |views| of the |sizes|, possibly reinterpreting
// type. The |views| need not completely cover all bytes of this. Requires
// that this spans ComputeTotalBytesWithAlignmentPadding(|sizes|) bytes. On
// error, returns non-OK and modifies nothing.
template <class OtherByte>
tensorflow::Status Split(
const std::vector<size_t> &sizes,
std::vector<AlignedViewImpl<OtherByte>> *views) const;
// Accessors.
Byte *data() const { return data_; }
size_t size() const { return size_; }
bool empty() const { return size() == 0; }
private:
template <class OtherByte>
friend class AlignedViewImpl;
template <class OtherByte>
friend class AlignedAreaImpl;
friend Operands;
friend UniqueAlignedArray;
// Directly creates an aligned view, bypassing alignment checks.
AlignedViewImpl(Byte *data, size_t size);
// Pointer to the start of the view.
Byte *data_ = nullptr;
// Number of bytes in the view.
size_t size_ = 0;
};
// A non-owning view of an aligned, 2-dimensional byte array. Templated so
// const and mutable versons can share implementation. Do not use this class
// directly, instead use (Mutable)AlignedArea below.
template <class Byte>
class AlignedAreaImpl {
public:
static_assert(sizeof(Byte) == 1, "Byte must be byte-sized");
// Creates an empty area.
AlignedAreaImpl() = default;
// Points this at the same bytes as |that|, possibly reinterpreting type.
template <class OtherByte>
explicit AlignedAreaImpl(AlignedAreaImpl<OtherByte> that);
template <class OtherByte>
AlignedAreaImpl &operator=(AlignedAreaImpl<OtherByte> that);
// Resets this to a sequence of |num_views| aligned sub-views of the |view|,
// each |view_size| bytes wide. The first sub-view covers [0,|view_size|) of
// |view|, and each subsequent sub-view starts at the next alignment boundary.
// Requires that |view| spans ComputeAlignedAreaSize(|num_views|,|view_size|)
// bytes or more. On error, returns non-OK and modifies nothing.
template <class OtherByte>
tensorflow::Status Reset(AlignedViewImpl<OtherByte> view, size_t num_views,
size_t view_size);
// Accessors.
AlignedViewImpl<Byte> view(size_t index) const;
Byte *data() const { return data_; }
size_t num_views() const { return num_views_; }
size_t view_size() const { return view_size_; }
size_t view_stride() const { return view_stride_; }
bool empty() const { return num_views() == 0; }
private:
template <class OtherByte>
friend class AlignedAreaImpl;
friend Operands;
// Directly creates an aligned view, bypassing alignment checks.
AlignedAreaImpl(Byte *data, size_t num_views, size_t view_size,
size_t view_stride);
// Pointer to the start of the first view.
Byte *data_ = nullptr;
// Number of views in the area.
size_t num_views_ = 0;
// Size of each view in bytes, excluding alignment padding.
size_t view_size_ = 0;
// Number of bytes between the starts of consecutive views. NB: This is not
// necessarily equal to PadToAlignment(|view_size_|).
size_t view_stride_ = 0;
};
} // namespace internal
// Public aliases; use these.
using AlignedView = internal::AlignedViewImpl<const char>;
using AlignedArea = internal::AlignedAreaImpl<const char>;
using MutableAlignedView = internal::AlignedViewImpl<char>;
using MutableAlignedArea = internal::AlignedAreaImpl<char>;
// A uniquely-owned aligned byte array.
class UniqueAlignedArray {
public:
// Creates an empty byte array.
UniqueAlignedArray() = default;
// Reallocates this to |new_size| bytes, and discards the current byte array.
// Contents are uninitialized.
void Reset(size_t new_size);
// Like Reset(), but only reallocates if |new_size| is more than the current
// capacity. NB: Does not preserve current content when reallocation occurs;
// use Resize() if that is desired.
void Reserve(size_t new_size);
// Resizes this to contain |new_size| bytes, preserving current content. If
// |new_size| exceeds the current size, the added bytes are uninitialized. If
// |new_size| exceeds the current capacity, reallocates, and copies current
// content. Returns true if reallocation occurred.
bool Resize(size_t new_size);
// Returns the aligned byte array.
MutableAlignedView view() const { return view_; }
private:
// Underlying byte array, which is padded for alignment.
std::unique_ptr<char[]> padded_array_;
// Size of the aligned portion of |padded_array_|.
size_t capacity_ = 0;
// Active range of the |storage_|.
MutableAlignedView view_;
};
// Implementation details below.
namespace internal {
// Required alignment for memory blocks. Only the runtime framework should use
// this; otherwise, DO NOT access or otherwise depend on this value.
enum : size_t { kAlignmentBytes = 32 };
} // namespace internal
template <class T>
constexpr bool IsAlignable() {
// Either T is divisible into alignment windows, or an alignment window is
// divisible into Ts. Likewise for T's alignment requirement. Finally, T
// must be POD because we won't call its constructor or destructor.
return (sizeof(T) % internal::kAlignmentBytes == 0 ||
internal::kAlignmentBytes % sizeof(T) == 0) &&
(alignof(T) % internal::kAlignmentBytes == 0 ||
internal::kAlignmentBytes % alignof(T) == 0) &&
(std::is_pod<T>::value ||
std::is_same<T, tensorflow::bfloat16>::value);
}
inline tensorflow::Status OkIfAligned(const void *pointer) {
const uintptr_t address = reinterpret_cast<uintptr_t>(pointer);
if (address % internal::kAlignmentBytes != 0) {
return tensorflow::errors::InvalidArgument(
"Pointer fails alignment requirement: ", address, " vs required ",
internal::kAlignmentBytes);
}
return tensorflow::Status::OK();
}
inline size_t PadToAlignment(size_t byte_offset) {
// Round up to the next alignment boundary by incrementing by a certain amount
// and then rounding down. Note that the bitmask clears the low-order bits of
// the offset, effectively rounding down to the previous alignment boundary.
return (byte_offset + internal::kAlignmentBytes - 1) &
~(internal::kAlignmentBytes - 1);
}
template <class T>
T *PadToAlignment(T *pointer) {
static_assert(IsAlignable<T>(), "T is not alignable");
uintptr_t address = reinterpret_cast<uintptr_t>(pointer);
address = (address + internal::kAlignmentBytes - 1) &
~(internal::kAlignmentBytes - 1);
return reinterpret_cast<T *>(address);
}
inline size_t ComputeAlignedAreaSize(size_t num_arrays, size_t array_size) {
return num_arrays * PadToAlignment(array_size);
}
inline size_t ComputeTotalBytesWithAlignmentPadding(
const std::vector<size_t> &sizes) {
size_t total = 0;
for (const size_t size : sizes) total += PadToAlignment(size);
return total;
}
namespace internal {
template <class Byte>
template <class OtherByte>
AlignedViewImpl<Byte>::AlignedViewImpl(AlignedViewImpl<OtherByte> that)
: data_(reinterpret_cast<Byte *>(that.data())), size_(that.size()) {}
template <class Byte>
template <class OtherByte>
AlignedViewImpl<Byte> &AlignedViewImpl<Byte>::operator=(
AlignedViewImpl<OtherByte> that) {
data_ = reinterpret_cast<Byte *>(that.data());
size_ = that.size();
return *this;
}
template <class Byte>
tensorflow::Status AlignedViewImpl<Byte>::Reset(Byte *data, size_t size) {
TF_RETURN_IF_ERROR(OkIfAligned(data));
// Success; make modifications.
data_ = data;
size_ = size;
return tensorflow::Status::OK();
}
template <class Byte>
template <class OtherByte>
tensorflow::Status AlignedViewImpl<Byte>::Split(
const std::vector<size_t> &sizes,
std::vector<AlignedViewImpl<OtherByte>> *views) const {
const size_t total_bytes = ComputeTotalBytesWithAlignmentPadding(sizes);
if (size() < total_bytes) {
return tensorflow::errors::InvalidArgument(
"View is too small to be split into sizes [",
tensorflow::str_util::Join(sizes, ", "), "]: need ", total_bytes,
" bytes but have ", size(), " bytes");
}
// Success; make modifications.
views->clear();
views->reserve(sizes.size());
Byte *base = data();
for (const size_t size : sizes) {
views->push_back(AlignedViewImpl<OtherByte>(base, size));
base = PadToAlignment(base + size);
}
DCHECK_EQ(base - data(), total_bytes);
return tensorflow::Status::OK();
}
template <class Byte>
AlignedViewImpl<Byte>::AlignedViewImpl(Byte *data, size_t size)
: data_(data), size_(size) {
TF_DCHECK_OK(OkIfAligned(data_));
}
template <class Byte>
template <class OtherByte>
AlignedAreaImpl<Byte>::AlignedAreaImpl(AlignedAreaImpl<OtherByte> that)
: data_(reinterpret_cast<Byte *>(that.data_)),
num_views_(that.num_views()),
view_size_(that.view_size()),
view_stride_(that.view_stride_) {}
template <class Byte>
template <class OtherByte>
AlignedAreaImpl<Byte> &AlignedAreaImpl<Byte>::operator=(
AlignedAreaImpl<OtherByte> that) {
data_ = reinterpret_cast<Byte *>(that.data_);
num_views_ = that.num_views();
view_size_ = that.view_size();
view_stride_ = that.view_stride_;
return *this;
}
template <class Byte>
template <class OtherByte>
tensorflow::Status AlignedAreaImpl<Byte>::Reset(AlignedViewImpl<OtherByte> view,
size_t num_views,
size_t view_size) {
const size_t total_bytes = ComputeAlignedAreaSize(num_views, view_size);
if (view.size() < total_bytes) {
return tensorflow::errors::InvalidArgument(
"View is too small for area of ", num_views, " views of ", view_size,
" bytes: need ", total_bytes, " bytes but got ", view.size(), " bytes");
}
// Success; make modifications.
data_ = reinterpret_cast<Byte *>(view.data());
num_views_ = num_views;
view_size_ = view_size;
view_stride_ = PadToAlignment(view_size_);
return tensorflow::Status::OK();
}
template <class Byte>
AlignedViewImpl<Byte> AlignedAreaImpl<Byte>::view(size_t index) const {
DCHECK_LT(index, num_views());
return AlignedViewImpl<Byte>(data_ + view_stride_ * index, view_size_);
}
template <class Byte>
AlignedAreaImpl<Byte>::AlignedAreaImpl(Byte *data, size_t num_views,
size_t view_size, size_t view_stride)
: data_(data),
num_views_(num_views),
view_size_(view_size),
view_stride_(view_stride) {
TF_DCHECK_OK(OkIfAligned(data_));
TF_DCHECK_OK(OkIfAligned(static_cast<const char *>(nullptr) + view_stride_));
}
} // namespace internal
inline void UniqueAlignedArray::Reset(size_t new_size) {
// Pad the |new_size| to the next alignment boundary, so the final bytes of
// the array are still in a full alignment window. E.g., if we resize to 48
// bytes with 32-byte alignment, then we allocate 64 bytes so the final 16
// bytes are still part of a full 32-byte alignment window.
const size_t aligned_size = PadToAlignment(new_size);
// To obtain an aligned address, allocate a sufficiently-padded byte array and
// find an aligned address near the start of the block.
//
// TODO(googleuser): Alternatively, we could use library functions such as
// memalign(), posix_memalign(), or aligned_alloc(), but those may not be
// present on all platforms. Consider adding some #ifs to allow use of those
// library functions when available.
padded_array_.reset(new char[aligned_size + internal::kAlignmentBytes - 1]);
capacity_ = aligned_size;
view_.size_ = new_size;
view_.data_ = PadToAlignment(padded_array_.get());
TF_DCHECK_OK(OkIfAligned(view_.data_));
}
inline void UniqueAlignedArray::Reserve(size_t new_size) {
if (new_size > capacity_) {
Reset(new_size);
} else {
view_.size_ = new_size;
}
}
inline bool UniqueAlignedArray::Resize(size_t new_size) {
// Avoid reallocation, if possible.
if (new_size <= capacity_) {
view_.size_ = new_size;
return false;
}
// Reallocate and copy. Extend the life of the old array until it is copied.
//
// Note: C realloc() can extend a byte array in place (i.e., without copying).
// Unfortunately, there is no aligned version of realloc(). Moreover, adding
// alignment padding could cause double-copying: first, when realloc() copies
// the data to the new buffer, and second, if the amount of padding required
// at the new address is not the same as before.
const std::unique_ptr<char[]> old_array = std::move(padded_array_);
const MutableAlignedView old_view = view_;
Reset(2 * new_size);
memcpy(view_.data(), old_view.data(), old_view.size());
view_.size_ = new_size;
return true;
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_ALIGNMENT_H_
This diff is collapsed.
// 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/array_variable_store.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/platform/cpu_info.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Increment this if the serialized format changes in an incompatible way that
// can't be detected through other means. For example,
// * If kAlignmentBytes is changed, then kVersion need not change because there
// is a separate field for detecting alignment mismatch.
// * If ArrayVariableStoreSpec.variable is no longer populated, perhaps replaced
// by some other approach, then kVersion should be incremented.
const uint32 ArrayVariableStore::kVersion = 0;
tensorflow::Status ArrayVariableStore::Reset(const ArrayVariableStoreSpec &spec,
AlignedView data) {
if (!spec.has_version() || !spec.has_alignment_bytes() ||
!spec.has_is_little_endian()) {
return tensorflow::errors::InvalidArgument(
"ArrayVariableStoreSpec is missing a required field: ",
spec.ShortDebugString());
}
if (spec.version() != kVersion) {
return tensorflow::errors::InvalidArgument(
"ArrayVariableStoreSpec.version (", spec.version(),
") does not match the binary (", kVersion, ")");
}
if (spec.alignment_bytes() != internal::kAlignmentBytes) {
return tensorflow::errors::InvalidArgument(
"ArrayVariableStoreSpec.alignment_bytes (", spec.alignment_bytes(),
") does not match the binary (", internal::kAlignmentBytes, ")");
}
// TODO(googleuser): It should be possible to correct an endian-ness mismatch.
// A rough outline is:
// * VariableStore::Lookup() takes an additional argument set to sizeof(T).
// * Capture sizeof(T) and write it into the VariableSpec.
// * Detect endian mismatch and byte-swap variables with multi-byte types.
if (spec.is_little_endian() != tensorflow::port::kLittleEndian) {
return tensorflow::errors::InvalidArgument(
"ArrayVariableStoreSpec.is_little_endian (", spec.is_little_endian(),
") does not match the binary (", tensorflow::port::kLittleEndian, ")");
}
for (const VariableSpec &variable_spec : spec.variable()) {
// When the proto parser encounters an unknown enumerator on the wire, it
// replaces it with the default value (i.e., FORMAT_UNKNOWN). Therefore,
// VariableSpec.format() will always return a valid enumerator.
DCHECK(VariableSpec::Format_IsValid(variable_spec.format()));
if (variable_spec.format() == VariableSpec::FORMAT_UNKNOWN) {
return tensorflow::errors::InvalidArgument(
"Unknown variable format: ", variable_spec.ShortDebugString());
}
if (variable_spec.format() == VariableSpec::FORMAT_FLAT &&
variable_spec.num_views() != 1) {
return tensorflow::errors::InvalidArgument(
"Flat variables must have 1 view: ",
variable_spec.ShortDebugString());
}
}
// Build into a temp mapping to avoid modification on error.
std::unique_ptr<std::map<Key, Value>> new_variables(
new std::map<Key, Value>());
// Slice sub-arrays off of the main byte array.
const char *base = data.data();
const char *const end = base + data.size();
for (const VariableSpec &variable_spec : spec.variable()) {
const size_t num_views = variable_spec.num_views();
const size_t view_size = variable_spec.view_size();
const size_t area_size = ComputeAlignedAreaSize(num_views, view_size);
if (base + area_size > end) {
return tensorflow::errors::InvalidArgument(
"Variable would overrun main byte array: ",
variable_spec.ShortDebugString());
}
AlignedView view;
TF_RETURN_IF_ERROR(view.Reset(base, area_size));
base += area_size; // remove claimed slice
// Set dimensions from the spec.
std::vector<size_t> dimensions(variable_spec.dimension().begin(),
variable_spec.dimension().end());
Value value(std::move(dimensions), AlignedArea());
AlignedArea &area = value.second;
TF_RETURN_IF_ERROR(area.Reset(view, num_views, view_size));
// Currently, blocked variables are meant for fast inference algorithms,
// which do not tolerate padding. Raise errors if there is padding.
if (variable_spec.format() ==
VariableSpec::FORMAT_COLUMN_BLOCKED_ROW_MAJOR_MATRIX) {
size_t padding = variable_spec.view_size() % internal::kAlignmentBytes;
if (padding != 0) {
return tensorflow::errors::Internal(
"Currently, fast matrix-vector operations do not support padded "
"blocked matrices, but variable '",
variable_spec.name(), "' has padding ", padding);
}
}
const Key key(variable_spec.name(), variable_spec.format());
if (!new_variables->emplace(key, value).second) {
return tensorflow::errors::InvalidArgument(
"Duplicate variable: ", variable_spec.ShortDebugString());
}
}
if (base != end) {
return tensorflow::errors::InvalidArgument(
"Variables do not completely cover main byte array: ", end - base,
" bytes remaining");
}
// Success; make modifications.
variables_ = std::move(new_variables);
return tensorflow::Status::OK();
}
tensorflow::Status ArrayVariableStore::Lookup(const string &name,
VariableSpec::Format format,
std::vector<size_t> *dimensions,
AlignedArea *area) {
if (!variables_) {
return tensorflow::errors::FailedPrecondition(
"ArrayVariableStore not initialized");
}
const Key key(name, format);
const auto it = variables_->find(key);
if (it == variables_->end()) {
return tensorflow::errors::NotFound(
"ArrayVariableStore has no variable with name '", name, "' and format ",
VariableSpec::Format_Name(format));
}
// Success; make modifications.
const Value &value = it->second;
*dimensions = value.first;
*area = value.second;
return tensorflow::Status::OK();
}
tensorflow::Status ArrayVariableStore::Close() {
if (!variables_) {
return tensorflow::errors::FailedPrecondition(
"ArrayVariableStore not initialized");
}
variables_.reset();
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_ARRAY_VARIABLE_STORE_H_
#define DRAGNN_RUNTIME_ARRAY_VARIABLE_STORE_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "dragnn/protos/runtime.pb.h"
#include "dragnn/runtime/alignment.h"
#include "dragnn/runtime/variable_store.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// A variable store that groups all variables into a single byte array. This
// class and its subclasses are intended for use in production.
//
// Each variable occupies a sub-array of the main byte array. The mapping from
// the name and format of a variable to the sub-array containing its content is
// defined in ArrayVariableStoreSpec. The variables may appear in any order.
//
// This format allows variables to be mapped directly into memory, which reduces
// initialization time and supports usage on-device, where mmap() is effectively
// obligatory for large data resources.
class ArrayVariableStore : public VariableStore {
public:
// Creates an uninitialized store.
ArrayVariableStore() = default;
// Resets this to represent the variables defined by the |spec| and |data|.
// The |data| must remain valid until this is destroyed or Reset(). (Note
// that subclasses have simpler lifetime requirements). On error, returns
// non-OK and modifies nothing.
tensorflow::Status Reset(const ArrayVariableStoreSpec &spec,
AlignedView data);
// Implements VariableStore.
using VariableStore::Lookup; // import Lookup<T>() convenience methods
tensorflow::Status Lookup(const string &name, VariableSpec::Format format,
std::vector<size_t> *dimensions,
AlignedArea *area) override;
tensorflow::Status Close() override;
private:
friend class ArrayVariableStoreBuilder; // for access to kVersion
// The current version of the serialized format.
static const uint32 kVersion;
// A (name,format) key associated with a variable.
using Key = std::pair<string, VariableSpec::Format>;
// Dimension vector and aligned area.
using Value = std::pair<const std::vector<size_t>, AlignedArea>;
// Mapping from variable key to variable content. Initially null, filled in
// Reset(), and deleted in Close(). Wrapped in std::unique_ptr so the entire
// mapping can be deleted.
std::unique_ptr<std::map<Key, Value>> variables_;
};
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_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/array_variable_store_builder.h"
#include <stddef.h>
#include <tuple>
#include "dragnn/runtime/alignment.h"
#include "dragnn/runtime/array_variable_store.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/platform/cpu_info.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Appends the content of the |view| to the |data|, followed by zero-padding to
// the next alignment boundary.
void Append(AlignedView view, string *data) {
DCHECK_EQ(PadToAlignment(data->size()), data->size());
const size_t alignment_padding = PadToAlignment(view.size()) - view.size();
data->append(view.data(), view.size());
data->append(alignment_padding, '\0');
}
// As above, but for an aligned |area|.
void Append(AlignedArea area, string *data) {
DCHECK_EQ(PadToAlignment(data->size()), data->size());
const size_t orig_size = data->size();
for (size_t i = 0; i < area.num_views(); ++i) Append(area.view(i), data);
DCHECK_EQ(data->size() - orig_size,
ComputeAlignedAreaSize(area.num_views(), area.view_size()));
}
} // namespace
tensorflow::Status ArrayVariableStoreBuilder::Build(
const Variables &variables, ArrayVariableStoreSpec *spec, string *data) {
data->clear();
spec->Clear();
spec->set_version(ArrayVariableStore::kVersion);
spec->set_alignment_bytes(internal::kAlignmentBytes);
spec->set_is_little_endian(tensorflow::port::kLittleEndian);
for (const auto &variable : variables) {
string name;
VariableSpec::Format format;
std::vector<size_t> dimensions;
AlignedArea area;
std::tie(name, format) = variable.first;
std::tie(dimensions, area) = variable.second;
if (format == VariableSpec::FORMAT_FLAT && area.num_views() != 1) {
return tensorflow::errors::InvalidArgument(
"Flat variables must have 1 view, but '", name, "' has ",
area.num_views());
}
VariableSpec *variable_spec = spec->add_variable();
variable_spec->set_name(name);
variable_spec->set_format(format);
variable_spec->set_num_views(area.num_views());
variable_spec->set_view_size(area.view_size());
for (size_t dimension : dimensions) {
variable_spec->add_dimension(dimension);
}
Append(area, 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_ARRAY_VARIABLE_STORE_BUILDER_H_
#define DRAGNN_RUNTIME_ARRAY_VARIABLE_STORE_BUILDER_H_
#include <map>
#include <string>
#include "dragnn/protos/runtime.pb.h"
#include "dragnn/runtime/variable_store_wrappers.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/status.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Utils for converting a set of variables into a byte array that can be loaded
// by ArrayVariableStore. See that class for details on the required format.
class ArrayVariableStoreBuilder {
public:
using Variables = CaptureUsedVariableStoreWrapper::Variables;
// Forbids instantiation; pure static class.
ArrayVariableStoreBuilder() = delete;
~ArrayVariableStoreBuilder() = delete;
// Overwrites the |data| with a byte array that represents the |variables|,
// and overwrites the |spec| with the associated configuration. On error,
// returns non-OK.
static tensorflow::Status Build(const Variables &variables,
ArrayVariableStoreSpec *spec, string *data);
};
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_ARRAY_VARIABLE_STORE_BUILDER_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/array_variable_store_builder.h"
#include <stddef.h>
#include <map>
#include <string>
#include <vector>
#include "dragnn/core/test/generic.h"
#include "dragnn/protos/runtime.pb.h"
#include "dragnn/runtime/alignment.h"
#include "dragnn/runtime/test/helpers.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/io/path.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// Tests that the builder rejects invalid flat variables.
TEST(ArrayVariableStoreBuilderTest, InvalidFlatVariable) {
AlignedView view;
ArrayVariableStoreBuilder::Variables variables;
ArrayVariableStoreSpec spec;
string data;
TF_ASSERT_OK(view.Reset(nullptr, 2 * internal::kAlignmentBytes));
// Try an empty area.
std::pair<string, VariableSpec::Format> foo_key("foo",
VariableSpec::FORMAT_FLAT);
AlignedArea area;
TF_ASSERT_OK(area.Reset(view, 0, 0));
std::pair<std::vector<size_t>, AlignedArea> foo_value({1}, area);
variables.push_back(std::make_pair(foo_key, foo_value));
EXPECT_THAT(ArrayVariableStoreBuilder::Build(variables, &spec, &data),
test::IsErrorWithSubstr(
"Flat variables must have 1 view, but 'foo' has 0"));
// Try an area with more than 1 sub-view.
TF_ASSERT_OK(area.Reset(view, 2, 0));
variables[0].second.second = area;
EXPECT_THAT(ArrayVariableStoreBuilder::Build(variables, &spec, &data),
test::IsErrorWithSubstr(
"Flat variables must have 1 view, but 'foo' has 2"));
}
// Tests that the builder succeeds on good inputs and reproduces an expected
// byte array.
//
// NB: Since this test directly compares the byte array, it implicitly requires
// that the builder lays out the variables in a particular order. If that order
// changes, the test expectations must be updated.
TEST(ArrayVariableStoreBuilderTest, RegressionTest) {
const string kLocalSpecPath =
"dragnn/runtime/testdata/array_variable_store_spec";
const string kLocalDataPath =
"dragnn/runtime/testdata/array_variable_store_data";
const string kExpectedSpecPath = tensorflow::io::JoinPath(
test::GetTestDataPrefix(),
"dragnn/runtime/testdata/array_variable_store_spec");
const string kExpectedDataPath = tensorflow::io::JoinPath(
test::GetTestDataPrefix(),
"dragnn/runtime/testdata/array_variable_store_data");
// If these values are changed, make sure to rewrite the test data and update
// array_variable_store_test.cc.
UniqueMatrix<float> foo({{0.0, 0.5, 1.0}, //
{1.5, 2.0, 2.5}, //
{3.0, 3.5, 4.0}, //
{4.5, 5.0, 5.5}});
UniqueMatrix<double> baz_data({{1.0, 2.0, 2.0, 2.0}, //
{3.0, 4.0, 4.0, 4.0}, //
{5.0, 6.0, 6.0, 6.0}, //
{7.0, 8.0, 8.0, 8.0}});
ArrayVariableStoreBuilder::Variables variables;
std::pair<string, VariableSpec::Format> foo_key(
"foo", VariableSpec::FORMAT_ROW_MAJOR_MATRIX);
std::pair<std::vector<size_t>, AlignedArea> foo_value(
{foo->num_rows(), foo->num_columns()}, AlignedArea(foo.area()));
variables.push_back(std::make_pair(foo_key, foo_value));
std::pair<string, VariableSpec::Format> baz_key(
"baz", VariableSpec::FORMAT_COLUMN_BLOCKED_ROW_MAJOR_MATRIX);
std::pair<std::vector<size_t>, AlignedArea> baz_value(
{2, 8, 4}, AlignedArea(baz_data.area()));
variables.push_back(std::make_pair(baz_key, baz_value));
ArrayVariableStoreSpec actual_spec;
actual_spec.set_version(999);
string actual_data = "garbage to be overwritten";
TF_ASSERT_OK(
ArrayVariableStoreBuilder::Build(variables, &actual_spec, &actual_data));
if (false) {
// Rewrite the test data.
TF_CHECK_OK(tensorflow::WriteTextProto(tensorflow::Env::Default(),
kLocalSpecPath, actual_spec));
TF_CHECK_OK(tensorflow::WriteStringToFile(tensorflow::Env::Default(),
kLocalDataPath, actual_data));
} else {
// Compare to the test data.
ArrayVariableStoreSpec expected_spec;
string expected_data;
TF_CHECK_OK(tensorflow::ReadTextProto(tensorflow::Env::Default(),
kExpectedSpecPath, &expected_spec));
TF_CHECK_OK(tensorflow::ReadFileToString(
tensorflow::Env::Default(), kExpectedDataPath, &expected_data));
EXPECT_THAT(actual_spec, test::EqualsProto(expected_spec));
EXPECT_EQ(actual_data, expected_data);
}
}
} // 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/array_variable_store.h"
#include <string.h>
#include "dragnn/core/test/generic.h"
#include "dragnn/protos/runtime.pb.h"
#include "dragnn/runtime/alignment.h"
#include "dragnn/runtime/file_array_variable_store.h"
#include "dragnn/runtime/math/types.h"
#include "dragnn/runtime/mmap_array_variable_store.h"
#include "dragnn/runtime/test/helpers.h"
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/errors.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/lib/strings/strcat.h"
#include "tensorflow/core/platform/cpu_info.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/test.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
template <class T>
void ExpectBlockedData(BlockedMatrix<T> matrix,
const std::vector<std::vector<T>> &data) {
EXPECT_EQ(matrix.num_vectors(), data.size());
// The indices don't really have semantic names, so we just use `i` and `j`.
// See BlockedMatrixFormat for details.
for (int i = 0; i < matrix.num_vectors(); ++i) {
EXPECT_EQ(matrix.block_size(), data[i].size());
for (int j = 0; j < data[i].size(); ++j) {
EXPECT_EQ(matrix.vector(i)[j], data[i][j]);
}
}
}
// Returns an ArrayVariableStoreSpec parsed from the |text|.
ArrayVariableStoreSpec MakeSpec(const string &text) {
ArrayVariableStoreSpec spec;
CHECK(TextFormat::ParseFromString(text, &spec));
return spec;
}
// Returns an ArrayVariableStoreSpec that has proper top-level settings and
// whose variables are parsed from the |variables_text|.
ArrayVariableStoreSpec MakeSpecWithVariables(const string &variables_text) {
return MakeSpec(tensorflow::strings::StrCat(
"version: 0 alignment_bytes: ", internal::kAlignmentBytes,
" is_little_endian: ", tensorflow::port::kLittleEndian, " ",
variables_text));
}
// Tests that kLittleEndian actually means little-endian.
TEST(ArrayVariableStoreTest, EndianDetection) {
static_assert(sizeof(uint32) == 4 * sizeof(uint8), "Unexpected int sizes");
const uint32 foo = 0xdeadbeef;
uint8 foo_bytes[4];
memcpy(foo_bytes, &foo, 4 * sizeof(uint8));
if (tensorflow::port::kLittleEndian) {
EXPECT_EQ(foo_bytes[3], 0xde);
EXPECT_EQ(foo_bytes[2], 0xad);
EXPECT_EQ(foo_bytes[1], 0xbe);
EXPECT_EQ(foo_bytes[0], 0xef);
} else {
EXPECT_EQ(foo_bytes[0], 0xde);
EXPECT_EQ(foo_bytes[1], 0xad);
EXPECT_EQ(foo_bytes[2], 0xbe);
EXPECT_EQ(foo_bytes[3], 0xef);
}
}
// Tests that the store checks for missing fields.
TEST(ArrayVariableStoreTest, MissingRequiredField) {
for (const string kSpec :
{"version: 0 alignment_bytes: 0", "version: 0 is_little_endian: true",
"alignment_bytes: 0 is_little_endian: true"}) {
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpec(kSpec), AlignedView()),
test::IsErrorWithSubstr(
"ArrayVariableStoreSpec is missing a required field"));
}
}
// Tests that the store checks for a matching version number.
TEST(ArrayVariableStoreTest, VersionMismatch) {
const string kSpec = "version: 999 alignment_bytes: 0 is_little_endian: true";
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpec(kSpec), AlignedView()),
test::IsErrorWithSubstr("ArrayVariableStoreSpec.version (999) "
"does not match the binary (0)"));
}
// Tests that the store checks for a matching alignment requirement.
TEST(ArrayVariableStoreTest, AlignmentMismatch) {
const string kSpec = "version: 0 alignment_bytes: 1 is_little_endian: true";
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpec(kSpec), AlignedView()),
test::IsErrorWithSubstr(tensorflow::strings::StrCat(
"ArrayVariableStoreSpec.alignment_bytes (1) does not match "
"the binary (", internal::kAlignmentBytes, ")")));
}
// Tests that the store checks for matching endian-ness.
TEST(ArrayVariableStoreTest, EndiannessMismatch) {
const string kSpec = tensorflow::strings::StrCat(
"version: 0 alignment_bytes: ", internal::kAlignmentBytes,
" is_little_endian: ", !tensorflow::port::kLittleEndian);
ArrayVariableStore store;
EXPECT_THAT(
store.Reset(MakeSpec(kSpec), AlignedView()),
test::IsErrorWithSubstr(tensorflow::strings::StrCat(
"ArrayVariableStoreSpec.is_little_endian (",
!tensorflow::port::kLittleEndian, ") does not match the binary (",
tensorflow::port::kLittleEndian, ")")));
}
// Tests that the store rejects FORMAT_UNKNOWN variables.
TEST(ArrayVariableStoreTest, RejectFormatUnknown) {
const string kVariables = "variable { format: FORMAT_UNKNOWN }";
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpecWithVariables(kVariables), AlignedView()),
test::IsErrorWithSubstr("Unknown variable format"));
}
// Tests that the store rejects FORMAT_FLAT variables with too few sub-views.
TEST(ArrayVariableStoreTest, TooFewViewsForFlatVariable) {
const string kVariables = "variable { format: FORMAT_FLAT num_views: 0 }";
ArrayVariableStore store;
EXPECT_THAT(
store.Reset(MakeSpecWithVariables(kVariables), AlignedView()),
test::IsErrorWithSubstr("Flat variables must have 1 view"));
}
// Tests that the store rejects FORMAT_FLAT variables with too many sub-views.
TEST(ArrayVariableStoreTest, TooManyViewsForFlatVariable) {
const string kVariables = "variable { format: FORMAT_FLAT num_views: 2 }";
ArrayVariableStore store;
EXPECT_THAT(
store.Reset(MakeSpecWithVariables(kVariables), AlignedView()),
test::IsErrorWithSubstr("Flat variables must have 1 view"));
}
// Tests that the store accepts FORMAT_ROW_MAJOR_MATRIX variables with one
// sub-view.
TEST(ArrayVariableStoreTest, MatrixWithOneRow) {
const string kVariables =
"variable { format: FORMAT_ROW_MAJOR_MATRIX num_views: 1 view_size: 0 }";
ArrayVariableStore store;
TF_EXPECT_OK(store.Reset(MakeSpecWithVariables(kVariables), AlignedView()));
}
// Tests that the store rejects variables that overrun the main byte array.
TEST(ArrayVariableStoreTest, VariableOverrunsMainByteArray) {
const string kVariables =
"variable { format: FORMAT_FLAT num_views: 1 view_size: 1024 }";
AlignedView data;
TF_ASSERT_OK(data.Reset(nullptr, 1023));
ArrayVariableStore store;
EXPECT_THAT(
store.Reset(MakeSpecWithVariables(kVariables), data),
test::IsErrorWithSubstr("Variable would overrun main byte array"));
}
// Tests that the store rejects duplicate variables.
TEST(ArrayVariableStoreTest, DuplicateVariables) {
const string kVariables = R"(
variable { name: 'x' format: FORMAT_FLAT num_views: 1 view_size: 1024 }
variable { name: 'y' format: FORMAT_FLAT num_views: 1 view_size: 2048 }
variable { name: 'x' format: FORMAT_FLAT num_views: 1 view_size: 4096 }
)";
AlignedView data;
TF_ASSERT_OK(data.Reset(nullptr, 1 << 20)); // 1MB
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpecWithVariables(kVariables), data),
test::IsErrorWithSubstr("Duplicate variable"));
}
// Tests that the store rejects sets of variables that do not completely cover
// the main byte array.
TEST(ArrayVariableStoreTest, LeftoverBytesInMainByteArray) {
const string kVariables = R"(
variable { name: 'x' format: FORMAT_FLAT num_views: 1 view_size: 1024 }
variable { name: 'y' format: FORMAT_FLAT num_views: 1 view_size: 2048 }
variable { name: 'z' format: FORMAT_FLAT num_views: 1 view_size: 4096 }
)";
AlignedView data;
TF_ASSERT_OK(data.Reset(nullptr, 1 << 20)); // 1MB
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpecWithVariables(kVariables), data),
test::IsErrorWithSubstr(
"Variables do not completely cover main byte array"));
}
// The fast matrix-vector routines do not support padding.
TEST(ArrayVariableStoreTest, PaddingInBlockedMatrix) {
const string kVariables = R"(
variable {
name: "baz"
format: FORMAT_COLUMN_BLOCKED_ROW_MAJOR_MATRIX
num_views: 4
view_size: 16
dimension: 2
dimension: 4
dimension: 2
}
)";
AlignedView data;
TF_ASSERT_OK(data.Reset(nullptr, 1 << 20)); // 1MB
ArrayVariableStore store;
EXPECT_THAT(store.Reset(MakeSpecWithVariables(kVariables), data),
test::IsErrorWithSubstr(
"Currently, fast matrix-vector operations do not support "
"padded blocked matrices"));
}
// Tests that the store cannot retrieve variables when it is uninitialized.
TEST(ArrayVariableStoreTest, LookupWhenUninitialized) {
ArrayVariableStore store;
Vector<float> vector;
EXPECT_THAT(store.Lookup("foo", &vector),
test::IsErrorWithSubstr("ArrayVariableStore not initialized"));
}
// Tests that the store can use an empty byte array when there are no variables.
TEST(ArrayVariableStoreTest, EmptyByteArrayWorksIfNoVariables) {
ArrayVariableStore store;
TF_EXPECT_OK(store.Reset(MakeSpecWithVariables(""), AlignedView()));
// The store contains nothing.
Vector<float> vector;
EXPECT_THAT(
store.Lookup("foo", &vector),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with name "
"'foo' and format FORMAT_FLAT"));
}
// Tests that the store fails if it is closed before it has been initialized.
TEST(ArrayVariableStoreTest, CloseBeforeReset) {
ArrayVariableStore store;
EXPECT_THAT(store.Close(),
test::IsErrorWithSubstr("ArrayVariableStore not initialized"));
}
// Tests that the store can be closed (once) after it has been initialized.
TEST(ArrayVariableStoreTest, CloseAfterReset) {
ArrayVariableStore store;
TF_ASSERT_OK(store.Reset(MakeSpecWithVariables(""), AlignedView()));
TF_EXPECT_OK(store.Close());
// Closing twice is still an error.
EXPECT_THAT(store.Close(),
test::IsErrorWithSubstr("ArrayVariableStore not initialized"));
}
// Templated on an ArrayVariableStore subclass.
template <class Subclass>
class ArrayVariableStoreSubclassTest : public ::testing::Test {};
typedef ::testing::Types<FileArrayVariableStore, MmapArrayVariableStore>
Subclasses;
TYPED_TEST_CASE(ArrayVariableStoreSubclassTest, Subclasses);
// Tests that the store fails to load a non-existent file.
TYPED_TEST(ArrayVariableStoreSubclassTest, NonExistentFile) {
// Paths to the spec and data produced by array_variable_store_builder_test.
const string kDataPath = tensorflow::io::JoinPath(
test::GetTestDataPrefix(), "dragnn/runtime/testdata/non_existent_file");
TypeParam store;
EXPECT_THAT(store.Reset(MakeSpecWithVariables(""), kDataPath),
test::IsErrorWithSubstr(""));
}
// Tests that the store can load an empty file if there are no variables.
TYPED_TEST(ArrayVariableStoreSubclassTest, EmptyFile) {
// Paths to the spec and data produced by array_variable_store_builder_test.
const string kDataPath = tensorflow::io::JoinPath(
test::GetTestDataPrefix(), "dragnn/runtime/testdata/empty_file");
TypeParam store;
TF_ASSERT_OK(store.Reset(MakeSpecWithVariables(""), kDataPath));
Vector<float> vector;
Matrix<float> row_major_matrix;
EXPECT_THAT(store.Lookup("foo", &vector),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with "
"name 'foo' and format FORMAT_FLAT"));
EXPECT_THAT(
store.Lookup("bar", &row_major_matrix),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with name "
"'bar' and format FORMAT_ROW_MAJOR_MATRIX"));
}
// Tests that the store, when loading a pre-built byte array, produces the same
// variables that the builder converted.
TYPED_TEST(ArrayVariableStoreSubclassTest, RegressionTest) {
// Paths to the spec and data produced by array_variable_store_builder_test.
const string kSpecPath = tensorflow::io::JoinPath(
test::GetTestDataPrefix(),
"dragnn/runtime/testdata/array_variable_store_spec");
const string kDataPath = tensorflow::io::JoinPath(
test::GetTestDataPrefix(),
"dragnn/runtime/testdata/array_variable_store_data");
ArrayVariableStoreSpec spec;
TF_CHECK_OK(
tensorflow::ReadTextProto(tensorflow::Env::Default(), kSpecPath, &spec));
TypeParam store;
TF_ASSERT_OK(store.Reset(spec, kDataPath));
Matrix<float> foo;
TF_ASSERT_OK(store.Lookup("foo", &foo));
// NB: These assertions must be kept in sync with the variables defined in
// array_variable_store_builder_test.cc.
ExpectMatrix(foo, {{0.0, 0.5, 1.0}, //
{1.5, 2.0, 2.5}, //
{3.0, 3.5, 4.0}, //
{4.5, 5.0, 5.5}});
// Blocked formats.
BlockedMatrix<double> baz;
TF_ASSERT_OK(store.Lookup("baz", &baz));
EXPECT_EQ(baz.num_rows(), 2);
EXPECT_EQ(baz.num_columns(), 8);
EXPECT_EQ(baz.block_size(), 4);
ExpectBlockedData(baz, {{1.0, 2.0, 2.0, 2.0}, //
{3.0, 4.0, 4.0, 4.0}, //
{5.0, 6.0, 6.0, 6.0}, //
{7.0, 8.0, 8.0, 8.0}});
// Try versions of "foo" and "baz" with the wrong format.
Vector<float> vector;
Matrix<float> row_major_matrix;
EXPECT_THAT(store.Lookup("foo", &vector),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with "
"name 'foo' and format FORMAT_FLAT"));
EXPECT_THAT(store.Lookup("baz", &vector),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with "
"name 'baz' and format FORMAT_FLAT"));
EXPECT_THAT(
store.Lookup("baz", &row_major_matrix),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with name "
"'baz' and format FORMAT_ROW_MAJOR_MATRIX"));
// Try totally unknown variables.
EXPECT_THAT(store.Lookup("missing", &vector),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with "
"name 'missing' and format FORMAT_FLAT"));
EXPECT_THAT(
store.Lookup("missing", &row_major_matrix),
test::IsErrorWithSubstr("ArrayVariableStore has no variable with name "
"'missing' and format FORMAT_ROW_MAJOR_MATRIX"));
}
} // 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/attributes.h"
#include <set>
#include "tensorflow/core/lib/strings/numbers.h"
#include "tensorflow/core/platform/logging.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
tensorflow::Status Attributes::Reset(
const tensorflow::protobuf::Map<string, string> &mapping) {
// First pass: Parse each value in the |mapping|.
for (const auto &name_value : mapping) {
const string &name = name_value.first;
const string &value = name_value.second;
const auto it = attributes_.find(name);
if (it == attributes_.end()) {
return tensorflow::errors::InvalidArgument("Unknown attribute: ", name);
}
TF_RETURN_IF_ERROR(it->second->Parse(value));
}
// Second pass: Look for missing mandatory attributes.
std::set<string> missing_mandatory_attributes;
for (const auto &it : attributes_) {
const string &name = it.first;
Attribute *attribute = it.second;
if (!attribute->IsMandatory()) continue;
if (mapping.find(name) == mapping.end()) {
missing_mandatory_attributes.insert(name);
}
}
if (!missing_mandatory_attributes.empty()) {
return tensorflow::errors::InvalidArgument(
"Missing mandatory attributes: ",
tensorflow::str_util::Join(missing_mandatory_attributes, " "));
}
return tensorflow::Status::OK();
}
void Attributes::Register(const string &name, Attribute *attribute) {
const bool unique = attributes_.emplace(name, attribute).second;
DCHECK(unique) << "Duplicate attribute '" << name << "'";
}
tensorflow::Status Attributes::ParseValue(const string &str, string *value) {
*value = str;
return tensorflow::Status::OK();
}
tensorflow::Status Attributes::ParseValue(const string &str, bool *value) {
const string lowercased_str = tensorflow::str_util::Lowercase(str);
if (lowercased_str != "true" && lowercased_str != "false") {
return tensorflow::errors::InvalidArgument(
"Attribute can't be parsed as bool: ", str);
}
*value = lowercased_str == "true";
return tensorflow::Status::OK();
}
tensorflow::Status Attributes::ParseValue(const string &str, int32 *value) {
if (!tensorflow::strings::safe_strto32(str, value)) {
return tensorflow::errors::InvalidArgument(
"Attribute can't be parsed as int32: ", str);
}
return tensorflow::Status::OK();
}
tensorflow::Status Attributes::ParseValue(const string &str, int64 *value) {
if (!tensorflow::strings::safe_strto64(str, value)) {
return tensorflow::errors::InvalidArgument(
"Attribute can't be parsed as int64: ", str);
}
return tensorflow::Status::OK();
}
tensorflow::Status Attributes::ParseValue(const string &str, size_t *value) {
int64 signed_value = 0;
if (!tensorflow::strings::safe_strto64(str, &signed_value) ||
signed_value < 0) {
return tensorflow::errors::InvalidArgument(
"Attribute can't be parsed as size_t: ", str);
}
*value = signed_value;
return tensorflow::Status::OK();
}
tensorflow::Status Attributes::ParseValue(const string &str, float *value) {
if (!tensorflow::strings::safe_strtof(str.c_str(), value)) {
return tensorflow::errors::InvalidArgument(
"Attribute can't be parsed as float: ", str);
}
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.
// =============================================================================
// Utils for parsing configuration attributes from (name,value) string pairs as
// typed values. Intended for parsing RegisteredModuleSpec.parameters, similar
// to get_attrs_with_defaults() in network_units.py. Example usage:
//
// // Create a subclass of Attributes.
// struct MyComponentAttributes : public Attributes {
// // Mandatory attribute with type and name. The "this" allows the attribute
// // to register itself in its container---i.e., MyComponentAttributes.
// Mandatory<float> coefficient{"coefficient", this};
//
// // Optional attributes with type, name, and default value.
// Optional<bool> ignore_case{"ignore_case", true, this};
// Optional<std::vector<int32>> layer_sizes{"layer_sizes", {1, 2, 3}, this};
//
// // Ignored attribute, which does not parse any value.
// Ignored dropout_keep_prob{"dropout_keep_prob", this};
// };
//
// // Initialize an instance of the subclass from a string-to-string mapping.
// RegisteredModuleSpec spec;
// MyComponentAttributes attributes;
// TF_RETURN_IF_ERROR(attributes.Reset(spec.parameters()));
//
// // Access the attributes as accessors.
// bool ignore_case = attributes.ignore_case();
// float coefficient = attributes.coefficient();
// const std::vector<int32> &layer_sizes = attributes.layer_sizes();
//
// See the unit test for additional usage examples.
//
// TODO(googleuser): Build typed attributes into the RegisteredModuleSpec and
// get rid of this module.
#ifndef DRAGNN_RUNTIME_ATTRIBUTES_H_
#define DRAGNN_RUNTIME_ATTRIBUTES_H_
#include <functional>
#include <map>
#include <string>
#include <vector>
#include "syntaxnet/base.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/platform/protobuf.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
// Base class for sets of attributes. Use as indicated in the file comment.
class Attributes {
public:
// Untyped mapping from which typed attributes are parsed.
using Mapping = tensorflow::protobuf::Map<string, string>;
// Forbids copying, which would invalidate the pointers in |attributes_|.
Attributes(const Attributes &that) = delete;
Attributes &operator=(const Attributes &that) = delete;
// Parses registered attributes from the name-to-value |mapping|. On error,
// returns non-OK. Errors include unknown names in |mapping|, string-to-value
// parsing failures, and missing mandatory attributes.
tensorflow::Status Reset(const Mapping &mapping);
protected:
// Implementations of the supported kinds of attributes, defined below.
class Ignored;
template <class T>
class Optional;
template <class T>
class Mandatory;
// Forbids lifecycle management except via subclasses.
Attributes() = default;
virtual ~Attributes() = default;
private:
// Base class for an individual attribute, defined below.
class Attribute;
// Registers the |attribute| with the |name|, which must be unique.
void Register(const string &name, Attribute *attribute);
// Parses the string |str| into the |value| object.
static tensorflow::Status ParseValue(const string &str, string *value);
static tensorflow::Status ParseValue(const string &str, bool *value);
static tensorflow::Status ParseValue(const string &str, int32 *value);
static tensorflow::Status ParseValue(const string &str, int64 *value);
static tensorflow::Status ParseValue(const string &str, size_t *value);
static tensorflow::Status ParseValue(const string &str, float *value);
template <class Element>
static tensorflow::Status ParseValue(const string &str,
std::vector<Element> *value);
// Registered attributes, keyed by name.
std::map<string, Attribute *> attributes_;
};
// Implementation details below.
// Base class for individual attributes.
class Attributes::Attribute {
public:
Attribute() = default;
Attribute(const Attribute &that) = delete;
Attribute &operator=(const Attribute &that) = delete;
virtual ~Attribute() = default;
// Parses the |value| string into a typed object. On error, returns non-OK.
virtual tensorflow::Status Parse(const string &value) = 0;
// Returns true if this is a mandatory attribute. Defaults to optional.
virtual bool IsMandatory() const { return false; }
};
// Implements an ignored attribute.
class Attributes::Ignored : public Attribute {
public:
// Registers this in the |attributes| with the |name|.
Ignored(const string &name, Attributes *attributes) {
attributes->Register(name, this);
}
// Ignores the |value|.
tensorflow::Status Parse(const string &value) override {
return tensorflow::Status::OK();
}
};
// Implements an optional attribute.
template <class T>
class Attributes::Optional : public Attribute {
public:
// Registers this in the |attributes| with the |name| and |default_value|.
Optional(const string &name, const T &default_value, Attributes *attributes)
: value_(default_value) {
attributes->Register(name, this);
}
// Parses the |value| into the |value_|.
tensorflow::Status Parse(const string &value) override {
return ParseValue(value, &value_);
}
// Returns the parsed |value_|. Overloading operator() allows a struct member
// to be called like an accessor.
const T &operator()() const { return value_; }
private:
// The parsed value, or the default value if not explicitly specified.
T value_;
};
// Implements a mandatory attribute.
template <class T>
class Attributes::Mandatory : public Optional<T> {
public:
// Registers this in the |attributes| with the |name|.
Mandatory(const string &name, Attributes *attributes)
: Optional<T>(name, T(), attributes) {}
// Returns true since this is mandatory.
bool IsMandatory() const override { return true; }
private:
// The parsed value, or the default value if not explicitly specified.
T value_;
};
template <class Element>
tensorflow::Status Attributes::ParseValue(const string &str,
std::vector<Element> *value) {
value->clear();
if (!str.empty()) {
for (const string &element_str : tensorflow::str_util::Split(str, ",")) {
value->emplace_back();
TF_RETURN_IF_ERROR(ParseValue(element_str, &value->back()));
}
}
return tensorflow::Status::OK();
}
} // namespace runtime
} // namespace dragnn
} // namespace syntaxnet
#endif // DRAGNN_RUNTIME_ATTRIBUTES_H_
This diff is collapsed.
// 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 <stddef.h>
#include <string>
#include "dragnn/core/compute_session.h"
#include "dragnn/protos/spec.pb.h"
#include "dragnn/runtime/bulk_network_unit.h"
#include "dragnn/runtime/extensions.h"
#include "dragnn/runtime/feed_forward_network_kernel.h"
#include "dragnn/runtime/feed_forward_network_layer.h"
#include "dragnn/runtime/math/types.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/errors.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/strings/strcat.h"
namespace syntaxnet {
namespace dragnn {
namespace runtime {
namespace {
// A network unit that evaluates a feed-forward multi-layer perceptron.
class BulkFeedForwardNetwork : public BulkNetworkUnit {
public:
// Implements BulkNetworkUnit.
tensorflow::Status Initialize(const ComponentSpec &component_spec,
VariableStore *variable_store,
NetworkStateManager *network_state_manager,
ExtensionManager *extension_manager) override;
tensorflow::Status ValidateInputDimension(size_t dimension) const override;
string GetLogitsName() const override { return kernel_.logits_name(); }
tensorflow::Status Evaluate(Matrix<float> inputs,
SessionState *session_state) const override;
private:
// Kernel that implements the feed-forward network.
FeedForwardNetworkKernel kernel_;
};
tensorflow::Status BulkFeedForwardNetwork::Initialize(
const ComponentSpec &component_spec, VariableStore *variable_store,
NetworkStateManager *network_state_manager,
ExtensionManager *extension_manager) {
for (const LinkedFeatureChannel &channel : component_spec.linked_feature()) {
if (channel.source_component() == component_spec.name()) {
return tensorflow::errors::InvalidArgument(
"BulkFeedForwardNetwork forbids recurrent links");
}
}
return kernel_.Initialize(component_spec, variable_store,
network_state_manager);
}
tensorflow::Status BulkFeedForwardNetwork::ValidateInputDimension(
size_t dimension) const {
return kernel_.ValidateInputDimension(dimension);
}
tensorflow::Status BulkFeedForwardNetwork::Evaluate(
Matrix<float> inputs, SessionState *session_state) const {
for (const FeedForwardNetworkLayer &layer : kernel_.layers()) {
inputs = layer.Apply(inputs, session_state->network_states);
}
return tensorflow::Status::OK();
}
DRAGNN_RUNTIME_REGISTER_BULK_NETWORK_UNIT(BulkFeedForwardNetwork);
} // namespace
} // 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