perf(shell): route local products through MKL and reduce Hessian storage work

This commit is contained in:
KOKO\Mimi
2026-10-14 18:07:16 +09:00
parent 8c7c41729e
commit f9e0df66dc
9 changed files with 758 additions and 179 deletions
@@ -11,6 +11,98 @@
namespace fesa::dense_blas_internal {
namespace {
TEST(DenseBlasInternal,
OuterProductUsesRectangularStorageAndPreservesRejection) {
const std::array<double, 2U> left{2.0, -3.0};
const std::array<double, 3U> right{4.0, -5.0, 0.25};
std::array<double, 6U> product{};
ASSERT_TRUE(OuterProduct(left.data(), left.size(), right.data(), right.size(),
product.data()));
const std::array<double, 6U> expected{8.0, -10.0, 0.5, -12.0, 15.0, -0.75};
EXPECT_EQ(product, expected);
const auto accepted = product;
const std::array<double, 2U> invalid{
1.0, std::numeric_limits<double>::quiet_NaN()};
EXPECT_FALSE(OuterProduct(invalid.data(), invalid.size(), right.data(),
right.size(), product.data()));
EXPECT_EQ(product, accepted);
EXPECT_TRUE(OuterProduct(nullptr, 0U, nullptr, 3U, nullptr));
EXPECT_TRUE(OuterProduct(nullptr, 2U, nullptr, 0U, nullptr));
EXPECT_THROW(OuterProduct(nullptr, 2U, right.data(), 3U, product.data()),
std::invalid_argument);
const auto too_large =
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)()) + 1U;
EXPECT_THROW(OuterProduct(nullptr, too_large, nullptr, 1U, nullptr),
std::length_error);
const auto too_many_elements =
std::numeric_limits<std::size_t>::max() / sizeof(double) + 1U;
EXPECT_THROW(OuterProduct(nullptr, too_many_elements, nullptr, 1U, nullptr),
std::length_error);
}
TEST(DenseBlasInternal, LocalDenseScopeRestoresTlsAfterProductsAndUnwind) {
const int previous = mkl_set_num_threads_local(0);
struct Restore {
int previous;
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
} restore{previous};
const int default_threads = mkl_get_max_threads();
const std::array<double, 2U> left{0.0, 2.0};
const std::array<double, 2U> right{3.0, 4.0};
std::array<double, 4U> product{};
product.fill(std::numeric_limits<double>::quiet_NaN());
{
const ScopedLocalDenseThreads scope{};
EXPECT_EQ(mkl_get_max_threads(), 1);
EXPECT_TRUE(
OuterProduct(left.data(), 2U, right.data(), 2U, product.data()));
EXPECT_DOUBLE_EQ(product[0U], 0.0);
EXPECT_DOUBLE_EQ(product[3U], 8.0);
}
EXPECT_EQ(mkl_get_max_threads(), default_threads);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
try {
const ScopedLocalDenseThreads scope{};
OuterProduct(nullptr, 2U, right.data(), 2U, product.data());
} catch (const std::invalid_argument&) {
}
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
static_cast<void>(mkl_set_num_threads_local(3));
{
const ScopedLocalDenseThreads scope{};
EXPECT_EQ(mkl_get_max_threads(), 3);
}
EXPECT_EQ(mkl_get_max_threads(), 3);
}
TEST(DenseBlasInternal, OuterProductLeavesFiniteInputOverflowForOwnerGate) {
const std::array<double, 1U> left{std::numeric_limits<double>::max()};
const std::array<double, 1U> right{2.0};
std::array<double, 1U> product{};
EXPECT_TRUE(OuterProduct(left.data(), 1U, right.data(), 1U, product.data()));
EXPECT_TRUE(std::isinf(product[0U]));
}
TEST(DenseBlasInternal, TransposeRightProductUsesInnerAndRectangularStrides) {
const std::array<double, 6U> left{1, 2, 3, 4, 5, 6};
const std::array<double, 12U> right{7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18};
std::array<double, 8U> product;
ASSERT_TRUE(MultiplyTransposeRight(left.data(), 2U, right.data(), 4U, 3U,
product.data()));
const std::array<double, 8U> expected{50, 68, 86, 104, 122, 167, 212, 257};
EXPECT_EQ(product, expected);
EXPECT_TRUE(
MultiplyTransposeRight(nullptr, 2U, nullptr, 4U, 0U, product.data()));
for (const auto value : product) EXPECT_DOUBLE_EQ(value, 0.0);
auto nonfinite = right;
nonfinite[8U] = std::numeric_limits<double>::infinity();
product.fill(31.0);
EXPECT_FALSE(MultiplyTransposeRight(left.data(), 2U, nonfinite.data(), 4U, 3U,
product.data()));
for (const auto value : product) EXPECT_DOUBLE_EQ(value, 31.0);
}
TEST(DenseBlasInternal, CDup003ConvertsZeroNormalAndOverflowLengths) {
const auto zero = ToMklSize(0U);
ASSERT_TRUE(zero.HasValue());
@@ -115,6 +207,30 @@ TEST(DenseBlasInternal, CholeskyAndInverseMatchKnownFactorsAndKeepFailedOutput)
std::invalid_argument);
}
TEST(DenseBlasInternal,
CholeskyFailureReasonPreservesCandidatesAndResetsOnReuse) {
Matrix matrix{2U, 2U};
matrix(0U, 0U) = 4.0;
matrix(1U, 1U) = 9.0;
Matrix lower{0U, 0U};
Matrix inverse{0U, 0U};
auto reason = CholeskyFailure::kInverse;
ASSERT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
EXPECT_EQ(reason, CholeskyFailure::kNone);
matrix(1U, 1U) = -1.0;
EXPECT_FALSE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
EXPECT_EQ(reason, CholeskyFailure::kPivot);
EXPECT_DOUBLE_EQ(lower(1U, 1U), 3.0);
EXPECT_DOUBLE_EQ(inverse(1U, 1U), 1.0 / 3.0);
matrix(1U, 1U) = 9.0;
EXPECT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
EXPECT_EQ(reason, CholeskyFailure::kNone);
reason = CholeskyFailure::kInverse;
EXPECT_THROW(CholeskyLowerAndInverse(Matrix{2U, 1U}, lower, inverse, &reason),
std::invalid_argument);
EXPECT_EQ(reason, CholeskyFailure::kNone);
}
TEST(DenseBlasInternal, SingleEquationScopeRestoresTlsAndRespectsOverrides) {
const int previous = mkl_set_num_threads_local(0);
struct Restore {