From 8bfe97dbf82b37a264109fe03028300600b63d1b Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Wed, 14 Oct 2026 19:46:35 +0900 Subject: [PATCH] perf(math): retain GEMM after scoped GER comparison --- .../t4-shell-optimization.md | 8 +- src/fesa/math/dense_blas_internal.cpp | 19 +-- tests/unit/math/dense_blas_internal_test.cpp | 112 ------------------ 3 files changed, 8 insertions(+), 131 deletions(-) diff --git a/docs/calculation-performance/t4-shell-optimization.md b/docs/calculation-performance/t4-shell-optimization.md index 5f0334a..b66228c 100644 --- a/docs/calculation-performance/t4-shell-optimization.md +++ b/docs/calculation-performance/t4-shell-optimization.md @@ -191,10 +191,16 @@ EvaluateAssembly의 hidden_second는 인접 shell 조립 Hessian map의 일반 b Current dger-contract-source.json manifestSHA7519444546C57A81F787DFCD21230081A093B02299B43DE8C60E571FA04405B4는 source2파일 및 fixture3파일을 고정한다. Production source SHA77748F73D8F4EECF40D658FC930016A7A6A3E640FE8BC3C055178266C8229E3D는 strict-bit 실패 당시와 동일하다. 정식 Debug rebuild native0와 관련109+기존serial/TBB/repeat 결정론1을 합친 **actual110/110 PASS(native0/98.21초)**를 확인했다. Successful CTest output도 dger-contract-ctest-last.log에 보존한다. Actual6x6 zero-sign 차이10, actual24x24 차이65를 기록했고 비영 strict bits·입력 bits·rounding/MXCSR control mode 검사는 통과했다. Sticky arithmetic exception status는 원래 연산도 발생시키므로 control mode와 구분한다. -동일 current source Release library 정식10edges build0, 생산 warning policy /W4 /WX /externalW0 standalone compile0, micro native0이다. 오래된 object/library를 복사하지 않고 actual production TU와 동일 source library를 링크했다. Frozen8c original, frozenf9 GEMM shadow route, current DGER를 동일 TU에서 비교한다. f9 Jet 계수/storage/연산식은 source span 그대로이며 OuterProduct 호출1곳만 변경되지 않은 checked MultiplyTransposeRight(inner1) GEMM wrapper로 명시적 shadow routing한다. This helper fixture는 전체 f9 executable 재현이 아니다. +동일 current source Release library 정식10edges build0, 생산 warning policy /W4 /WX /externalW0 standalone compile0, micro native0이다. 오래된 object/library를 복사하지 않고 actual production TU와 동일 source library를 링크했다. Frozen8c original, frozenf9 GEMM shadow route, current DGER를 동일 TU에서 비교한다. f9 Jet 계수/storage/연산식은 source span 그대로이며 OuterProduct 호출1곳만 변경되지 않은 checked MultiplyTransposeRight(inner1) GEMM wrapper로 명시적 shadow routing한다. 이 helper fixture는 전체 f9 executable 재현이 아니다. 3000iterations 각0warmup/1batch 측정 결과(seconds): original0.0025600, f9GEMMdefault0.0043713, DGERdefault0.0031520, f9GEMMscope1 0.0029526, DGERscope1 0.0030267. 같은 checksum32.759844928979874, 모든 coefficient 일치/TLS 복원 확인. DGER default는 f9 default보다 약27.9% 짧지만 실제 production local scope1에서는 약2.5% 길고 original보다 약18.2% 길다. **현재 production scope 비용 개선을 입증하지 못했다.** 단일 짧은 batch의 결과를 전체 모델 성능이나100초 합격으로 사용하지 않는다. Timing 밖 actual Jet14cases 모두 PASS: signed-zero Constant value, seeded Variable mixed, Constant×seed, Compose mixed-third, thirdInf 실패, Sqrt0 실패, Atan2 seeded mixed, +/-0 value branch, zero/zero 실패, x2^300/y0 radius_fourthInf 수용, tiny-radius underflow 실패, zero×Infmixed 실패, allocated-zero copy. Value/first/directional/mixed 영 부호와 비영 bits는 strict 비교하고 Hessian의 양쪽 numeric zero만 허용했다. NaN payload는 결과 계약이 아니므로 양쪽 NaN 및 IsFinite 거부를 비교한다. 14case의 Hessian zero-sign 차이 수는 모두0이었다. 이 좁은 fixture는 모든 Compose/Atan2 입력·환경을 보증하지 않는다. Actual source/lib/exe hash 및 native argv는 parent dger-contract-native.json/dger-contract-built-hashes.json에 보존한다. Release library SHA FA4F911DEB913EAA8042A28CAA42CDADE13F5AD85715C75C537EB63F2D9DAFCA, micro exe SHA BF6DC244E6C1653F632C90ED2BCCA3DDAAB25CA9C59BBAB351336F50CC901579. 전체 shell 목표, 후속 whole-model VTune/reference/C08/최종전체Debug는 미검증이다. DGER source와 검사는 Constant specialization/reuse source와 분리하여 고정한다. + +### DGER 미채택 및 GEMM 복원 + +Root는 실제 production local thread1 scope에서 개선 근거가 없으므로 DGER를 채택하지 않기로 결정했다. 위110 PASS 및14 Jet 행동/단일 kernel 자료는 실험 source11af92809a674567262c51c0c0f2e7e130241ca7의 기록이며 현재 복원 source의 새 실행 결과가 아니다. 기본 thread 설정에서의27.9% 감소를 실제 production scope1의2.5% 증가와 바꾸어 주장하지 않는다. + +Working/index에 다른 변경이 없음을 확인하고 T4가 소유한 dense_blas_internal.cpp와 dense_blas_internal_test.cpp만 c58d499727e2370cb405c96decea0efcb2645bea의 checked GEMM 상태로 복원했다. 새 GEMM 대 GEMM 비교로만 남는 DGER 실험 검사도 제거했으며 기존 c58 검사/MKL 적용/TLS 정책은 유지한다. 검증된 DGER 원문은 parent dger-verified-dense_blas_internal.cpp/dger-verified-dense_blas_internal_test.cpp, 실험 commit/manifest/micro/review/native8 실패·진단·native0 행동 자료와 함께 보존한다. 복원 후 전체검사/micro를 반복하지 않았다. 실제 복원 file hash는 parent dger-gemm-restored-source.json에 기록한다. 다음 변경은 프로파일 근거의 Constant provenance 및 정확한 true-trial 재사용이며100초 목표는 계속 미달이다. diff --git a/src/fesa/math/dense_blas_internal.cpp b/src/fesa/math/dense_blas_internal.cpp index 66c04b9..a544715 100644 --- a/src/fesa/math/dense_blas_internal.cpp +++ b/src/fesa/math/dense_blas_internal.cpp @@ -127,24 +127,7 @@ Matrix MultiplyTransposeLeft(const Matrix& left, const Matrix& right) { bool OuterProduct(const double* left, const std::size_t rows, const double* right, const std::size_t columns, double* destination) { - const auto m = ToMklSize(rows); - const auto n = ToMklSize(columns); - const auto maximum = std::numeric_limits::max() / sizeof(double); - if (!m.HasValue() || !n.HasValue() || rows > maximum || columns > maximum || - (columns != 0U && rows > maximum / columns)) - throw std::length_error{"Dense product dimensions exceed backend limits."}; - if (rows == 0U || columns == 0U) return true; - if (destination == nullptr || left == nullptr || right == nullptr) - throw std::invalid_argument{"Dense product requires nonnull storage."}; - for (std::size_t i = 0U; i < rows; ++i) - if (!std::isfinite(left[i])) return false; - for (std::size_t i = 0U; i < columns; ++i) - if (!std::isfinite(right[i])) return false; - // GER updates A, so replace every destination entry only after validation. - std::fill_n(destination, rows * columns, 0.0); - cblas_dger(CblasRowMajor, m.Value(), n.Value(), 1.0, left, 1, right, 1, - destination, n.Value()); - return true; + return MultiplyTransposeRight(left, rows, right, columns, 1U, destination); } bool MultiplyTransposeRight(const double* left, const std::size_t rows, diff --git a/tests/unit/math/dense_blas_internal_test.cpp b/tests/unit/math/dense_blas_internal_test.cpp index 55dc9c6..d3be2a4 100644 --- a/tests/unit/math/dense_blas_internal_test.cpp +++ b/tests/unit/math/dense_blas_internal_test.cpp @@ -3,65 +3,14 @@ #include #include -#include #include #include -#include -#include -#include #include #include -#if defined(_M_X64) || defined(_M_IX86) -#include -#endif - namespace fesa::dense_blas_internal { namespace { -template -void ExpectRankOneContract(const std::array& left, - const std::array& right) { - const auto saved_left = left; - const auto saved_right = right; - const int rounding = std::fegetround(); -#if defined(_M_X64) || defined(_M_IX86) - const unsigned control = _mm_getcsr() & ~0x3fU; -#endif - std::array product{}; - std::array original{}; - product.fill(std::numeric_limits::quiet_NaN()); - const ScopedLocalDenseThreads scope{}; - ASSERT_TRUE(MultiplyTransposeRight(left.data(), N, right.data(), N, 1U, - original.data())); - ASSERT_TRUE(OuterProduct(left.data(), N, right.data(), N, product.data())); - std::size_t zero_sign_differences = 0U; - for (std::size_t i = 0U; i < product.size(); ++i) { - std::uint64_t original_bits = 0U; - std::uint64_t product_bits = 0U; - std::memcpy(&original_bits, &original[i], sizeof(original_bits)); - std::memcpy(&product_bits, &product[i], sizeof(product_bits)); - if (original[i] == 0.0 && product[i] == 0.0) { - zero_sign_differences += original_bits != product_bits ? 1U : 0U; - EXPECT_EQ(original[i], product[i]); - } else { - EXPECT_EQ(original_bits, product_bits) - << "entry=" << i << " left=" << left[i / N] - << " right=" << right[i % N] - << " original=" << original[i] << " candidate=" << product[i]; - } - } - std::cout << "DGER boundary shape=" << N << "x" << N - << " zero_sign_differences=" << zero_sign_differences << '\n'; - EXPECT_EQ(std::memcmp(saved_left.data(), left.data(), sizeof(left)), 0); - EXPECT_EQ(std::memcmp(saved_right.data(), right.data(), sizeof(right)), 0); - EXPECT_EQ(std::fegetround(), rounding); -#if defined(_M_X64) || defined(_M_IX86) - // Arithmetic may raise sticky exception flags; control bits must be retained. - EXPECT_EQ(_mm_getcsr() & ~0x3fU, control); -#endif -} - TEST(DenseBlasInternal, OuterProductUsesRectangularStorageAndPreservesRejection) { const std::array left{2.0, -3.0}; @@ -89,13 +38,6 @@ TEST(DenseBlasInternal, std::numeric_limits::max() / sizeof(double) + 1U; EXPECT_THROW(OuterProduct(nullptr, too_many_elements, nullptr, 1U, nullptr), std::length_error); - product = accepted; - EXPECT_THROW(OuterProduct(left.data(), too_large, right.data(), 1U, - product.data()), std::length_error); - EXPECT_EQ(product, accepted); - EXPECT_THROW(OuterProduct(nullptr, 2U, right.data(), 3U, product.data()), - std::invalid_argument); - EXPECT_EQ(product, accepted); } TEST(DenseBlasInternal, LocalDenseScopeRestoresTlsAfterProductsAndUnwind) { @@ -141,60 +83,6 @@ TEST(DenseBlasInternal, OuterProductLeavesFiniteInputOverflowForOwnerGate) { EXPECT_TRUE(std::isinf(product[0U])); } -TEST(DenseBlasInternal, OuterProductMatchesK1ForSignedZeroAndExtremeScale) { - const std::array left{ - 0.0, -0.0, std::numeric_limits::denorm_min(), - std::ldexp(1.0, -500), std::numeric_limits::max(), - -std::numeric_limits::max()}; - const std::array right{ - 0.0, -0.0, 2.0, -2.0, std::ldexp(1.0, 500), - std::numeric_limits::denorm_min()}; - ExpectRankOneContract(left, right); -} - -TEST(DenseBlasInternal, OuterProduct24PreservesNonzeroAndFailureBoundaries) { - std::array left{}; - std::array right{}; - for (std::size_t i = 0U; i < left.size(); ++i) { - left[i] = std::ldexp(static_cast(i + 1U), -8); - right[i] = std::ldexp(-static_cast(i + 2U), -10); - } - left[0U] = 0.0; - left[1U] = -0.0; - left[2U] = std::numeric_limits::denorm_min(); - left[3U] = -std::numeric_limits::denorm_min(); - left[4U] = std::ldexp(1.0, -500); - left[5U] = std::numeric_limits::max(); - right[0U] = -0.0; - right[1U] = 0.0; - right[2U] = std::numeric_limits::denorm_min(); - right[3U] = 2.0; - right[4U] = -2.0; - right[5U] = std::ldexp(1.0, 500); - ExpectRankOneContract(left, right); -} - -TEST(DenseBlasInternal, OuterProductRejectsEitherNonfiniteInputBeforeWriting) { - std::array left{2.0, -3.0}; - std::array right{4.0, -5.0, 0.25}; - std::array destination{1.0, -2.0, 3.0, -4.0, 5.0, -6.0}; - const auto accepted = destination; - for (const double invalid : {std::numeric_limits::quiet_NaN(), - std::numeric_limits::infinity(), - -std::numeric_limits::infinity()}) { - right[2U] = invalid; - EXPECT_FALSE(OuterProduct(left.data(), left.size(), right.data(), - right.size(), destination.data())); - EXPECT_EQ(destination, accepted); - right[2U] = 0.25; - left[1U] = invalid; - EXPECT_FALSE(OuterProduct(left.data(), left.size(), right.data(), - right.size(), destination.data())); - EXPECT_EQ(destination, accepted); - left[1U] = -3.0; - } -} - TEST(DenseBlasInternal, TransposeRightProductUsesInnerAndRectangularStrides) { const std::array left{1, 2, 3, 4, 5, 6}; const std::array right{7, 8, 9, 10, 11, 12,