From 33265bb76c0244b66d198e51667256b15ec33e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=9E=AC=EC=98=81?= <144660874+Jaeyeong-CHOI@users.noreply.github.com> Date: Wed, 7 Oct 2026 17:41:51 +0900 Subject: [PATCH 1/3] fix(ssm): preserve float32 timestep computation with BF16 bias --- src/lib/mlxcel-core/cpp/mlx_cxx_kernels.cpp | 8 +-- .../src/ssm_update_parity_tests.rs | 51 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/lib/mlxcel-core/cpp/mlx_cxx_kernels.cpp b/src/lib/mlxcel-core/cpp/mlx_cxx_kernels.cpp index 2b44cc1d9..4c83b7802 100644 --- a/src/lib/mlxcel-core/cpp/mlx_cxx_kernels.cpp +++ b/src/lib/mlxcel-core/cpp/mlx_cxx_kernels.cpp @@ -852,13 +852,15 @@ const mlxcel::KernelPorts& ssm_ports() { return ports; } - // Compiled compute_dt: float32 promotion + softplus + clip → single fused kernel - // Matches Python's @mx.compile compute_dt (casts dt to float32 before softplus for precision) + // Compiled compute_dt: float32 promotion + softplus + clip → single fused kernel. + // Used by: GraniteMoeHybrid, NemotronH, FalconH1, Plamo2. + // Cast both operands: CUDA's bf16-preserving promotion would otherwise + // narrow the timestep even when dt is float32 (#2142). static std::function(const std::vector&)> get_compiled_compute_dt() { auto fn = [](const std::vector& inputs) -> std::vector { auto dt = mlx::core::astype(inputs[0], mlx::core::float32); - const auto& dt_bias = inputs[1]; + auto dt_bias = mlx::core::astype(inputs[1], mlx::core::float32); const auto& lo = inputs[2]; const auto& hi = inputs[3]; auto result = mlx::core::add(dt, dt_bias); diff --git a/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs b/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs index 6734c4e1f..200e42a44 100644 --- a/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs +++ b/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs @@ -467,3 +467,54 @@ fn ssm_kernel_kill_switches_turn_predicate_off() { std::panic::resume_unwind(panic); } } + +/// A zero carried state and x = B = 1 make every next-state element equal +/// to the processed timestep. This isolates compute_dt from A_log rounding +/// and the output reduction. CUDA's bf16-preserving mixed-dtype promotion +/// must not narrow that timestep when the stored bias is bf16 (#2142). +#[test] +fn ssm_update_kernel_keeps_timestep_f32_with_bf16_bias() { + let _guard = crate::test_support::env_lock::env_lock(); + if skip() { + return; + } + // A small, distinct geometry isolates the timestep from the model-sized + // random recurrence cases above. + let shape = Shape { + name: "f32 timestep with mixed bias dtype", + batch: 1, + heads: 2, + head_dim: 8, + groups: 1, + state_dim: 32, + dt_limits: (0.0, f32::INFINITY), + }; + // Both -1.5 and -1.0 are exactly representable in f32 and bf16, so the + // expected value depends on no pre-quantization random inputs or GPU op. + let expected_dt = (-2.5_f32).exp().ln_1p(); + let state_shape = [1, 2, 8, 32]; + let expected_state = full_f32(&state_shape, expected_dt, dtype::FLOAT32); + eval(&expected_state); + + for act_dtype in [dtype::FLOAT32, dtype::BFLOAT16] { + // Exercise both bias-dtype transitions at the same geometry. The + // processed timestep must stay f32 regardless of the stored bias. + for bias_dtype in [dtype::BFLOAT16, dtype::FLOAT32, dtype::BFLOAT16] { + let case = Case { + x: full_f32(&[1, 1, 2, 8], 1.0, act_dtype), + a_log: full_f32(&[2], 0.0, dtype::FLOAT32), + b: full_f32(&[1, 1, 1, 32], 1.0, act_dtype), + c: full_f32(&[1, 1, 1, 32], 0.0, act_dtype), + d: full_f32(&[2], 0.0, act_dtype), + dt: full_f32(&[1, 1, 2], -1.5, act_dtype), + dt_bias: full_f32(&[2], -1.0, bias_dtype), + state: full_f32(&state_shape, 0.0, dtype::FLOAT32), + }; + let (out, state) = run_kernel(&shape, &case); + let label = format!("act dtype {act_dtype}, dt_bias dtype {bias_dtype}"); + assert_eq!(array_dtype(&out), act_dtype, "{label}: output dtype"); + assert_eq!(array_dtype(&state), dtype::FLOAT32, "{label}: state dtype"); + assert_within(&label, &state, &expected_state, dtype::FLOAT32); + } + } +} From a9696bd8cb3d0550d312ce5b9c561c544f9886e4 Mon Sep 17 00:00:00 2001 From: Jeongkyu Shin Date: Mon, 12 Oct 2026 22:25:19 +0900 Subject: [PATCH 2/3] test(ssm): split timestep precision regression Keep the focused mixed-bias precision regression in a child module so the parity suite remains below the 500-line file limit. The six CUDA cases and their tolerances are unchanged, and the focused test target compiles with the CUDA feature. Refs #2142 --- .../src/ssm_update_parity_tests.rs | 54 +-------------- .../ssm_update_timestep_precision_tests.rs | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+), 51 deletions(-) create mode 100644 src/lib/mlxcel-core/src/ssm_update_timestep_precision_tests.rs diff --git a/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs b/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs index 200e42a44..1282f2d73 100644 --- a/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs +++ b/src/lib/mlxcel-core/src/ssm_update_parity_tests.rs @@ -52,6 +52,9 @@ use super::*; use crate::hardware::{GpuBackendKind, gpu_backend_kind}; +#[path = "ssm_update_timestep_precision_tests.rs"] +mod timestep_precision_tests; + const KILL_SWITCHES: [&str; 2] = ["MLXCEL_SSM_KERNEL", "MLXCEL_SSM_CUDA_KERNEL"]; /// Normalized (RMS, max) deviation budget for values stored in `dtype`. @@ -467,54 +470,3 @@ fn ssm_kernel_kill_switches_turn_predicate_off() { std::panic::resume_unwind(panic); } } - -/// A zero carried state and x = B = 1 make every next-state element equal -/// to the processed timestep. This isolates compute_dt from A_log rounding -/// and the output reduction. CUDA's bf16-preserving mixed-dtype promotion -/// must not narrow that timestep when the stored bias is bf16 (#2142). -#[test] -fn ssm_update_kernel_keeps_timestep_f32_with_bf16_bias() { - let _guard = crate::test_support::env_lock::env_lock(); - if skip() { - return; - } - // A small, distinct geometry isolates the timestep from the model-sized - // random recurrence cases above. - let shape = Shape { - name: "f32 timestep with mixed bias dtype", - batch: 1, - heads: 2, - head_dim: 8, - groups: 1, - state_dim: 32, - dt_limits: (0.0, f32::INFINITY), - }; - // Both -1.5 and -1.0 are exactly representable in f32 and bf16, so the - // expected value depends on no pre-quantization random inputs or GPU op. - let expected_dt = (-2.5_f32).exp().ln_1p(); - let state_shape = [1, 2, 8, 32]; - let expected_state = full_f32(&state_shape, expected_dt, dtype::FLOAT32); - eval(&expected_state); - - for act_dtype in [dtype::FLOAT32, dtype::BFLOAT16] { - // Exercise both bias-dtype transitions at the same geometry. The - // processed timestep must stay f32 regardless of the stored bias. - for bias_dtype in [dtype::BFLOAT16, dtype::FLOAT32, dtype::BFLOAT16] { - let case = Case { - x: full_f32(&[1, 1, 2, 8], 1.0, act_dtype), - a_log: full_f32(&[2], 0.0, dtype::FLOAT32), - b: full_f32(&[1, 1, 1, 32], 1.0, act_dtype), - c: full_f32(&[1, 1, 1, 32], 0.0, act_dtype), - d: full_f32(&[2], 0.0, act_dtype), - dt: full_f32(&[1, 1, 2], -1.5, act_dtype), - dt_bias: full_f32(&[2], -1.0, bias_dtype), - state: full_f32(&state_shape, 0.0, dtype::FLOAT32), - }; - let (out, state) = run_kernel(&shape, &case); - let label = format!("act dtype {act_dtype}, dt_bias dtype {bias_dtype}"); - assert_eq!(array_dtype(&out), act_dtype, "{label}: output dtype"); - assert_eq!(array_dtype(&state), dtype::FLOAT32, "{label}: state dtype"); - assert_within(&label, &state, &expected_state, dtype::FLOAT32); - } - } -} diff --git a/src/lib/mlxcel-core/src/ssm_update_timestep_precision_tests.rs b/src/lib/mlxcel-core/src/ssm_update_timestep_precision_tests.rs new file mode 100644 index 000000000..ed661044e --- /dev/null +++ b/src/lib/mlxcel-core/src/ssm_update_timestep_precision_tests.rs @@ -0,0 +1,66 @@ +// Copyright 2025-2026 Lablup Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +/// A zero carried state and x = B = 1 make every next-state element equal +/// to the processed timestep. This isolates compute_dt from A_log rounding +/// and the output reduction. CUDA's bf16-preserving mixed-dtype promotion +/// must not narrow that timestep when the stored bias is bf16 (#2142). +#[test] +fn ssm_update_kernel_keeps_timestep_f32_with_bf16_bias() { + let _guard = crate::test_support::env_lock::env_lock(); + if skip() { + return; + } + // A small, distinct geometry isolates the timestep from the model-sized + // random recurrence cases above. + let shape = Shape { + name: "f32 timestep with mixed bias dtype", + batch: 1, + heads: 2, + head_dim: 8, + groups: 1, + state_dim: 32, + dt_limits: (0.0, f32::INFINITY), + }; + // Both -1.5 and -1.0 are exactly representable in f32 and bf16, so the + // expected value depends on no pre-quantization random inputs or GPU op. + let expected_dt = (-2.5_f32).exp().ln_1p(); + let state_shape = [1, 2, 8, 32]; + let expected_state = full_f32(&state_shape, expected_dt, dtype::FLOAT32); + eval(&expected_state); + + for act_dtype in [dtype::FLOAT32, dtype::BFLOAT16] { + // Exercise both bias-dtype transitions at the same geometry. The + // processed timestep must stay f32 regardless of the stored bias. + for bias_dtype in [dtype::BFLOAT16, dtype::FLOAT32, dtype::BFLOAT16] { + let case = Case { + x: full_f32(&[1, 1, 2, 8], 1.0, act_dtype), + a_log: full_f32(&[2], 0.0, dtype::FLOAT32), + b: full_f32(&[1, 1, 1, 32], 1.0, act_dtype), + c: full_f32(&[1, 1, 1, 32], 0.0, act_dtype), + d: full_f32(&[2], 0.0, act_dtype), + dt: full_f32(&[1, 1, 2], -1.5, act_dtype), + dt_bias: full_f32(&[2], -1.0, bias_dtype), + state: full_f32(&state_shape, 0.0, dtype::FLOAT32), + }; + let (out, state) = run_kernel(&shape, &case); + let label = format!("act dtype {act_dtype}, dt_bias dtype {bias_dtype}"); + assert_eq!(array_dtype(&out), act_dtype, "{label}: output dtype"); + assert_eq!(array_dtype(&state), dtype::FLOAT32, "{label}: state dtype"); + assert_within(&label, &state, &expected_state, dtype::FLOAT32); + } + } +} From f2b086117791c2f4afb105776f29c8af7b179062 Mon Sep 17 00:00:00 2001 From: Jeongkyu Shin Date: Mon, 12 Oct 2026 23:19:18 +0900 Subject: [PATCH 3/3] update: document SSM timestep regression verification Record the independent float32 timestep oracle and the completed GB10 CUDA workspace, focused parity, contract, and Clippy checks in English and Korean reports. Distinguish the regression coverage from the production correction already merged in PR #2270. --- ...sm-bf16-timestep-regression-20261012.en.md | 65 +++++++++++++++++++ ...sm-bf16-timestep-regression-20261012.ko.md | 65 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.en.md create mode 100644 TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.ko.md diff --git a/TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.en.md b/TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.en.md new file mode 100644 index 000000000..7fca84d3a --- /dev/null +++ b/TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.en.md @@ -0,0 +1,65 @@ +# Technical Report: PR #2209 - SSM BF16 timestep regression + +**Date**: 2026-10-12 + +**Status**: Local CUDA runtime and Clippy verification completed + +**Languages**: Rust + +**Risk Level**: Low + +## Executive Summary + +PR #2209 originally diagnosed the CUDA SSM parity failure and promoted `dt_bias` to float32 before the compiled timestep calculation. PR #2270 subsequently landed the same production correction on `main`, so after merging current `main` this PR preserves the contributor's original history and contributes an isolated regression test for the precision contract. + +The regression reduces the recurrence to an analytically known value, covers float32 and BF16 activations plus BF16-to-float32-to-BF16 bias transitions, and keeps the existing float32 state tolerance unchanged. The test lives in a child module to keep the parent parity file below the repository workflow's 500-line limit. + +## 1. Problem Statement + +The Granite BF16 cases in `ssm_update_parity_tests` failed because the compiled preprocessing cast `dt` to float32 but left `dt_bias` in BF16. MLX selected BF16 for the mixed-type addition, so `softplus(dt + dt_bias)` was rounded before the backend recurrence kernel received it; storing the resulting state as float32 could not recover that precision. + +The CUDA kernel body and the float32 MLX reference were correct. The defect was in the shared pre-dispatch `compute_dt_compiled` helper. Mainline PR #2270 already casts both operands to float32, leaving this PR to pin that behavior against regression. + +## 2. Change Summary + +The new case starts with zero state and sets `x = B = 1`, `C = D = 0`, and `A_log = 0`. Every next-state element therefore equals the processed timestep `softplus(-2.5)`. Both inputs are exactly representable in float32 and BF16, and the expected value is calculated with host float32 arithmetic, so the oracle does not depend on another GPU operation or on input quantization noise. + +The case runs float32 and BF16 activation variants while cycling the stored bias through BF16, float32, and BF16. This checks the precision contract and compilation-cache dtype transitions while preserving the existing output/state dtype assertions and tolerances. + +## 3. Technical Decisions + +### 3.1 Preserve mainline production code + +The merge conflict in `compute_dt_compiled` was resolved to the current `main` implementation because PR #2270 had already landed the same explicit float32 cast with measured evidence. No duplicate production change is claimed by the final diff, and the original PR commit remains in history. + +### 3.2 Use an independent scalar oracle + +Comparing the fused kernel only with another MLX graph could repeat the same promotion behavior. The isolated recurrence and host float32 expected value identify timestep narrowing directly and fail under the old mixed-type preprocessing without widening a tolerance. + +## 4. Verification + +| Check | Result | +|---|---| +| Published PR baseline | `33265bb76c0244b66d198e51667256b15ec33e1a` | +| Stable source candidate | `a9696bd8cb3d0550d312ce5b9c561c544f9886e4` | +| Focused CUDA no-run build | Passed with `-p mlxcel-core --profile test-fast --features cuda --lib --no-run` and two build jobs | +| Rust formatting and diff whitespace | Passed | +| Focused GB10 SSM runtime | 6 passed, 0 failed, 0 ignored under `gpu-lock`; all six cases executed in 9.65 seconds | +| Repository contract gate | Passed: crate-version consistency, kernel dtype/cache-key checks, kernel-port dispatch, llama-server compatibility manifest, ROCm overlay records, 123 Python tooling tests, and 49 WebUI fixtures/DTO/schema checks | +| Full CUDA workspace gate | Passed under `gpu-lock`: 12,247 passed, 0 failed, and 389 ignored across 163 suites, including doc tests, with `cargo test --locked --workspace --profile test-fast --features cuda -j2 --no-fail-fast -- --test-threads=1` | +| Broad CUDA Clippy | Passed: `cargo clippy --locked --workspace --all-targets --profile test-fast --features cuda -j 2 -- -D warnings` | + +PR #2270's preserved GB10 evidence measured normalized state RMS/max changing from `2.532402801e-3 / 4.127457397e-2` to `2.838613609e-8 / 1.701009653e-6` without changing tolerances. Its deterministic Granite comparison produced identical 64-token fused-kernel and graph sequences. + +## 5. Review Findings and Limitations + +Implementation review found the regression independent and correctly integrated. Security review found no new input, memory, privilege, or data-exposure surface. Performance impact is confined to test execution; production code is unchanged relative to current `main`. + +Runtime scope is GB10 CUDA. Metal and ROCm runtime execution, and real-checkpoint testing of FalconH1 and Plamo2, are outside this PR's current validation. + +## 6. Related Records + +- Issue #2142: CUDA SSM BF16 parity failure +- PR #2209: original diagnosis and regression contribution +- PR #2270: production correction and preserved GB10 numerical/checkpoint evidence +- `docs/benchmark_results/data/ssm-bf16-parity-gb10-2026-10-11/`: stage deviations and Granite kernel-versus-graph artifacts diff --git a/TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.ko.md b/TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.ko.md new file mode 100644 index 000000000..4bfb620e5 --- /dev/null +++ b/TECHNICAL_REPORTS/2209-ssm-bf16-timestep-regression-20261012.ko.md @@ -0,0 +1,65 @@ +# 기술 보고서: PR #2209 - SSM BF16 timestep 회귀 테스트 + +**날짜**: 2026-10-12 + +**상태**: 로컬 CUDA runtime 및 Clippy 검증 완료 + +**언어**: Rust + +**위험 수준**: 낮음 + +## 요약 + +PR #2209는 CUDA SSM parity 실패를 진단하고 compiled timestep 계산 전에 `dt_bias`를 float32로 변환했습니다. 이후 PR #2270이 동일한 production 수정을 `main`에 반영했으므로, 현재 `main`을 병합한 최종 PR은 기여자의 원래 이력을 보존하면서 정밀도 계약을 독립적으로 검증하는 회귀 테스트를 추가합니다. + +회귀 테스트는 recurrence를 해석적으로 계산할 수 있는 값으로 단순화하고, float32 및 BF16 activation과 BF16-to-float32-to-BF16 bias 전환을 검사하며, 기존 float32 state tolerance를 그대로 유지합니다. Parent parity file이 저장소 workflow의 500줄 제한 아래에 머물도록 테스트는 child module에 배치했습니다. + +## 1. 문제 정의 + +`ssm_update_parity_tests`의 Granite BF16 case는 compiled preprocessing이 `dt`만 float32로 변환하고 `dt_bias`는 BF16으로 남겨 두었기 때문에 실패했습니다. MLX는 mixed-type 덧셈 결과로 BF16을 선택했고, backend recurrence kernel이 값을 받기 전에 `softplus(dt + dt_bias)`가 반올림되었습니다. 이후 state를 float32로 저장해도 이미 손실된 정밀도를 복구할 수 없었습니다. + +CUDA kernel body와 float32 MLX reference는 올바랐습니다. 결함은 backend dispatch 전의 공유 `compute_dt_compiled` helper에 있었습니다. Mainline PR #2270은 이미 두 operand를 float32로 변환하므로, 이 PR은 해당 동작을 회귀하지 않도록 고정합니다. + +## 2. 변경 요약 + +새 case는 초기 state를 0으로 두고 `x = B = 1`, `C = D = 0`, `A_log = 0`으로 설정합니다. 따라서 모든 next-state 원소는 처리된 timestep `softplus(-2.5)`와 같습니다. 두 입력은 float32와 BF16에서 정확히 표현되며 expected value는 host float32 연산으로 계산하므로, oracle은 다른 GPU op나 입력 quantization 오차에 의존하지 않습니다. + +Case는 float32 및 BF16 activation을 실행하면서 저장된 bias를 BF16, float32, BF16 순서로 바꿉니다. 이를 통해 기존 output/state dtype assertion과 tolerance를 유지하면서 정밀도 계약과 compilation cache의 dtype 전환을 함께 검사합니다. + +## 3. 기술적 선택과 그 이유 + +### 3.1 Mainline production code 유지 + +PR #2270이 동일한 명시적 float32 cast와 측정 근거를 이미 반영했으므로 `compute_dt_compiled` merge conflict는 현재 `main` 구현으로 해결했습니다. 최종 diff는 production 변경을 중복 주장하지 않으며, 원래 PR commit은 history에 그대로 남습니다. + +### 3.2 독립 scalar oracle 사용 + +Fused kernel을 다른 MLX graph와만 비교하면 같은 promotion 동작을 반복할 수 있습니다. 단순화한 recurrence와 host float32 expected value는 timestep narrowing을 직접 식별하고 tolerance를 넓히지 않은 채 기존 mixed-type preprocessing에서 실패합니다. + +## 4. 검증 + +| 검사 | 결과 | +|---|---| +| 게시된 PR baseline | `33265bb76c0244b66d198e51667256b15ec33e1a` | +| 안정화한 source candidate | `a9696bd8cb3d0550d312ce5b9c561c544f9886e4` | +| 집중 CUDA no-run build | 두 build job과 `-p mlxcel-core --profile test-fast --features cuda --lib --no-run`으로 통과 | +| Rust formatting 및 diff whitespace | 통과 | +| 집중 GB10 SSM runtime | `gpu-lock` 아래에서 6 passed, 0 failed, 0 ignored, 여섯 case 모두 9.65초에 실행 | +| 저장소 contract gate | crate version 일치, kernel dtype/cache-key 검사, kernel-port dispatch, llama-server compatibility manifest, ROCm overlay record, Python tooling test 123개, WebUI fixture/DTO/schema 검사 49개 통과 | +| 전체 CUDA workspace gate | `gpu-lock` 아래에서 `cargo test --locked --workspace --profile test-fast --features cuda -j2 --no-fail-fast -- --test-threads=1`로 실행하여 doc test를 포함한 163개 suite에서 12,247 passed, 0 failed, 389 ignored로 통과 | +| 전체 CUDA Clippy | `cargo clippy --locked --workspace --all-targets --profile test-fast --features cuda -j 2 -- -D warnings` 통과 | + +PR #2270이 보존한 GB10 evidence에서 normalized state RMS/max는 tolerance 변경 없이 `2.532402801e-3 / 4.127457397e-2`에서 `2.838613609e-8 / 1.701009653e-6`으로 줄었습니다. Deterministic Granite 비교에서는 fused-kernel과 graph가 동일한 64-token sequence를 생성했습니다. + +## 5. 리뷰 결과와 한계 + +구현 리뷰는 회귀 테스트가 독립적이며 기존 test flow에 올바르게 통합되었음을 확인했습니다. 보안 리뷰에서 새로운 input, memory, privilege, data-exposure surface는 발견되지 않았습니다. 성능 영향은 test 실행에만 있으며 production code는 현재 `main`과 같습니다. + +Runtime 검증 범위는 GB10 CUDA입니다. Metal과 ROCm runtime 실행, FalconH1과 Plamo2의 실제 checkpoint 검증은 이 PR의 현재 범위 밖입니다. + +## 6. 관련 기록 + +- Issue #2142: CUDA SSM BF16 parity failure +- PR #2209: 원래 진단 및 회귀 테스트 기여 +- PR #2270: production 수정 및 보존된 GB10 수치/checkpoint evidence +- `docs/benchmark_results/data/ssm-bf16-parity-gb10-2026-10-11/`: stage deviation 및 Granite kernel-versus-graph artifact