diff options
author | Arne H Juul <arnej27959@users.noreply.github.com> | 2019-06-26 14:10:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-26 14:10:08 +0200 |
commit | 0f7b172e464f4a89d22b12fe0e6ff9082e420b8c (patch) | |
tree | e8aaac992d907ea33612285f3fe4db0f3998330a | |
parent | 4b3b9f5d2dc616d69e0dbba999595ffc558251a9 (diff) | |
parent | 2e6a0ea56d399ba6966a39b0e11ef0b276c3e5af (diff) |
Merge pull request #9889 from vespa-engine/arnej/add-dense-dimension-combiner
Arnej/add dense dimension combiner
11 files changed, 420 insertions, 280 deletions
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt index 26e046ec52a..e0159febdfe 100644 --- a/eval/CMakeLists.txt +++ b/eval/CMakeLists.txt @@ -25,6 +25,7 @@ vespa_define_module( src/tests/eval/value_cache src/tests/eval/value_type src/tests/gp/ponder_nov2017 + src/tests/tensor/dense_dimension_combiner src/tests/tensor/dense_add_dimension_optimizer src/tests/tensor/dense_dot_product_function src/tests/tensor/dense_fast_rename_optimizer @@ -32,7 +33,6 @@ vespa_define_module( src/tests/tensor/dense_inplace_map_function src/tests/tensor/dense_remove_dimension_optimizer src/tests/tensor/dense_replace_type_function - src/tests/tensor/dense_tensor_address_combiner src/tests/tensor/dense_xw_product_function src/tests/tensor/direct_dense_tensor_builder src/tests/tensor/direct_sparse_tensor_builder diff --git a/eval/src/tests/tensor/dense_dimension_combiner/CMakeLists.txt b/eval/src/tests/tensor/dense_dimension_combiner/CMakeLists.txt new file mode 100644 index 00000000000..eaee8ebb4e4 --- /dev/null +++ b/eval/src/tests/tensor/dense_dimension_combiner/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +vespa_add_executable(eval_dense_dimension_combiner_test_app TEST + SOURCES + dense_dimension_combiner_test.cpp + DEPENDS + vespaeval +) +vespa_add_test(NAME eval_dense_dimension_combiner_test_app COMMAND eval_dense_dimension_combiner_test_app) diff --git a/eval/src/tests/tensor/dense_dimension_combiner/dense_dimension_combiner_test.cpp b/eval/src/tests/tensor/dense_dimension_combiner/dense_dimension_combiner_test.cpp new file mode 100644 index 00000000000..b8949e3a7e6 --- /dev/null +++ b/eval/src/tests/tensor/dense_dimension_combiner/dense_dimension_combiner_test.cpp @@ -0,0 +1,185 @@ +// Copyright 2019 Oath 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/tensor/dense/dense_dimension_combiner.h> + +using namespace vespalib; +using namespace vespalib::eval; +using namespace vespalib::tensor; + +void verifyLeft(DenseDimensionCombiner &d, size_t last) { + d.commonReset(); + d.leftReset(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_EQUAL(d.leftIdx(), 0u); + size_t expect = 0; + while (d.leftInRange()) { + d.stepLeft(); + EXPECT_GREATER(d.leftIdx(), expect); + expect = d.leftIdx(); + } + EXPECT_FALSE(d.leftInRange()); + EXPECT_EQUAL(expect, last); + d.leftReset(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_EQUAL(d.leftIdx(), 0u); +} + +void verifyRight(DenseDimensionCombiner &d, size_t last) { + d.commonReset(); + d.rightReset(); + EXPECT_TRUE(d.rightInRange()); + EXPECT_EQUAL(d.rightIdx(), 0u); + size_t expect = 0; + while (d.rightInRange()) { + d.stepRight(); + EXPECT_GREATER(d.rightIdx(), expect); + expect = d.rightIdx(); + } + EXPECT_FALSE(d.rightInRange()); + EXPECT_EQUAL(expect, last); + d.rightReset(); + EXPECT_TRUE(d.rightInRange()); + EXPECT_EQUAL(d.rightIdx(), 0u); +} + + +TEST("require that one left, one common, one right dimension works") { + ValueType t12_lc = ValueType::tensor_type({{"d1_l", 3},{"d2_c", 4}}); + ValueType t23_cr = ValueType::tensor_type({{"d2_c", 4},{"d3_r", 5}}); + + DenseDimensionCombiner d(t12_lc, t23_cr); + + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 0u); + EXPECT_EQUAL(d.rightIdx(), 0u); + EXPECT_EQUAL(d.outputIdx(), 0u); + + d.stepCommon(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 1u); + EXPECT_EQUAL(d.rightIdx(), 5u); + EXPECT_EQUAL(d.outputIdx(), 5u); + + d.stepRight(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 1u); + EXPECT_EQUAL(d.rightIdx(), 6u); + EXPECT_EQUAL(d.outputIdx(), 6u); + + d.stepLeft(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 5u); + EXPECT_EQUAL(d.rightIdx(), 6u); + EXPECT_EQUAL(d.outputIdx(), 26u); + + d.stepLeft(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 9u); + EXPECT_EQUAL(d.rightIdx(), 6u); + EXPECT_EQUAL(d.outputIdx(), 46u); + + d.stepLeft(); + EXPECT_FALSE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 13u); + EXPECT_EQUAL(d.rightIdx(), 6u); + EXPECT_EQUAL(d.outputIdx(), 6u); + + d.leftReset(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 1u); + EXPECT_EQUAL(d.rightIdx(), 6u); + EXPECT_EQUAL(d.outputIdx(), 6u); + + d.stepCommon(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 2u); + EXPECT_EQUAL(d.rightIdx(), 11u); + EXPECT_EQUAL(d.outputIdx(), 11u); + + d.stepRight(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 2u); + EXPECT_EQUAL(d.rightIdx(), 12u); + EXPECT_EQUAL(d.outputIdx(), 12u); + + TEST_DO(verifyLeft(d, 12)); + TEST_DO(verifyRight(d, 20)); +} + +TEST("require that two left, no common, two right dimensions works") { + ValueType t12_ll = ValueType::tensor_type({{"d1_l", 3},{"d2_l", 4}}); + ValueType t34_rr = ValueType::tensor_type({{"d3_r", 5},{"d4_r", 2}}); + + DenseDimensionCombiner d(t12_ll, t34_rr); + + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 0u); + EXPECT_EQUAL(d.rightIdx(), 0u); + EXPECT_EQUAL(d.outputIdx(), 0u); + + d.stepCommon(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_FALSE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 0u); + EXPECT_EQUAL(d.rightIdx(), 0u); + EXPECT_EQUAL(d.outputIdx(), 120u); + + d.commonReset(); + d.stepRight(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 0u); + EXPECT_EQUAL(d.rightIdx(), 1u); + EXPECT_EQUAL(d.outputIdx(), 1u); + + d.stepLeft(); + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 1u); + EXPECT_EQUAL(d.rightIdx(), 1u); + EXPECT_EQUAL(d.outputIdx(), 11u); + + d.stepLeft(); + d.stepLeft(); + d.stepLeft(); + d.stepLeft(); + d.stepLeft(); + d.stepLeft(); + d.stepLeft(); + + EXPECT_TRUE(d.leftInRange()); + EXPECT_TRUE(d.rightInRange()); + EXPECT_TRUE(d.commonInRange()); + EXPECT_EQUAL(d.leftIdx(), 8u); + EXPECT_EQUAL(d.rightIdx(), 1u); + EXPECT_EQUAL(d.outputIdx(), 81u); + + TEST_DO(verifyLeft(d, 12)); + TEST_DO(verifyRight(d, 10)); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/tests/tensor/dense_tensor_address_combiner/CMakeLists.txt b/eval/src/tests/tensor/dense_tensor_address_combiner/CMakeLists.txt deleted file mode 100644 index a006d70935d..00000000000 --- a/eval/src/tests/tensor/dense_tensor_address_combiner/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_executable(eval_dense_tensor_address_combiner_test_app TEST - SOURCES - dense_tensor_address_combiner_test.cpp - DEPENDS - vespaeval -) -vespa_add_test(NAME eval_dense_tensor_address_combiner_test_app COMMAND eval_dense_tensor_address_combiner_test_app) diff --git a/eval/src/tests/tensor/dense_tensor_address_combiner/dense_tensor_address_combiner_test.cpp b/eval/src/tests/tensor/dense_tensor_address_combiner/dense_tensor_address_combiner_test.cpp deleted file mode 100644 index 91a6087ea3a..00000000000 --- a/eval/src/tests/tensor/dense_tensor_address_combiner/dense_tensor_address_combiner_test.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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/tensor/dense/dense_tensor_address_combiner.h> -#include <vespa/vespalib/test/insertion_operators.h> - -using namespace vespalib::tensor; -using vespalib::eval::ValueType; - -ValueType -combine(const std::vector<ValueType::Dimension> &lhs, - const std::vector<ValueType::Dimension> &rhs) -{ - return DenseTensorAddressCombiner::combineDimensions( - ValueType::tensor_type(lhs), - ValueType::tensor_type(rhs)); -} - -TEST("require that dimensions can be combined") -{ - EXPECT_EQUAL(ValueType::tensor_type({{"a", 3}, {"b", 5}}), combine({{"a", 3}}, {{"b", 5}})); - EXPECT_EQUAL(ValueType::tensor_type({{"a", 3}, {"b", 5}}), combine({{"a", 3}, {"b", 5}}, {{"b", 5}})); - EXPECT_EQUAL(ValueType::tensor_type({{"a", 3}, {"b", 5}}), combine({{"a", 3}, {"b", 5}}, {{"b", 5}})); - EXPECT_EQUAL(ValueType::tensor_type({{"a", 3}, {"b", 11}, {"c", 5}, {"d", 7}, {"e", 17}}), - combine({{"a", 3}, {"c", 5}, {"d", 7}}, - {{"b", 11}, {"c", 5}, {"e", 17}})); - EXPECT_EQUAL(ValueType::tensor_type({{"a", 3}, {"b", 11}, {"c", 5}, {"d", 7}, {"e", 17}}), - combine({{"b", 11}, {"c", 5}, {"e", 17}}, - {{"a", 3}, {"c", 5}, {"d", 7}})); -} - -TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/eval/src/vespa/eval/tensor/dense/CMakeLists.txt b/eval/src/vespa/eval/tensor/dense/CMakeLists.txt index c2638466de6..ce20d6ba6d9 100644 --- a/eval/src/vespa/eval/tensor/dense/CMakeLists.txt +++ b/eval/src/vespa/eval/tensor/dense/CMakeLists.txt @@ -2,16 +2,16 @@ vespa_add_library(eval_tensor_dense OBJECT SOURCES dense_add_dimension_optimizer.cpp + dense_dimension_combiner.cpp dense_dot_product_function.cpp dense_fast_rename_optimizer.cpp dense_inplace_join_function.cpp dense_inplace_map_function.cpp dense_remove_dimension_optimizer.cpp dense_replace_type_function.cpp - dense_tensor.cpp - dense_tensor_address_combiner.cpp dense_tensor_address_mapper.cpp dense_tensor_cells_iterator.cpp + dense_tensor.cpp dense_tensor_modify.cpp dense_tensor_reduce.cpp dense_tensor_view.cpp diff --git a/eval/src/vespa/eval/tensor/dense/dense_dimension_combiner.cpp b/eval/src/vespa/eval/tensor/dense/dense_dimension_combiner.cpp new file mode 100644 index 00000000000..22c8ff12ad1 --- /dev/null +++ b/eval/src/vespa/eval/tensor/dense/dense_dimension_combiner.cpp @@ -0,0 +1,91 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "dense_dimension_combiner.h" +#include <cassert> + +namespace vespalib::tensor { + +DenseDimensionCombiner::~DenseDimensionCombiner() = default; + +DenseDimensionCombiner::DenseDimensionCombiner(const eval::ValueType &lhs, + const eval::ValueType &rhs) + : _left(), _right(), + _commonDims(), + _outputIndex(0), + _outputSize(1u), + result_type(eval::ValueType::join(lhs, rhs)) +{ + assert(lhs.is_dense()); + assert(rhs.is_dense()); + assert(result_type.is_dense()); + + const auto &lDims = lhs.dimensions(); + const auto &rDims = rhs.dimensions(); + const auto &oDims = result_type.dimensions(); + + size_t i = lDims.size(); + size_t j = rDims.size(); + size_t k = oDims.size(); + + uint32_t lMul = 1; + uint32_t rMul = 1; + uint32_t oMul = 1; + + while (k-- > 0) { + if ((i > 0) && (lDims[i-1].name == oDims[k].name)) { + --i; + // left dim match + if ((j > 0) && (rDims[j-1].name == oDims[k].name)) { + // both dim match + --j; + CommonDim cd; + cd.idx = 0; + cd.leftMultiplier = lMul; + cd.rightMultiplier = rMul; + cd.outputMultiplier = oMul; + assert(lDims[i].size == oDims[k].size); + assert(rDims[j].size == oDims[k].size); + cd.size = oDims[k].size; + lMul *= cd.size; + rMul *= cd.size; + oMul *= cd.size; + _left.totalSize *= cd.size; + _right.totalSize *= cd.size; + _outputSize *= cd.size; + _commonDims.push_back(cd); + } else { + SideDim ld; + ld.idx = 0; + ld.sideMultiplier = lMul; + ld.outputMultiplier = oMul; + assert(lDims[i].size == oDims[k].size); + ld.size = oDims[k].size; + lMul *= ld.size; + oMul *= ld.size; + _outputSize *= ld.size; + _left.totalSize *= ld.size; + _left.dims.push_back(ld); + } + } else { + // right dim match + assert(j > 0); + assert(rDims[j-1].name == oDims[k].name); + --j; + SideDim rd; + rd.idx = 0; + rd.sideMultiplier = rMul; + rd.outputMultiplier = oMul; + assert(rDims[j].size == oDims[k].size); + rd.size = oDims[k].size; + rMul *= rd.size; + oMul *= rd.size; + _outputSize *= rd.size; + _right.totalSize *= rd.size; + _right.dims.push_back(rd); + } + } +} + + +} // namespace + diff --git a/eval/src/vespa/eval/tensor/dense/dense_dimension_combiner.h b/eval/src/vespa/eval/tensor/dense/dense_dimension_combiner.h new file mode 100644 index 00000000000..dd3f74bad9b --- /dev/null +++ b/eval/src/vespa/eval/tensor/dense/dense_dimension_combiner.h @@ -0,0 +1,114 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/eval/tensor/tensor.h> +#include <vespa/eval/tensor/types.h> +#include <vespa/eval/eval/value_type.h> + +namespace vespalib::tensor { + +class DenseDimensionCombiner { + + struct SideDim { + uint32_t idx; + uint32_t size; + uint32_t sideMultiplier; + uint32_t outputMultiplier; + }; + struct CommonDim { + uint32_t idx; + uint32_t size; + uint32_t leftMultiplier; + uint32_t rightMultiplier; + uint32_t outputMultiplier; + }; + + struct SideDims { + std::vector<SideDim> dims; + uint32_t index; + uint32_t totalSize; + + SideDims() : dims(), index(0), totalSize(1u) {} + + void reset(uint32_t &outIndex) { + for (SideDim& d : dims) { + index -= d.idx * d.sideMultiplier; + outIndex -= d.idx * d.outputMultiplier; + d.idx = 0; + } + if (index >= totalSize) { + index -= totalSize; + } + } + void step(uint32_t &outIndex) { + for (SideDim& d : dims) { + d.idx++; + index += d.sideMultiplier; + outIndex += d.outputMultiplier; + if (d.idx < d.size) return; + index -= d.idx * d.sideMultiplier; + outIndex -= d.idx * d.outputMultiplier; + d.idx = 0; + } + index += totalSize; + } + }; + SideDims _left; + SideDims _right; + std::vector<CommonDim> _commonDims; + uint32_t _outputIndex; + uint32_t _outputSize; + +public: + size_t leftIdx() const { return _left.index; } + size_t rightIdx() const { return _right.index; } + size_t outputIdx() const { return _outputIndex; } + + bool leftInRange() const { return _left.index < _left.totalSize; } + bool rightInRange() const { return _right.index < _right.totalSize; } + bool commonInRange() const { return _outputIndex < _outputSize; } + + void leftReset() { _left.reset(_outputIndex); } + void stepLeft() { _left.step(_outputIndex); } + + void rightReset() { _right.reset(_outputIndex); } + void stepRight() { _right.step(_outputIndex); } + + void commonReset() { + for (CommonDim& cd : _commonDims) { + _left.index -= cd.idx * cd.leftMultiplier; + _right.index -= cd.idx * cd.rightMultiplier; + _outputIndex -= cd.idx * cd.outputMultiplier; + cd.idx = 0; + } + if (_outputIndex >= _outputSize) { + _outputIndex -= _outputSize; + } + } + + void stepCommon() { + size_t lim = _commonDims.size(); + for (size_t i = 0; i < lim; ++i) { + CommonDim &cd = _commonDims[i]; + cd.idx++; + _left.index += cd.leftMultiplier; + _right.index += cd.rightMultiplier; + _outputIndex += cd.outputMultiplier; + if (cd.idx < cd.size) return; + _left.index -= cd.idx * cd.leftMultiplier; + _right.index -= cd.idx * cd.rightMultiplier; + _outputIndex -= cd.idx * cd.outputMultiplier; + cd.idx = 0; + } + _outputIndex += _outputSize; + } + + const eval::ValueType result_type; + + DenseDimensionCombiner(const eval::ValueType &lhs, const eval::ValueType &rhs); + + ~DenseDimensionCombiner(); +}; + +} diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_address_combiner.cpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_address_combiner.cpp deleted file mode 100644 index b5c5d9b6a04..00000000000 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_address_combiner.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "dense_tensor_address_combiner.h" -#include <vespa/vespalib/util/exceptions.h> -#include <cassert> - -namespace vespalib::tensor { - -DenseTensorAddressCombiner::~DenseTensorAddressCombiner() = default; - -DenseTensorAddressCombiner::DenseTensorAddressCombiner(const eval::ValueType &combined, const eval::ValueType &lhs, - const eval::ValueType &rhs) - : _rightAddress(rhs), - _combinedAddress(combined), - _left(), - _commonRight(), - _right() -{ - auto rhsItr = rhs.dimensions().cbegin(); - auto rhsItrEnd = rhs.dimensions().cend(); - uint32_t numDimensions(0); - for (const auto &lhsDim : lhs.dimensions()) { - while ((rhsItr != rhsItrEnd) && (rhsItr->name < lhsDim.name)) { - _right.emplace_back(numDimensions++, rhsItr-rhs.dimensions().cbegin()); - ++rhsItr; - } - if ((rhsItr != rhsItrEnd) && (rhsItr->name == lhsDim.name)) { - _left.emplace_back(numDimensions, _left.size()); - _commonRight.emplace_back(numDimensions, rhsItr-rhs.dimensions().cbegin()); - ++numDimensions; - ++rhsItr; - } else { - _left.emplace_back(numDimensions++, _left.size()); - } - } - while (rhsItr != rhsItrEnd) { - _right.emplace_back(numDimensions++, rhsItr-rhs.dimensions().cbegin()); - ++rhsItr; - } -} - -AddressContext::AddressContext(const eval::ValueType &type) - : _type(type), - _accumulatedSize(_type.dimensions().size()), - _address(type.dimensions().size(), 0) - -{ - size_t multiplier = 1; - for (int32_t i(_address.size() - 1); i >= 0; i--) { - _accumulatedSize[i] = multiplier; - multiplier *= type.dimensions()[i].size; - } -} - -AddressContext::~AddressContext() = default; - -eval::ValueType -DenseTensorAddressCombiner::combineDimensions(const eval::ValueType &lhs, const eval::ValueType &rhs) -{ - return eval::ValueType::join(lhs, rhs); -} - -} diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_address_combiner.h b/eval/src/vespa/eval/tensor/dense/dense_tensor_address_combiner.h deleted file mode 100644 index 3f6e347490c..00000000000 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_address_combiner.h +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#pragma once - -#include "dense_tensor_cells_iterator.h" -#include <vespa/eval/tensor/tensor.h> -#include <vespa/eval/tensor/types.h> -#include <vespa/eval/eval/value_type.h> - -namespace vespalib::tensor { - -class AddressContext { -public: - using Address = DenseTensorCellsIterator::Address; - using size_type = eval::ValueType::Dimension::size_type; - using Mapping = std::vector<std::pair<uint32_t, uint32_t>>; - AddressContext(const eval::ValueType &type); - ~AddressContext(); - size_type dimSize(uint32_t dim) const { return _type.dimensions()[dim].size; } - size_type wholeDimStep(uint32_t dim) const { return _accumulatedSize[dim] * dimSize(dim); } - size_t index() const { - size_t cellIdx(0); - for (uint32_t i(0); i < _address.size(); i++) { - cellIdx += _address[i]*_accumulatedSize[i]; - } - return cellIdx; - } - void update(const Address & addr, const Mapping & mapping) { - for (const auto & m : mapping) { - _address[m.first] = addr[m.second]; - } - } - bool updateCommon(const Address & addr, const Mapping & mapping) { - for (const auto & m : mapping) { - if (addr[m.first] >= dimSize(m.second)) { - return false; - } - _address[m.second] = addr[m.first]; - } - return true; - } - - const eval::ValueType &_type; - std::vector<size_t> _accumulatedSize; - Address _address; - -}; - -/** - * Combines two dense tensor addresses to a new tensor address. - * The resulting dimensions is the union of the input dimensions and - * common dimensions must have matching labels. - */ -class DenseTensorAddressCombiner -{ -public: - using Mapping = AddressContext::Mapping; - -private: - using Address = DenseTensorCellsIterator::Address; - using CellsRef = vespalib::ConstArrayRef<double>; - using size_type = eval::ValueType::Dimension::size_type; - - AddressContext _rightAddress; - AddressContext _combinedAddress; - - Mapping _left; - Mapping _commonRight; - Mapping _right; - -public: - DenseTensorAddressCombiner(const eval::ValueType &combined, const eval::ValueType &lhs, const eval::ValueType &rhs); - ~DenseTensorAddressCombiner(); - void updateLeftAndCommon(const Address & addr) { _combinedAddress.update(addr, _left); } - bool updateCommon() { return _rightAddress.updateCommon(_combinedAddress._address, _commonRight); } - bool hasAnyRightOnlyDimensions() const { return ! _right.empty(); } - - const Address & address() const { return _combinedAddress._address; } - size_t rightCellIndex() const { return _rightAddress.index(); } - - template <typename Func> - void for_each_right(const CellsRef & rhsCells, Func && func) { - // The rightAddress oly holds the starting point for iteration and what is need to efficiently maintain - // an index for addressing th ecells. - const int32_t lastDimension = _right.size() - 1; - int32_t curDimension = lastDimension; - size_t rightCellIdx = _rightAddress.index(); - size_t combinedCellIdx = _combinedAddress.index(); - while (curDimension >= 0) { - const uint32_t rdim = _right[curDimension].second; - const uint32_t cdim = _right[curDimension].first; - size_type & cindex = _combinedAddress._address[cdim]; - if (curDimension == lastDimension) { - for (cindex = 0; cindex < _rightAddress.dimSize(rdim); cindex++) { - func(combinedCellIdx, rhsCells[rightCellIdx]); - rightCellIdx += _rightAddress._accumulatedSize[rdim]; - combinedCellIdx += _combinedAddress._accumulatedSize[cdim]; - } - cindex = 0; - rightCellIdx -= _rightAddress.wholeDimStep(rdim); - combinedCellIdx -= _combinedAddress.wholeDimStep(cdim); - curDimension--; - } else { - if ((cindex + 1) < _rightAddress.dimSize(rdim)) { - cindex++; - rightCellIdx += _rightAddress._accumulatedSize[rdim]; - combinedCellIdx += _combinedAddress._accumulatedSize[cdim]; - curDimension++; - } else { - rightCellIdx -= _rightAddress.wholeDimStep(rdim); - combinedCellIdx -= _combinedAddress.wholeDimStep(cdim); - cindex = 0; - curDimension--; - } - } - } - } - - static eval::ValueType combineDimensions(const eval::ValueType &lhs, const eval::ValueType &rhs); -}; - - -} diff --git a/eval/src/vespa/eval/tensor/dense/dense_tensor_apply.hpp b/eval/src/vespa/eval/tensor/dense/dense_tensor_apply.hpp index fa1e59c87db..e71840f392c 100644 --- a/eval/src/vespa/eval/tensor/dense/dense_tensor_apply.hpp +++ b/eval/src/vespa/eval/tensor/dense/dense_tensor_apply.hpp @@ -3,78 +3,45 @@ #pragma once #include "dense_tensor_apply.h" -#include "dense_tensor_address_combiner.h" +#include "dense_dimension_combiner.h" #include "direct_dense_tensor_builder.h" namespace vespalib::tensor::dense { template <typename Function> std::unique_ptr<Tensor> -apply(DenseTensorAddressCombiner & combiner, DirectDenseTensorBuilder & builder, - const DenseTensorView &lhs, const DenseTensorView::CellsRef & rhsCells, Function &&func) __attribute__((noinline)); +apply(DenseDimensionCombiner & combiner, DirectDenseTensorBuilder & builder, + const DenseTensorView::CellsRef & lhsCells, + const DenseTensorView::CellsRef & rhsCells, Function &&func) __attribute__((noinline)); template <typename Function> std::unique_ptr<Tensor> -apply(DenseTensorAddressCombiner & combiner, DirectDenseTensorBuilder & builder, - const DenseTensorView &lhs, const DenseTensorView::CellsRef & rhsCells, Function &&func) +apply(DenseDimensionCombiner & combiner, DirectDenseTensorBuilder & builder, + const DenseTensorView::CellsRef & lhsCells, + const DenseTensorView::CellsRef & rhsCells, Function &&func) { - for (DenseTensorCellsIterator lhsItr = lhs.cellsIterator(); lhsItr.valid(); lhsItr.next()) { - combiner.updateLeftAndCommon(lhsItr.address()); - if (combiner.updateCommon()) { - combiner.for_each_right(rhsCells, [&func, &builder, &lhsItr](size_t combined, double rhsCell) { - builder.insertCell(combined, func(lhsItr.cell(), rhsCell)); - }); + for (combiner.leftReset(); combiner.leftInRange(); combiner.stepLeft()) { + for (combiner.rightReset(); combiner.rightInRange(); combiner.stepRight()) { + for (combiner.commonReset(); combiner.commonInRange(); combiner.stepCommon()) { + size_t outIdx = combiner.outputIdx(); + size_t l = combiner.leftIdx(); + size_t r = combiner.rightIdx(); + builder.insertCell(outIdx, func(lhsCells[l], rhsCells[r])); + } } } return builder.build(); } - -template <typename Function> -std::unique_ptr<Tensor> -apply_no_rightonly_dimensions(DenseTensorAddressCombiner & combiner, DirectDenseTensorBuilder & builder, - const DenseTensorView &lhs, const DenseTensorView::CellsRef & rhsCells, - Function &&func) __attribute__((noinline)); - -template <typename Function> -std::unique_ptr<Tensor> -apply_no_rightonly_dimensions(DenseTensorAddressCombiner & combiner, DirectDenseTensorBuilder & builder, - const DenseTensorView &lhs, const DenseTensorView::CellsRef & rhsCells, Function &&func) -{ - for (DenseTensorCellsIterator lhsItr = lhs.cellsIterator(); lhsItr.valid(); lhsItr.next()) { - combiner.updateLeftAndCommon(lhsItr.address()); - if (combiner.updateCommon()) { - builder.insertCell(combiner.address(), func(lhsItr.cell(), rhsCells[combiner.rightCellIndex()])); - } - } - return builder.build(); -} - -template <typename Function> -std::unique_ptr<Tensor> -apply(const DenseTensorView &lhs, const DenseTensorView &rhs, Function &&func) -{ - eval::ValueType resultType = DenseTensorAddressCombiner::combineDimensions(lhs.fast_type(), rhs.fast_type()); - DenseTensorAddressCombiner combiner(resultType, lhs.fast_type(), rhs.fast_type()); - DirectDenseTensorBuilder builder(resultType); - if (combiner.hasAnyRightOnlyDimensions()) { - return apply(combiner, builder, lhs, rhs.cellsRef(), std::move(func)); - } else { - return apply_no_rightonly_dimensions(combiner, builder, lhs, rhs.cellsRef(), std::move(func)); - } -} - template <typename Function> std::unique_ptr<Tensor> apply(const DenseTensorView &lhs, const Tensor &rhs, Function &&func) { const DenseTensorView *view = dynamic_cast<const DenseTensorView *>(&rhs); if (view) { - return apply(lhs, *view, func); - } - const DenseTensor *dense = dynamic_cast<const DenseTensor *>(&rhs); - if (dense) { - return apply(lhs, *dense, func); + DenseDimensionCombiner combiner(lhs.fast_type(), view->fast_type()); + DirectDenseTensorBuilder builder(combiner.result_type); + return apply(combiner, builder, lhs.cellsRef(), view->cellsRef(), std::move(func)); } return Tensor::UP(); } |