aboutsummaryrefslogtreecommitdiffstats
path: root/eval/src
diff options
context:
space:
mode:
authorHaavard <havardpe@yahoo-inc.com>2017-05-12 13:25:34 +0000
committerHaavard <havardpe@yahoo-inc.com>2017-05-18 11:36:25 +0000
commit34da9abd91caf91a2f53f05dbe64413d60d7de0a (patch)
tree364eb5d25a4e81f99d9e385d92910eb4903de06b /eval/src
parent480270b558640b0bc5f4d63f64cb0b36a85a85fc (diff)
wrapped simple tensor
fall back to wrapping SimpleTensors for cases where the default tensor implementation does not support the data model (mixed dimensions). remove special handling of mixed cases in tensor conformance tests. require that the default tensor implementation passes all conformance tests. ironed out a small mismatch in the tensor binary format tag.
Diffstat (limited to 'eval/src')
-rw-r--r--eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp6
-rw-r--r--eval/src/vespa/eval/eval/simple_tensor.cpp12
-rw-r--r--eval/src/vespa/eval/eval/test/tensor_conformance.cpp122
-rw-r--r--eval/src/vespa/eval/eval/test/tensor_conformance.h2
-rw-r--r--eval/src/vespa/eval/tensor/CMakeLists.txt1
-rw-r--r--eval/src/vespa/eval/tensor/default_tensor_engine.cpp95
-rw-r--r--eval/src/vespa/eval/tensor/dense/dense_tensor.h1
-rw-r--r--eval/src/vespa/eval/tensor/serialization/format.txt2
-rw-r--r--eval/src/vespa/eval/tensor/serialization/typed_binary_format.cpp12
-rw-r--r--eval/src/vespa/eval/tensor/serialization/typed_binary_format.h1
-rw-r--r--eval/src/vespa/eval/tensor/tensor.cpp15
-rw-r--r--eval/src/vespa/eval/tensor/tensor.h3
-rw-r--r--eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp130
-rw-r--r--eval/src/vespa/eval/tensor/wrapped_simple_tensor.h52
14 files changed, 312 insertions, 142 deletions
diff --git a/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp b/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
index 4ba5e1180cd..726bd4be3cf 100644
--- a/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
+++ b/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp
@@ -12,11 +12,11 @@ vespalib::string module_path(TEST_PATH("../../../../"));
TEST("require that reference tensor implementation passes all conformance tests") {
- TEST_DO(TensorConformance::run_tests(module_path, SimpleTensorEngine::ref(), true));
+ TEST_DO(TensorConformance::run_tests(module_path, SimpleTensorEngine::ref()));
}
-IGNORE_TEST("require that production tensor implementation passes non-mixed conformance tests") {
- TEST_DO(TensorConformance::run_tests(module_path, DefaultTensorEngine::ref(), false));
+TEST("require that production tensor implementation passes all conformance tests") {
+ TEST_DO(TensorConformance::run_tests(module_path, DefaultTensorEngine::ref()));
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/eval/src/vespa/eval/eval/simple_tensor.cpp b/eval/src/vespa/eval/eval/simple_tensor.cpp
index 71cf9b9b24f..20076d398d4 100644
--- a/eval/src/vespa/eval/eval/simple_tensor.cpp
+++ b/eval/src/vespa/eval/eval/simple_tensor.cpp
@@ -416,14 +416,14 @@ public:
};
struct Format {
- bool is_sparse;
- bool is_dense;
- uint8_t tag;
+ bool is_sparse;
+ bool is_dense;
+ uint32_t tag;
explicit Format(const TypeMeta &meta)
: is_sparse(meta.mapped.size() > 0),
is_dense((meta.indexed.size() > 0) || !is_sparse),
tag((is_sparse ? 0x1 : 0) | (is_dense ? 0x2 : 0)) {}
- explicit Format(uint8_t tag_in)
+ explicit Format(uint32_t tag_in)
: is_sparse((tag_in & 0x1) != 0),
is_dense((tag_in & 0x2) != 0),
tag(tag_in) {}
@@ -689,7 +689,7 @@ SimpleTensor::encode(const SimpleTensor &tensor, nbostream &output)
{
TypeMeta meta(tensor.type());
Format format(meta);
- output << format.tag;
+ output.putInt1_4Bytes(format.tag);
encode_type(output, format, tensor.type(), meta);
maybe_encode_num_blocks(output, meta, tensor.cells().size() / meta.block_size);
View view(tensor, meta.mapped);
@@ -705,7 +705,7 @@ SimpleTensor::encode(const SimpleTensor &tensor, nbostream &output)
std::unique_ptr<SimpleTensor>
SimpleTensor::decode(nbostream &input)
{
- Format format(input.readValue<uint8_t>());
+ Format format(input.getInt1_4Bytes());
ValueType type = decode_type(input, format);
TypeMeta meta(type);
Builder builder(type);
diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
index 9adcf77a052..6a575df5426 100644
--- a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
+++ b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp
@@ -625,21 +625,6 @@ nbostream extract_data(const Memory &hex_dump) {
return data;
}
-bool is_mixed(const vespalib::string &type_str) {
- bool dense = false;
- bool sparse = false;
- ValueType type = ValueType::from_spec(type_str);
- for (const auto &dim: type.dimensions()) {
- dense = (dense || dim.is_indexed());
- sparse = (sparse || dim.is_mapped());
- }
- return (dense && sparse);
-}
-
-bool is_mixed(nbostream &data) {
- return ((data.size() > 0) && (data.peek()[0] == 0x3));
-}
-
bool is_same(const nbostream &a, const nbostream &b) {
return (Memory(a.peek(), a.size()) == Memory(b.peek(), b.size()));
}
@@ -650,12 +635,9 @@ struct TestContext {
vespalib::string module_path;
const TensorEngine &ref_engine;
const TensorEngine &engine;
- bool test_mixed_cases;
- size_t skip_count;
- TestContext(const vespalib::string &module_path_in, const TensorEngine &engine_in, bool test_mixed_cases_in)
- : module_path(module_path_in), ref_engine(SimpleTensorEngine::ref()), engine(engine_in),
- test_mixed_cases(test_mixed_cases_in), skip_count(0) {}
+ TestContext(const vespalib::string &module_path_in, const TensorEngine &engine_in)
+ : module_path(module_path_in), ref_engine(SimpleTensorEngine::ref()), engine(engine_in) {}
std::unique_ptr<Tensor> tensor(const TensorSpec &spec) {
auto result = engine.create(spec);
@@ -663,13 +645,6 @@ struct TestContext {
return result;
}
- bool mixed(size_t n) {
- if (!test_mixed_cases) {
- skip_count += n;
- }
- return test_mixed_cases;
- }
-
//-------------------------------------------------------------------------
void verify_create_type(const vespalib::string &type_spec) {
@@ -684,10 +659,8 @@ struct TestContext {
TEST_DO(verify_create_type("tensor(x{},y{})"));
TEST_DO(verify_create_type("tensor(x[5])"));
TEST_DO(verify_create_type("tensor(x[5],y[10])"));
- if (mixed(2)) {
- TEST_DO(verify_create_type("tensor(x{},y[10])"));
- TEST_DO(verify_create_type("tensor(x[5],y{})"));
- }
+ TEST_DO(verify_create_type("tensor(x{},y[10])"));
+ TEST_DO(verify_create_type("tensor(x[5],y{})"));
}
//-------------------------------------------------------------------------
@@ -710,10 +683,8 @@ struct TestContext {
TEST_DO(verify_equal(spec({x({"a"}),y({"a"})}, Seq({1})), spec({y({"a"}),x({"a"})}, Seq({1}))));
TEST_DO(verify_equal(spec(x(3)), spec(x(3))));
TEST_DO(verify_equal(spec({x(1),y(1)}, Seq({1})), spec({y(1),x(1)}, Seq({1}))));
- if (mixed(2)) {
- TEST_DO(verify_equal(spec({x({"a"}),y(1)}, Seq({1})), spec({y(1),x({"a"})}, Seq({1}))));
- TEST_DO(verify_equal(spec({y({"a"}),x(1)}, Seq({1})), spec({x(1),y({"a"})}, Seq({1}))));
- }
+ TEST_DO(verify_equal(spec({x({"a"}),y(1)}, Seq({1})), spec({y(1),x({"a"})}, Seq({1}))));
+ TEST_DO(verify_equal(spec({y({"a"}),x(1)}, Seq({1})), spec({x(1),y({"a"})}, Seq({1}))));
}
//-------------------------------------------------------------------------
@@ -743,12 +714,10 @@ struct TestContext {
TEST_DO(verify_not_equal(spec(x(2), Seq({1,1}), Bits({1,0})),
spec(x(2), Seq({1,1}), Bits({0,1}))));
TEST_DO(verify_not_equal(spec(x(1), Seq({1})), spec({x(1),y(1)}, Seq({1}))));
- if (mixed(3)) {
- TEST_DO(verify_not_equal(spec({x({"a"}),y(1)}, Seq({1})), spec({x({"a"}),y(1)}, Seq({2}))));
- TEST_DO(verify_not_equal(spec({x({"a"}),y(1)}, Seq({1})), spec({x({"b"}),y(1)}, Seq({1}))));
- TEST_DO(verify_not_equal(spec({x(2),y({"a"})}, Seq({1}), Bits({1,0})),
- spec({x(2),y({"a"})}, Seq({X,1}), Bits({0,1}))));
- }
+ TEST_DO(verify_not_equal(spec({x({"a"}),y(1)}, Seq({1})), spec({x({"a"}),y(1)}, Seq({2}))));
+ TEST_DO(verify_not_equal(spec({x({"a"}),y(1)}, Seq({1})), spec({x({"b"}),y(1)}, Seq({1}))));
+ TEST_DO(verify_not_equal(spec({x(2),y({"a"})}, Seq({1}), Bits({1,0})),
+ spec({x(2),y({"a"})}, Seq({X,1}), Bits({0,1}))));
}
//-------------------------------------------------------------------------
@@ -764,12 +733,10 @@ struct TestContext {
{x(3),y(5),z(7)},
{x({"a","b","c"})},
{x({"a","b","c"}),y({"foo","bar"})},
- {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})}
+ {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})},
+ {x(3),y({"foo", "bar"}),z(7)},
+ {x({"a","b","c"}),y(5),z({"i","j","k","l"})}
};
- if (mixed(2 * 4)) {
- layouts.push_back({x(3),y({"foo", "bar"}),z(7)});
- layouts.push_back({x({"a","b","c"}),y(5),z({"i","j","k","l"})});
- }
for (const Layout &layout: layouts) {
TensorSpec input = spec(layout, seq);
for (const Domain &domain: layout) {
@@ -804,12 +771,10 @@ struct TestContext {
{x(3),y(5),z(7)},
{x({"a","b","c"})},
{x({"a","b","c"}),y({"foo","bar"})},
- {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})}
+ {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})},
+ {x(3),y({"foo", "bar"}),z(7)},
+ {x({"a","b","c"}),y(5),z({"i","j","k","l"})}
};
- if (mixed(2 * 4)) {
- layouts.push_back({x(3),y({"foo", "bar"}),z(7)});
- layouts.push_back({x({"a","b","c"}),y(5),z({"i","j","k","l"})});
- }
for (const Layout &layout: layouts) {
TensorSpec input = spec(layout, seq);
for (const Domain &domain: layout) {
@@ -851,12 +816,10 @@ struct TestContext {
{x(3),y(5),z(7)},
{x({"a","b","c"})},
{x({"a","b","c"}),y({"foo","bar"})},
- {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})}
+ {x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})},
+ {x(3),y({"foo", "bar"}),z(7)},
+ {x({"a","b","c"}),y(5),z({"i","j","k","l"})}
};
- if (mixed(2)) {
- layouts.push_back({x(3),y({"foo", "bar"}),z(7)});
- layouts.push_back({x({"a","b","c"}),y(5),z({"i","j","k","l"})});
- }
for (const Layout &layout: layouts) {
TEST_DO(verify_result(eval.eval(engine, spec(layout, seq)), spec(layout, OpSeq(seq, ref_op))));
}
@@ -1121,14 +1084,10 @@ struct TestContext {
{x({"a","b","c"})}, {y({"foo","bar","baz"})},
{x({"a","b","c"})}, {x({"a","b","c"}),y({"foo","bar","baz"})},
{x({"a","b"}),y({"foo","bar","baz"})}, {x({"a","b","c"}),y({"foo","bar"})},
- {x({"a","b"}),y({"foo","bar","baz"})}, {y({"foo","bar"}),z({"i","j","k","l"})}
+ {x({"a","b"}),y({"foo","bar","baz"})}, {y({"foo","bar"}),z({"i","j","k","l"})},
+ {x(3),y({"foo", "bar"})}, {y({"foo", "bar"}),z(7)},
+ {x({"a","b","c"}),y(5)}, {y(5),z({"i","j","k","l"})}
};
- if (mixed(2)) {
- layouts.push_back({x(3),y({"foo", "bar"})});
- layouts.push_back({y({"foo", "bar"}),z(7)});
- layouts.push_back({x({"a","b","c"}),y(5)});
- layouts.push_back({y(5),z({"i","j","k","l"})});
- }
ASSERT_TRUE((layouts.size() % 2) == 0);
for (size_t i = 0; i < layouts.size(); i += 2) {
TensorSpec lhs_input = spec(layouts[i], seq);
@@ -1289,21 +1248,17 @@ struct TestContext {
TensorSpec spec = TensorSpec::from_slime(test["tensor"]);
const Inspector &binary = test["binary"];
EXPECT_GREATER(binary.entries(), 0u);
- if (!is_mixed(spec.type()) || mixed(binary.entries() + 1)) {
- nbostream encoded;
- engine.encode(make_value(engine, spec, stash), encoded, stash);
- test.setData("encoded", Memory(encoded.peek(), encoded.size()));
- bool matched_encode = false;
- for (size_t i = 0; i < binary.entries(); ++i) {
- nbostream data = extract_data(binary[i].asString());
- matched_encode = (matched_encode || is_same(encoded, data));
- if (!is_mixed(data) || mixed(1)) {
- TEST_DO(verify_result(Eval::Result(engine.decode(data, stash)), spec));
- EXPECT_EQUAL(data.size(), 0u);
- }
- }
- EXPECT_TRUE(matched_encode);
+ nbostream encoded;
+ engine.encode(make_value(engine, spec, stash), encoded, stash);
+ test.setData("encoded", Memory(encoded.peek(), encoded.size()));
+ bool matched_encode = false;
+ for (size_t i = 0; i < binary.entries(); ++i) {
+ nbostream data = extract_data(binary[i].asString());
+ matched_encode = (matched_encode || is_same(encoded, data));
+ TEST_DO(verify_result(Eval::Result(engine.decode(data, stash)), spec));
+ EXPECT_EQUAL(data.size(), 0u);
}
+ EXPECT_TRUE(matched_encode);
}
void test_binary_format_spec() {
@@ -1335,10 +1290,8 @@ struct TestContext {
TEST_DO(verify_encode_decode(spec({x({"a","b","c"})}, N())));
TEST_DO(verify_encode_decode(spec({x({"a","b","c"}),y({"foo","bar"})}, N())));
TEST_DO(verify_encode_decode(spec({x({"a","b","c"}),y({"foo","bar"}),z({"i","j","k","l"})}, N())));
- if (mixed(2)) {
- TEST_DO(verify_encode_decode(spec({x(3),y({"foo", "bar"}),z(7)}, N())));
- TEST_DO(verify_encode_decode(spec({x({"a","b","c"}),y(5),z({"i","j","k","l"})}, N())));
- }
+ TEST_DO(verify_encode_decode(spec({x(3),y({"foo", "bar"}),z(7)}, N())));
+ TEST_DO(verify_encode_decode(spec({x({"a","b","c"}),y(5),z({"i","j","k","l"})}, N())));
}
//-------------------------------------------------------------------------
@@ -1361,14 +1314,11 @@ struct TestContext {
} // namespace vespalib::eval::test::<unnamed>
void
-TensorConformance::run_tests(const vespalib::string &module_path, const TensorEngine &engine, bool test_mixed_cases)
+TensorConformance::run_tests(const vespalib::string &module_path, const TensorEngine &engine)
{
- TestContext ctx(module_path, engine, test_mixed_cases);
+ TestContext ctx(module_path, engine);
fprintf(stderr, "module path: '%s'\n", ctx.module_path.c_str());
ctx.run_tests();
- if (ctx.skip_count > 0) {
- fprintf(stderr, "WARNING: skipped %zu mixed test cases\n", ctx.skip_count);
- }
}
} // namespace vespalib::eval::test
diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.h b/eval/src/vespa/eval/eval/test/tensor_conformance.h
index 91abd54b7ee..b9d4940bdba 100644
--- a/eval/src/vespa/eval/eval/test/tensor_conformance.h
+++ b/eval/src/vespa/eval/eval/test/tensor_conformance.h
@@ -14,7 +14,7 @@ namespace test {
* implementations of the TensorEngine interface.
**/
struct TensorConformance {
- static void run_tests(const vespalib::string &module_path, const TensorEngine &engine, bool test_mixed_cases);
+ static void run_tests(const vespalib::string &module_path, const TensorEngine &engine);
};
} // namespace vespalib::eval::test
diff --git a/eval/src/vespa/eval/tensor/CMakeLists.txt b/eval/src/vespa/eval/tensor/CMakeLists.txt
index 3b4111cc723..649b90de92e 100644
--- a/eval/src/vespa/eval/tensor/CMakeLists.txt
+++ b/eval/src/vespa/eval/tensor/CMakeLists.txt
@@ -7,4 +7,5 @@ vespa_add_library(eval_tensor OBJECT
tensor_apply.cpp
tensor_factory.cpp
tensor_mapper.cpp
+ wrapped_simple_tensor.cpp
)
diff --git a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp
index 571fe8a293e..910f92128ca 100644
--- a/eval/src/vespa/eval/tensor/default_tensor_engine.cpp
+++ b/eval/src/vespa/eval/tensor/default_tensor_engine.cpp
@@ -12,6 +12,7 @@
#include "dense/dense_tensor_builder.h"
#include "dense/dense_tensor_function_compiler.h"
#include "default_tensor.h"
+#include "wrapped_simple_tensor.h"
namespace vespalib {
namespace tensor {
@@ -25,19 +26,47 @@ using TensorSpec = eval::TensorSpec;
namespace {
-const Value &to_value(std::unique_ptr<Tensor> tensor, Stash &stash) {
- if (tensor->getType().is_tensor()) {
- return stash.create<TensorValue>(std::move(tensor));
+const eval::TensorEngine &simple_engine() { return eval::SimpleTensorEngine::ref(); }
+const eval::TensorEngine &default_engine() { return DefaultTensorEngine::ref(); }
+
+// map tensors to simple tensors before fall-back evaluation
+
+const eval::SimpleTensor &to_simple(const eval::Tensor &tensor, Stash &stash) {
+ if (auto wrapped = dynamic_cast<const WrappedSimpleTensor *>(&tensor)) {
+ return wrapped->get();
}
- return stash.create<DoubleValue>(tensor->sum());
+ TensorSpec spec = tensor.engine().to_spec(tensor);
+ using PTR = std::unique_ptr<eval::SimpleTensor>;
+ return *stash.create<PTR>(eval::SimpleTensor::create(spec));
+}
+
+const Value &to_simple(const Value &value, Stash &stash) {
+ if (auto tensor = value.as_tensor()) {
+ return stash.create<TensorValue>(to_simple(*tensor, stash));
+ }
+ return value;
}
-const Tensor &to_tensor(const Value &value, Stash &stash) {
+// map tensors to default tensors after fall-back evaluation
+
+const Value &to_default(const Value &value, Stash &stash) {
if (auto tensor = value.as_tensor()) {
- return static_cast<const Tensor &>(*tensor);
+ if (auto simple = dynamic_cast<const eval::SimpleTensor *>(tensor)) {
+ if (!Tensor::supported({simple->type()})) {
+ return stash.create<TensorValue>(std::make_unique<WrappedSimpleTensor>(*simple));
+ }
+ }
+ TensorSpec spec = tensor->engine().to_spec(*tensor);
+ return stash.create<TensorValue>(default_engine().create(spec));
+ }
+ return value;
+}
+
+const Value &to_value(std::unique_ptr<Tensor> tensor, Stash &stash) {
+ if (tensor->getType().is_tensor()) {
+ return stash.create<TensorValue>(std::move(tensor));
}
- return stash.create<DenseTensor>(ValueType::double_type(),
- std::vector<double>({value.as_double()}));
+ return stash.create<DoubleValue>(tensor->sum());
}
} // namespace vespalib::tensor::<unnamed>
@@ -59,9 +88,6 @@ DefaultTensorEngine::equal(const Tensor &a, const Tensor &b) const
assert(&b.engine() == this);
const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(a);
const tensor::Tensor &my_b = static_cast<const tensor::Tensor &>(b);
- if (my_a.getType().type() != my_b.getType().type()) {
- return false;
- }
return my_a.equals(my_b);
}
@@ -108,7 +134,7 @@ DefaultTensorEngine::create(const TensorSpec &spec) const
}
}
if (is_dense && is_sparse) {
- return DefaultTensor::builder().build();
+ return std::make_unique<WrappedSimpleTensor>(eval::SimpleTensor::create(spec));
} else if (is_dense) {
DenseTensorBuilder builder;
std::map<vespalib::string,DenseTensorBuilder::Dimension> dimension_map;
@@ -145,6 +171,9 @@ DefaultTensorEngine::reduce(const Tensor &tensor, const BinaryOperation &op, con
{
assert(&tensor.engine() == this);
const tensor::Tensor &my_tensor = static_cast<const tensor::Tensor &>(tensor);
+ if (!tensor::Tensor::supported({my_tensor.getType()})) {
+ return to_default(simple_engine().reduce(to_simple(my_tensor, stash), op, dimensions, stash), stash);
+ }
IsAddOperation check;
op.accept(check);
tensor::Tensor::UP result;
@@ -180,6 +209,9 @@ DefaultTensorEngine::map(const UnaryOperation &op, const Tensor &a, Stash &stash
{
assert(&a.engine() == this);
const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(a);
+ if (!tensor::Tensor::supported({my_a.getType()})) {
+ return to_default(simple_engine().map(op, to_simple(my_a, stash), stash), stash);
+ }
CellFunctionAdapter cell_function(op);
return to_value(my_a.apply(cell_function), stash);
}
@@ -227,8 +259,8 @@ DefaultTensorEngine::apply(const BinaryOperation &op, const Tensor &a, const Ten
assert(&b.engine() == this);
const tensor::Tensor &my_a = static_cast<const tensor::Tensor &>(a);
const tensor::Tensor &my_b = static_cast<const tensor::Tensor &>(b);
- if (my_a.getType().type() != my_b.getType().type()) {
- return stash.create<ErrorValue>();
+ if (!tensor::Tensor::supported({my_a.getType(), my_b.getType()})) {
+ return to_default(simple_engine().apply(op, to_simple(my_a, stash), to_simple(my_b, stash), stash), stash);
}
TensorOperationOverride tensor_override(my_a, my_b);
op.accept(tensor_override);
@@ -241,37 +273,14 @@ DefaultTensorEngine::apply(const BinaryOperation &op, const Tensor &a, const Ten
//-----------------------------------------------------------------------------
-namespace {
-
-const eval::TensorEngine &simple_engine() { return eval::SimpleTensorEngine::ref(); }
-const eval::TensorEngine &default_engine() { return DefaultTensorEngine::ref(); }
-
-// map tensors to simple tensors before fall-back evaluation
-const Value &to_simple(const Value &value, Stash &stash) {
- if (auto tensor = value.as_tensor()) {
- TensorSpec spec = tensor->engine().to_spec(*tensor);
- return stash.create<TensorValue>(simple_engine().create(spec));
- }
- return value;
-}
-
-// map tensors to default tensors after fall-back evaluation
-const Value &to_default(const Value &value, Stash &stash) {
- if (auto tensor = value.as_tensor()) {
- TensorSpec spec = tensor->engine().to_spec(*tensor);
- return stash.create<TensorValue>(default_engine().create(spec));
- }
- return value;
-}
-
-} // namespace vespalib::tensor::<unnamed>
-
-//-----------------------------------------------------------------------------
-
void
-DefaultTensorEngine::encode(const Value &value, nbostream &output, Stash &stash) const
+DefaultTensorEngine::encode(const Value &value, nbostream &output, Stash &) const
{
- TypedBinaryFormat::serialize(output, to_tensor(value, stash));
+ if (auto tensor = value.as_tensor()) {
+ TypedBinaryFormat::serialize(output, static_cast<const tensor::Tensor &>(*tensor));
+ } else {
+ TypedBinaryFormat::serialize(output, DenseTensor(ValueType::double_type(), {value.as_double()}));
+ }
}
const Value &
diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor.h b/eval/src/vespa/eval/tensor/dense/dense_tensor.h
index f4d6877bf61..91450ce3ce4 100644
--- a/eval/src/vespa/eval/tensor/dense/dense_tensor.h
+++ b/eval/src/vespa/eval/tensor/dense/dense_tensor.h
@@ -28,6 +28,7 @@ private:
public:
DenseTensor();
+ ~DenseTensor() {}
DenseTensor(const eval::ValueType &type_in,
const Cells &cells_in);
DenseTensor(const eval::ValueType &type_in,
diff --git a/eval/src/vespa/eval/tensor/serialization/format.txt b/eval/src/vespa/eval/tensor/serialization/format.txt
index 8c5d3b331d2..780f88af01a 100644
--- a/eval/src/vespa/eval/tensor/serialization/format.txt
+++ b/eval/src/vespa/eval/tensor/serialization/format.txt
@@ -8,7 +8,7 @@ effortlessly as possible with both existing formats.
//-----------------------------------------------------------------------------
-byte: type (1:sparse, 2:dense, 3:mixed)
+1_4_int: type (1:sparse, 2:dense, 3:mixed)
bit 0 -> 'sparse'
bit 1 -> 'dense'
(mixed tensors are tagged as both 'sparse' and 'dense')
diff --git a/eval/src/vespa/eval/tensor/serialization/typed_binary_format.cpp b/eval/src/vespa/eval/tensor/serialization/typed_binary_format.cpp
index e3800b3d7ad..229e9aa164e 100644
--- a/eval/src/vespa/eval/tensor/serialization/typed_binary_format.cpp
+++ b/eval/src/vespa/eval/tensor/serialization/typed_binary_format.cpp
@@ -8,6 +8,8 @@
#include <vespa/eval/tensor/default_tensor.h>
#include <vespa/eval/tensor/tensor.h>
#include <vespa/eval/tensor/dense/dense_tensor.h>
+#include <vespa/eval/eval/simple_tensor.h>
+#include <vespa/eval/tensor/wrapped_simple_tensor.h>
using vespalib::nbostream;
@@ -18,10 +20,11 @@ namespace tensor {
void
TypedBinaryFormat::serialize(nbostream &stream, const Tensor &tensor)
{
- const DenseTensor *denseTensor = dynamic_cast<const DenseTensor *>(&tensor);
- if (denseTensor != nullptr) {
+ if (auto denseTensor = dynamic_cast<const DenseTensor *>(&tensor)) {
stream.putInt1_4Bytes(DENSE_BINARY_FORMAT_TYPE);
DenseBinaryFormat::serialize(stream, *denseTensor);
+ } else if (auto wrapped = dynamic_cast<const WrappedSimpleTensor *>(&tensor)) {
+ eval::SimpleTensor::encode(wrapped->get(), stream);
} else {
stream.putInt1_4Bytes(SPARSE_BINARY_FORMAT_TYPE);
SparseBinaryFormat::serialize(stream, tensor);
@@ -32,6 +35,7 @@ TypedBinaryFormat::serialize(nbostream &stream, const Tensor &tensor)
std::unique_ptr<Tensor>
TypedBinaryFormat::deserialize(nbostream &stream)
{
+ auto read_pos = stream.rp();
auto formatId = stream.getInt1_4Bytes();
if (formatId == SPARSE_BINARY_FORMAT_TYPE) {
DefaultTensor::builder builder;
@@ -41,6 +45,10 @@ TypedBinaryFormat::deserialize(nbostream &stream)
if (formatId == DENSE_BINARY_FORMAT_TYPE) {
return DenseBinaryFormat::deserialize(stream);
}
+ if (formatId == MIXED_BINARY_FORMAT_TYPE) {
+ stream.adjustReadPos(read_pos - stream.rp());
+ return std::make_unique<WrappedSimpleTensor>(eval::SimpleTensor::decode(stream));
+ }
abort();
}
diff --git a/eval/src/vespa/eval/tensor/serialization/typed_binary_format.h b/eval/src/vespa/eval/tensor/serialization/typed_binary_format.h
index 772f820ffc5..5a042b4460a 100644
--- a/eval/src/vespa/eval/tensor/serialization/typed_binary_format.h
+++ b/eval/src/vespa/eval/tensor/serialization/typed_binary_format.h
@@ -18,6 +18,7 @@ class TypedBinaryFormat
{
static constexpr uint32_t SPARSE_BINARY_FORMAT_TYPE = 1u;
static constexpr uint32_t DENSE_BINARY_FORMAT_TYPE = 2u;
+ static constexpr uint32_t MIXED_BINARY_FORMAT_TYPE = 3u;
public:
static void serialize(nbostream &stream, const Tensor &tensor);
static std::unique_ptr<Tensor> deserialize(nbostream &stream);
diff --git a/eval/src/vespa/eval/tensor/tensor.cpp b/eval/src/vespa/eval/tensor/tensor.cpp
index 11ef2b0ad00..7c113e94d27 100644
--- a/eval/src/vespa/eval/tensor/tensor.cpp
+++ b/eval/src/vespa/eval/tensor/tensor.cpp
@@ -13,6 +13,21 @@ Tensor::Tensor()
{
}
+bool
+Tensor::supported(TypeList types)
+{
+ bool sparse = false;
+ bool dense = false;
+ for (const eval::ValueType &type: types) {
+ dense = (dense || type.is_double());
+ for (const auto &dim: type.dimensions()) {
+ dense = (dense || dim.is_indexed());
+ sparse = (sparse || dim.is_mapped());
+ }
+ }
+ return (dense != sparse);
+}
+
std::ostream &
operator<<(std::ostream &out, const Tensor &value)
{
diff --git a/eval/src/vespa/eval/tensor/tensor.h b/eval/src/vespa/eval/tensor/tensor.h
index 379dedcb2a2..856f01d15b9 100644
--- a/eval/src/vespa/eval/tensor/tensor.h
+++ b/eval/src/vespa/eval/tensor/tensor.h
@@ -50,6 +50,9 @@ struct Tensor : public eval::Tensor
virtual Tensor::UP clone() const = 0;
virtual eval::TensorSpec toSpec() const = 0;
virtual void accept(TensorVisitor &visitor) const = 0;
+
+ using TypeList = std::initializer_list<std::reference_wrapper<const eval::ValueType>>;
+ static bool supported(TypeList types);
};
std::ostream &operator<<(std::ostream &out, const Tensor &value);
diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp
new file mode 100644
index 00000000000..e6c22fcb1db
--- /dev/null
+++ b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.cpp
@@ -0,0 +1,130 @@
+// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "wrapped_simple_tensor.h"
+#include <vespa/eval/eval/simple_tensor_engine.h>
+
+namespace vespalib::tensor {
+
+bool
+WrappedSimpleTensor::equals(const Tensor &arg) const
+{
+ if (auto other = dynamic_cast<const WrappedSimpleTensor *>(&arg)) {
+ return eval::SimpleTensor::equal(_tensor, other->_tensor);
+ }
+ return false;
+}
+
+vespalib::string
+WrappedSimpleTensor::toString() const
+{
+ return eval::SimpleTensorEngine::ref().to_string(_tensor);
+}
+
+eval::TensorSpec
+WrappedSimpleTensor::toSpec() const
+{
+ return eval::SimpleTensorEngine::ref().to_spec(_tensor);
+}
+
+double
+WrappedSimpleTensor::sum() const
+{
+ double result = 0.0;
+ for (const auto &cell: _tensor.cells()) {
+ result += cell.value;
+ }
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+Tensor::UP
+WrappedSimpleTensor::add(const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::subtract(const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::multiply(const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::min(const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::max(const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::match(const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::apply(const CellFunction &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::sum(const vespalib::string &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::apply(const eval::BinaryOperation &, const Tensor &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+Tensor::UP
+WrappedSimpleTensor::reduce(const eval::BinaryOperation &, const std::vector<vespalib::string> &) const
+{
+ abort();
+ return Tensor::UP();
+}
+
+void
+WrappedSimpleTensor::print(std::ostream &) const
+{
+ abort();
+}
+
+Tensor::UP
+WrappedSimpleTensor::clone() const
+{
+ abort();
+ return Tensor::UP();
+}
+
+void
+WrappedSimpleTensor::accept(TensorVisitor &) const
+{
+ abort();
+}
+
+} // namespace vespalib::tensor
diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h
new file mode 100644
index 00000000000..7ea82946e96
--- /dev/null
+++ b/eval/src/vespa/eval/tensor/wrapped_simple_tensor.h
@@ -0,0 +1,52 @@
+// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "tensor.h"
+#include <vespa/eval/eval/simple_tensor.h>
+
+namespace vespalib::tensor {
+
+/**
+ * A thin wrapper around a SimpleTensor (tensor reference
+ * implementation) to be used as fallback for tensors with data
+ * layouts not supported by the default tensor implementation.
+ *
+ * Tensor implementation class is currently inferred from its value
+ * type. Consider adding explicit tagging to the tensor::Tensor
+ * default implementation top-level class in the future.
+ **/
+class WrappedSimpleTensor : public Tensor
+{
+private:
+ std::unique_ptr<eval::SimpleTensor> _space;
+ const eval::SimpleTensor &_tensor;
+public:
+ explicit WrappedSimpleTensor(const eval::SimpleTensor &tensor)
+ : _space(), _tensor(tensor) {}
+ explicit WrappedSimpleTensor(std::unique_ptr<eval::SimpleTensor> tensor)
+ : _space(std::move(tensor)), _tensor(*_space) {}
+ ~WrappedSimpleTensor() {}
+ const eval::SimpleTensor &get() const { return _tensor; }
+ const eval::ValueType &getType() const override { return _tensor.type(); }
+ bool equals(const Tensor &arg) const override;
+ vespalib::string toString() const override;
+ eval::TensorSpec toSpec() const override;
+ double sum() const override;
+ // functions below should not be used for this implementation
+ Tensor::UP add(const Tensor &) const override;
+ Tensor::UP subtract(const Tensor &) const override;
+ Tensor::UP multiply(const Tensor &) const override;
+ Tensor::UP min(const Tensor &) const override;
+ Tensor::UP max(const Tensor &) const override;
+ Tensor::UP match(const Tensor &) const override;
+ Tensor::UP apply(const CellFunction &) const override;
+ Tensor::UP sum(const vespalib::string &) const override;
+ Tensor::UP apply(const eval::BinaryOperation &, const Tensor &) const override;
+ Tensor::UP reduce(const eval::BinaryOperation &, const std::vector<vespalib::string> &) const override;
+ void print(std::ostream &out) const override;
+ Tensor::UP clone() const override;
+ void accept(TensorVisitor &visitor) const override;
+};
+
+} // namespace vespalib::tensor