summaryrefslogtreecommitdiffstats
path: root/eval
diff options
context:
space:
mode:
authorArne H Juul <arnej27959@users.noreply.github.com>2020-11-03 13:26:07 +0100
committerGitHub <noreply@github.com>2020-11-03 13:26:07 +0100
commit0b902f8a83015de3f37c8adb449f360add305780 (patch)
tree8802556daa7d2de9ea12364f389e5dd75dbb947c /eval
parent83b771a2507be0b3783c2d8329c48125dbb108a1 (diff)
parent17a344730bca802521110c56c37f47ed16965a03 (diff)
Merge pull request #15132 from vespa-engine/arnej/add-generic-peek-instruction
Arnej/add generic peek instruction
Diffstat (limited to 'eval')
-rw-r--r--eval/CMakeLists.txt1
-rw-r--r--eval/src/tests/instruction/generic_peek/CMakeLists.txt9
-rw-r--r--eval/src/tests/instruction/generic_peek/generic_peek_test.cpp236
-rw-r--r--eval/src/vespa/eval/eval/tensor_function.cpp19
-rw-r--r--eval/src/vespa/eval/instruction/CMakeLists.txt1
-rw-r--r--eval/src/vespa/eval/instruction/generic_peek.cpp352
-rw-r--r--eval/src/vespa/eval/instruction/generic_peek.h29
7 files changed, 646 insertions, 1 deletions
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt
index b0418c4f80d..19b75c7ff46 100644
--- a/eval/CMakeLists.txt
+++ b/eval/CMakeLists.txt
@@ -42,6 +42,7 @@ vespa_define_module(
src/tests/instruction/generic_join
src/tests/instruction/generic_map
src/tests/instruction/generic_merge
+ src/tests/instruction/generic_peek
src/tests/instruction/generic_reduce
src/tests/instruction/generic_rename
src/tests/tensor/default_value_builder_factory
diff --git a/eval/src/tests/instruction/generic_peek/CMakeLists.txt b/eval/src/tests/instruction/generic_peek/CMakeLists.txt
new file mode 100644
index 00000000000..11732c865ec
--- /dev/null
+++ b/eval/src/tests/instruction/generic_peek/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(eval_generic_peek_test_app TEST
+ SOURCES
+ generic_peek_test.cpp
+ DEPENDS
+ vespaeval
+ GTest::GTest
+)
+vespa_add_test(NAME eval_generic_peek_test_app NO_VALGRIND COMMAND eval_generic_peek_test_app)
diff --git a/eval/src/tests/instruction/generic_peek/generic_peek_test.cpp b/eval/src/tests/instruction/generic_peek/generic_peek_test.cpp
new file mode 100644
index 00000000000..3874b254ad8
--- /dev/null
+++ b/eval/src/tests/instruction/generic_peek/generic_peek_test.cpp
@@ -0,0 +1,236 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/eval/eval/simple_value.h>
+#include <vespa/eval/eval/fast_value.h>
+#include <vespa/eval/eval/tensor_function.h>
+#include <vespa/eval/eval/value_codec.h>
+#include <vespa/eval/instruction/generic_peek.h>
+#include <vespa/eval/eval/interpreted_function.h>
+#include <vespa/eval/eval/test/tensor_model.hpp>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/overload.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <stdlib.h>
+#include <variant>
+
+using namespace vespalib;
+using namespace vespalib::eval;
+using namespace vespalib::eval::instruction;
+using namespace vespalib::eval::test;
+
+using vespalib::make_string_short::fmt;
+
+std::vector<Layout> peek_layouts = {
+ {x(4)},
+ {x(4),y(5)},
+ {x(4),y(5),z(3)},
+ float_cells({x(4),y(5),z(3)}),
+ {x({"-1","0","2"})},
+ {x({"-1","0","2"}),y({"-2","0","1"}),z({"-2","-1","0","1","2"})},
+ float_cells({x({"-1","0","2"}),y({"-2","0","1"})}),
+ {x(4),y({"-2","0","1"}),z(3)},
+ {x({"-1","0","2"}),y(5),z({"-2","-1","0","1","2"})},
+ float_cells({x({"-1","0","2"}),y(5),z({"-2","-1","0","1","2"})})
+};
+
+using PeekSpec = GenericPeek::SpecMap;
+
+TensorSpec reference_peek(const TensorSpec &param, const vespalib::string &result_type, const PeekSpec &spec) {
+ TensorSpec result(result_type);
+ ValueType param_type = ValueType::from_spec(param.type());
+ auto is_mapped_dim = [&](const vespalib::string &name) {
+ size_t dim_idx = param_type.dimension_index(name);
+ assert(dim_idx != ValueType::Dimension::npos);
+ const auto &param_dim = param_type.dimensions()[dim_idx];
+ return param_dim.is_mapped();
+ };
+ TensorSpec::Address addr;
+ for (const auto & [dim_name, label_or_child] : spec) {
+ std::visit(vespalib::overload
+ {
+ [&](const TensorSpec::Label &label) {
+ addr.emplace(dim_name, label);
+ },
+ [&](const size_t &child_value) {
+ // here, label_or_child is a size_t specifying the value
+ // we pretend a child produced
+ if (is_mapped_dim(dim_name)) {
+ // (but cast to signed first, to allow labels like the string "-2")
+ addr.emplace(dim_name, vespalib::make_string("%zd", ssize_t(child_value)));
+ } else {
+ addr.emplace(dim_name, child_value);
+ }
+ }
+ }, label_or_child);
+ }
+ for (const auto &cell: param.cells()) {
+ bool keep = true;
+ TensorSpec::Address my_addr;
+ for (const auto &binding: cell.first) {
+ auto pos = addr.find(binding.first);
+ if (pos == addr.end()) {
+ my_addr.emplace(binding.first, binding.second);
+ } else {
+ if (!(pos->second == binding.second)) {
+ keep = false;
+ }
+ }
+ }
+ if (keep) {
+ result.add(my_addr, cell.second);
+ }
+ }
+ return spec_from_value(*value_from_spec(result, SimpleValueBuilderFactory::get()));
+}
+
+
+TensorSpec perform_generic_peek(const TensorSpec &a, const ValueType &result_type,
+ PeekSpec spec, const ValueBuilderFactory &factory)
+{
+ auto param = value_from_spec(a, factory);
+ EXPECT_FALSE(param->type().is_error());
+ EXPECT_FALSE(result_type.is_error());
+ Stash stash;
+ std::vector<Value::CREF> my_stack;
+ my_stack.push_back(*param);
+ size_t child_idx = 0;
+ for (auto & [dim_name, label_or_child] : spec) {
+ if (std::holds_alternative<size_t>(label_or_child)) {
+ // here, label_or_child is a size_t specifying the value
+ // this child should produce (but cast to signed first,
+ // to allow negative values)
+ ssize_t child_value = std::get<size_t>(label_or_child);
+ my_stack.push_back(stash.create<DoubleValue>(child_value));
+ // overwrite label_or_child, now it should be the index of
+ // the child for make_instruction
+ label_or_child = child_idx++;
+ }
+ }
+ auto my_op = GenericPeek::make_instruction(param->type(), result_type, spec, factory, stash);
+ InterpretedFunction::EvalSingle single(factory, my_op);
+ return spec_from_value(single.eval(my_stack));
+}
+
+TensorSpec tensor_function_peek(const TensorSpec &a, const ValueType &result_type,
+ PeekSpec spec, const ValueBuilderFactory &factory)
+{
+ Stash stash;
+ auto param = value_from_spec(a, factory);
+ EXPECT_FALSE(param->type().is_error());
+ EXPECT_FALSE(result_type.is_error());
+ std::vector<Value::CREF> my_stack;
+ my_stack.push_back(*param);
+ const auto &func_double = tensor_function::inject(ValueType::double_type(), 1, stash);
+ std::map<vespalib::string, std::variant<TensorSpec::Label, TensorFunction::CREF>> func_spec;
+ for (auto & [dim_name, label_or_child] : spec) {
+ if (std::holds_alternative<size_t>(label_or_child)) {
+ // here, label_or_child is a size_t specifying the value
+ // this child should produce (but cast to signed first,
+ // to allow negative values)
+ ssize_t child_value = std::get<size_t>(label_or_child);
+ my_stack.push_back(stash.create<DoubleValue>(double(child_value)));
+ func_spec.emplace(dim_name, func_double);
+ } else {
+ auto label = std::get<TensorSpec::Label>(label_or_child);
+ func_spec.emplace(dim_name, label);
+ }
+ }
+ const auto &func_param = tensor_function::inject(param->type(), 0, stash);
+ const auto &peek_node = tensor_function::peek(func_param, func_spec, stash);
+ auto my_op = peek_node.compile_self(factory, stash);
+ InterpretedFunction::EvalSingle single(factory, my_op);
+ return spec_from_value(single.eval(my_stack));
+}
+
+vespalib::string to_str(const PeekSpec &spec) {
+ vespalib::asciistream os;
+ os << "{ ";
+ for (const auto & [dim, label_or_index] : spec) {
+ os << dim << " : ";
+ if (std::holds_alternative<size_t>(label_or_index)) {
+ os << "[" << ssize_t(std::get<size_t>(label_or_index)) << "] ";
+ } else {
+ auto label = std::get<TensorSpec::Label>(label_or_index);
+ if (label.is_mapped()) {
+ os << "'" << label.name << "' ";
+ } else {
+ os << "(" << ssize_t(label.index) << ") ";
+ }
+ }
+ }
+ os << "}";
+ return os.str();
+}
+
+void verify_peek_equal(const TensorSpec &input,
+ const PeekSpec &spec,
+ const ValueBuilderFactory &factory)
+{
+ ValueType param_type = ValueType::from_spec(input.type());
+ std::vector<vespalib::string> reduce_dims;
+ for (const auto & [dim_name, ignored] : spec) {
+ reduce_dims.push_back(dim_name);
+ }
+ if (reduce_dims.empty()) return;
+ ValueType result_type = param_type.reduce(reduce_dims);
+ auto expect = reference_peek(input, result_type.to_spec(), spec);
+ SCOPED_TRACE(fmt("peek input: %s\n peek spec: %s\n peek result %s\n",
+ input.to_string().c_str(),
+ to_str(spec).c_str(),
+ expect.to_string().c_str()));
+ auto actual = perform_generic_peek(input, result_type, spec, factory);
+ EXPECT_EQ(actual, expect);
+ auto from_func = tensor_function_peek(input, result_type, spec, factory);
+ EXPECT_EQ(from_func, expect);
+}
+
+void fill_dims_and_check(const TensorSpec &input,
+ PeekSpec spec,
+ std::vector<ValueType::Dimension> dimensions,
+ const ValueBuilderFactory &factory)
+{
+ if (dimensions.empty()) {
+ verify_peek_equal(input, spec, factory);
+ return;
+ }
+ auto dim = dimensions.back();
+ dimensions.pop_back();
+ fill_dims_and_check(input, spec, dimensions, factory);
+ for (int64_t label_value : {-2, -1, 0, 1, 3}) {
+ if (dim.is_indexed()) {
+ size_t index = label_value;
+ if (index >= dim.size) continue;
+ TensorSpec::Label label(index);
+ spec.insert_or_assign(dim.name, label);
+ } else {
+ TensorSpec::Label label(make_string("%" PRId64, label_value));
+ spec.insert_or_assign(dim.name, label);
+ }
+ fill_dims_and_check(input, spec, dimensions, factory);
+ }
+ for (int64_t child_value : {-2, -1, 0, 1, 3}) {
+ spec.insert_or_assign(dim.name, size_t(child_value));
+ fill_dims_and_check(input, spec, dimensions, factory);
+ }
+}
+
+void test_generic_peek_with(const ValueBuilderFactory &factory) {
+ for (const auto & layout : peek_layouts) {
+ TensorSpec input = spec(layout, N());
+ ValueType input_type = ValueType::from_spec(input.type());
+ const auto &dims = input_type.dimensions();
+ PeekSpec spec;
+ fill_dims_and_check(input, spec, dims, factory);
+ }
+}
+
+TEST(GenericPeekTest, generic_peek_works_for_simple_values) {
+ test_generic_peek_with(SimpleValueBuilderFactory::get());
+}
+
+TEST(GenericPeekTest, generic_peek_works_for_fast_values) {
+ test_generic_peek_with(FastValueBuilderFactory::get());
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/eval/src/vespa/eval/eval/tensor_function.cpp b/eval/src/vespa/eval/eval/tensor_function.cpp
index 57036f04e9a..8464aa14b59 100644
--- a/eval/src/vespa/eval/eval/tensor_function.cpp
+++ b/eval/src/vespa/eval/eval/tensor_function.cpp
@@ -13,6 +13,7 @@
#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_peek.h>
#include <vespa/eval/instruction/generic_reduce.h>
#include <vespa/eval/instruction/generic_rename.h>
#include <vespa/vespalib/objects/objectdumper.h>
@@ -494,8 +495,23 @@ Peek::push_children(std::vector<Child::CREF> &children) const
}
Instruction
-Peek::compile_self(EngineOrFactory, Stash &) const
+Peek::compile_self(EngineOrFactory engine, Stash &stash) const
{
+ if (engine.is_factory()) {
+ instruction::GenericPeek::SpecMap generic_spec;
+ size_t child_idx = 0;
+ for (const auto & [dim_name, label_or_child] : spec()) {
+ std::visit(vespalib::overload {
+ [&](const TensorSpec::Label &label) {
+ generic_spec.emplace(dim_name, label);
+ },
+ [&](const TensorFunction::Child &) {
+ generic_spec.emplace(dim_name, child_idx++);
+ }
+ }, label_or_child);
+ }
+ return instruction::GenericPeek::make_instruction(param_type(), result_type(), generic_spec, engine.factory(), stash);
+ }
return Instruction(op_tensor_peek, wrap_param<Peek>(*this));
}
@@ -613,6 +629,7 @@ const TensorFunction &peek(const TensorFunction &param, const std::map<vespalib:
for (const auto &dim_spec: spec) {
dimensions.push_back(dim_spec.first);
}
+ assert(!dimensions.empty());
ValueType result_type = param.result_type().reduce(dimensions);
return stash.create<Peek>(result_type, param, spec);
}
diff --git a/eval/src/vespa/eval/instruction/CMakeLists.txt b/eval/src/vespa/eval/instruction/CMakeLists.txt
index 2cd0577acc9..52f411dc543 100644
--- a/eval/src/vespa/eval/instruction/CMakeLists.txt
+++ b/eval/src/vespa/eval/instruction/CMakeLists.txt
@@ -5,6 +5,7 @@ vespa_add_library(eval_instruction OBJECT
generic_concat
generic_create
generic_join
+ generic_peek
generic_reduce
generic_map
generic_merge
diff --git a/eval/src/vespa/eval/instruction/generic_peek.cpp b/eval/src/vespa/eval/instruction/generic_peek.cpp
new file mode 100644
index 00000000000..651ce4df28a
--- /dev/null
+++ b/eval/src/vespa/eval/instruction/generic_peek.cpp
@@ -0,0 +1,352 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "generic_peek.h"
+#include <vespa/eval/eval/nested_loop.h>
+#include <vespa/eval/eval/wrap_param.h>
+#include <vespa/vespalib/util/overload.h>
+#include <vespa/vespalib/util/stash.h>
+#include <vespa/vespalib/util/typify.h>
+#include <vespa/vespalib/util/visit_ranges.h>
+#include <cassert>
+
+using namespace vespalib::eval::tensor_function;
+
+namespace vespalib::eval::instruction {
+
+using State = InterpretedFunction::State;
+using Instruction = InterpretedFunction::Instruction;
+
+namespace {
+
+static constexpr size_t npos = -1;
+
+using Spec = GenericPeek::SpecMap;
+
+size_t count_children(const Spec &spec)
+{
+ size_t num_children = 0;
+ for (const auto & [dim_name, child_or_label] : spec) {
+ if (std::holds_alternative<size_t>(child_or_label)) {
+ ++num_children;
+ }
+ }
+ return num_children;
+}
+
+struct DimSpec {
+ vespalib::stringref name;
+ GenericPeek::MyLabel child_or_label;
+ bool has_child() const {
+ return std::holds_alternative<size_t>(child_or_label);
+ }
+ bool has_label() const {
+ return std::holds_alternative<TensorSpec::Label>(child_or_label);
+ }
+ size_t get_child_idx() const {
+ return std::get<size_t>(child_or_label);
+ }
+ vespalib::stringref get_label_name() const {
+ auto label = std::get<TensorSpec::Label>(child_or_label);
+ assert(label.is_mapped());
+ return label.name;
+ }
+ size_t get_label_index() const {
+ auto label = std::get<TensorSpec::Label>(child_or_label);
+ assert(label.is_indexed());
+ return label.index;
+ }
+};
+
+struct ExtractedSpecs {
+ using Dimension = ValueType::Dimension;
+ struct MyComp {
+ bool operator() (const Dimension &a, const Spec::value_type &b) { return a.name < b.first; }
+ bool operator() (const Spec::value_type &a, const Dimension &b) { return a.first < b.name; }
+ };
+ std::vector<Dimension> dimensions;
+ std::vector<DimSpec> specs;
+
+ ExtractedSpecs(bool indexed,
+ const std::vector<Dimension> &input_dims,
+ const Spec &spec)
+ {
+ auto visitor = overload
+ {
+ [&](visit_ranges_first, const auto &a) {
+ if (a.is_indexed() == indexed) dimensions.push_back(a);
+ },
+ [&](visit_ranges_second, const auto &) {
+ // spec has unknown dimension
+ abort();
+ },
+ [&](visit_ranges_both, const auto &a, const auto &b) {
+ if (a.is_indexed() == indexed) {
+ dimensions.push_back(a);
+ const auto & [spec_dim_name, child_or_label] = b;
+ assert(a.name == spec_dim_name);
+ specs.emplace_back(DimSpec{a.name, child_or_label});
+ }
+ }
+ };
+ visit_ranges(visitor,
+ input_dims.begin(), input_dims.end(),
+ spec.begin(), spec.end(), MyComp());
+ }
+ ~ExtractedSpecs();
+};
+ExtractedSpecs::~ExtractedSpecs() = default;
+
+struct DenseSizes {
+ std::vector<size_t> size;
+ std::vector<size_t> stride;
+ size_t cur_size;
+
+ DenseSizes(const std::vector<ValueType::Dimension> &dims)
+ : size(), stride(), cur_size(1)
+ {
+ for (const auto &dim : dims) {
+ assert(dim.is_indexed());
+ size.push_back(dim.size);
+ }
+ stride.resize(size.size());
+ for (size_t i = size.size(); i-- > 0; ) {
+ stride[i] = cur_size;
+ cur_size *= size[i];
+ }
+ }
+};
+
+/** Compute input offsets for all output cells */
+struct DensePlan {
+ size_t in_dense_size;
+ size_t out_dense_size;
+ std::vector<size_t> loop_cnt;
+ std::vector<size_t> in_stride;
+ size_t verbatim_offset = 0;
+ struct Child {
+ size_t idx;
+ size_t stride;
+ size_t limit;
+ };
+ std::vector<Child> children;
+
+ DensePlan(const ValueType &input_type, const Spec &spec)
+ {
+ const ExtractedSpecs mine(true, input_type.dimensions(), spec);
+ DenseSizes sizes(mine.dimensions);
+ in_dense_size = sizes.cur_size;
+ out_dense_size = 1;
+ auto pos = mine.specs.begin();
+ for (size_t i = 0; i < mine.dimensions.size(); ++i) {
+ const auto &dim = mine.dimensions[i];
+ if ((pos == mine.specs.end()) || (dim.name < pos->name)) {
+ loop_cnt.push_back(sizes.size[i]);
+ in_stride.push_back(sizes.stride[i]);
+ out_dense_size *= sizes.size[i];
+ } else {
+ assert(dim.name == pos->name);
+ if (pos->has_child()) {
+ children.push_back(Child{pos->get_child_idx(), sizes.stride[i], sizes.size[i]});
+ } else {
+ assert(pos->has_label());
+ size_t label_index = pos->get_label_index();
+ assert(label_index < sizes.size[i]);
+ verbatim_offset += label_index * sizes.stride[i];
+ }
+ ++pos;
+ }
+ }
+ assert(pos == mine.specs.end());
+ }
+
+ /** Get initial offset (from verbatim labels and child values) */
+ template <typename Getter>
+ size_t get_offset(const Getter &get_child_value) const {
+ size_t offset = verbatim_offset;
+ for (size_t i = 0; i < children.size(); ++i) {
+ size_t from_child = get_child_value(children[i].idx);
+ if (from_child < children[i].limit) {
+ offset += from_child * children[i].stride;
+ } else {
+ return npos;
+ }
+ }
+ return offset;
+ }
+
+ template<typename F> void execute(size_t offset, const F &f) const {
+ run_nested_loop<F>(offset, loop_cnt, in_stride, f);
+ }
+};
+
+struct SparseState {
+ std::vector<vespalib::string> view_addr;
+ std::vector<vespalib::stringref> view_refs;
+ std::vector<const vespalib::stringref *> lookup_refs;
+ std::vector<vespalib::stringref> output_addr;
+ std::vector<vespalib::stringref *> fetch_addr;
+
+ SparseState(std::vector<vespalib::string> view_addr_in, size_t out_dims)
+ : view_addr(std::move(view_addr_in)),
+ view_refs(view_addr.size()),
+ lookup_refs(view_addr.size()),
+ output_addr(out_dims),
+ fetch_addr(out_dims)
+ {
+ for (size_t i = 0; i < view_addr.size(); ++i) {
+ view_refs[i] = view_addr[i];
+ lookup_refs[i] = &view_refs[i];
+ }
+ for (size_t i = 0; i < out_dims; ++i) {
+ fetch_addr[i] = &output_addr[i];
+ }
+ }
+ ~SparseState();
+};
+SparseState::~SparseState() = default;
+
+struct SparsePlan {
+ size_t out_mapped_dims;
+ std::vector<DimSpec> lookup_specs;
+ std::vector<size_t> view_dims;
+
+ SparsePlan(const ValueType &input_type,
+ const GenericPeek::SpecMap &spec)
+ : out_mapped_dims(0),
+ view_dims()
+ {
+ ExtractedSpecs mine(false, input_type.dimensions(), spec);
+ lookup_specs = std::move(mine.specs);
+ auto pos = lookup_specs.begin();
+ for (size_t dim_idx = 0; dim_idx < mine.dimensions.size(); ++dim_idx) {
+ const auto & dim = mine.dimensions[dim_idx];
+ if ((pos == lookup_specs.end()) || (dim.name < pos->name)) {
+ ++out_mapped_dims;
+ } else {
+ assert(dim.name == pos->name);
+ view_dims.push_back(dim_idx);
+ ++pos;
+ }
+ }
+ assert(pos == lookup_specs.end());
+ }
+
+ ~SparsePlan();
+
+ template <typename Getter>
+ SparseState make_state(const Getter &get_child_value) const {
+ std::vector<vespalib::string> view_addr;
+ for (const auto & dim : lookup_specs) {
+ if (dim.has_child()) {
+ int64_t child_value = get_child_value(dim.get_child_idx());
+ view_addr.push_back(vespalib::make_string("%" PRId64, child_value));
+ } else {
+ view_addr.push_back(dim.get_label_name());
+ }
+ }
+ assert(view_addr.size() == view_dims.size());
+ return SparseState(std::move(view_addr), out_mapped_dims);
+ }
+};
+SparsePlan::~SparsePlan() = default;
+
+struct PeekParam {
+ const ValueType res_type;
+ DensePlan dense_plan;
+ SparsePlan sparse_plan;
+ size_t num_children;
+ const ValueBuilderFactory &factory;
+
+ PeekParam(const ValueType &input_type,
+ const ValueType &res_type_in,
+ const GenericPeek::SpecMap &spec_in,
+ const ValueBuilderFactory &factory_in)
+ : res_type(res_type_in),
+ dense_plan(input_type, spec_in),
+ sparse_plan(input_type, spec_in),
+ num_children(count_children(spec_in)),
+ factory(factory_in)
+ {
+ assert(dense_plan.in_dense_size == input_type.dense_subspace_size());
+ assert(dense_plan.out_dense_size == res_type.dense_subspace_size());
+ }
+};
+
+template <typename ICT, typename OCT, typename Getter>
+Value::UP
+generic_mixed_peek(const ValueType &res_type,
+ const Value &input_value,
+ const SparsePlan &sparse_plan,
+ const DensePlan &dense_plan,
+ const ValueBuilderFactory &factory,
+ const Getter &get_child_value)
+{
+ auto input_cells = input_value.cells().typify<ICT>();
+ size_t bad_guess = 1;
+ auto builder = factory.create_value_builder<OCT>(res_type,
+ sparse_plan.out_mapped_dims,
+ dense_plan.out_dense_size,
+ bad_guess);
+ size_t filled_subspaces = 0;
+ size_t dense_offset = dense_plan.get_offset(get_child_value);
+ if (dense_offset != npos) {
+ SparseState state = sparse_plan.make_state(get_child_value);
+ auto view = input_value.index().create_view(sparse_plan.view_dims);
+ view->lookup(state.lookup_refs);
+ size_t input_subspace;
+ while (view->next_result(state.fetch_addr, input_subspace)) {
+ auto dst = builder->add_subspace(state.output_addr).begin();
+ auto input_offset = input_subspace * dense_plan.in_dense_size;
+ dense_plan.execute(dense_offset + input_offset,
+ [&](size_t idx) { *dst++ = input_cells[idx]; });
+ ++filled_subspaces;
+ }
+ }
+ if ((sparse_plan.out_mapped_dims == 0) && (filled_subspaces == 0)) {
+ for (auto & v : builder->add_subspace({})) {
+ v = OCT{};
+ }
+ }
+ return builder->build(std::move(builder));
+}
+
+template <typename ICT, typename OCT>
+void my_generic_peek_op(State &state, uint64_t param_in) {
+ const auto &param = unwrap_param<PeekParam>(param_in);
+ const Value & input_value = state.peek(param.num_children);
+ const size_t last_child = param.num_children - 1;
+ auto get_child_value = [&] (size_t child_idx) {
+ size_t stack_idx = last_child - child_idx;
+ return int64_t(state.peek(stack_idx).as_double());
+ };
+ auto up = generic_mixed_peek<ICT,OCT>(param.res_type, input_value,
+ param.sparse_plan, param.dense_plan,
+ param.factory, get_child_value);
+ const Value &result = *state.stash.create<Value::UP>(std::move(up));
+ // num_children does not include the "input" param
+ state.pop_n_push(param.num_children + 1, result);
+}
+
+struct SelectGenericPeekOp {
+ template <typename ICT, typename OCT> static auto invoke() {
+ return my_generic_peek_op<ICT,OCT>;
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+} // namespace <unnamed>
+
+Instruction
+GenericPeek::make_instruction(const ValueType &input_type,
+ const ValueType &res_type,
+ const SpecMap &spec,
+ const ValueBuilderFactory &factory,
+ Stash &stash)
+{
+ const auto &param = stash.create<PeekParam>(input_type, res_type, spec, factory);
+ auto fun = typify_invoke<2,TypifyCellType,SelectGenericPeekOp>(input_type.cell_type(), res_type.cell_type());
+ return Instruction(fun, wrap_param<PeekParam>(param));
+}
+
+} // namespace
diff --git a/eval/src/vespa/eval/instruction/generic_peek.h b/eval/src/vespa/eval/instruction/generic_peek.h
new file mode 100644
index 00000000000..d31b47238cb
--- /dev/null
+++ b/eval/src/vespa/eval/instruction/generic_peek.h
@@ -0,0 +1,29 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/eval/eval/value_type.h>
+#include <vespa/eval/eval/tensor_spec.h>
+#include <vespa/eval/eval/interpreted_function.h>
+#include <map>
+
+namespace vespalib { class Stash; }
+namespace vespalib::eval { struct ValueBuilderFactory; }
+
+namespace vespalib::eval::instruction {
+
+//-----------------------------------------------------------------------------
+
+struct GenericPeek {
+ using MyLabel = std::variant<TensorSpec::Label, size_t>;
+ using SpecMap = std::map<vespalib::string, MyLabel>;
+
+ static InterpretedFunction::Instruction
+ make_instruction(const ValueType &input_type,
+ const ValueType &res_type,
+ const SpecMap &spec,
+ const ValueBuilderFactory &factory,
+ Stash &stash);
+};
+
+} // namespace