Merge reviewed geometric packet optimization and FP fallback

This commit is contained in:
KOKO\Mimi
2026-10-14 23:34:29 +09:00
6 changed files with 478 additions and 17 deletions
@@ -656,3 +656,61 @@ Dot3-term과 geometric9-term의 독립 b4 lane을 기존 zero-start·component/j
각 기존 diagonal accumulator Add에 쓰는 작은 대안도 명시했다. High/low 정밀도는 각 기존 diagonal accumulator Add에 쓰는 작은 대안도 명시했다. High/low 정밀도는
유지하며 일반 Float64 MKL 경로를 바꾸지 않는다. 이는 아직 구현·검증 전인 제안이고 유지하며 일반 Float64 MKL 경로를 바꾸지 않는다. 이는 아직 구현·검증 전인 제안이고
현재 source를 변경하지 않았다. Whole-model 추가 반복이나 profile 추가 수집은 없다. 현재 source를 변경하지 않았다. Whole-model 추가 반복이나 profile 추가 수집은 없다.
## 기하 수축 packet 구현과 Release 상태 차이 수정
위 제안을 `9677265`에서 구현했다. 독립 b4 lane은 기존 `dots[b]` 및 material의
`col=3*b+k`와 연결하며, gradient-stress의 원래 준비 제품과 Dot의 component 순서,
geometric의 j,l 순서 및 zero-start를 보존한다. 동일 `geo*weight`는 공유하지만 세
diagonal accumulator의 Add는 각각 수행한다. Unsupported 환경과 linear 응답은
원래 scalar loop를 사용한다. Private POD·baseline bridge·AVX2 경계를 유지하고
일반 Float64 MKL, CMake, 공통 math 및 public ABI를 바꾸지 않았다.
이 후보의 Debug 관련 81개 검사는 통과했으나 Release는 80/81 통과였다.
Ordered overflow fixture에서 raw bit는 같고 selected FE21/baseline5 및 MXCSR41/40,
prior 상태에서는43/42로 INVALID 차이가 있었다. 당시 wrapper가 stderr를 먼저
예외 처리하여 CTest native는 unknown, wrapper native는1이었다. 이 실패를 현재
통과로 소급하지 않는다. [실패 XML](../../.harness/performance/t3/packet-geometric-release-gtest/fesa_unit_tests_53.xml),
[실패 binary 보존](../../.harness/performance/t3/packet-geometric-9677265/failure-release/manifest.json)을 유지한다.
후속 Release codegen·snapshot·전체 모델·profile은 당시 수행하지 않았다.
최소 수정 `0c29a9f`는 private AVX2 cpp와 기존 Hex8 검사 두 파일에 한정한다.
Vector TwoSum의 첫 sum이 nonfinite이면 error 계산 전에 false를 반환한다. 각 Add의
head/tail/middle/joined/final 위치와 Product의 마지막 TwoSum 실패는 원래 입력과
mode로 기존 noinline scalar bridge를 호출한다. Partial 결과를 재사용하지 않으며
조건부 Debug 분기, flags clear, 기대값 완화, 산술식 및 진단 gate 변경은 없다.
[고정 manifest](../../.harness/performance/t3/packet-geometric-fix-0c29a9f/manifest.json)와
[독립 source 검토](../../.harness/performance/t3/source-numerical-review-packet-geometric-fix.md)는
10개 hash의 전후 일치와 source-only PASS를 기록한다.
새 검사는 normalized near-max joined overflow, N=2^-1022와 d=2^-1074에 의한
subnormal error, 다른 lane의 성공 prefix 이후 overflow, 각 lane 위치 및 prior 상태를
포함한다. 원래 세 weight 제품과 서로 다른 세 accumulator의 raw bit·FE_ALL_EXCEPT·
MXCSR status를 기준으로 삼았다. 첫 narrow 실행은 Debug·Release 각각2/2,
actual AVX2의 기존 실패6개와 새 prefix34개가 통과했다. 이후 실제 discovery는
82개였고 Debug 82/82(10.43초), Release 82/82(4.46초), 모든 단계 native0이었다.
[Debug 명령/native](../../.harness/performance/t3/packet-geometric-fix-full-debug-native.json),
[Release 명령/native](../../.harness/performance/t3/packet-geometric-fix-full-release-native.json),
[Release XML](../../.harness/performance/t3/packet-geometric-fix-full-release-gtest/)을 보존한다.
Configure/build는 corrected MSVC dependency prefix, 병렬4 이하로 순차 수행했다.
두 구성의 전체 객체 symbol/disassembly는 AVX2 외부 실행 정의2개, 공통 math 정의0개,
baseline AVX·YMM·ANDN0개를 확인했다. Kernel의 fused 명령은 명시 residual VFMSUB
1개뿐이고 Product의 충분조건 true 분기4개는 각각 두 BSF를 건너뛰며 false는 원래
두 BSF를 유지한다. [코드 생성 assertion](../../.harness/performance/t3/packet-geometric-fix-codegen-assertion.json)을 보존한다.
실제 Release dispatch 객체의 scalar Add는 first-sum0x16e 이후 finite 분기0x18a에서
error 계산0x18f를 건너뛴다. TwoSum도0x25에서0xb1로 같은 순서의 return을 한다.
Linked unit exe에서 해당 Add byte signature가 한 곳에 존재함을 확인했다.
[Hoist 근거](../../.harness/performance/t3/packet-geometric-fix-release-scalar-hoist.json)는
PDB를 통한 개별 callsite나 COMDAT winner 식별까지 주장하지 않는다. 실제 bridge의
raw/status 동등성은 위 Debug·Release oracle로 별도 확인했다.
기존 actual native0 원본 oracle의 정합 header/LIB/입력 연결을 검증한 뒤 새 candidate만
compile/run했다. 두 native와 엄격한 비교 wrapper는0이며9,804행과 전체 byte가 동일하다.
양쪽 SHA는 `DED8D2C380D15E297A73BDD018B1B840C1ADC0D8372934D9FC2EF2B628A36F63`이다.
[Snapshot 명령/native](../../.harness/performance/t3/packet-geometric-fix-snapshot-native.json),
[엄격한 비교](../../.harness/performance/t3/packet-geometric-fix-snapshot-comparison.json)를 보존한다.
[종료 확인](../../.harness/performance/t3/packet-geometric-fix-full-cleanup.json)은 source/hash
불변, tracked clean 및 관련 actor0을 기록한다. CPU를 반환했고 이번 슬롯에서 전체 모델과
profile은 실행하지 않았다. 이 후보의 전체 경과 시간 및 최종101-frame 비교는 미실행이며,
기존135.9302297초 개발 결과와 목표100초 미달, 최종 전체·공식 검증 미완료는 유지한다.
+31 -3
View File
@@ -246,11 +246,38 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
gradient_stress[a][j][l] = gradients[a][j] * s[l][j]; gradient_stress[a][j][l] = gradients[a][j] * s[l][j];
for (std::size_t a = 0; a < 8; ++a) { for (std::size_t a = 0; a < 8; ++a) {
std::array<HighLow, 8> dots{}; std::array<HighLow, 8> dots{};
std::array<HighLow, 8> weighted_geo{};
if (use_packets)
for (std::size_t first_b = 0; first_b < 8; first_b += 4) {
hex8_internal::GeometryPacketInput input{};
const auto raw_pair = [](HighLow pair) {
return hex8_internal::PacketPair{pair.high, pair.low};
};
input.weight = raw_pair(HighLow{point.weight});
for (std::size_t j = 0; j < 3; ++j) {
input.gradient_a[j] = raw_pair(gradients[a][j]);
for (std::size_t l = 0; l < 3; ++l)
input.gradient_stress[j][l] = raw_pair(gradient_stress[a][j][l]);
for (std::size_t lane = 0; lane < 4; ++lane)
input.gradient_b[j].values[lane] =
raw_pair(gradients[first_b + lane][j]);
}
hex8_internal::GeometryPacketOutput output;
// Same selector contract as material; unsupported/linear stays below.
hex8_internal::EvaluateGeometryPacketAvx2(input, &output);
for (std::size_t lane = 0; lane < 4; ++lane) {
dots[first_b + lane] = {output.gradient_dot.values[lane].high,
output.gradient_dot.values[lane].low};
weighted_geo[first_b + lane] =
{output.weighted_geometric.values[lane].high,
output.weighted_geometric.values[lane].low};
}
}
for (std::size_t b = 0; b < 8; ++b) { for (std::size_t b = 0; b < 8; ++b) {
const auto dot = Dot(gradients[a], gradients[b]); const auto dot = use_packets ? dots[b] : Dot(gradients[a], gradients[b]);
dots[b] = dot; dots[b] = dot;
HighLow geo; HighLow geo;
if (nonlinear) if (nonlinear && !use_packets)
for (std::size_t j = 0; j < 3; ++j) for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l) for (std::size_t l = 0; l < 3; ++l)
geo += gradient_stress[a][j][l] * gradients[b][l]; geo += gradient_stress[a][j][l] * gradients[b][l];
@@ -265,7 +292,8 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
material[row][column] += mat * HighLow{point.weight}; material[row][column] += mat * HighLow{point.weight};
} }
if (i == k) if (i == k)
geometric[row][column] += geo * HighLow{point.weight}; geometric[row][column] +=
use_packets ? weighted_geo[b] : geo * HighLow{point.weight};
} }
} }
if (use_packets) if (use_packets)
+24
View File
@@ -23,6 +23,30 @@ struct MaterialPacketInput {
PairPacket accumulated; PairPacket accumulated;
PacketPair weight; PacketPair weight;
}; };
/// @brief One owned trial point/a and four independent b gradients, raw only.
struct GeometryPacketInput {
PacketPair gradient_a[3];
PacketPair gradient_stress[3][3];
PairPacket gradient_b[3];
PacketPair weight;
};
/// @brief Independent raw contractions; accumulator entries remain caller-owned.
struct GeometryPacketOutput {
PairPacket gradient_dot;
PairPacket weighted_geometric;
};
using GeometryPacketEvaluator = void (*)(const GeometryPacketInput&,
GeometryPacketOutput*);
/// @brief Baseline checked zero-start Dot3/ordered j,l Geo9 and weight products.
/// @note Lane order is 0..3. Invalid pairs remain invalid for original gates.
void EvaluateGeometryPacketScalar(const GeometryPacketInput& input,
GeometryPacketOutput* output);
/// @brief Evaluates independent b lanes without horizontal reduction.
/// @pre The material selector has admitted CPU/OS/current masked FP state.
/// @note Keeps original zero-start, product association and j,l Add order.
void EvaluateGeometryPacketAvx2(const GeometryPacketInput& input,
GeometryPacketOutput* output);
using MaterialPacketEvaluator = void (*)(const MaterialPacketInput&, PairPacket*); using MaterialPacketEvaluator = void (*)(const MaterialPacketInput&, PairPacket*);
/// @brief Evaluates each lane's original checked material graph in order 0..3. /// @brief Evaluates each lane's original checked material graph in order 0..3.
+54 -14
View File
@@ -111,6 +111,10 @@ VectorPair Load(const PairPacket& value) {
_mm256_setr_pd(value.values[0].low, value.values[1].low, _mm256_setr_pd(value.values[0].low, value.values[1].low,
value.values[2].low, value.values[3].low)}; value.values[2].low, value.values[3].low)};
} }
/// @brief Replicates one raw first product without changing either component.
VectorPair Splat(PacketPair value) {
return {_mm256_set1_pd(value.high), _mm256_set1_pd(value.low)};
}
/// @brief Stores raw high/low fields without rounding or lane reduction. /// @brief Stores raw high/low fields without rounding or lane reduction.
PairPacket Store(VectorPair value) { PairPacket Store(VectorPair value) {
double high[4]; double high[4];
@@ -120,9 +124,14 @@ PairPacket Store(VectorPair value) {
return {{{high[0], low[0]}, {high[1], low[1]}, return {{{high[0], low[0]}, {high[1], low[1]},
{high[2], low[2]}, {high[3], low[3]}}}; {high[2], low[2]}, {high[3], low[3]}}};
} }
/// @brief Retains the original per-lane subtraction/addition association and gates. /// @brief Retains finite-sum error association; false requests original graph.
VectorPair TwoSum(__m256d left, __m256d right) { /// @note False leaves output unreadable and preserves the necessary sum flags.
/// Prefix flags still require optimized scalar-oracle/codegen validation.
bool TwoSum(__m256d left, __m256d right, VectorPair* output) {
const auto sum = _mm256_add_pd(left, right); const auto sum = _mm256_add_pd(left, right);
// Do not speculate Inf-Inf error arithmetic beyond a failed first sum.
// The caller retains original inputs for its existing baseline bridge.
if (_mm256_movemask_pd(Finite(sum)) != 15) return false;
const auto bridge = _mm256_sub_pd(sum, left); const auto bridge = _mm256_sub_pd(sum, left);
const auto restored = _mm256_sub_pd(sum, bridge); const auto restored = _mm256_sub_pd(sum, bridge);
const auto error = _mm256_add_pd(_mm256_sub_pd(left, restored), const auto error = _mm256_add_pd(_mm256_sub_pd(left, restored),
@@ -130,8 +139,9 @@ VectorPair TwoSum(__m256d left, __m256d right) {
const auto valid = _mm256_and_pd(_mm256_and_pd(Finite(sum), Finite(bridge)), const auto valid = _mm256_and_pd(_mm256_and_pd(Finite(sum), Finite(bridge)),
_mm256_and_pd(Finite(restored), Finite(error))); _mm256_and_pd(Finite(restored), Finite(error)));
const auto exact_zero = _mm256_and_pd(Zero(sum), Zero(error)); const auto exact_zero = _mm256_and_pd(Zero(sum), Zero(error));
return InvalidOutside({Select(exact_zero, _mm256_setzero_pd(), sum), *output = InvalidOutside({Select(exact_zero, _mm256_setzero_pd(), sum),
Select(exact_zero, _mm256_setzero_pd(), error)}, valid); Select(exact_zero, _mm256_setzero_pd(), error)}, valid);
return true;
} }
/// @brief Routes exceptional packets to baseline checked Add in lane order. /// @brief Routes exceptional packets to baseline checked Add in lane order.
VectorPair ScalarAdd(VectorPair left, VectorPair right, bool pair_shortcuts) { VectorPair ScalarAdd(VectorPair left, VectorPair right, bool pair_shortcuts) {
@@ -163,10 +173,15 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
if (left_zero != 0 || right_zero != 0) if (left_zero != 0 || right_zero != 0)
return ScalarAdd(left, right, true); return ScalarAdd(left, right, true);
} }
const auto head = TwoSum(left.high, right.high); VectorPair head;
const auto tail = TwoSum(left.low, right.low); VectorPair tail;
const auto middle = TwoSum(head.low, tail.high); VectorPair middle;
const auto joined = TwoSum(head.high, middle.high); VectorPair joined;
if (!TwoSum(left.high, right.high, &head) ||
!TwoSum(left.low, right.low, &tail) ||
!TwoSum(head.low, tail.high, &middle) ||
!TwoSum(head.high, middle.high, &joined))
return ScalarAdd(left, right, pair_shortcuts);
const auto remainder = _mm256_add_pd( const auto remainder = _mm256_add_pd(
_mm256_add_pd(joined.low, middle.low), tail.low); _mm256_add_pd(joined.low, middle.low), tail.low);
const auto valid = _mm256_and_pd(Finite(remainder), _mm256_and_pd( const auto valid = _mm256_and_pd(Finite(remainder), _mm256_and_pd(
@@ -175,9 +190,11 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
_mm256_and_pd(Finite(middle.high), Finite(middle.low)), _mm256_and_pd(Finite(middle.high), Finite(middle.low)),
_mm256_and_pd(Finite(joined.high), Finite(joined.low)))))); _mm256_and_pd(Finite(joined.high), Finite(joined.low))))));
// Original Add returns before its last TwoSum when a prior finite gate fails. // Original Add returns before its last TwoSum when a prior finite gate fails.
return InvalidOutside(TwoSum( VectorPair result;
Select(valid, joined.high, _mm256_setzero_pd()), if (!TwoSum(Select(valid, joined.high, _mm256_setzero_pd()),
Select(valid, remainder, _mm256_setzero_pd())), valid); Select(valid, remainder, _mm256_setzero_pd()), &result))
return ScalarAdd(left, right, pair_shortcuts);
return InvalidOutside(result, valid);
} }
/// @brief Checks finite/zero/tail premises before products; neutralizes rejected /// @brief Checks finite/zero/tail premises before products; neutralizes rejected
/// lanes before the explicit residual FMA and final TwoSum. /// lanes before the explicit residual FMA and final TwoSum.
@@ -207,9 +224,14 @@ VectorPair Product(__m256d left, __m256d right) {
const auto error = _mm256_fmsub_pd(Select(product_valid, left, one), const auto error = _mm256_fmsub_pd(Select(product_valid, left, one),
Select(product_valid, right, one), Select(product_valid, product, one)); Select(product_valid, right, one), Select(product_valid, product, one));
const auto valid = _mm256_and_pd(product_valid, Finite(error)); const auto valid = _mm256_and_pd(product_valid, Finite(error));
return InvalidOutside(TwoSum( VectorPair result;
Select(valid, product, _mm256_setzero_pd()), if (!TwoSum(Select(valid, product, _mm256_setzero_pd()),
Select(valid, error, _mm256_setzero_pd())), valid); Select(valid, error, _mm256_setzero_pd()), &result)) {
PairPacket scalar;
ScalarPacketTwoProduct(a, b, &scalar);
return Load(scalar);
}
return InvalidOutside(result, valid);
} }
/// @brief Uses uniform low-zero branches with original four-product association; /// @brief Uses uniform low-zero branches with original four-product association;
/// mixed or invalid packets preserve the baseline checked path. /// mixed or invalid packets preserve the baseline checked path.
@@ -251,6 +273,20 @@ void EvaluateMaterialPacketAvx2(const MaterialPacketInput& input,
*output = Store(Add(Load(input.accumulated), *output = Store(Add(Load(input.accumulated),
Multiply(material, Load(weight)), true)); Multiply(material, Load(weight)), true));
} }
void EvaluateGeometryPacketAvx2(const GeometryPacketInput& input,
GeometryPacketOutput* output) {
VectorPair dot{_mm256_setzero_pd(), _mm256_setzero_pd()};
for (std::size_t i = 0; i < 3; ++i)
dot = Add(dot, Multiply(Splat(input.gradient_a[i]),
Load(input.gradient_b[i])), true);
VectorPair geo{_mm256_setzero_pd(), _mm256_setzero_pd()};
for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l)
geo = Add(geo, Multiply(Splat(input.gradient_stress[j][l]),
Load(input.gradient_b[l])), true);
output->gradient_dot = Store(dot);
output->weighted_geometric = Store(Multiply(geo, Splat(input.weight)));
}
} // namespace fesa::hex8_internal } // namespace fesa::hex8_internal
#else #else
namespace fesa::hex8_internal { namespace fesa::hex8_internal {
@@ -258,5 +294,9 @@ void EvaluateMaterialPacketAvx2(const MaterialPacketInput& input,
PairPacket* output) { PairPacket* output) {
EvaluateMaterialPacketScalar(input, output); EvaluateMaterialPacketScalar(input, output);
} }
void EvaluateGeometryPacketAvx2(const GeometryPacketInput& input,
GeometryPacketOutput* output) {
EvaluateGeometryPacketScalar(input, output);
}
} // namespace fesa::hex8_internal } // namespace fesa::hex8_internal
#endif #endif
@@ -42,6 +42,27 @@ void ScalarPacketTwoProduct(const double left[4], const double right[4],
output->values[lane] = {value.high, value.low}; output->values[lane] = {value.high, value.low};
} }
} }
FESA_HEX8_BASELINE_NOINLINE
void EvaluateGeometryPacketScalar(const GeometryPacketInput& input,
GeometryPacketOutput* output) {
const auto value = [](PacketPair pair) {
return math_internal::HighLow{pair.high, pair.low};
};
for (std::size_t lane = 0; lane < 4; ++lane) {
math_internal::HighLow dot;
for (std::size_t i = 0; i < 3; ++i)
dot = AddPair(dot, MultiplyPair(value(input.gradient_a[i]),
value(input.gradient_b[i].values[lane])));
math_internal::HighLow geo;
for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l)
geo = AddPair(geo, MultiplyPair(value(input.gradient_stress[j][l]),
value(input.gradient_b[l].values[lane])));
const auto weighted = MultiplyPair(geo, value(input.weight));
output->gradient_dot.values[lane] = {dot.high, dot.low};
output->weighted_geometric.values[lane] = {weighted.high, weighted.low};
}
}
#undef FESA_HEX8_BASELINE_NOINLINE #undef FESA_HEX8_BASELINE_NOINLINE
bool SupportsMaterialPacketInstructions(std::uint32_t leaf1_ecx, bool SupportsMaterialPacketInstructions(std::uint32_t leaf1_ecx,
+290
View File
@@ -449,6 +449,296 @@ TEST(Hex8HighLow, PacketTailScreenRetainsExactBoundaryAndFallback) {
} }
#endif #endif
#if defined(_M_X64) || defined(__SSE2__)
fesa::hex8_internal::GeometryPacketOutput FrozenGeometryPacket(
const fesa::hex8_internal::GeometryPacketInput& input) {
const auto pair = [](fesa::hex8_internal::PacketPair value) {
return fesa::high_low_original::HighLow{value.high, value.low};
};
fesa::hex8_internal::GeometryPacketOutput output{};
for (std::size_t lane = 0; lane < 4; ++lane) {
fesa::high_low_original::HighLow dot;
for (std::size_t i = 0; i < 3; ++i)
dot = fesa::high_low_original::Add(dot,
fesa::high_low_original::Multiply(pair(input.gradient_a[i]),
pair(input.gradient_b[i].values[lane])));
fesa::high_low_original::HighLow geo;
for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l)
geo = fesa::high_low_original::Add(geo,
fesa::high_low_original::Multiply(pair(input.gradient_stress[j][l]),
pair(input.gradient_b[l].values[lane])));
const auto weighted = fesa::high_low_original::Multiply(geo, pair(input.weight));
output.gradient_dot.values[lane] = {dot.high, dot.low};
output.weighted_geometric.values[lane] = {weighted.high, weighted.low};
}
return output;
}
fesa::hex8_internal::GeometryPacketInput GeometryDyadicFixture() {
fesa::hex8_internal::GeometryPacketInput input{};
input.weight = {0.125, 0};
for (std::size_t j = 0; j < 3; ++j) {
input.gradient_a[j] = {static_cast<double>(j + 1) * 0.25, 0};
for (std::size_t l = 0; l < 3; ++l) {
// Deliberately asymmetric first-products and distinct b/component mapping.
input.gradient_stress[j][l] =
{static_cast<double>(1 + 3 * j + l) * 0.125, 0};
for (std::size_t lane = 0; lane < 4; ++lane)
input.gradient_b[l].values[lane] =
{static_cast<double>(1 + 4 * l + lane) * 0.0625, 0};
}
}
return input;
}
// Original observation repeats geo*weight for three independent accumulators.
// Selected observation reuses the weighted pair but retains each checked Add.
void ExpectGeometryPacketBitsAndFlags(
const fesa::hex8_internal::GeometryPacketInput& input, bool prior_status) {
SCOPED_TRACE(prior_status);
std::fenv_t original;
ASSERT_EQ(std::fegetenv(&original), 0);
const auto original_csr = _mm_getcsr();
struct Observation {
fesa::hex8_internal::GeometryPacketOutput output;
fesa::hex8_internal::PairPacket accumulators[3];
int flags;
unsigned int status;
};
const auto current_pair = [](fesa::hex8_internal::PacketPair value) {
return HighLow{value.high, value.low};
};
const auto observe = [&](bool original_order) {
EXPECT_EQ(std::fesetenv(&original), 0);
_mm_setcsr(original_csr & ~0x3fU);
EXPECT_EQ(std::feclearexcept(FE_ALL_EXCEPT), 0);
if (prior_status) {
EXPECT_EQ(std::feraiseexcept(FE_INEXACT), 0);
_mm_setcsr(_mm_getcsr() | 0x02U);
}
Observation value{};
if (!original_order)
fesa::hex8_internal::EvaluateGeometryPacketAvx2(input, &value.output);
for (std::size_t lane = 0; lane < 4; ++lane) {
HighLow geo;
if (original_order) {
HighLow dot;
for (std::size_t i = 0; i < 3; ++i)
dot = fesa::hex8_internal::AddPair(dot,
fesa::hex8_internal::MultiplyPair(current_pair(input.gradient_a[i]),
current_pair(input.gradient_b[i].values[lane])));
for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l)
geo = fesa::hex8_internal::AddPair(geo,
fesa::hex8_internal::MultiplyPair(
current_pair(input.gradient_stress[j][l]),
current_pair(input.gradient_b[l].values[lane])));
value.output.gradient_dot.values[lane] = {dot.high, dot.low};
}
const HighLow accumulators[3]{{},
{static_cast<double>(lane + 1), std::ldexp(1.0, -54)}, {-1, -0.0}};
for (std::size_t component = 0; component < 3; ++component) {
const auto weighted = original_order
? fesa::hex8_internal::MultiplyPair(geo, current_pair(input.weight))
: current_pair(value.output.weighted_geometric.values[lane]);
if (original_order)
value.output.weighted_geometric.values[lane] =
{weighted.high, weighted.low};
const auto accumulated = fesa::hex8_internal::AddPair(
accumulators[component], weighted);
value.accumulators[component].values[lane] =
{accumulated.high, accumulated.low};
}
}
value.flags = std::fetestexcept(FE_ALL_EXCEPT);
value.status = _mm_getcsr() & 0x3fU;
return value;
};
const auto baseline = observe(true);
const auto selected = observe(false);
fesa::hex8_internal::GeometryPacketOutput scalar;
fesa::hex8_internal::EvaluateGeometryPacketScalar(input, &scalar);
const auto expected = FrozenGeometryPacket(input);
EXPECT_EQ(std::fesetenv(&original), 0);
_mm_setcsr(original_csr);
EXPECT_EQ(selected.flags, baseline.flags);
EXPECT_EQ(selected.status, baseline.status);
const auto same_bits = [](const fesa::hex8_internal::PairPacket& a,
const fesa::hex8_internal::PairPacket& b) {
for (std::size_t lane = 0; lane < 4; ++lane) {
EXPECT_EQ(PairBits(a.values[lane].high), PairBits(b.values[lane].high));
EXPECT_EQ(PairBits(a.values[lane].low), PairBits(b.values[lane].low));
}
};
same_bits(selected.output.gradient_dot, expected.gradient_dot);
same_bits(selected.output.weighted_geometric, expected.weighted_geometric);
same_bits(scalar.gradient_dot, expected.gradient_dot);
same_bits(scalar.weighted_geometric, expected.weighted_geometric);
for (std::size_t component = 0; component < 3; ++component)
same_bits(selected.accumulators[component], baseline.accumulators[component]);
}
TEST(Hex8HighLow, GeometryPacketPreservesAsymmetricLanesAndUniformShapes) {
if (fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
fesa::hex8_internal::EvaluateMaterialPacketScalar)
GTEST_SKIP() << "Geometry AVX2 masked environment is not selected.";
RecordProperty("private_geometry_packet_backend", "avx2");
static_assert(std::is_trivial_v<fesa::hex8_internal::GeometryPacketInput>);
static_assert(std::is_trivial_v<fesa::hex8_internal::GeometryPacketOutput>);
std::size_t checks = 0;
for (int shape = 0; shape < 4; ++shape) {
auto input = GeometryDyadicFixture();
for (std::size_t j = 0; j < 3; ++j) {
if ((shape & 1) != 0) {
input.gradient_a[j].low = std::ldexp(input.gradient_a[j].high, -55);
for (std::size_t l = 0; l < 3; ++l)
input.gradient_stress[j][l].low =
std::ldexp(input.gradient_stress[j][l].high, -55);
}
if ((shape & 2) != 0)
for (std::size_t lane = 0; lane < 4; ++lane)
input.gradient_b[j].values[lane].low =
std::ldexp(input.gradient_b[j].values[lane].high, -55);
}
ExpectGeometryPacketBitsAndFlags(input, false);
ExpectGeometryPacketBitsAndFlags(input, true);
checks += 2;
}
RecordProperty("avx2_geometry_uniform_bit_flag_checks", static_cast<int>(checks));
}
TEST(Hex8HighLow, GeometryPacketRetainsSpecialLanesAndExactTailBoundary) {
if (fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
fesa::hex8_internal::EvaluateMaterialPacketScalar)
GTEST_SKIP() << "Geometry AVX2 masked environment is not selected.";
using fesa::hex8_internal::PacketPair;
const double tiny = std::numeric_limits<double>::denorm_min();
const double maximum = std::numeric_limits<double>::max();
const std::vector<PacketPair> special{{0, 0}, {-0.0, 0}, {1, -0.0}, {1, 1},
{tiny, 0}, {maximum, maximum},
{std::numeric_limits<double>::signaling_NaN(), 0},
{0, std::numeric_limits<double>::signaling_NaN()}};
std::size_t checks = 0;
for (const auto pair : special)
for (std::size_t lane = 0; lane < 4; ++lane) {
auto input = GeometryDyadicFixture();
input.gradient_b[1].values[lane] = pair;
ExpectGeometryPacketBitsAndFlags(input, false);
ExpectGeometryPacketBitsAndFlags(input, true);
checks += 2;
}
const auto odd = [](int exponent) {
return std::nextafter(std::ldexp(1.0, exponent),
std::numeric_limits<double>::infinity());
};
for (int exponent : {-470, -471})
for (std::size_t lane = 0; lane < 4; ++lane) {
auto input = GeometryDyadicFixture();
input.gradient_stress[0][0] = {odd(-500), 0};
input.gradient_b[0].values[lane] = {odd(exponent), 0};
ExpectGeometryPacketBitsAndFlags(input, false);
checks++;
}
RecordProperty("avx2_geometry_special_tail_checks", static_cast<int>(checks));
}
TEST(Hex8HighLow, GeometryPacketPreservesOrderedOverflowAndPreparedInvalid) {
if (fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
fesa::hex8_internal::EvaluateMaterialPacketScalar)
GTEST_SKIP() << "Geometry AVX2 masked environment is not selected.";
const double maximum = std::numeric_limits<double>::max();
std::size_t checks = 0;
for (bool overflow_order : {false, true}) {
SCOPED_TRACE(overflow_order);
fesa::hex8_internal::GeometryPacketInput input{};
input.weight = {0.5, 0};
for (std::size_t l = 0; l < 3; ++l)
for (std::size_t lane = 0; lane < 4; ++lane)
input.gradient_b[l].values[lane] = {1, 0};
input.gradient_stress[0][0] = {maximum, 0};
input.gradient_stress[0][1] = {overflow_order ? maximum : -maximum, 0};
input.gradient_stress[0][2] = {overflow_order ? -maximum : maximum, 0};
ExpectGeometryPacketBitsAndFlags(input, false);
ExpectGeometryPacketBitsAndFlags(input, true);
checks += 2;
}
auto input = GeometryDyadicFixture();
const auto invalid_first = fesa::high_low_original::Multiply(
{std::ldexp(1.0, -600), 0}, {std::ldexp(1.0, -500), 0});
input.gradient_stress[0][0] = {invalid_first.high, invalid_first.low};
for (std::size_t lane = 0; lane < 4; ++lane)
input.gradient_b[0].values[lane] = {std::ldexp(1.0, 600), 0};
ExpectGeometryPacketBitsAndFlags(input, false);
ExpectGeometryPacketBitsAndFlags(input, true);
checks += 2;
RecordProperty("avx2_geometry_ordered_invalid_checks", static_cast<int>(checks));
}
TEST(Hex8HighLow, GeometryPacketSumFallbackPreservesNearMaxAndSubnormalStatus) {
if (fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
fesa::hex8_internal::EvaluateMaterialPacketScalar)
GTEST_SKIP() << "Geometry AVX2 masked environment is not selected.";
const double maximum = std::numeric_limits<double>::max();
const double normal = std::ldexp(1.0, -1022);
const double tiny = std::ldexp(1.0, -1074);
const double quarter = std::ldexp(1.0, 969);
std::size_t checks = 0;
for (double tail : {0.0, normal, tiny})
for (std::size_t bad = 0; bad < 4; ++bad) {
SCOPED_TRACE(tail);
SCOPED_TRACE(bad);
fesa::hex8_internal::GeometryPacketInput input{};
input.weight = {0.5, 0};
// Both pairs are normalized. Head sum is finite; middle contributes
// 2^970 and the joined sum overflows. Other lanes retain finite values.
input.gradient_stress[0][0] = {maximum, quarter};
input.gradient_stress[0][1] = {quarter, tail};
for (std::size_t lane = 0; lane < 4; ++lane) {
const double scale = lane == bad ? 1.0 : 0.5;
input.gradient_b[0].values[lane] = {scale, 0};
input.gradient_b[1].values[lane] = {scale, 0};
input.gradient_b[2].values[lane] = {1, 0};
}
ExpectGeometryPacketBitsAndFlags(input, false);
ExpectGeometryPacketBitsAndFlags(input, true);
checks += 2;
}
fesa::hex8_internal::GeometryPacketInput finite{};
finite.weight = {1, 0};
finite.gradient_stress[0][0] = {normal, 0};
finite.gradient_stress[0][1] = {normal + tiny, 0};
for (std::size_t lane = 0; lane < 4; ++lane) {
finite.gradient_b[0].values[lane] = {1, 0};
finite.gradient_b[1].values[lane] = {1, 0};
}
// TwoSum(N,N+d) has finite normal inputs and a subnormal error d.
ExpectGeometryPacketBitsAndFlags(finite, false);
ExpectGeometryPacketBitsAndFlags(finite, true);
checks += 2;
// Mixed prefix: finite subnormal-error lanes run before another lane's head
// overflow. The bridge must preserve the whole packet's status union.
for (std::size_t bad = 0; bad < 4; ++bad) {
SCOPED_TRACE(bad);
fesa::hex8_internal::GeometryPacketInput mixed{};
mixed.weight = {1, 0};
mixed.gradient_stress[0][2] = {normal, 0};
mixed.gradient_stress[1][2] = {normal + tiny, 0};
mixed.gradient_stress[2][0] = {maximum, 0};
mixed.gradient_stress[2][1] = {maximum, 0};
for (std::size_t lane = 0; lane < 4; ++lane) {
mixed.gradient_b[0].values[lane] = {lane == bad ? 1.0 : 0.0, 0};
mixed.gradient_b[1].values[lane] = {lane == bad ? 1.0 : 0.0, 0};
mixed.gradient_b[2].values[lane] = {lane == bad ? 0.0 : 1.0, 0};
}
ExpectGeometryPacketBitsAndFlags(mixed, false);
ExpectGeometryPacketBitsAndFlags(mixed, true);
checks += 2;
}
RecordProperty("avx2_geometry_sum_fallback_checks", static_cast<int>(checks));
}
#endif
TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) { TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) {
const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator(); const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator();
const int rounding = std::fegetround(); const int rounding = std::fegetround();