diff options
author | Arne H Juul <arnej27959@users.noreply.github.com> | 2020-10-19 16:07:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-19 16:07:33 +0200 |
commit | 8fd4f84b036b55d53324088393bdee8312fbd262 (patch) | |
tree | ffda0eec401abf11264117bc4a29e832c15aef4a | |
parent | 60bfc13f5d855705d888f1c80a6265913bd0d98d (diff) | |
parent | 07c7bec5b7b0c94b3b8a18db6ae922ac5258471f (diff) |
Merge pull request #14945 from vespa-engine/arnej/add-wrapped-simple-value-2
Arnej/add wrapped simple value 2
24 files changed, 628 insertions, 58 deletions
diff --git a/eval/src/tests/instruction/generic_concat/generic_concat_test.cpp b/eval/src/tests/instruction/generic_concat/generic_concat_test.cpp index b48c1e2253c..675aa36d63a 100644 --- a/eval/src/tests/instruction/generic_concat/generic_concat_test.cpp +++ b/eval/src/tests/instruction/generic_concat/generic_concat_test.cpp @@ -177,4 +177,24 @@ TEST(GenericConcatTest, dense_concat_plan_can_be_created) { EXPECT_EQ(plan.right.out_stride, expect_right_out_s); } +TensorSpec immediate_generic_concat(const TensorSpec &a, const TensorSpec &b, const std::string &concat_dim) { + const auto &factory = SimpleValueBuilderFactory::get(); + auto lhs = value_from_spec(a, factory); + auto rhs = value_from_spec(b, factory); + auto up = GenericConcat::perform_concat(*lhs, *rhs, concat_dim, factory); + return spec_from_value(*up); +} + +TEST(GenericConcatTest, immediate_generic_concat_works) { + ASSERT_TRUE((concat_layouts.size() % 2) == 0); + for (size_t i = 0; i < concat_layouts.size(); i += 2) { + const TensorSpec lhs = spec(concat_layouts[i], N()); + const TensorSpec rhs = spec(concat_layouts[i + 1], Div16(N())); + SCOPED_TRACE(fmt("\n===\nin LHS: %s\nin RHS: %s\n===\n", lhs.to_string().c_str(), rhs.to_string().c_str())); + auto actual = immediate_generic_concat(lhs, rhs, "y"); + auto expect = reference_concat(lhs, rhs, "y"); + EXPECT_EQ(actual, expect); + } +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/eval/src/tests/instruction/generic_join/generic_join_test.cpp b/eval/src/tests/instruction/generic_join/generic_join_test.cpp index 82696bbbd5e..cf04b2ca990 100644 --- a/eval/src/tests/instruction/generic_join/generic_join_test.cpp +++ b/eval/src/tests/instruction/generic_join/generic_join_test.cpp @@ -148,4 +148,26 @@ TEST(GenericJoinTest, generic_join_works_for_simple_and_fast_values) { } } +TensorSpec immediate_generic_join(const TensorSpec &a, const TensorSpec &b, join_fun_t function) { + const auto &factory = SimpleValueBuilderFactory::get(); + auto lhs = value_from_spec(a, factory); + auto rhs = value_from_spec(b, factory); + auto up = GenericJoin::perform_join(*lhs, *rhs, function, factory); + return spec_from_value(*up); +} + +TEST(GenericJoinTest, immediate_generic_join_works) { + ASSERT_TRUE((join_layouts.size() % 2) == 0); + for (size_t i = 0; i < join_layouts.size(); i += 2) { + TensorSpec lhs = spec(join_layouts[i], Div16(N())); + TensorSpec rhs = spec(join_layouts[i + 1], Div16(N())); + for (auto fun: {operation::Add::f, operation::Sub::f, operation::Mul::f, operation::Div::f}) { + SCOPED_TRACE(fmt("\n===\nLHS: %s\nRHS: %s\n===\n", lhs.to_string().c_str(), rhs.to_string().c_str())); + auto expect = reference_join(lhs, rhs, fun); + auto actual = immediate_generic_join(lhs, rhs, fun); + EXPECT_EQ(actual, expect); + } + } +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/eval/src/tests/instruction/generic_map/generic_map_test.cpp b/eval/src/tests/instruction/generic_map/generic_map_test.cpp index 77f5ffc33db..69fb417e0ad 100644 --- a/eval/src/tests/instruction/generic_map/generic_map_test.cpp +++ b/eval/src/tests/instruction/generic_map/generic_map_test.cpp @@ -64,4 +64,24 @@ TEST(GenericMapTest, generic_map_works_for_simple_values) { test_generic_map(SimpleValueBuilderFactory::get()); } +TensorSpec immediate_generic_map(const TensorSpec &a, map_fun_t func, const ValueBuilderFactory &factory) +{ + auto lhs = value_from_spec(a, factory); + auto up = GenericMap::perform_map(*lhs, func, factory); + return spec_from_value(*up); +} + +TEST(GenericMapTest, immediate_generic_map_works) { + for (const auto & layout : map_layouts) { + TensorSpec lhs = spec(layout, Div16(N())); + ValueType lhs_type = ValueType::from_spec(lhs.type()); + for (auto func : {operation::Floor::f, operation::Fabs::f, operation::Square::f, operation::Inv::f}) { + SCOPED_TRACE(fmt("\n===\nLHS: %s\n===\n", lhs.to_string().c_str())); + auto expect = reference_map(lhs, func); + auto actual = immediate_generic_map(lhs, func, SimpleValueBuilderFactory::get()); + EXPECT_EQ(actual, expect); + } + } +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/eval/src/tests/instruction/generic_merge/generic_merge_test.cpp b/eval/src/tests/instruction/generic_merge/generic_merge_test.cpp index ae2e100176b..fd9b8513acb 100644 --- a/eval/src/tests/instruction/generic_merge/generic_merge_test.cpp +++ b/eval/src/tests/instruction/generic_merge/generic_merge_test.cpp @@ -79,4 +79,26 @@ TEST(GenericMergeTest, generic_merge_works_for_simple_values) { } } +TensorSpec immediate_generic_merge(const TensorSpec &a, const TensorSpec &b, join_fun_t fun) { + const auto &factory = SimpleValueBuilderFactory::get(); + auto lhs = value_from_spec(a, factory); + auto rhs = value_from_spec(b, factory); + auto up = GenericMerge::perform_merge(*lhs, *rhs, fun, factory); + return spec_from_value(*up); +} + +TEST(GenericMergeTest, immediate_generic_merge_works) { + ASSERT_TRUE((merge_layouts.size() % 2) == 0); + for (size_t i = 0; i < merge_layouts.size(); i += 2) { + TensorSpec lhs = spec(merge_layouts[i], N()); + TensorSpec rhs = spec(merge_layouts[i + 1], Div16(N())); + SCOPED_TRACE(fmt("\n===\nLHS: %s\nRHS: %s\n===\n", lhs.to_string().c_str(), rhs.to_string().c_str())); + for (auto fun: {operation::Add::f, operation::Mul::f, operation::Sub::f, operation::Max::f}) { + auto expect = reference_merge(lhs, rhs, fun); + auto actual = immediate_generic_merge(lhs, rhs, fun); + EXPECT_EQ(actual, expect); + } + } +} + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/eval/src/tests/instruction/generic_reduce/generic_reduce_test.cpp b/eval/src/tests/instruction/generic_reduce/generic_reduce_test.cpp index 1329ad549c5..490055bf02f 100644 --- a/eval/src/tests/instruction/generic_reduce/generic_reduce_test.cpp +++ b/eval/src/tests/instruction/generic_reduce/generic_reduce_test.cpp @@ -110,4 +110,28 @@ TEST(GenericReduceTest, generic_reduce_works_for_simple_values) { } } +TensorSpec immediate_generic_reduce(const TensorSpec &a, const std::vector<vespalib::string> &dims, Aggr aggr) { + const auto &factory = SimpleValueBuilderFactory::get(); + auto lhs = value_from_spec(a, factory); + auto up = GenericReduce::perform_reduce(*lhs, aggr, dims, factory); + return spec_from_value(*up); +} + +TEST(GenericReduceTest, immediate_generic_reduce_works) { + for (const Layout &layout: layouts) { + TensorSpec input = spec(layout, Div16(N())); + for (Aggr aggr: {Aggr::SUM, Aggr::AVG, Aggr::MIN, Aggr::MAX}) { + for (const Domain &domain: layout) { + auto expect = reference_reduce(input, {domain.dimension}, aggr); + auto actual = immediate_generic_reduce(input, {domain.dimension}, aggr); + EXPECT_EQ(actual, expect); + } + auto expect = reference_reduce(input, {}, aggr); + auto actual = immediate_generic_reduce(input, {}, aggr); + EXPECT_EQ(actual, expect); + } + } +} + + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/eval/src/tests/instruction/generic_rename/generic_rename_test.cpp b/eval/src/tests/instruction/generic_rename/generic_rename_test.cpp index 3f151af1fd6..124462f0e0f 100644 --- a/eval/src/tests/instruction/generic_rename/generic_rename_test.cpp +++ b/eval/src/tests/instruction/generic_rename/generic_rename_test.cpp @@ -142,4 +142,29 @@ TEST(GenericRenameTest, generic_rename_works_for_simple_values) { test_generic_rename(SimpleValueBuilderFactory::get()); } +TensorSpec immediate_generic_rename(const TensorSpec &a, const FromTo &ft) +{ + auto &factory = SimpleValueBuilderFactory::get(); + auto lhs = value_from_spec(a, factory); + auto up = GenericRename::perform_rename(*lhs, ft.from, ft.to, factory); + return spec_from_value(*up); +} + +TEST(GenericRenameTest, immediate_generic_rename_works) { + for (const auto & layout : rename_layouts) { + TensorSpec lhs = spec(layout, N()); + ValueType lhs_type = ValueType::from_spec(lhs.type()); + // printf("lhs_type: %s\n", lhs_type.to_spec().c_str()); + for (const auto & from_to : rename_from_to) { + ValueType renamed_type = lhs_type.rename(from_to.from, from_to.to); + if (renamed_type.is_error()) continue; + // printf("type %s -> %s\n", lhs_type.to_spec().c_str(), renamed_type.to_spec().c_str()); + SCOPED_TRACE(fmt("\n===\nLHS: %s\n===\n", lhs.to_string().c_str())); + auto expect = reference_rename(lhs, from_to); + auto actual = immediate_generic_rename(lhs, from_to); + EXPECT_EQ(actual, expect); + } + } +} + GTEST_MAIN_RUN_ALL_TESTS() 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 a9681cd85c9..233aff0e425 100644 --- a/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp +++ b/eval/src/tests/tensor/tensor_conformance/tensor_conformance_test.cpp @@ -25,11 +25,11 @@ TEST("require that production tensor implementation passes all conformance tests TEST_DO(TensorConformance::run_tests(module_src_path, DefaultTensorEngine::ref())); } -TEST("require that SimpleValue implementation passes all conformance tests (except ImmediateAPI tests)") { +TEST("require that SimpleValue implementation passes all conformance tests") { TEST_DO(TensorConformance::run_tests(module_src_path, SimpleValueBuilderFactory::get())); } -TEST("require that FastValue implementation passes all conformance tests (except ImmediateAPI tests)") { +TEST("require that FastValue implementation passes all conformance tests") { TEST_DO(TensorConformance::run_tests(module_src_path, FastValueBuilderFactory::get())); } diff --git a/eval/src/vespa/eval/eval/engine_or_factory.cpp b/eval/src/vespa/eval/eval/engine_or_factory.cpp index 2a3ba2e543f..e4f710be625 100644 --- a/eval/src/vespa/eval/eval/engine_or_factory.cpp +++ b/eval/src/vespa/eval/eval/engine_or_factory.cpp @@ -5,16 +5,25 @@ #include "simple_value.h" #include "value_codec.h" #include "simple_tensor_engine.h" +#include <vespa/eval/instruction/generic_concat.h> +#include <vespa/eval/instruction/generic_join.h> +#include <vespa/eval/instruction/generic_map.h> +#include <vespa/eval/instruction/generic_merge.h> +#include <vespa/eval/instruction/generic_reduce.h> +#include <vespa/eval/instruction/generic_rename.h> #include <vespa/eval/tensor/default_tensor_engine.h> #include <vespa/eval/tensor/default_value_builder_factory.h> #include <vespa/eval/tensor/mixed/packed_mixed_tensor_builder_factory.h> #include <vespa/vespalib/data/memory.h> #include <vespa/vespalib/objects/nbostream.h> #include <vespa/vespalib/util/exceptions.h> +#include <vespa/vespalib/util/stash.h> #include <vespa/vespalib/util/stringfmt.h> using vespalib::make_string_short::fmt; +using namespace vespalib::eval::instruction; + namespace vespalib::eval { EngineOrFactory EngineOrFactory::_default{tensor::DefaultTensorEngine::ref()}; @@ -88,32 +97,56 @@ EngineOrFactory::copy(const Value &value) const Value & EngineOrFactory::map(const Value &a, operation::op1_t function, Stash &stash) const { - return engine().map(a, function, stash); + if (is_engine()) { + return engine().map(a, function, stash); + } else { + return *stash.create<Value::UP>(GenericMap::perform_map(a, function, factory())); + } } const Value & EngineOrFactory::join(const Value &a, const Value &b, operation::op2_t function, Stash &stash) const { - return engine().join(a, b, function, stash); + if (is_engine()) { + return engine().join(a, b, function, stash); + } else { + return *stash.create<Value::UP>(GenericJoin::perform_join(a, b, function, factory())); + } } const Value & EngineOrFactory::merge(const Value &a, const Value &b, operation::op2_t function, Stash &stash) const { - return engine().merge(a, b, function, stash); + if (is_engine()) { + return engine().merge(a, b, function, stash); + } else { + return *stash.create<Value::UP>(GenericMerge::perform_merge(a, b, function, factory())); + } } const Value & EngineOrFactory::reduce(const Value &a, Aggr aggr, const std::vector<vespalib::string> &dimensions, Stash &stash) const { - return engine().reduce(a, aggr, dimensions, stash); + if (is_engine()) { + return engine().reduce(a, aggr, dimensions, stash); + } else { + return *stash.create<Value::UP>(GenericReduce::perform_reduce(a, aggr, dimensions, factory())); + } } const Value & EngineOrFactory::concat(const Value &a, const Value &b, const vespalib::string &dimension, Stash &stash) const { - return engine().concat(a, b, dimension, stash); + if (is_engine()) { + return engine().concat(a, b, dimension, stash); + } else { + return *stash.create<Value::UP>(GenericConcat::perform_concat(a, b, dimension, factory())); + } } const Value & EngineOrFactory::rename(const Value &a, const std::vector<vespalib::string> &from, const std::vector<vespalib::string> &to, Stash &stash) const { - return engine().rename(a, from, to, stash); + if (is_engine()) { + return engine().rename(a, from, to, stash); + } else { + return *stash.create<Value::UP>(GenericRename::perform_rename(a, from, to, factory())); + } } void diff --git a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp index c6f73d611cc..0e703e81073 100644 --- a/eval/src/vespa/eval/eval/test/tensor_conformance.cpp +++ b/eval/src/vespa/eval/eval/test/tensor_conformance.cpp @@ -339,9 +339,7 @@ struct TestContext { vespalib::string expr = make_string("reduce(a,%s,%s)", AggrNames::name_of(aggr)->c_str(), domain.dimension.c_str()); TEST_DO(verify_reduce_result(Expr_T(expr), input, expect)); - if (engine.is_engine()) { - TEST_DO(verify_reduce_result(ImmediateReduce(aggr, domain.dimension), input, expect)); - } + TEST_DO(verify_reduce_result(ImmediateReduce(aggr, domain.dimension), input, expect)); } { Eval::Result expect = ImmediateReduce(aggr).eval(ref_engine, input); @@ -349,9 +347,7 @@ struct TestContext { infer_type(layout).c_str()).c_str()); vespalib::string expr = make_string("reduce(a,%s)", AggrNames::name_of(aggr)->c_str()); TEST_DO(verify_reduce_result(Expr_T(expr), input, expect)); - if (engine.is_engine()) { - TEST_DO(verify_reduce_result(ImmediateReduce(aggr), input, expect)); - } + TEST_DO(verify_reduce_result(ImmediateReduce(aggr), input, expect)); } } } @@ -388,9 +384,7 @@ struct TestContext { } void test_map_op(const vespalib::string &expr, map_fun_t op, const Sequence &seq) { - if (engine.is_engine()) { - TEST_DO(test_map_op(ImmediateMap(op), op, seq)); - } + TEST_DO(test_map_op(ImmediateMap(op), op, seq)); TEST_DO(test_map_op(Expr_T(expr), op, seq)); TEST_DO(test_map_op(Expr_T(make_string("map(x,f(a)(%s))", expr.c_str())), op, seq)); } @@ -667,9 +661,7 @@ struct TestContext { } void test_apply_op(const vespalib::string &expr, join_fun_t op, const Sequence &seq) { - if (engine.is_engine()) { - TEST_DO(test_apply_op(ImmediateJoin(op), op, seq)); - } + TEST_DO(test_apply_op(ImmediateJoin(op), op, seq)); TEST_DO(test_apply_op(Expr_TT(expr), op, seq)); TEST_DO(test_apply_op(Expr_TT(make_string("join(x,y,f(a,b)(%s))", expr.c_str())), op, seq)); } @@ -732,10 +724,8 @@ struct TestContext { const TensorSpec &expect) { vespalib::string expr = make_string("concat(a,b,%s)", dimension.c_str()); - if (engine.is_engine()) { - ImmediateConcat eval(dimension); - TEST_DO(verify_result(eval.eval(engine, a, b), expect)); - } + ImmediateConcat eval(dimension); + TEST_DO(verify_result(eval.eval(engine, a, b), expect)); TEST_DO(verify_result(Expr_TT(expr).eval(engine, a, b), expect)); } @@ -773,10 +763,8 @@ struct TestContext { const std::vector<vespalib::string> &to, const TensorSpec &expect) { - if (engine.is_engine()) { - ImmediateRename eval(from, to); - TEST_DO(verify_result(eval.eval(engine, input), expect)); - } + ImmediateRename eval(from, to); + TEST_DO(verify_result(eval.eval(engine, input), expect)); TEST_DO(verify_result(Expr_T(expr).eval(engine, input), expect)); } diff --git a/eval/src/vespa/eval/instruction/generic_concat.cpp b/eval/src/vespa/eval/instruction/generic_concat.cpp index cb8b5ee3b2f..eaa2dbf95ae 100644 --- a/eval/src/vespa/eval/instruction/generic_concat.cpp +++ b/eval/src/vespa/eval/instruction/generic_concat.cpp @@ -121,6 +121,15 @@ struct SelectGenericConcatOp { } }; +struct PerformGenericConcat { + template <typename LCT, typename RCT, typename OCT> + static auto invoke(const Value &a, const Value &b, const ConcatParam ¶m) { + return generic_concat<LCT, RCT, OCT>(a, b, + param.sparse_plan, param.dense_plan, + param.res_type, param.factory); + } +}; + enum class Case { NONE, OUT, CONCAT, BOTH }; } // namespace <unnamed> @@ -183,6 +192,21 @@ DenseConcatPlan::InOutLoop::fill_from(const ValueType &in_type, return std::make_pair(offset_for_concat, output_size_for_concat); } +DenseConcatPlan::DenseConcatPlan(const ValueType &lhs_type, + const ValueType &rhs_type, + std::string concat_dimension, + const ValueType &out_type) +{ + std::tie(right_offset, output_size) = left.fill_from(lhs_type, concat_dimension, out_type); + auto [ other_offset, other_size ] = right.fill_from(rhs_type, concat_dimension, out_type); + assert(other_offset > 0); + assert(output_size == other_size); +} + +DenseConcatPlan::~DenseConcatPlan() = default; +DenseConcatPlan::InOutLoop::~InOutLoop() = default; + + InterpretedFunction::Instruction GenericConcat::make_instruction(const ValueType &lhs_type, const ValueType &rhs_type, const vespalib::string &dimension, @@ -195,18 +219,15 @@ GenericConcat::make_instruction(const ValueType &lhs_type, const ValueType &rhs_ return Instruction(fun, wrap_param<ConcatParam>(param)); } -DenseConcatPlan::DenseConcatPlan(const ValueType &lhs_type, - const ValueType &rhs_type, - std::string concat_dimension, - const ValueType &out_type) +Value::UP +GenericConcat::perform_concat(const Value &a, const Value &b, + const vespalib::string &dimension, + const ValueBuilderFactory &factory) { - std::tie(right_offset, output_size) = left.fill_from(lhs_type, concat_dimension, out_type); - auto [ other_offset, other_size ] = right.fill_from(rhs_type, concat_dimension, out_type); - assert(other_offset > 0); - assert(output_size == other_size); + ConcatParam param(a.type(), b.type(), dimension, factory); + return typify_invoke<3,TypifyCellType,PerformGenericConcat>( + a.type().cell_type(), b.type().cell_type(), param.res_type.cell_type(), + a, b, param); } -DenseConcatPlan::~DenseConcatPlan() = default; -DenseConcatPlan::InOutLoop::~InOutLoop() = default; - } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_concat.h b/eval/src/vespa/eval/instruction/generic_concat.h index 5578c5a0dca..d41d161900a 100644 --- a/eval/src/vespa/eval/instruction/generic_concat.h +++ b/eval/src/vespa/eval/instruction/generic_concat.h @@ -17,6 +17,11 @@ struct GenericConcat { make_instruction(const ValueType &lhs_type, const ValueType &rhs_type, const vespalib::string &dimension, const ValueBuilderFactory &factory, Stash &stash); + + static Value::UP + perform_concat(const Value &a, const Value &b, + const vespalib::string &dimension, + const ValueBuilderFactory &factory); }; struct DenseConcatPlan { diff --git a/eval/src/vespa/eval/instruction/generic_join.cpp b/eval/src/vespa/eval/instruction/generic_join.cpp index 83c50fdc0a2..c7bb17a808e 100644 --- a/eval/src/vespa/eval/instruction/generic_join.cpp +++ b/eval/src/vespa/eval/instruction/generic_join.cpp @@ -24,8 +24,9 @@ namespace { //----------------------------------------------------------------------------- template <typename LCT, typename RCT, typename OCT, typename Fun> -void my_mixed_join_op(State &state, uint64_t param_in) { - const auto ¶m = unwrap_param<JoinParam>(param_in); +Value::UP +generic_mixed_join(const Value &lhs, const Value &rhs, const JoinParam ¶m) +{ Fun fun(param.function); auto dense_join = [&](const LCT *my_lhs, const RCT *my_rhs, OCT *my_res) { @@ -33,8 +34,6 @@ void my_mixed_join_op(State &state, uint64_t param_in) { *my_res++ = fun(my_lhs[lhs_idx], my_rhs[rhs_idx]); }); }; - const Value &lhs = state.peek(1); - const Value &rhs = state.peek(0); auto lhs_cells = lhs.cells().typify<LCT>(); auto rhs_cells = rhs.cells().typify<RCT>(); SparseJoinState sparse(param.sparse_plan, lhs.index(), rhs.index()); @@ -50,7 +49,16 @@ void my_mixed_join_op(State &state, uint64_t param_in) { builder->add_subspace(sparse.full_address).begin()); } } - auto &result = state.stash.create<std::unique_ptr<Value>>(builder->build(std::move(builder))); + return builder->build(std::move(builder)); +}; + +template <typename LCT, typename RCT, typename OCT, typename Fun> +void my_mixed_join_op(State &state, uint64_t param_in) { + const auto ¶m = unwrap_param<JoinParam>(param_in); + const Value &lhs = state.peek(1); + const Value &rhs = state.peek(0); + auto up = generic_mixed_join<LCT, RCT, OCT, Fun>(lhs, rhs, param); + auto &result = state.stash.create<std::unique_ptr<Value>>(std::move(up)); const Value &result_ref = *(result.get()); state.pop_pop_push(result_ref); }; @@ -141,6 +149,16 @@ struct SelectGenericJoinOp { } }; +struct PerformGenericJoin { + template <typename LCT, typename RCT, typename OCT, typename Fun> + static auto invoke(const Value &a, const Value &b, const JoinParam ¶m) + { + return generic_mixed_join<LCT, RCT, OCT, Fun>(a, b, param); + } +}; + + + //----------------------------------------------------------------------------- } // namespace <unnamed> @@ -263,4 +281,15 @@ GenericJoin::make_instruction(const ValueType &lhs_type, const ValueType &rhs_ty return Instruction(fun, wrap_param<JoinParam>(param)); } + +Value::UP +GenericJoin::perform_join(const Value &a, const Value &b, join_fun_t function, + const ValueBuilderFactory &factory) +{ + JoinParam param(a.type(), b.type(), function, factory); + return typify_invoke<4,JoinTypify,PerformGenericJoin>( + a.type().cell_type(), b.type().cell_type(), param.res_type.cell_type(), function, + a, b, param); +} + } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_join.h b/eval/src/vespa/eval/instruction/generic_join.h index 3b1b4433032..49cdb3499a9 100644 --- a/eval/src/vespa/eval/instruction/generic_join.h +++ b/eval/src/vespa/eval/instruction/generic_join.h @@ -18,8 +18,13 @@ using join_fun_t = vespalib::eval::operation::op2_t; struct GenericJoin { static InterpretedFunction::Instruction - make_instruction(const ValueType &lhs_type, const ValueType &rhs_type, join_fun_t function, + make_instruction(const ValueType &lhs_type, const ValueType &rhs_type, + join_fun_t function, const ValueBuilderFactory &factory, Stash &stash); + + static Value::UP + perform_join(const Value &a, const Value &b, join_fun_t function, + const ValueBuilderFactory &factory); }; //----------------------------------------------------------------------------- diff --git a/eval/src/vespa/eval/instruction/generic_map.cpp b/eval/src/vespa/eval/instruction/generic_map.cpp index 7ead705328a..689339af5b3 100644 --- a/eval/src/vespa/eval/instruction/generic_map.cpp +++ b/eval/src/vespa/eval/instruction/generic_map.cpp @@ -38,6 +38,36 @@ struct SelectGenericMapOp { } }; +struct PerformGenericMap { + template <typename CT, typename Func> + static auto invoke(const Value &input, map_fun_t function_in, + const ValueBuilderFactory &factory) + { + Func fun(function_in); + const auto &type = input.type(); + size_t subspace_size = type.dense_subspace_size(); + size_t num_mapped = type.count_mapped_dimensions(); + auto builder = factory.create_value_builder<CT>(type, num_mapped, subspace_size, input.index().size()); + auto input_cells = input.cells().typify<CT>(); + auto view = input.index().create_view({}); + std::vector<vespalib::stringref> output_address(num_mapped); + std::vector<vespalib::stringref *> input_address; + for (auto & label : output_address) { + input_address.push_back(&label); + } + view->lookup({}); + size_t subspace; + while (view->next_result(input_address, subspace)) { + auto dst = builder->add_subspace(output_address); + size_t input_offset = subspace_size * subspace; + for (size_t i = 0; i < subspace_size; ++i) { + dst[i] = fun(input_cells[input_offset + i]); + } + } + return builder->build(std::move(builder)); + } +}; + } // namespace <unnamed> using MapTypify = TypifyValue<TypifyCellType,operation::TypifyOp1>; @@ -49,4 +79,12 @@ GenericMap::make_instruction(const ValueType &lhs_type, map_fun_t function) return Instruction(op, to_param(function)); } +Value::UP +GenericMap::perform_map(const Value &a, map_fun_t function, + const ValueBuilderFactory &factory) +{ + return typify_invoke<2,MapTypify,PerformGenericMap>(a.type().cell_type(), function, + a, function, factory); +} + } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_map.h b/eval/src/vespa/eval/instruction/generic_map.h index ad29d2c1073..9c8fb17c153 100644 --- a/eval/src/vespa/eval/instruction/generic_map.h +++ b/eval/src/vespa/eval/instruction/generic_map.h @@ -15,6 +15,10 @@ using map_fun_t = vespalib::eval::operation::op1_t; struct GenericMap { static InterpretedFunction::Instruction make_instruction(const ValueType &input_type, map_fun_t function); + + static Value::UP + perform_map(const Value &a, map_fun_t function, + const ValueBuilderFactory &factory); }; } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_merge.cpp b/eval/src/vespa/eval/instruction/generic_merge.cpp index f54ae651374..51c9dcdbb15 100644 --- a/eval/src/vespa/eval/instruction/generic_merge.cpp +++ b/eval/src/vespa/eval/instruction/generic_merge.cpp @@ -122,6 +122,13 @@ struct SelectGenericMergeOp { } }; +struct PerformGenericMerge { + template <typename LCT, typename RCT, typename OCT, typename Fun> + static auto invoke(const Value &a, const Value &b, const MergeParam ¶m) { + return generic_mixed_merge<LCT,RCT,OCT,Fun>(a, b, param); + } +}; + //----------------------------------------------------------------------------- } // namespace <unnamed> @@ -137,4 +144,18 @@ GenericMerge::make_instruction(const ValueType &lhs_type, const ValueType &rhs_t return Instruction(fun, wrap_param<MergeParam>(param)); } + +Value::UP +GenericMerge::perform_merge(const Value &a, const Value &b, join_fun_t function, + const ValueBuilderFactory &factory) +{ + MergeParam param(a.type(), b.type(), function, factory); + return typify_invoke<4,MergeTypify,PerformGenericMerge>( + a.type().cell_type(), + b.type().cell_type(), + param.res_type.cell_type(), function, + a, b, param); +} + + } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_merge.h b/eval/src/vespa/eval/instruction/generic_merge.h index 02e2d18715a..e9ffcc87997 100644 --- a/eval/src/vespa/eval/instruction/generic_merge.h +++ b/eval/src/vespa/eval/instruction/generic_merge.h @@ -8,8 +8,13 @@ namespace vespalib::eval::instruction { struct GenericMerge { static InterpretedFunction::Instruction - make_instruction(const ValueType &lhs_type, const ValueType &rhs_type, join_fun_t function, + make_instruction(const ValueType &lhs_type, const ValueType &rhs_type, + join_fun_t function, const ValueBuilderFactory &factory, Stash &stash); + + static Value::UP + perform_merge(const Value &a, const Value &b, join_fun_t function, + const ValueBuilderFactory &factory); }; } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_reduce.cpp b/eval/src/vespa/eval/instruction/generic_reduce.cpp index a76ee322759..305f51ea445 100644 --- a/eval/src/vespa/eval/instruction/generic_reduce.cpp +++ b/eval/src/vespa/eval/instruction/generic_reduce.cpp @@ -94,10 +94,9 @@ struct SparseReduceState { SparseReduceState::~SparseReduceState() = default; template <typename ICT, typename OCT, typename AGGR> -void my_generic_reduce_op(State &state, uint64_t param_in) { - const auto ¶m = unwrap_param<ReduceParam>(param_in); +Value::UP +generic_reduce(const Value &value, const ReduceParam ¶m) { SparseReduceState sparse(param.sparse_plan); - const Value &value = state.peek(0); sparse.populate_map(value.index().create_view({})); auto cells = value.cells().typify<ICT>(); AGGR aggr; @@ -123,7 +122,15 @@ void my_generic_reduce_op(State &state, uint64_t param_in) { zero[i] = OCT{}; } } - auto &result = state.stash.create<std::unique_ptr<Value>>(builder->build(std::move(builder))); + return builder->build(std::move(builder)); +} + +template <typename ICT, typename OCT, typename AGGR> +void my_generic_reduce_op(State &state, uint64_t param_in) { + const auto ¶m = unwrap_param<ReduceParam>(param_in); + const Value &value = state.peek(0); + auto up = generic_reduce<ICT, OCT, AGGR>(value, param); + auto &result = state.stash.create<std::unique_ptr<Value>>(std::move(up)); const Value &result_ref = *(result.get()); state.pop_push(result_ref); }; @@ -134,6 +141,13 @@ struct SelectGenericReduceOp { } }; +struct PerformGenericReduce { + template <typename ICT, typename OCT, typename AGGR> + static auto invoke(const Value &input, const ReduceParam ¶m) { + return generic_reduce<ICT, OCT, typename AGGR::template templ<ICT>>(input, param); + } +}; + //----------------------------------------------------------------------------- } // namespace <unnamed> @@ -213,4 +227,16 @@ GenericReduce::make_instruction(const ValueType &type, Aggr aggr, const std::vec return Instruction(fun, wrap_param<ReduceParam>(param)); } + +Value::UP +GenericReduce::perform_reduce(const Value &a, Aggr aggr, + const std::vector<vespalib::string> &dimensions, + const ValueBuilderFactory &factory) +{ + ReduceParam param(a.type(), dimensions, factory); + return typify_invoke<3,ReduceTypify,PerformGenericReduce>( + a.type().cell_type(), param.res_type.cell_type(), aggr, + a, param); +} + } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_reduce.h b/eval/src/vespa/eval/instruction/generic_reduce.h index e412f8795ac..856cfe362ae 100644 --- a/eval/src/vespa/eval/instruction/generic_reduce.h +++ b/eval/src/vespa/eval/instruction/generic_reduce.h @@ -14,14 +14,6 @@ namespace vespalib::eval::instruction { //----------------------------------------------------------------------------- -struct GenericReduce { - static InterpretedFunction::Instruction - make_instruction(const ValueType &type, Aggr aggr, const std::vector<vespalib::string> &dimensions, - const ValueBuilderFactory &factory, Stash &stash); -}; - -//----------------------------------------------------------------------------- - struct DenseReducePlan { size_t in_size; size_t out_size; @@ -51,4 +43,18 @@ struct SparseReducePlan { //----------------------------------------------------------------------------- +struct GenericReduce { + static InterpretedFunction::Instruction + make_instruction(const ValueType &type, Aggr aggr, + const std::vector<vespalib::string> &dimensions, + const ValueBuilderFactory &factory, Stash &stash); + + static Value::UP + perform_reduce(const Value &a, Aggr aggr, + const std::vector<vespalib::string> &dimensions, + const ValueBuilderFactory &factory); +}; + +//----------------------------------------------------------------------------- + } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_rename.cpp b/eval/src/vespa/eval/instruction/generic_rename.cpp index 1ce18597ec2..3d8de356001 100644 --- a/eval/src/vespa/eval/instruction/generic_rename.cpp +++ b/eval/src/vespa/eval/instruction/generic_rename.cpp @@ -107,6 +107,14 @@ struct SelectGenericRenameOp { } }; +struct PerformGenericRename { + template <typename CT> + static auto invoke(const Value &a, const RenameParam ¶m) { + return generic_rename<CT>(a, param.sparse_plan, param.dense_plan, + param.res_type, param.factory); + } +}; + } // namespace <unnamed> //----------------------------------------------------------------------------- @@ -186,5 +194,19 @@ GenericRename::make_instruction(const ValueType &lhs_type, return Instruction(fun, wrap_param<RenameParam>(param)); } + +Value::UP +GenericRename::perform_rename(const Value &a, + const std::vector<vespalib::string> &rename_dimension_from, + const std::vector<vespalib::string> &rename_dimension_to, + const ValueBuilderFactory &factory) +{ + RenameParam param(a.type(), + rename_dimension_from, rename_dimension_to, + factory); + return typify_invoke<1,TypifyCellType,PerformGenericRename>(a.type().cell_type(), + a, param); +} + } // namespace diff --git a/eval/src/vespa/eval/instruction/generic_rename.h b/eval/src/vespa/eval/instruction/generic_rename.h index 6c94ff02b24..4088e817b90 100644 --- a/eval/src/vespa/eval/instruction/generic_rename.h +++ b/eval/src/vespa/eval/instruction/generic_rename.h @@ -44,6 +44,12 @@ struct GenericRename { const std::vector<vespalib::string> &rename_dimension_from, const std::vector<vespalib::string> &rename_dimension_to, const ValueBuilderFactory &factory, Stash &stash); + + static Value::UP + perform_rename(const Value &a, + const std::vector<vespalib::string> &rename_dimension_from, + const std::vector<vespalib::string> &rename_dimension_to, + const ValueBuilderFactory &factory); }; } // namespace diff --git a/eval/src/vespa/eval/tensor/CMakeLists.txt b/eval/src/vespa/eval/tensor/CMakeLists.txt index b75b34098f5..77ae1daec88 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.cpp tensor_address.cpp wrapped_simple_tensor.cpp + wrapped_simple_value.cpp ) diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_value.cpp b/eval/src/vespa/eval/tensor/wrapped_simple_value.cpp new file mode 100644 index 00000000000..eb552c21aa0 --- /dev/null +++ b/eval/src/vespa/eval/tensor/wrapped_simple_value.cpp @@ -0,0 +1,174 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "wrapped_simple_value.h" +#include "cell_values.h" +#include "tensor_address_builder.h" +#include "tensor_visitor.h" +#include <vespa/eval/eval/memory_usage_stuff.h> +#include <vespa/eval/eval/simple_value.h> +#include <vespa/eval/eval/tensor_spec.h> +#include <vespa/eval/eval/value_codec.h> +#include <vespa/vespalib/util/stringfmt.h> + +#include <vespa/log/log.h> +LOG_SETUP(".eval.tensor.wrapped_simple_value"); + +using vespalib::eval::TensorSpec; +using vespalib::eval::SimpleValueBuilderFactory; + +namespace vespalib::tensor { + +namespace { + +TensorSpec::Address +sparsify_address(const TensorSpec::Address &address) +{ + TensorSpec::Address result; + for (const auto &elem : address) { + if (elem.second.is_indexed()) { + auto val = vespalib::make_string("%zu", elem.second.index); + result.emplace(elem.first, TensorSpec::Label(val)); + } else { + result.emplace(elem); + } + } + return result; +} + +TensorSpec::Address +extract_sparse_address(const TensorSpec::Address &address) +{ + TensorSpec::Address result; + for (const auto &elem : address) { + if (elem.second.is_mapped()) { + result.emplace(elem); + } + } + return result; +} + +Tensor::UP wrap(eval::Value::UP value) { + return std::make_unique<WrappedSimpleValue>(std::move(value)); +} + +} // namespace <unnamed> + + +eval::TensorSpec +WrappedSimpleValue::toSpec() const +{ + return spec_from_value(_tensor); +} + +void +WrappedSimpleValue::accept(TensorVisitor &visitor) const +{ + TensorSpec myspec = toSpec(); + TensorAddressBuilder addr; + for (const auto & cell : myspec.cells()) { + auto sparse_addr = sparsify_address(cell.first); + addr.clear(); + for (const auto & dim_and_label : sparse_addr) { + addr.add(dim_and_label.first, dim_and_label.second.name); + } + visitor.visit(addr.build(), cell.second); + } +} + +MemoryUsage +WrappedSimpleValue::get_memory_usage() const +{ + MemoryUsage rv = eval::self_memory_usage<WrappedSimpleValue>(); + if (_space) { + rv.merge(_space->get_memory_usage()); + } + return rv; +} + +//----------------------------------------------------------------------------- + +Tensor::UP +WrappedSimpleValue::apply(const CellFunction &) const +{ + LOG_ABORT("should not be reached"); +} + +Tensor::UP +WrappedSimpleValue::join(join_fun_t, const Tensor &) const +{ + LOG_ABORT("should not be reached"); +} + +Tensor::UP +WrappedSimpleValue::merge(join_fun_t, const Tensor &) const +{ + LOG_ABORT("should not be reached"); +} + +Tensor::UP +WrappedSimpleValue::reduce(join_fun_t, const std::vector<vespalib::string> &) const +{ + LOG_ABORT("should not be reached"); +} + +Tensor::UP +WrappedSimpleValue::modify(join_fun_t fun, const CellValues &cellValues) const +{ + TensorSpec a = toSpec(); + TensorSpec b = cellValues.toSpec(); + TensorSpec result(a.type()); + auto end_iter = b.cells().end(); + for (const auto &cell: a.cells()) { + double v = cell.second; + auto sparse_addr = sparsify_address(cell.first); + auto iter = b.cells().find(sparse_addr); + if (iter == end_iter) { + result.add(cell.first, v); + } else { + result.add(cell.first, fun(v, iter->second)); + } + } + return wrap(value_from_spec(result, SimpleValueBuilderFactory::get())); +} + +Tensor::UP +WrappedSimpleValue::add(const Tensor &rhs) const +{ + TensorSpec a = toSpec(); + TensorSpec b = rhs.toSpec(); + if (a.type() != b.type()) { + return {}; + } + TensorSpec result(a.type()); + for (const auto &cell: b.cells()) { + result.add(cell.first, cell.second); + } + auto end_iter = b.cells().end(); + for (const auto &cell: a.cells()) { + auto iter = b.cells().find(cell.first); + if (iter == end_iter) { + result.add(cell.first, cell.second); + } + } + return wrap(value_from_spec(result, SimpleValueBuilderFactory::get())); +} + + +Tensor::UP +WrappedSimpleValue::remove(const CellValues &rhs) const +{ + TensorSpec a = toSpec(); + TensorSpec b = rhs.toSpec(); + TensorSpec result(a.type()); + auto end_iter = b.cells().end(); + for (const auto &cell: a.cells()) { + TensorSpec::Address mappedAddress = extract_sparse_address(cell.first); + auto iter = b.cells().find(mappedAddress); + if (iter == end_iter) { + result.add(cell.first, cell.second); + } + } + return wrap(value_from_spec(result, SimpleValueBuilderFactory::get())); +} + +} diff --git a/eval/src/vespa/eval/tensor/wrapped_simple_value.h b/eval/src/vespa/eval/tensor/wrapped_simple_value.h new file mode 100644 index 00000000000..3d8c3e43757 --- /dev/null +++ b/eval/src/vespa/eval/tensor/wrapped_simple_value.h @@ -0,0 +1,53 @@ +// Copyright Verizon Media. 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/value.h> +#include <vespa/eval/eval/aggr.h> + +namespace vespalib::tensor { + +/** + * A thin wrapper around a SimpleValue 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 WrappedSimpleValue : public Tensor +{ +private: + std::unique_ptr<eval::Value> _space; + const eval::Value &_tensor; +public: + explicit WrappedSimpleValue(const eval::Value &tensor) + : _space(), _tensor(tensor) {} + explicit WrappedSimpleValue(std::unique_ptr<eval::Value> tensor) + : _space(std::move(tensor)), _tensor(*_space) {} + ~WrappedSimpleValue() {} + const eval::Value &unwrap() const { return _tensor; } + + // Value API + const eval::ValueType &type() const override { return _tensor.type(); } + eval::TypedCells cells() const override { return _tensor.cells(); } + const Index &index() const override { return _tensor.index(); } + double as_double() const override { return _tensor.as_double(); } + + // tensor API + eval::TensorSpec toSpec() const override; + void accept(TensorVisitor &visitor) const override; + MemoryUsage get_memory_usage() const override; + + Tensor::UP join(join_fun_t fun, const Tensor &rhs) const override; + Tensor::UP merge(join_fun_t fun, const Tensor &rhs) const override; + Tensor::UP reduce(join_fun_t fun, const std::vector<vespalib::string> &dims) const override; + + Tensor::UP apply(const CellFunction & func) const override; + Tensor::UP modify(join_fun_t fun, const CellValues &cellValues) const override; + Tensor::UP add(const Tensor &rhs) const override; + Tensor::UP remove(const CellValues &rhs) const override; +}; + +} // namespace vespalib::tensor |