perf(elements): reuse masked Jet product outer transpose
This commit is contained in:
@@ -320,6 +320,154 @@ std::string Verify(bool corrupt_oracle) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Separate supplemental evidence: never changes the original T1 snapshot.
|
||||
// A changed Hessian association, a lost transpose term, or a changed MKL zero
|
||||
// convention must fail the bit/classification and complete FP-state comparison.
|
||||
std::string ProductSnapshots(bool omit_transpose) {
|
||||
const std::array<double, 16> boundary{
|
||||
0.0, -0.0, 1.0, -1.0, 0.1, -0.3,
|
||||
std::numeric_limits<double>::denorm_min(),
|
||||
-std::numeric_limits<double>::denorm_min(),
|
||||
std::numeric_limits<double>::min(),
|
||||
std::nextafter(std::numeric_limits<double>::min(), 0.0),
|
||||
std::numeric_limits<double>::max(),
|
||||
-std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN(),
|
||||
std::numeric_limits<double>::signaling_NaN()};
|
||||
std::string out;
|
||||
for (const int rounding : {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD,
|
||||
FE_TOWARDZERO}) {
|
||||
for (const unsigned flush : {0u, 0x40u, 0x8000u, 0x8040u}) {
|
||||
for (const bool sticky : {false, true}) {
|
||||
auto reset = [&] {
|
||||
ResetFp();
|
||||
std::fesetround(rounding);
|
||||
_mm_setcsr((_mm_getcsr() & ~0x8040u) | flush);
|
||||
if (sticky) std::feraiseexcept(FE_ALL_EXCEPT);
|
||||
return FpState();
|
||||
};
|
||||
for (std::size_t kind = 0; kind < 96; ++kind) {
|
||||
ResetFp(); // Construct inputs outside the observed environment.
|
||||
Jet left{}, right{};
|
||||
left.value = 0.75;
|
||||
right.value = -1.25;
|
||||
left.directional_first = 0.25;
|
||||
right.directional_first = -0.5;
|
||||
left.directional_second = -0.125;
|
||||
right.directional_second = 0.375;
|
||||
std::uint64_t random = 0x719b245af51d6723ULL + kind;
|
||||
auto coefficient = [&] {
|
||||
random ^= random << 13;
|
||||
random ^= random >> 7;
|
||||
random ^= random << 17;
|
||||
return (static_cast<double>(random % 2000001) - 1000000) /
|
||||
1048576.0;
|
||||
};
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
left.first[i] = kind < 32 ? boundary[(kind + i) % 16]
|
||||
: coefficient();
|
||||
right.first[i] = kind < 32 ? boundary[(kind / 2 + 3 * i) % 16]
|
||||
: coefficient();
|
||||
left.mixed_first[i] = coefficient();
|
||||
right.mixed_first[i] = coefficient();
|
||||
left.mixed_second[i] = coefficient();
|
||||
right.mixed_second[i] = coefficient();
|
||||
}
|
||||
// Include dense, asymmetric inputs, implicit zeros and aliased input.
|
||||
if (kind % 3 != 0) {
|
||||
double* l = left.second.OverwriteData();
|
||||
double* r = right.second.OverwriteData();
|
||||
for (std::size_t i = 0; i < 576; ++i) {
|
||||
l[i] = coefficient();
|
||||
r[i] = coefficient();
|
||||
}
|
||||
}
|
||||
if (kind == 32) left = Constant(-0.0);
|
||||
if (kind == 33) right = Constant(0.0);
|
||||
if (kind == 34) left.value = boundary[10];
|
||||
if (kind == 35) right.value = boundary[12];
|
||||
if (kind == 36) left.second.Data()[17] = boundary[10];
|
||||
if (kind == 37) right.second.Data()[24] = boundary[10];
|
||||
if (kind == 38) left.second.Data()[17] = boundary[14];
|
||||
if (kind == 39) right = left; // Shared immutable Hessian storage.
|
||||
if (kind == 40) {
|
||||
left.first[0] = boundary[12];
|
||||
right.first[0] = boundary[15];
|
||||
}
|
||||
if (kind == 41) {
|
||||
left.first[0] = boundary[15];
|
||||
right.first[0] = boundary[12];
|
||||
}
|
||||
if (kind >= 42 && kind < 64) {
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
left.first[i] = boundary[(kind + i) % 12];
|
||||
right.first[i] = boundary[(kind / 2 + 3 * i) % 12];
|
||||
if (kind == 42) {
|
||||
left.first[i] = boundary[6];
|
||||
right.first[i] = i % 2 ? -0.5 : 0.5;
|
||||
}
|
||||
if (kind == 43) {
|
||||
left.first[i] = boundary[10];
|
||||
right.first[i] = i % 2 ? -2.0 : 2.0;
|
||||
}
|
||||
if (kind == 44) {
|
||||
left.first[i] = i % 2 ? -0.0 : 0.0;
|
||||
right.first[i] = i % 3 ? -1.0 : 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto left_before = Snapshot("input", left, 0, 0, 0);
|
||||
const auto right_before = Snapshot("input", right, 0, 0, 0);
|
||||
const auto before = reset();
|
||||
Jet product = left * right;
|
||||
if (omit_transpose && kind >= 64) {
|
||||
// Deliberately omit the reverse outer term in one finite entry.
|
||||
// This mutant tests arithmetic detection, not just file integrity.
|
||||
product.second.Data()[1] =
|
||||
left.second.ReadData()[1] * right.value +
|
||||
left.first[0] * right.first[1] +
|
||||
left.value * right.second.ReadData()[1];
|
||||
}
|
||||
const int exceptions = std::fetestexcept(FE_ALL_EXCEPT);
|
||||
const unsigned status = _statusfp(), mxcsr = _mm_getcsr();
|
||||
const auto after = FpState();
|
||||
out += "product " + std::to_string(kind) + " initial-fp=" + before +
|
||||
" final-fp=" + after + " " +
|
||||
Snapshot("result", product, exceptions, status, mxcsr);
|
||||
if (left_before != Snapshot("input", left, 0, 0, 0) ||
|
||||
right_before != Snapshot("input", right, 0, 0, 0))
|
||||
throw std::runtime_error("supplemental product modified input");
|
||||
|
||||
// Independently pin the actual MKL operand-swap/transpose contract.
|
||||
// Capture FP state before inspecting values (sNaN classification can
|
||||
// itself raise INVALID). Each call starts from the same environment.
|
||||
Jet forward{}, reverse{};
|
||||
reset();
|
||||
Outer(left.first, right.first, forward.second.OverwriteData());
|
||||
const auto forward_fp = FpState();
|
||||
reset();
|
||||
Outer(right.first, left.first, reverse.second.OverwriteData());
|
||||
const auto reverse_fp = FpState();
|
||||
for (std::size_t row = 0; row < 24; ++row) {
|
||||
for (std::size_t column = 0; column < 24; ++column) {
|
||||
const double a = forward.second.ReadData()[row * 24 + column];
|
||||
const double b = reverse.second.ReadData()[column * 24 + row];
|
||||
if (!(std::isnan(a) && std::isnan(b)) && Bits(a) != Bits(b))
|
||||
throw std::runtime_error("outer transpose coefficient mismatch");
|
||||
}
|
||||
}
|
||||
out += "forward-outer-fp=" + forward_fp +
|
||||
" reverse-outer-fp=" + reverse_fp + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ResetFp();
|
||||
return out;
|
||||
}
|
||||
|
||||
// Public request-mode evidence for later direction-channel separation. Existing
|
||||
// tangent tests check nonzero-direction derivatives but do not freeze the full
|
||||
// omitted-direction response inventory and invalid-direction diagnostics.
|
||||
@@ -523,6 +671,29 @@ int main(int argc, char** argv) {
|
||||
fesa::Bench();
|
||||
return 0;
|
||||
}
|
||||
if (argc >= 3 && (std::string(argv[1]) == "--product-write" ||
|
||||
std::string(argv[1]) == "--product-compare")) {
|
||||
auto actual = fesa::ProductSnapshots(
|
||||
argc > 3 && std::string(argv[3]) == "--omit-transpose");
|
||||
if (argc > 3 && std::string(argv[3]) == "--mutate")
|
||||
actual[actual.find('\n') - 1] ^= 1;
|
||||
if (std::string(argv[1]) == "--product-write") {
|
||||
if (std::ifstream(argv[2], std::ios::binary))
|
||||
throw std::runtime_error("supplemental snapshot already exists");
|
||||
std::ofstream output(argv[2], std::ios::binary);
|
||||
output << actual;
|
||||
output.close();
|
||||
if (!output) throw std::runtime_error("supplemental snapshot write failure");
|
||||
} else {
|
||||
std::ifstream input(argv[2], std::ios::binary);
|
||||
const std::string expected((std::istreambuf_iterator<char>(input)), {});
|
||||
if (expected != actual)
|
||||
throw std::runtime_error("supplemental product snapshot mismatch");
|
||||
}
|
||||
std::cout << argv[1] << " passed; 3072 product cases and paired MKL outer "
|
||||
"checks, 651 channels, input aliases, rounding/FTZ/DAZ/sticky\n";
|
||||
return 0;
|
||||
}
|
||||
const bool verify_only = argc == 2 && std::string(argv[1]) == "--verify";
|
||||
if (argc < 3 && !verify_only)
|
||||
throw std::runtime_error(
|
||||
|
||||
@@ -38,3 +38,30 @@ snapshot은 finite 값·signed zero·Inf 비트, NaN 분류, provenance와 impli
|
||||
복원한 초기 FP 환경 및 연산 뒤 FE/rounding/control word/full MXCSR를 기록한다.
|
||||
공개 반환의 방향 요청 유무·실패 진단·회복량 identity도 포함한다. 수식의 정확성은
|
||||
독립 oracle로 검사하고, snapshot은 기준의 계산 순서와 관측 의미 보존을 검사한다.
|
||||
|
||||
T2 곱셈 경계는 기존 T1 snapshot과 별도 CLI로 보존한다. 원본 제품 source의 hash를
|
||||
확인하고 보존한 원본 archive를 연결한 실행물에서만 새 경로에 한 번 생성한다.
|
||||
후보는 `--product-compare`만 실행한다. 기존 파일을 덮어쓰거나 T1 기대값을 확장하지 않는다.
|
||||
|
||||
```bat
|
||||
<original-fixture.exe> --product-write <new-immutable-product-snapshot>
|
||||
<candidate-fixture.exe> --product-compare <immutable-product-snapshot>
|
||||
<candidate-fixture.exe> --product-compare <immutable-product-snapshot> --mutate
|
||||
<candidate-fixture.exe> --product-compare <immutable-product-snapshot> --omit-transpose
|
||||
```
|
||||
|
||||
마지막 두 명령은 각각 출력 변조와 reverse 외적 항 누락의 fixture 전용 음성 대조로
|
||||
exit 1이 기대값이다. 이 그룹은 96개 입력을 4 rounding × 4 FTZ/DAZ × 2 초기 sticky
|
||||
환경에서 실행한 3072개 곱셈이다. 전체 651 채널, 비대칭·공유·implicit Hessian과
|
||||
입력 불변성, provenance, 전체 FP 상태를 비교한다. finite 전용 ±0·subnormal·underflow·
|
||||
overflow gradient와 비유한 입력 거부를 구분하며, 실제 MKL 두 외적의 operand 교환과
|
||||
transpose 성분도 비교한다. 두 호출의 FP 상태는 각각 기록하므로 입력 검사 조기 종료로
|
||||
차이가 있는 경우를 수치 동등성으로 숨기지 않는다. NaN payload는 비교하지 않는다.
|
||||
이 그룹은 예외를 mask한 상태의 관측이다. Unmasked 환경의 최초 trap 순서는 별도
|
||||
SEH/process 관측이나 생성 코드 근거가 필요하며, source의 row-major 순서만으로 보장하지 않는다.
|
||||
|
||||
T2의 무가드 단일 외적 후보는 unmasked 최초 INVALID/OVERFLOW 순서가 달라 비채택했다.
|
||||
채택한 guarded 경로는 MXCSR의 여섯 예외가 모두 masked이고 첫 외적이 성공한 경우에만
|
||||
전치 성분과 276개 상삼각 scratch를 재사용한다. Unmasked 환경과 외적 거부는 기존 두 호출,
|
||||
576개 reverse 배열과 row-major 결합을 유지한다. Masked snapshot과 별도 원본/후보 SEH
|
||||
관측을 함께 확인하며, 정상 경로의 임시 쓰기 감소를 전체 stack 예약 감소로 보고하지 않는다.
|
||||
|
||||
Reference in New Issue
Block a user