blob: 8dc638f35626a4bf7977e448cf4f25828cdec483 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include "main.h"
#include <Eigen/Core>
#include <cfenv>
#ifndef EIGEN_VECTORIZE_SME
#error "vector_sme requires an SME2 target"
#endif
template <typename Lhs, typename Rhs>
void sme_check_dot(const Lhs& lhs, const Rhs& rhs) {
using Scalar = typename Lhs::Scalar;
long double expected = 0, magnitude = 0;
for (Index i = 0; i < lhs.size(); ++i) {
const long double term = static_cast<long double>(lhs[i]) * static_cast<long double>(rhs[i]);
expected += term;
magnitude += numext::abs(term);
}
// gamma_(2*n+4) bounds multiplication and accumulation in both the kernel and reference.
const long double rounding =
(2 * static_cast<long double>(lhs.size()) + 4) * static_cast<long double>(NumTraits<Scalar>::epsilon());
const long double bound = rounding / (1 - rounding) * magnitude;
const Scalar actual = lhs.dot(rhs);
VERIFY(rounding < 1 && (numext::isfinite)(bound));
VERIFY((numext::isfinite)(actual));
VERIFY(numext::abs(static_cast<long double>(actual) - expected) <= bound);
}
template <typename Scalar>
void sme_vectors() {
using Vec = Vector<Scalar, Dynamic>;
using Row = Matrix<Scalar, 1, Dynamic>;
using Strided = Map<Vec, 0, InnerStride<Dynamic>>;
STATIC_CHECK((internal::sme_dot_supported<Vec, Row>::value));
STATIC_CHECK((internal::sme_dot_supported<Strided, Vec>::value));
STATIC_CHECK((!internal::sme_dot_supported<decltype(std::declval<Vec>() + std::declval<Vec>()), Vec>::value));
STATIC_CHECK((!internal::sme_vector_scalar<std::complex<Scalar>>::value));
STATIC_CHECK((!internal::sme_vector_scalar<int>::value));
std::vector<Index> sizes;
for (Index n = 0; n <= 260; ++n) {
sizes.push_back(n);
sizes.push_back(32768 + n);
}
for (Index n : {511, 512, 513, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096,
4097, 8191, 8192, 8193, 16383, 16384, 16385, 32767, 32768, 32769})
sizes.push_back(n);
for (Index n : sizes) {
Vec x = Vec::Ones(n), storage = Vec::Ones(n + 2);
VERIFY_IS_EQUAL(x.dot(storage.head(n)), Scalar(n));
x.setRandom();
storage.setRandom();
const Vec before = storage;
auto y = storage.segment(1, n);
sme_check_dot(x, y);
sme_check_dot(x.transpose(), y);
for (int side = 0; side < 2; ++side) {
storage = before;
if (side == 0)
y += Scalar(0.75) * x;
else
y += x * Scalar(0.75);
for (Index i = 0; i < n; ++i) {
const long double expected = static_cast<long double>(before[i + 1]) + 0.75L * static_cast<long double>(x[i]);
const long double magnitude =
numext::abs(static_cast<long double>(before[i + 1])) + 0.75L * numext::abs(static_cast<long double>(x[i]));
VERIFY(numext::abs(static_cast<long double>(y[i]) - expected) <=
4 * static_cast<long double>(NumTraits<Scalar>::epsilon()) * magnitude);
}
VERIFY_IS_EQUAL(storage[0], before[0]);
VERIFY_IS_EQUAL(storage[n + 1], before[n + 1]);
}
Vec alias = x;
alias += Scalar(0.75) * alias;
for (Index i = 0; i < n; ++i) {
const long double expected = static_cast<long double>(x[i]) * 1.75L;
VERIFY(numext::abs(static_cast<long double>(alias[i]) - expected) <=
4 * static_cast<long double>(NumTraits<Scalar>::epsilon()) * numext::abs(expected));
}
Strided contiguous(x.data(), n, InnerStride<Dynamic>(1));
sme_check_dot(contiguous, y);
}
Vec x = Vec::Random(65538), y = Vec::Random(32769), expected = x;
Strided strided(x.data(), y.size(), InnerStride<Dynamic>(2));
Strided negative(x.data() + x.size() - 2, y.size(), InnerStride<Dynamic>(-2));
sme_check_dot(strided, y);
sme_check_dot(negative, y);
for (Index i = 0; i < y.size(); ++i) expected[2 * i] += Scalar(0.75) * y[i];
strided += Scalar(0.75) * y;
VERIFY_IS_APPROX(x, expected);
sme_check_dot(-y, strided);
sme_check_dot(y + y, strided);
sme_check_dot(y.reverse(), strided);
VERIFY_RAISES_ASSERT(y.dot(x));
for (int pattern = 0; pattern < 3; ++pattern) {
Vec a = Vec::Random(65537), b = Vec::Random(65537);
for (Index i = 0; i < a.size(); ++i) {
if (pattern == 0) b[i] = (i & 1) ? -a[i] : a[i];
if (pattern == 1) a[i] = numext::ldexp(a[i], int(i % 81) - 40);
if (pattern == 2) {
a[i] = Scalar(1);
b[i] = i % 3 == 0 ? Scalar(1) / NumTraits<Scalar>::epsilon()
: i % 3 == 1 ? Scalar(1)
: -Scalar(1) / NumTraits<Scalar>::epsilon();
}
}
sme_check_dot(a, b);
}
for (Index n : {524287, 524288, 524289, 1048575, 1048576, 1048577}) {
Vec a = Vec::Ones(n), b = Vec::Ones(n);
VERIFY_IS_EQUAL(a.dot(b), Scalar(n));
b += Scalar(0.75) * a;
VERIFY((b.array() == Scalar(1.75)).all());
}
}
template <typename Scalar>
void sme_vector_cache_sizes() {
using Vec = Vector<Scalar, Dynamic>;
const std::ptrdiff_t old_l1 = l1CacheSize(), old_l2 = l2CacheSize(), old_l3 = l3CacheSize();
const Index n = 32769;
const std::ptrdiff_t bytes = n * sizeof(Scalar);
const Vec x = Vec::Ones(n);
Vec y(n);
for (std::ptrdiff_t l2 : {bytes, bytes - 1, std::ptrdiff_t(0), std::ptrdiff_t(-1), bytes + 1}) {
setCpuCacheSizes(old_l1, l2, old_l3);
VERIFY_IS_EQUAL(internal::sme_vector_size_suitable<Scalar>(n), l2 >= bytes);
VERIFY(!internal::sme_vector_size_suitable<Scalar>((std::numeric_limits<Index>::max)()));
VERIFY_IS_EQUAL(x.dot(x), Scalar(n));
y.setOnes();
y += Scalar(0.75) * x;
VERIFY((y.array() == Scalar(1.75)).all());
y.setOnes();
y += x * Scalar(0.75);
VERIFY((y.array() == Scalar(1.75)).all());
}
for (std::ptrdiff_t l1 : {bytes, bytes - 1, std::ptrdiff_t(0), std::ptrdiff_t(-1), bytes + 1}) {
setCpuCacheSizes(l1, bytes, old_l3);
const bool suitable = l1 > 0 && l1 <= bytes;
VERIFY_IS_EQUAL(internal::sme_vector_size_suitable<Scalar>(n), suitable);
y.setOnes();
VERIFY_IS_EQUAL(internal::sme_try_axpy(y, x, Scalar(0.75)), suitable);
VERIFY((y.array() == (suitable ? Scalar(1.75) : Scalar(1))).all());
VERIFY_IS_EQUAL(x.dot(x), Scalar(n));
}
for (std::ptrdiff_t l1 : {2 * bytes - 1, 2 * bytes, 2 * bytes + 1}) {
setCpuCacheSizes(l1, bytes, old_l3);
VERIFY_IS_EQUAL((internal::sme_vector_size_suitable<Scalar, 2>(n)), l1 <= 2 * bytes);
VERIFY_IS_EQUAL(x.dot(x), Scalar(n));
}
#ifndef EIGEN_USE_BLAS
const std::ptrdiff_t matrix_bytes = 128 * 4 * sizeof(Scalar);
for (std::ptrdiff_t l1 : {matrix_bytes - 1, matrix_bytes, matrix_bytes + 1, std::ptrdiff_t(0)}) {
setCpuCacheSizes(l1, old_l2, old_l3);
VERIFY_IS_EQUAL(internal::sme_gemv_size_suitable<Scalar>(Index(128), Index(4)), l1 > 0 && l1 <= matrix_bytes);
VERIFY(!internal::sme_gemv_size_suitable<Scalar>(Index(0), Index(4)));
}
#endif
setCpuCacheSizes(old_l1, old_l2, old_l3);
}
template <typename Scalar>
void sme_vector_scalar_factors() {
using Vec = Vector<Scalar, Dynamic>;
const Index n = 32769;
Vec x = Vec::Constant(n, Scalar(0.25)), y = Vec::Zero(n);
y += x.cwiseProduct(x);
VERIFY((y.array() == Scalar(0.0625)).all());
y.setZero();
y += Vec::Constant(n, Scalar(0.75)).cwiseProduct(x);
VERIFY((y.array() == Scalar(0.1875)).all());
// Folding the two scale factors would overflow even though every output is finite.
const Scalar large = (std::numeric_limits<Scalar>::max)() / Scalar(2);
y.setZero();
y += large * (Scalar(4) * x);
VERIFY((y.array() == large).all());
y.setZero();
y += (x * Scalar(4)) * large;
VERIFY((y.array() == large).all());
Array<Scalar, Dynamic, 1> a = x.array(), b = Array<Scalar, Dynamic, 1>::Zero(n);
b += Scalar(0.75) * a;
VERIFY((b == Scalar(0.1875)).all());
b.setZero();
b += a * Scalar(0.75);
VERIFY((b == Scalar(0.1875)).all());
}
template <typename Scalar>
void sme_matrix_vectors() {
using Vec = Vector<Scalar, Dynamic>;
using Mat = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
for (Index rows : {127, 128, 129, 255, 256, 257, 513}) {
for (Index cols : {3, 4, 5, 15, 16, 17, 31, 32, 33, 63, 64, 65, 129}) {
for (Index padding : {0, 3}) {
Mat storage = Mat::Random(rows + padding, cols);
auto a = storage.topRows(rows);
Vec x = Vec::Random(cols), original = Vec::Random(rows), result(rows);
for (Scalar alpha : {Scalar(0), Scalar(0.75), Scalar(-1)}) {
result = original;
result.noalias() += alpha * a * x;
for (Index i = 0; i < rows; ++i) {
long double expected = static_cast<long double>(original[i]), magnitude = numext::abs(expected);
for (Index j = 0; j < cols; ++j) {
const long double term =
static_cast<long double>(alpha) * static_cast<long double>(a(i, j)) * static_cast<long double>(x[j]);
expected += term;
magnitude += numext::abs(term);
}
const long double bound =
8 * (cols + 1) * static_cast<long double>(NumTraits<Scalar>::epsilon()) * magnitude;
VERIFY((numext::isfinite)(bound));
VERIFY(numext::abs(static_cast<long double>(result[i]) - expected) <= bound);
}
}
Vec strided_storage = Vec::Random(2 * rows), untouched = strided_storage;
Map<Vec, 0, InnerStride<2>> strided(strided_storage.data(), rows);
Vec reference = strided;
reference.noalias() += a * x;
strided.noalias() += a * x;
VERIFY_IS_APPROX(strided, reference);
for (Index i = 0; i < rows; ++i) VERIFY_IS_EQUAL(strided_storage[2 * i + 1], untouched[2 * i + 1]);
}
}
}
}
template <typename Scalar>
void sme_vector_alignment() {
using Vec = Vector<Scalar, Dynamic>;
const Index padding = 64 / sizeof(Scalar);
for (Index tail : {0, 1, 15, 31, 63, 127, 255, 256}) {
const Index n = 32768 + tail;
Vec x_storage = Vec::Ones(n + padding), y_storage(n + 2 * padding);
for (Index offset = 0; offset < padding; ++offset) {
y_storage.setConstant(Scalar(2));
const auto x = x_storage.segment(offset, n);
auto y = y_storage.segment(padding + offset, n);
VERIFY(internal::sme_try_axpy(y, x, Scalar(0.75)));
VERIFY((y.array() == Scalar(2.75)).all());
VERIFY((y_storage.head(padding + offset).array() == Scalar(2)).all());
VERIFY((y_storage.tail(padding - offset).array() == Scalar(2)).all());
VERIFY_IS_EQUAL(x.dot(y), Scalar(2.75) * Scalar(n));
}
}
}
template <typename Scalar>
void sme_gemv_tails() {
using Vec = Vector<Scalar, Dynamic>;
using Mat = Matrix<Scalar, Dynamic, Dynamic>;
// Every predicated chunk pattern of a row block, including at the largest architectural SVL.
for (Index rows = 128; rows <= 1100; ++rows) {
const Mat a = Mat::Ones(rows, 65);
const Vec x = Vec::Ones(65);
Vec storage = Vec::Ones(rows + 2);
auto y = storage.segment(1, rows);
y.noalias() += Scalar(0.75) * a * x;
VERIFY((y.array() == Scalar(49.75)).all());
VERIFY_IS_EQUAL(storage[0], Scalar(1));
VERIFY_IS_EQUAL(storage[rows + 1], Scalar(1));
}
}
template <typename Scalar>
void sme_vector_special_values() {
using Vec = Vector<Scalar, Dynamic>;
using Mat = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
const Scalar inf = NumTraits<Scalar>::infinity(), nan = NumTraits<Scalar>::quiet_NaN();
const Scalar tiny = (std::numeric_limits<Scalar>::denorm_min)();
for (Index pos : {0, 7, 31, 63, 127, 255, 256, 32768}) {
Vec x = Vec::Zero(32769), y = Vec::Ones(32769), result(32769);
for (Scalar special : {inf, -inf, nan, tiny, -tiny, Scalar(-0.0)}) {
x[pos] = special;
const Scalar actual = x.dot(y);
if ((numext::isnan)(special))
VERIFY((numext::isnan)(actual));
else
VERIFY_IS_EQUAL(actual, special);
result.setZero();
result += Scalar(1) * x;
for (Index i = 0; i < result.size(); ++i) {
const Scalar expected = x[i] + Scalar(0);
if ((numext::isnan)(expected))
VERIFY((numext::isnan)(result[i]));
else {
VERIFY_IS_EQUAL(result[i], expected);
VERIFY_IS_EQUAL(std::signbit(result[i]), std::signbit(expected));
}
}
}
}
for (Scalar special : {inf, -inf, nan, tiny, -tiny}) {
Mat a = Mat::Zero(257, 65);
Vec x = Vec::Ones(65), y = Vec::Zero(257);
a(0, 0) = a(256, 64) = special;
y.noalias() += a * x;
for (Index i = 0; i < y.size(); ++i) {
const Scalar expected = i == 0 || i == 256 ? special : Scalar(0);
if ((numext::isnan)(expected))
VERIFY((numext::isnan)(y[i]));
else
VERIFY_IS_EQUAL(y[i], expected);
}
y.setOnes();
y.noalias() += Scalar(0) * a * x;
VERIFY((y.array() == Scalar(1)).all());
}
Vec x = Vec::Zero(32769), y = Vec::Zero(32769);
x[0] = inf;
VERIFY((numext::isnan)(x.dot(y)));
}
template <typename Scalar>
void sme_gemv_scaled_range() {
using Mat = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
using Vec = Vector<Scalar, Dynamic>;
const Scalar largest = (std::numeric_limits<Scalar>::max)();
const std::ptrdiff_t l1 = l1CacheSize(), l2 = l2CacheSize(), l3 = l3CacheSize();
for (std::ptrdiff_t cache : {l1, std::ptrdiff_t(0)}) {
setCpuCacheSizes(cache, l2, l3);
for (Index rows : {256, 257}) {
for (Index cols : {128, 129}) {
Mat matrix = Mat::Constant(rows, cols, largest / Scalar(64));
Vec rhs = Vec::Ones(cols), result = Vec::Zero(rows);
result.noalias() += Scalar(0.25) * matrix * rhs;
const Scalar expected = (largest / Scalar(256)) * Scalar(cols);
const Scalar bound = Scalar(4 * cols) * NumTraits<Scalar>::epsilon();
for (Index i = 0; i < rows; ++i) {
VERIFY((numext::isfinite)(result[i]));
VERIFY(numext::abs(result[i] / expected - Scalar(1)) <= bound);
}
}
}
}
setCpuCacheSizes(l1, l2, l3);
}
template <typename Scalar>
void sme_dot_signed_zero() {
using Vec = Vector<Scalar, Dynamic>;
const Scalar tiny = (std::numeric_limits<Scalar>::denorm_min)();
for (Index n : {32768, 32769, 32799, 33791}) {
Vec x = Vec::Constant(n, Scalar(-0.0)), y = Vec::Ones(n);
const Scalar negative = x.dot(y);
VERIFY_IS_EQUAL(negative, Scalar(0));
VERIFY((numext::signbit)(negative));
x.setZero();
const Scalar positive = x.dot(y);
VERIFY_IS_EQUAL(positive, Scalar(0));
VERIFY(!(numext::signbit)(positive));
// A single positive-zero product, in the first group or the last element, gives +0.
for (Index pos : {Index(0), n - 1}) {
x.setConstant(Scalar(-0.0));
x[pos] = Scalar(0);
const Scalar mixed = x.dot(y);
VERIFY_IS_EQUAL(mixed, Scalar(0));
VERIFY(!(numext::signbit)(mixed));
}
// Exact cancellation, across lanes (adjacent terms) or within a fused lane (halves), rounds to +0.
for (int within_lane = 0; within_lane < 2; ++within_lane) {
x.setConstant(Scalar(-0.0));
const Index half = n / 2;
for (Index i = 0; i < half; ++i) {
x[within_lane ? i : 2 * i] = Scalar(1);
x[within_lane ? half + i : 2 * i + 1] = Scalar(-1);
}
const Scalar cancelled = x.dot(y);
VERIFY_IS_EQUAL(cancelled, Scalar(0));
VERIFY(!(numext::signbit)(cancelled));
}
// Products that underflow to -0 keep the sign of every fused partial sum.
x.setConstant(-tiny);
const Scalar underflow = x.dot(Vec::Constant(n, Scalar(0.25)));
VERIFY_IS_EQUAL(underflow, Scalar(0));
VERIFY((numext::signbit)(underflow));
}
}
template <typename Scalar>
void sme_dot_directed_rounding() {
#if EIGEN_COMP_CLANG
#pragma STDC FENV_ACCESS ON
#endif
using Vec = Vector<Scalar, Dynamic>;
const int saved = std::fegetround();
const Index n = 32769;
const Vec y = Vec::Ones(n);
for (int mode : {FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO}) {
VERIFY_IS_EQUAL(std::fesetround(mode), 0);
// IEEE 754 6.3: x + x keeps the sign of x; an exact zero sum of opposite signs is -0 only when rounding down.
Vec x = Vec::Zero(n);
const Scalar positive = x.dot(y);
x.setConstant(Scalar(-0.0));
const Scalar negative = x.dot(y);
x[0] = Scalar(0);
const Scalar mixed = x.dot(y);
std::fesetround(saved);
VERIFY(positive == Scalar(0) && !std::signbit(positive));
VERIFY(negative == Scalar(0) && std::signbit(negative));
VERIFY(mixed == Scalar(0) && std::signbit(mixed) == (mode == FE_DOWNWARD));
}
}
template <typename Scalar>
void sme_vector_exception_state() {
#if EIGEN_COMP_CLANG
#pragma STDC FENV_ACCESS ON
#endif
using Vec = Vector<Scalar, Dynamic>;
using Mat = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
std::fenv_t saved;
std::fegetenv(&saved);
const Scalar inf = NumTraits<Scalar>::infinity(), largest = (std::numeric_limits<Scalar>::max)();
// The kernels leave the caller's flags as they were, whether or not an SVE tail or row remainder follows the
// ZA loop: exact inputs, then inf * 0 (invalid), then max * 2 (overflow).
for (Index n : {32768, 32769}) {
Vec x(n), y(n);
Mat matrix(n == 32768 ? 256 : 257, 128);
Vec rhs(128), result(matrix.rows());
for (int input = 0; input < 3; ++input) {
const Scalar value = input == 0 ? Scalar(1) : input == 1 ? inf : largest;
const Scalar factor = input == 0 ? Scalar(1) : input == 1 ? Scalar(0) : Scalar(2);
for (int prior : {0, FE_DIVBYZERO}) {
for (int operation = 0; operation < 3; ++operation) {
x.setConstant(value);
y.setConstant(factor);
matrix.setConstant(value);
rhs.setConstant(factor);
result.setZero();
std::feclearexcept(FE_ALL_EXCEPT);
std::feraiseexcept(prior);
Scalar actual;
if (operation == 0) {
actual = x.dot(y);
} else if (operation == 1) {
y += factor * x;
actual = y[n - 1];
} else {
result.noalias() += matrix * rhs;
actual = result[result.size() - 1];
}
const int flags = std::fetestexcept(FE_ALL_EXCEPT);
VERIFY_IS_EQUAL(flags, prior);
if (input == 1) {
VERIFY((numext::isnan)(actual));
} else if (input == 2) {
VERIFY_IS_EQUAL(actual, inf);
} else {
VERIFY_IS_EQUAL(actual, operation == 0 ? Scalar(n) : operation == 1 ? Scalar(2) : Scalar(128));
}
}
}
}
}
// Longer calls are more likely to be preempted while streaming.
Vec x = Vec::Ones(Index(1) << 20), y = Vec::Ones(Index(1) << 20);
for (int repetition = 0; repetition < 64; ++repetition) {
std::feclearexcept(FE_ALL_EXCEPT);
const Scalar dot = x.dot(y);
y += Scalar(repetition % 2 ? -0.75 : 0.75) * x;
VERIFY_IS_EQUAL(std::fetestexcept(FE_ALL_EXCEPT), 0);
VERIFY_IS_EQUAL(dot, Scalar(repetition % 2 ? 1.75 : 1) * Scalar(x.size()));
}
std::fesetenv(&saved);
}
EIGEN_DECLARE_TEST(vector_sme) {
const std::ptrdiff_t l1 = l1CacheSize(), l2 = l2CacheSize(), l3 = l3CacheSize();
setCpuCacheSizes(128 * 1024, 4 * 1024 * 1024, l3);
CALL_SUBTEST_1(sme_vector_cache_sizes<float>());
CALL_SUBTEST_1(sme_vector_scalar_factors<float>());
CALL_SUBTEST_1(sme_vectors<float>());
CALL_SUBTEST_1(sme_matrix_vectors<float>());
CALL_SUBTEST_1(sme_vector_alignment<float>());
CALL_SUBTEST_1(sme_gemv_tails<float>());
CALL_SUBTEST_1(sme_vector_special_values<float>());
CALL_SUBTEST_3(sme_gemv_scaled_range<float>());
CALL_SUBTEST_3(sme_dot_signed_zero<float>());
CALL_SUBTEST_3(sme_dot_directed_rounding<float>());
CALL_SUBTEST_3(sme_vector_exception_state<float>());
#ifdef EIGEN_VECTORIZE_SME_F64F64
CALL_SUBTEST_2(sme_vector_cache_sizes<double>());
CALL_SUBTEST_2(sme_vector_scalar_factors<double>());
CALL_SUBTEST_2(sme_vectors<double>());
CALL_SUBTEST_2(sme_matrix_vectors<double>());
CALL_SUBTEST_2(sme_vector_alignment<double>());
CALL_SUBTEST_2(sme_gemv_tails<double>());
CALL_SUBTEST_2(sme_vector_special_values<double>());
CALL_SUBTEST_4(sme_gemv_scaled_range<double>());
CALL_SUBTEST_4(sme_dot_signed_zero<double>());
CALL_SUBTEST_4(sme_dot_directed_rounding<double>());
CALL_SUBTEST_4(sme_vector_exception_state<double>());
#endif
setCpuCacheSizes(l1, l2, l3);
}