gam_gpu/encode_throughput.rs
1//! Measured device-resident throughput of the SAE/LLM batched-solve COMPONENT —
2//! the resident penalized normal-equations inner solve, NOT the full exact SAE
3//! encode (see the SCOPE section below) (#1412, #988, #1017 Phase-3).
4//!
5//! ## Why this module exists
6//!
7//! The historical throughput "decision gate" (#1412) asserted a `100_000`
8//! rows/sec/GPU deployment target **without ever measuring a device**. Its
9//! successor still keyed the deployment decision on a *CPU* measurement scaled
10//! by a hardcoded `CPU_TO_GPU_SCALING = 100.0` fudge factor — so passing the
11//! gate established nothing about real GPU throughput. #988 closed
12//! `COMPLETED` while the maintainer's own follow-up confirmed the GPU
13//! steady-state encode rate had never been measured.
14//!
15//! This module makes the measurement real and *testable as a library function*
16//! (the prior real benchmark lived only in `examples/throughput_1412.rs`, which
17//! nothing in CI ran or asserted). `measure_resident_solve_throughput` runs
18//! the production IRLS inner step — upload `X` once, then repeatedly solve the
19//! penalized normal equations `(XᵀWX + ridge·I)β = rhs` with the `p×p` Gram and
20//! its Cholesky factor kept DEVICE-RESIDENT, downloading only the `p`-vector
21//! `β` — on the real device, and reports the measured design-rows/sec.
22//!
23//! ## SCOPE — this is a COMPONENT benchmark, not the full exact SAE encode
24//!
25//! What is timed here is the resident penalized normal-equations *inner solve*
26//! `(XᵀWX + ridge·I)β = rhs` ONLY. That is one component of the SAE encode, NOT
27//! the full exact per-row SAE encode, and the measured rate is therefore NOT
28//! evidence for a "batched exact per-row GPU encode" title claim. The full exact
29//! encode would additionally require, per row: active-set routing (which atoms
30//! are live), the per-row latent-coordinate Newton refinement on the manifold,
31//! the assignment/gate (softmax/IBP) solve, and the certificate/fallback +
32//! reconstruction-validation path. None of those are exercised or timed by this
33//! function. Establishing the end-to-end encode-throughput claim requires a
34//! separate benchmark that times the *production encode path itself* (routing +
35//! latent-coordinate Newton + assignment/gate solve + fallback/certificate), not
36//! this inner-solve cell. Treat the number below strictly as the resident
37//! normal-equations inner-solve throughput.
38//!
39//! ## Fail-loud, never false-route
40//!
41//! The single recurring failure mode this guards against is *false GPU
42//! routing*: claiming a device measurement while the work silently ran on the
43//! CPU. [`ResidentSolveThroughput::engaged`] is `true` only when
44//! `ResidentDesignGram::try_new` actually staged `X` on the device AND every
45//! timed solve returned a device result. If the device path declines or fails
46//! mid-measurement, `engaged` is `false` and `measured_rows_per_sec` is left at
47//! `0.0` — a non-measurement that [`GpuThroughputVerdict`] can never report as
48//! meeting the target. There is no CPU fallback inside the measurement: a
49//! caller that wants the CPU oracle runs it separately for parity.
50
51use super::policy::GpuThroughputVerdict;
52
53/// A representative LLM/SAE batched-solve work cell: `n` design rows, `p` wide
54/// decoder border. (`d`, the per-atom reduced-Schur block size, is fixed by the
55/// term and does not enter the resident-solve throughput.)
56#[derive(Clone, Copy, Debug)]
57pub struct EncodeShape {
58 /// Human-readable label for reporting.
59 pub label: &'static str,
60 /// Design rows pushed through the device per fit.
61 pub n: usize,
62 /// Decoder-border width (the resident Gram is `p×p`).
63 pub p: usize,
64}
65
66/// The canonical qwen/olmo-scale SAE residual-block shapes (matches the
67/// `examples/throughput_1412.rs` workload so the library measurement and the
68/// example agree).
69pub const CANONICAL_ENCODE_SHAPES: &[EncodeShape] = &[
70 EncodeShape {
71 label: "sae-2k-2048",
72 n: 2_000,
73 p: 2_048,
74 },
75 EncodeShape {
76 label: "sae-4k-4096",
77 n: 4_000,
78 p: 4_096,
79 },
80 EncodeShape {
81 label: "sae-8k-1024",
82 n: 8_000,
83 p: 1_024,
84 },
85];
86
87/// Outcome of measuring the device-resident penalized-solve throughput for one
88/// [`EncodeShape`].
89#[derive(Clone, Copy, Debug)]
90pub struct ResidentSolveThroughput {
91 /// The shape that was measured.
92 pub shape: EncodeShape,
93 /// `true` iff `X` was staged on the device AND every timed solve returned a
94 /// device result. `false` means the device path declined or failed — the
95 /// number below is **not** a device measurement.
96 pub engaged: bool,
97 /// Measured design-rows/sec for the resident solve, or `0.0` when the
98 /// device path did not engage (a non-measurement).
99 pub measured_rows_per_sec: f64,
100 /// The verdict comparing `measured_rows_per_sec` against
101 /// [`super::policy::GPU_THROUGHPUT_TARGET_ROWS_PER_SEC`].
102 pub verdict: GpuThroughputVerdict,
103}
104
105// ===========================================================================
106// FULL exact per-row encode throughput + correctness (#1412 follow-up).
107//
108// The component benchmark above times ONLY the resident normal-equations inner
109// solve `(XᵀWX+ridge·I)β=rhs` and is explicit (see the SCOPE section) that this
110// is NOT the full exact per-row SAE encode. The pieces below are the reusable,
111// gam-sae-free instrument for benchmarking the *full* production encode path
112// end-to-end — active-set/chart routing + per-row latent-coordinate Newton +
113// gate/assignment (amplitude) + Kantorovich certificate/fallback +
114// reconstruction. They live here (CPU-linkable, no `gam-sae` dependency: this
115// crate is *below* `gam-sae`) so the timing harness and the correctness gate
116// are shared, while the driver that actually calls the production
117// `EncodeAtlas::certified_encode_batch` lives in
118// `crates/gam-gpu/tests/encode_full_path_throughput.rs` (a dev-dependency cycle
119// onto `gam-sae`, allowed by cargo for test-only edges).
120//
121// HONEST DEVICE STATUS. This helper is still backend-agnostic instrumentation:
122// callers must set `device_encode_engaged` to `true` only when their encode was
123// produced by a real device-resident exact-encode kernel. The current SAE device
124// driver that can make that assertion lives in
125// `gam_sae::gpu_kernels::sae_encode_resident::measure_device_encode_throughput`;
126// older host-only full-path harnesses pass `false`. This benchmark therefore
127// never fabricates a device "batched exact per-row GPU encode" number from a
128// host encode — it reports the full-path timing and a correctness contract
129// (support agreement, coordinate error, reconstruction explained-variance, and
130// fallback rate), while the caller-owned engagement flag decides whether the
131// #988 deployment/surrogate gate may consume the rate as a device measurement.
132// ===========================================================================
133
134/// End-to-end throughput of the FULL exact per-row encode for one batch.
135///
136/// Distinct from [`ResidentSolveThroughput`] (which times only the inner solve):
137/// `rows_per_sec` here is `n_rows / encode_secs` for the *entire* production
138/// `certified_encode_batch` — routing, per-row Newton, certificate, fallback,
139/// and the per-row reconstruction selection included.
140#[derive(Clone, Copy, Debug)]
141pub struct FullEncodeThroughput {
142 /// Rows encoded in the timed batch.
143 pub n_rows: usize,
144 /// Wall-clock seconds for the full encode of the batch.
145 pub encode_secs: f64,
146 /// `n_rows / encode_secs` (`0.0` for a degenerate / non-positive time).
147 pub rows_per_sec: f64,
148 /// `true` ONLY if a device-resident exact-encode kernel actually ran the
149 /// encode. No such kernel exists yet, so this is `false` even on a GPU host
150 /// — the flag is the false-routing guard that keeps the CPU encode rate from
151 /// ever being reported as a device measurement.
152 pub device_encode_engaged: bool,
153}
154
155/// Correctness of an encode result, measured against the production CPU encode
156/// (a per-row reference) and the reconstruction it implies.
157///
158/// Every field is a quantity a "batched exact per-row encode" claim has to
159/// stand on: it must AGREE with the production per-row encode (support +
160/// coordinates), it must RECONSTRUCT the targets (explained variance), and it
161/// must be honest about how many rows it could not certify (fallback rate).
162#[derive(Clone, Copy, Debug)]
163pub struct EncodeQualityMetrics {
164 /// Rows compared.
165 pub n_rows: usize,
166 /// Rows the encode-under-test certified (`h ≤ ½`, exact-into-the-ball).
167 pub certified_rows: usize,
168 /// Fraction of rows the encode-under-test could NOT certify and flagged for
169 /// the multi-start fallback (`1 - certified_rows/n_rows`). This is the
170 /// "fallback rate".
171 pub fallback_rate: f64,
172 /// Fraction of rows whose certificate flag AGREES with the per-row reference
173 /// encode. For a correct batched encode this is `1.0` (the batch is just the
174 /// per-row encode fanned out).
175 pub support_agreement: f64,
176 /// Largest absolute latent-coordinate difference between the encode-under-test
177 /// and the per-row reference encode, over all rows and coordinate dims. A
178 /// correct batched encode matches the per-row encode to round-off (≈ `0`).
179 pub max_coord_abs_err: f64,
180 /// Largest absolute element-wise reconstruction residual `|x̂ − x|` over the
181 /// whole batch (the "amplitude"/reconstruction error in raw output units).
182 pub max_reconstruction_abs_err: f64,
183 /// Reconstruction explained variance `1 − ‖X − X̂‖²_F / ‖X − X̄‖²_F`, with each
184 /// output column centered by its own mean `X̄`. `1.0` is a perfect on-manifold
185 /// reconstruction; `0.0` is no better than the per-column mean.
186 pub reconstruction_ev: f64,
187}
188