perf(math): retain GEMM after scoped GER comparison
This commit is contained in:
@@ -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초 목표는 계속 미달이다.
|
||||
|
||||
@@ -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<std::size_t>::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,
|
||||
|
||||
@@ -3,65 +3,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cfenv>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace fesa::dense_blas_internal {
|
||||
namespace {
|
||||
|
||||
template <std::size_t N>
|
||||
void ExpectRankOneContract(const std::array<double, N>& left,
|
||||
const std::array<double, N>& 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<double, N * N> product{};
|
||||
std::array<double, N * N> original{};
|
||||
product.fill(std::numeric_limits<double>::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<double, 2U> left{2.0, -3.0};
|
||||
@@ -89,13 +38,6 @@ TEST(DenseBlasInternal,
|
||||
std::numeric_limits<std::size_t>::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<double, 6U> left{
|
||||
0.0, -0.0, std::numeric_limits<double>::denorm_min(),
|
||||
std::ldexp(1.0, -500), std::numeric_limits<double>::max(),
|
||||
-std::numeric_limits<double>::max()};
|
||||
const std::array<double, 6U> right{
|
||||
0.0, -0.0, 2.0, -2.0, std::ldexp(1.0, 500),
|
||||
std::numeric_limits<double>::denorm_min()};
|
||||
ExpectRankOneContract(left, right);
|
||||
}
|
||||
|
||||
TEST(DenseBlasInternal, OuterProduct24PreservesNonzeroAndFailureBoundaries) {
|
||||
std::array<double, 24U> left{};
|
||||
std::array<double, 24U> right{};
|
||||
for (std::size_t i = 0U; i < left.size(); ++i) {
|
||||
left[i] = std::ldexp(static_cast<double>(i + 1U), -8);
|
||||
right[i] = std::ldexp(-static_cast<double>(i + 2U), -10);
|
||||
}
|
||||
left[0U] = 0.0;
|
||||
left[1U] = -0.0;
|
||||
left[2U] = std::numeric_limits<double>::denorm_min();
|
||||
left[3U] = -std::numeric_limits<double>::denorm_min();
|
||||
left[4U] = std::ldexp(1.0, -500);
|
||||
left[5U] = std::numeric_limits<double>::max();
|
||||
right[0U] = -0.0;
|
||||
right[1U] = 0.0;
|
||||
right[2U] = std::numeric_limits<double>::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<double, 2U> left{2.0, -3.0};
|
||||
std::array<double, 3U> right{4.0, -5.0, 0.25};
|
||||
std::array<double, 6U> destination{1.0, -2.0, 3.0, -4.0, 5.0, -6.0};
|
||||
const auto accepted = destination;
|
||||
for (const double invalid : {std::numeric_limits<double>::quiet_NaN(),
|
||||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::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<double, 6U> left{1, 2, 3, 4, 5, 6};
|
||||
const std::array<double, 12U> right{7, 8, 9, 10, 11, 12,
|
||||
|
||||
Reference in New Issue
Block a user