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(
|
||||
|
||||
Reference in New Issue
Block a user