diff options
author | Haavard <havardpe@yahoo-inc.com> | 2017-05-09 17:26:21 +0000 |
---|---|---|
committer | Haavard <havardpe@yahoo-inc.com> | 2017-05-10 15:22:31 +0000 |
commit | 2144879b22fdd47972a53009401eb6061648887a (patch) | |
tree | cf9599e0b852316e4dac37c9d5c7951d2b0a574b | |
parent | 70ab982ead81e15260e16a1448ce027a50aad875 (diff) |
test tensor binary format
12 files changed, 862 insertions, 7 deletions
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt index 79998163249..d4528cc8d28 100644 --- a/eval/CMakeLists.txt +++ b/eval/CMakeLists.txt @@ -5,6 +5,7 @@ vespa_define_module( APPS src/apps/eval_expr + src/apps/make_tensor_binary_format_test_spec TESTS src/tests/eval/aggr @@ -18,6 +19,7 @@ vespa_define_module( src/tests/eval/param_usage src/tests/eval/simple_tensor src/tests/eval/tensor_function + src/tests/eval/tensor_spec src/tests/eval/value_cache src/tests/eval/value_type src/tests/tensor/dense_dot_product_function diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/.gitignore b/eval/src/apps/make_tensor_binary_format_test_spec/.gitignore new file mode 100644 index 00000000000..aff1914d6bf --- /dev/null +++ b/eval/src/apps/make_tensor_binary_format_test_spec/.gitignore @@ -0,0 +1 @@ +/eval_make_tensor_binary_format_test_spec_app diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt b/eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt new file mode 100644 index 00000000000..b12bd919124 --- /dev/null +++ b/eval/src/apps/make_tensor_binary_format_test_spec/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(eval_make_tensor_binary_format_test_spec_app + SOURCES + make_tensor_binary_format_test_spec.cpp + DEPENDS + vespaeval +) diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp b/eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp new file mode 100644 index 00000000000..4e274aa513d --- /dev/null +++ b/eval/src/apps/make_tensor_binary_format_test_spec/make_tensor_binary_format_test_spec.cpp @@ -0,0 +1,320 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/data/slime/slime.h> +#include <vespa/vespalib/objects/nbostream.h> +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/eval/eval/tensor_spec.h> +#include <vespa/eval/eval/value_type.h> +#include <iostream> + +using namespace vespalib; +using namespace vespalib::eval; +using namespace vespalib::slime::convenience; + +using Opts = std::initializer_list<std::reference_wrapper<const nbostream>>; +using Dict = std::vector<vespalib::string>; + +//----------------------------------------------------------------------------- + +nbostream make_sparse() { + nbostream data; + data << uint8_t(0x1); + return data; +} + +nbostream make_dense() { + nbostream data; + data << uint8_t(0x2); + return data; +} + +nbostream make_mixed() { + nbostream data; + data << uint8_t(0x3); + return data; +} + +void set_tensor(Cursor &test, const TensorSpec &spec) { + const Inspector &old_tensor = test["tensor"]; + if (old_tensor.valid()) { + TensorSpec old_spec = TensorSpec::from_slime(old_tensor); + if (!(old_spec == spec)) { + fprintf(stderr, "inconsistent specs\n"); + std::cerr << old_spec; + std::cerr << spec; + abort(); // inconsistent specs across binary permutations + } + } else { + Cursor &tensor = test.setObject("tensor"); + spec.to_slime(tensor); + } +} + +void add_binary(Cursor &test, const nbostream &data) { + if (!test["binary"].valid()) { + test.setArray("binary"); + } + test["binary"].addData(Memory(data.peek(), data.size())); +} + +void add_binary(Cursor &test, Opts opts) { + for (const nbostream &opt: opts) { + add_binary(test, opt); + } +} + +std::vector<Dict> perm(const Dict &dict) { + std::vector<Dict> list; + if (dict.empty()) { + } else if (dict.size() == 1) { + list.push_back(dict); + } else if (dict.size() == 2) { + list.push_back({dict[0], dict[1]}); + list.push_back({dict[1], dict[0]}); + } else if (dict.size() == 3) { + list.push_back({dict[0], dict[1], dict[2]}); + list.push_back({dict[0], dict[2], dict[1]}); + list.push_back({dict[1], dict[0], dict[2]}); + list.push_back({dict[1], dict[2], dict[0]}); + list.push_back({dict[2], dict[0], dict[1]}); + list.push_back({dict[2], dict[1], dict[0]}); + } else { + fprintf(stderr, "unsupported permutation size: %zu\n", dict.size()); + abort(); // perm only implemented for sizes (0,1,2,3) + } + return list; +}; + +const std::map<std::string, double> val_map{ + {"a", 1.0}, + {"b", 2.0}, + {"c", 3.0}, + {"foo", 1.0}, + {"bar", 2.0}}; + +double val(size_t idx) { return double(idx + 1); } +double val(const vespalib::string &label) { + auto res = val_map.find(label); + if (res == val_map.end()) { + fprintf(stderr, "unsupported label: '%s'\n", label.c_str()); + abort(); // unsupported label + } + return res->second; +} +double mix(std::initializer_list<double> vals) { + double value = 0.0; + for (double val: vals) { + value = ((value * 10) + val); + } + return value; +} + +//----------------------------------------------------------------------------- + +void make_number_test(Cursor &test, double value) { + TensorSpec spec("double"); + spec.add({{}}, value); + nbostream sparse = make_sparse(); + sparse.putInt1_4Bytes(0); + sparse.putInt1_4Bytes(1); + sparse << value; + nbostream dense = make_dense(); + dense.putInt1_4Bytes(0); + dense << value; + nbostream mixed = make_mixed(); + mixed.putInt1_4Bytes(0); + mixed.putInt1_4Bytes(0); + mixed << value; + set_tensor(test, spec); + add_binary(test, {sparse, dense, mixed}); + if (value == 0.0) { + nbostream empty = make_sparse(); + empty.putInt1_4Bytes(0); + empty.putInt1_4Bytes(0); + add_binary(test, empty); + } +} + +//----------------------------------------------------------------------------- + +void make_vector_test(Cursor &test, size_t x_size) { + TensorSpec spec(vespalib::make_string("tensor(x[%zu])", x_size)); + nbostream dense = make_dense(); + dense.putInt1_4Bytes(1); + dense.writeSmallString("x"); + dense.putInt1_4Bytes(x_size); + nbostream mixed = make_mixed(); + mixed.putInt1_4Bytes(0); + mixed.putInt1_4Bytes(1); + mixed.writeSmallString("x"); + mixed.putInt1_4Bytes(x_size); + for (size_t x = 0; x < x_size; ++x) { + double value = val(x); + spec.add({{"x", x}}, value); + dense << value; + mixed << value; + } + set_tensor(test, spec); + add_binary(test, {dense, mixed}); +} + +void make_matrix_test(Cursor &test, size_t x_size, size_t y_size) { + TensorSpec spec(vespalib::make_string("tensor(x[%zu],y[%zu])", x_size, y_size)); + nbostream dense = make_dense(); + dense.putInt1_4Bytes(2); + dense.writeSmallString("x"); + dense.putInt1_4Bytes(x_size); + dense.writeSmallString("y"); + dense.putInt1_4Bytes(y_size); + nbostream mixed = make_mixed(); + mixed.putInt1_4Bytes(0); + mixed.putInt1_4Bytes(2); + mixed.writeSmallString("x"); + mixed.putInt1_4Bytes(x_size); + mixed.writeSmallString("y"); + mixed.putInt1_4Bytes(y_size); + for (size_t x = 0; x < x_size; ++x) { + for (size_t y = 0; y < y_size; ++y) { + double value = mix({val(x), val(y)}); + spec.add({{"x", x}, {"y", y}}, value); + dense << value; + mixed << value; + } + } + set_tensor(test, spec); + add_binary(test, {dense, mixed}); +} + +//----------------------------------------------------------------------------- + +void make_map_test(Cursor &test, const Dict &x_dict_in) { + TensorSpec spec("tensor(x{})"); + nbostream sparse_base = make_sparse(); + sparse_base.putInt1_4Bytes(1); + sparse_base.writeSmallString("x"); + sparse_base.putInt1_4Bytes(x_dict_in.size()); + nbostream mixed_base = make_mixed(); + mixed_base.putInt1_4Bytes(1); + mixed_base.writeSmallString("x"); + mixed_base.putInt1_4Bytes(0); + mixed_base.putInt1_4Bytes(x_dict_in.size()); + auto x_perm = perm(x_dict_in); + for (const Dict &x_dict: x_perm) { + nbostream sparse = sparse_base; + nbostream mixed = mixed_base; + for (vespalib::string x: x_dict) { + double value = val(x); + spec.add({{"x", x}}, value); + sparse.writeSmallString(x); + mixed.writeSmallString(x); + sparse << value; + mixed << value; + } + set_tensor(test, spec); + add_binary(test, {sparse, mixed}); + } + if (x_dict_in.empty()) { + set_tensor(test, spec); + add_binary(test, {sparse_base, mixed_base}); + } +} + +void make_mesh_test(Cursor &test, const Dict &x_dict_in, const vespalib::string &y) { + TensorSpec spec("tensor(x{},y{})"); + nbostream sparse_base = make_sparse(); + sparse_base.putInt1_4Bytes(2); + sparse_base.writeSmallString("x"); + sparse_base.writeSmallString("y"); + sparse_base.putInt1_4Bytes(x_dict_in.size() * 1); + nbostream mixed_base = make_mixed(); + mixed_base.putInt1_4Bytes(2); + mixed_base.writeSmallString("x"); + mixed_base.writeSmallString("y"); + mixed_base.putInt1_4Bytes(0); + mixed_base.putInt1_4Bytes(x_dict_in.size() * 1); + auto x_perm = perm(x_dict_in); + for (const Dict &x_dict: x_perm) { + nbostream sparse = sparse_base; + nbostream mixed = mixed_base; + for (vespalib::string x: x_dict) { + double value = mix({val(x), val(y)}); + spec.add({{"x", x}, {"y", y}}, value); + sparse.writeSmallString(x); + sparse.writeSmallString(y); + mixed.writeSmallString(x); + mixed.writeSmallString(y); + sparse << value; + mixed << value; + } + set_tensor(test, spec); + add_binary(test, {sparse, mixed}); + } + if (x_dict_in.empty()) { + set_tensor(test, spec); + add_binary(test, {sparse_base, mixed_base}); + } +} + +//----------------------------------------------------------------------------- + +void make_vector_map_test(Cursor &test, + const vespalib::string &mapped_name, const Dict &mapped_dict, + const vespalib::string &indexed_name, size_t indexed_size) +{ + auto type_str = vespalib::make_string("tensor(%s{},%s[%zu])", + mapped_name.c_str(), indexed_name.c_str(), indexed_size); + ValueType type = ValueType::from_spec(type_str); + TensorSpec spec(type.to_spec()); // ensures type string is normalized + nbostream mixed_base = make_mixed(); + mixed_base.putInt1_4Bytes(1); + mixed_base.writeSmallString(mapped_name); + mixed_base.putInt1_4Bytes(1); + mixed_base.writeSmallString(indexed_name); + mixed_base.putInt1_4Bytes(indexed_size); + mixed_base.putInt1_4Bytes(mapped_dict.size()); + auto mapped_perm = perm(mapped_dict); + for (const Dict &dict: mapped_perm) { + nbostream mixed = mixed_base; + for (vespalib::string label: dict) { + mixed.writeSmallString(label); + for (size_t idx = 0; idx < indexed_size; ++idx) { + double value = mix({val(label), val(idx)}); + spec.add({{mapped_name, label}, {indexed_name, idx}}, value); + mixed << value; + } + } + set_tensor(test, spec); + add_binary(test, mixed); + } + if (mapped_dict.empty()) { + set_tensor(test, spec); + add_binary(test, mixed_base); + } +} + +//----------------------------------------------------------------------------- + +void make_tests(Cursor &tests) { + make_number_test(tests.addObject(), 0.0); + make_number_test(tests.addObject(), 42.0); + make_vector_test(tests.addObject(), 3); + make_matrix_test(tests.addObject(), 2, 3); + make_map_test(tests.addObject(), {}); + make_map_test(tests.addObject(), {"a", "b", "c"}); + make_mesh_test(tests.addObject(), {}, "a"); + make_mesh_test(tests.addObject(), {"foo", "bar"}, "a"); + make_vector_map_test(tests.addObject(), "x", {}, "y", 10); + make_vector_map_test(tests.addObject(), "y", {}, "x", 10); + make_vector_map_test(tests.addObject(), "x", {"a", "b"}, "y", 3); + make_vector_map_test(tests.addObject(), "y", {"a", "b"}, "x", 3); +} + +int main(int, char **) { + Slime slime; + Cursor &top = slime.setObject(); + Cursor &tests = top.setArray("tests"); + make_tests(tests); + top.setLong("num_tests", tests.entries()); + fprintf(stdout, "%s", slime.toString().c_str()); + return 0; +} diff --git a/eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json b/eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json new file mode 100644 index 00000000000..1b74b4b8838 --- /dev/null +++ b/eval/src/apps/make_tensor_binary_format_test_spec/test_spec.json @@ -0,0 +1,336 @@ +{ + "tests": [ + { + "tensor": { + "type": "double", + "cells": [ + { + "address": { + }, + "value": 0 + } + ] + }, + "binary": [ + "0x0100010000000000000000", + "0x02000000000000000000", + "0x0300000000000000000000", + "0x010000" + ] + }, + { + "tensor": { + "type": "double", + "cells": [ + { + "address": { + }, + "value": 42 + } + ] + }, + "binary": [ + "0x0100014045000000000000", + "0x02004045000000000000", + "0x0300004045000000000000" + ] + }, + { + "tensor": { + "type": "tensor(x[3])", + "cells": [ + { + "address": { + "x": 0 + }, + "value": 1 + }, + { + "address": { + "x": 1 + }, + "value": 2 + }, + { + "address": { + "x": 2 + }, + "value": 3 + } + ] + }, + "binary": [ + "0x02010178033FF000000000000040000000000000004008000000000000", + "0x0300010178033FF000000000000040000000000000004008000000000000" + ] + }, + { + "tensor": { + "type": "tensor(x[2],y[3])", + "cells": [ + { + "address": { + "x": 0, + "y": 0 + }, + "value": 11 + }, + { + "address": { + "x": 0, + "y": 1 + }, + "value": 12 + }, + { + "address": { + "x": 0, + "y": 2 + }, + "value": 13 + }, + { + "address": { + "x": 1, + "y": 0 + }, + "value": 21 + }, + { + "address": { + "x": 1, + "y": 1 + }, + "value": 22 + }, + { + "address": { + "x": 1, + "y": 2 + }, + "value": 23 + } + ] + }, + "binary": [ + "0x020201780201790340260000000000004028000000000000402A000000000000403500000000000040360000000000004037000000000000", + "0x03000201780201790340260000000000004028000000000000402A000000000000403500000000000040360000000000004037000000000000" + ] + }, + { + "tensor": { + "type": "tensor(x{})", + "cells": [ + ] + }, + "binary": [ + "0x0101017800", + "0x030101780000" + ] + }, + { + "tensor": { + "type": "tensor(x{})", + "cells": [ + { + "address": { + "x": "a" + }, + "value": 1 + }, + { + "address": { + "x": "b" + }, + "value": 2 + }, + { + "address": { + "x": "c" + }, + "value": 3 + } + ] + }, + "binary": [ + "0x010101780301613FF00000000000000162400000000000000001634008000000000000", + "0x03010178000301613FF00000000000000162400000000000000001634008000000000000", + "0x010101780301613FF00000000000000163400800000000000001624000000000000000", + "0x03010178000301613FF00000000000000163400800000000000001624000000000000000", + "0x01010178030162400000000000000001613FF000000000000001634008000000000000", + "0x0301017800030162400000000000000001613FF000000000000001634008000000000000", + "0x0101017803016240000000000000000163400800000000000001613FF0000000000000", + "0x030101780003016240000000000000000163400800000000000001613FF0000000000000", + "0x01010178030163400800000000000001613FF000000000000001624000000000000000", + "0x0301017800030163400800000000000001613FF000000000000001624000000000000000", + "0x0101017803016340080000000000000162400000000000000001613FF0000000000000", + "0x030101780003016340080000000000000162400000000000000001613FF0000000000000" + ] + }, + { + "tensor": { + "type": "tensor(x{},y{})", + "cells": [ + ] + }, + "binary": [ + "0x01020178017900", + "0x0302017801790000" + ] + }, + { + "tensor": { + "type": "tensor(x{},y{})", + "cells": [ + { + "address": { + "x": "bar", + "y": "a" + }, + "value": 21 + }, + { + "address": { + "x": "foo", + "y": "a" + }, + "value": 11 + } + ] + }, + "binary": [ + "0x0102017801790203666F6F016140260000000000000362617201614035000000000000", + "0x030201780179000203666F6F016140260000000000000362617201614035000000000000", + "0x01020178017902036261720161403500000000000003666F6F01614026000000000000", + "0x0302017801790002036261720161403500000000000003666F6F01614026000000000000" + ] + }, + { + "tensor": { + "type": "tensor(x{},y[10])", + "cells": [ + ] + }, + "binary": [ + "0x030101780101790A00" + ] + }, + { + "tensor": { + "type": "tensor(x[10],y{})", + "cells": [ + ] + }, + "binary": [ + "0x030101790101780A00" + ] + }, + { + "tensor": { + "type": "tensor(x{},y[3])", + "cells": [ + { + "address": { + "x": "a", + "y": 0 + }, + "value": 11 + }, + { + "address": { + "x": "a", + "y": 1 + }, + "value": 12 + }, + { + "address": { + "x": "a", + "y": 2 + }, + "value": 13 + }, + { + "address": { + "x": "b", + "y": 0 + }, + "value": 21 + }, + { + "address": { + "x": "b", + "y": 1 + }, + "value": 22 + }, + { + "address": { + "x": "b", + "y": 2 + }, + "value": 23 + } + ] + }, + "binary": [ + "0x030101780101790302016140260000000000004028000000000000402A0000000000000162403500000000000040360000000000004037000000000000", + "0x0301017801017903020162403500000000000040360000000000004037000000000000016140260000000000004028000000000000402A000000000000" + ] + }, + { + "tensor": { + "type": "tensor(x[3],y{})", + "cells": [ + { + "address": { + "x": 0, + "y": "a" + }, + "value": 11 + }, + { + "address": { + "x": 0, + "y": "b" + }, + "value": 21 + }, + { + "address": { + "x": 1, + "y": "a" + }, + "value": 12 + }, + { + "address": { + "x": 1, + "y": "b" + }, + "value": 22 + }, + { + "address": { + "x": 2, + "y": "a" + }, + "value": 13 + }, + { + "address": { + "x": 2, + "y": "b" + }, + "value": 23 + } + ] + }, + "binary": [ + "0x030101790101780302016140260000000000004028000000000000402A0000000000000162403500000000000040360000000000004037000000000000", + "0x0301017901017803020162403500000000000040360000000000004037000000000000016140260000000000004028000000000000402A000000000000" + ] + } + ], + "num_tests": 12 +} diff --git a/eval/src/tests/eval/tensor_spec/CMakeLists.txt b/eval/src/tests/eval/tensor_spec/CMakeLists.txt new file mode 100644 index 00000000000..65e927f8b61 --- /dev/null +++ b/eval/src/tests/eval/tensor_spec/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(eval_tensor_spec_test_app TEST + SOURCES + tensor_spec_test.cpp + DEPENDS + vespaeval +) +vespa_add_test(NAME eval_tensor_spec_test_app COMMAND eval_tensor_spec_test_app) diff --git a/eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp b/eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp new file mode 100644 index 00000000000..ecbaf292037 --- /dev/null +++ b/eval/src/tests/eval/tensor_spec/tensor_spec_test.cpp @@ -0,0 +1,22 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/eval/eval/tensor_spec.h> +#include <vespa/vespalib/data/slime/slime.h> + +using vespalib::Slime; +using vespalib::eval::TensorSpec; + +TEST("require that a tensor spec can be converted to and from slime") { + TensorSpec spec("tensor(x[2],y{})"); + spec.add({{"x", 0}, {"y", "xxx"}}, 1.0) + .add({{"x", 0}, {"y", "yyy"}}, 2.0) + .add({{"x", 1}, {"y", "xxx"}}, 3.0) + .add({{"x", 1}, {"y", "yyy"}}, 4.0); + Slime slime; + spec.to_slime(slime.setObject()); + fprintf(stderr, "tensor spec as slime: \n%s\n", slime.get().toString().c_str()); + EXPECT_EQUAL(TensorSpec::from_slime(slime.get()), spec); +} + +TEST_MAIN() { TEST_RUN_ALL(); } 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 1865f863e4d..4ba5e1180cd 100644 --- a/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp +++ b/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp @@ -8,12 +8,15 @@ using vespalib::eval::SimpleTensorEngine; using vespalib::eval::test::TensorConformance; using vespalib::tensor::DefaultTensorEngine; +vespalib::string module_path(TEST_PATH("../../../../")); + + TEST("require that reference tensor implementation passes all conformance tests") { - TEST_DO(TensorConformance::run_tests(SimpleTensorEngine::ref(), true)); + TEST_DO(TensorConformance::run_tests(module_path, SimpleTensorEngine::ref(), true)); } IGNORE_TEST("require that production tensor implementation passes non-mixed conformance tests") { - TEST_DO(TensorConformance::run_tests(DefaultTensorEngine::ref(), false)); + TEST_DO(TensorConformance::run_tests(module_path, DefaultTensorEngine::ref(), false)); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/vespa/eval/eval/tensor_spec.cpp b/eval/src/vespa/eval/eval/tensor_spec.cpp index ac59f0d4424..9f07f5fe7b2 100644 --- a/eval/src/vespa/eval/eval/tensor_spec.cpp +++ b/eval/src/vespa/eval/eval/tensor_spec.cpp @@ -2,11 +2,33 @@ #include "tensor_spec.h" #include <vespa/vespalib/util/stringfmt.h> +#include <vespa/vespalib/data/slime/slime.h> #include <ostream> namespace vespalib { namespace eval { +namespace { + +TensorSpec::Address extract_address(const slime::Inspector &address) { + struct Extractor : slime::ObjectTraverser { + TensorSpec::Address address; + void field(const Memory &dimension, const slime::Inspector &label) override { + if (label.type().getId() == slime::STRING::ID) { + address.emplace(dimension.make_string(), TensorSpec::Label(label.asString().make_string())); + } else if (label.type().getId() == slime::LONG::ID) { + address.emplace(dimension.make_string(), TensorSpec::Label(label.asLong())); + } + } + }; + Extractor extractor; + address.traverse(extractor); + return extractor.address; +} + +} // namespace vespalib::eval::<unnamed> + + TensorSpec::TensorSpec(const vespalib::string &type_spec) : _type(type_spec), _cells() @@ -40,6 +62,38 @@ TensorSpec::to_string() const return out; } +void +TensorSpec::to_slime(slime::Cursor &tensor) const +{ + tensor.setString("type", _type); + slime::Cursor &cells = tensor.setArray("cells"); + for (const auto &my_cell: _cells) { + slime::Cursor &cell = cells.addObject(); + slime::Cursor &address = cell.setObject("address"); + for (const auto &label: my_cell.first) { + if (label.second.is_mapped()) { + address.setString(label.first, label.second.name); + } else { + address.setLong(label.first, label.second.index); + } + } + cell.setDouble("value", my_cell.second.value); + } +} + +TensorSpec +TensorSpec::from_slime(const slime::Inspector &tensor) +{ + TensorSpec spec(tensor["type"].asString().make_string()); + const slime::Inspector &cells = tensor["cells"]; + for (size_t i = 0; i < cells.entries(); ++i) { + const slime::Inspector &cell = cells[i]; + Address address = extract_address(cell["address"]); + spec.add(address, cell["value"].asDouble()); + } + return spec; +} + bool operator==(const TensorSpec &lhs, const TensorSpec &rhs) { diff --git a/eval/src/vespa/eval/eval/tensor_spec.h b/eval/src/vespa/eval/eval/tensor_spec.h index 268b870aab9..27ca0060ecb 100644 --- a/eval/src/vespa/eval/eval/tensor_spec.h +++ b/eval/src/vespa/eval/eval/tensor_spec.h @@ -8,6 +8,14 @@ #include <map> namespace vespalib { + +namespace slime { + +class Cursor; +class Inspector; + +} // namespace vespalib::slime + namespace eval { /** @@ -65,6 +73,8 @@ public: const vespalib::string &type() const { return _type; } const Cells &cells() const { return _cells; } vespalib::string to_string() const; + void to_slime(slime::Cursor &tensor) const; + static TensorSpec from_slime(const slime::Inspector &tensor); }; bool operator==(const TensorSpec &lhs, const TensorSpec &rhs); diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp index 74dfeaebe31..9adcf77a052 100644 --- a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp +++ b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp @@ -11,12 +11,18 @@ #include <vespa/eval/eval/interpreted_function.h> #include <vespa/eval/eval/aggr.h> #include <vespa/vespalib/objects/nbostream.h> +#include <vespa/vespalib/data/slime/slime.h> +#include <vespa/vespalib/io/mapped_file_input.h> namespace vespalib { namespace eval { namespace test { namespace { +using slime::Cursor; +using slime::Inspector; +using slime::JsonFormat; + // Random access sequence of numbers struct Sequence { virtual double operator[](size_t i) const = 0; @@ -598,16 +604,57 @@ void verify_result(const Eval::Result &result, const TensorSpec &expect) { } } +uint8_t unhex(char c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } + if (c >= 'A' && c <= 'F') { + return ((c - 'A') + 10); + } + TEST_ERROR("bad hex char"); + return 0; +} + +nbostream extract_data(const Memory &hex_dump) { + nbostream data; + if ((hex_dump.size > 2) && (hex_dump.data[0] == '0') && (hex_dump.data[1] == 'x')) { + for (size_t i = 2; i < (hex_dump.size - 1); i += 2) { + data << uint8_t((unhex(hex_dump.data[i]) << 4) | unhex(hex_dump.data[i + 1])); + } + } + 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())); +} + // Test wrapper to avoid passing global test parameters around struct TestContext { + vespalib::string module_path; const TensorEngine &ref_engine; const TensorEngine &engine; bool test_mixed_cases; size_t skip_count; - TestContext(const TensorEngine &engine_in, bool test_mixed_cases_in) - : ref_engine(SimpleTensorEngine::ref()), engine(engine_in), + 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) {} std::unique_ptr<Tensor> tensor(const TensorSpec &spec) { @@ -1237,7 +1284,50 @@ struct TestContext { } } + void test_binary_format_spec(Cursor &test) { + Stash stash; + 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); + } + } + + void test_binary_format_spec() { + vespalib::string path = module_path; + path.append("src/apps/make_tensor_binary_format_test_spec/test_spec.json"); + MappedFileInput file(path); + Slime slime; + EXPECT_TRUE(file.valid()); + EXPECT_EQUAL(JsonFormat::decode(file, slime), file.get().size); + int64_t num_tests = slime.get()["num_tests"].asLong(); + Cursor &tests = slime.get()["tests"]; + EXPECT_GREATER(num_tests, 0u); + EXPECT_EQUAL(size_t(num_tests), tests.entries()); + for (size_t i = 0; i < tests.entries(); ++i) { + size_t fail_cnt = TEST_MASTER.getProgress().failCnt; + TEST_DO(test_binary_format_spec(tests[i])); + if (TEST_MASTER.getProgress().failCnt > fail_cnt) { + fprintf(stderr, "failed:\n%s", tests[i].toString().c_str()); + } + } + } + void test_binary_format() { + TEST_DO(test_binary_format_spec()); TEST_DO(verify_encode_decode(spec(42))); TEST_DO(verify_encode_decode(spec({x(3)}, N()))); TEST_DO(verify_encode_decode(spec({x(3),y(5)}, N()))); @@ -1271,9 +1361,10 @@ struct TestContext { } // namespace vespalib::eval::test::<unnamed> void -TensorConformance::run_tests(const TensorEngine &engine, bool test_mixed_cases) +TensorConformance::run_tests(const vespalib::string &module_path, const TensorEngine &engine, bool test_mixed_cases) { - TestContext ctx(engine, test_mixed_cases); + TestContext ctx(module_path, engine, test_mixed_cases); + 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); diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.h b/eval/src/vespa/eval/eval/test/tensor_conformance.h index 054a1bb8faa..91abd54b7ee 100644 --- a/eval/src/vespa/eval/eval/test/tensor_conformance.h +++ b/eval/src/vespa/eval/eval/test/tensor_conformance.h @@ -3,6 +3,7 @@ #pragma once #include <vespa/eval/eval/tensor_engine.h> +#include <vespa/vespalib/stllike/string.h> namespace vespalib { namespace eval { @@ -13,7 +14,7 @@ namespace test { * implementations of the TensorEngine interface. **/ struct TensorConformance { - static void run_tests(const TensorEngine &engine, bool test_mixed_cases); + static void run_tests(const vespalib::string &module_path, const TensorEngine &engine, bool test_mixed_cases); }; } // namespace vespalib::eval::test |