Skip to main content

gam_solve/gpu_kernels/
sae_resident.rs

1//! Device-resident SAE inner-iteration workspace for issue #1017.
2//!
3//! This first vertical slice keeps production fitting untouched. It accepts
4//! host-evaluated SAE basis/gate values plus already-assembled data-fit
5//! Arrow-Schur slabs, uploads those buffers once, and runs one Newton step
6//! through the existing GPU Arrow-Schur sequence when the runtime probe admits
7//! the workload. Later slices can replace the host slab feed with on-device
8//! basis/gate evaluation without changing the public step API.
9
10use ndarray::Array1;
11
12use crate::gpu_kernels::arrow_schur::{
13    ArrowSchurGpuFailure, ArrowSchurGpuSolution, solve_arrow_newton_step,
14    solve_arrow_newton_step_dense_reference,
15};
16use gam_problem::ExecutionPath;
17
18/// Per-iterate solve backend for the resident inner Newton loop.
19///
20/// All three modes run the IDENTICAL host control flow (`run_inner_loop`):
21/// residual-gradient assembly, LM trust-region accept/reject, ridge schedule.
22/// They differ ONLY in how the per-iterate arrow step is computed, which is
23/// exactly the residency lever #1017 measures:
24///
25/// * [`InnerSolveMode::DeviceResident`] — the Phase-3 fix: factor the constant
26///   Hessian blocks ONCE into a [`crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle`]
27///   and, every iterate, upload only the `O(n·d + k)` gradient and read back
28///   only `δ`. No per-solve D/B re-upload, no per-solve POTRF.
29/// * [`InnerSolveMode::DeviceReupload`] — the BEFORE path: call
30///   `solve_arrow_newton_step` per iterate, which re-packs and re-uploads
31///   `D`/`B`/`g` and re-runs the per-row POTRF + border Schur factor every call.
32///   This is the residency baseline the bench divides against.
33/// * [`InnerSolveMode::CpuReference`] — the dense f64 oracle (re-factors per
34///   iterate on the host), used for the correctness parity check.
35/// * [`InnerSolveMode::CpuArrow`] — the honest CPU COMPETITOR: the production
36///   structured Arrow-Schur solve (`ArrowSchurSystem::solve_with_options` with
37///   the device policy off), which exploits the block structure exactly as a
38///   CPU-only production host would. `CpuReference`'s dense
39///   `(n·d+k) × (n·d+k)` Cholesky is an ORACLE, not a competitor — quoting a
40///   speedup against it would flatter the device by a baseline no production
41///   host would ever run (#2393).
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum InnerSolveMode {
44    DeviceResident,
45    DeviceReupload,
46    CpuReference,
47    CpuArrow,
48}
49
50impl InnerSolveMode {
51    /// Truthful [`ExecutionPath`] this solve mode realizes (issue #1017): the
52    /// resident loop keeps factors on-device (`GpuResidentFull`), the baseline
53    /// re-uploads/re-factors every iterate (`GpuReupload`), and the reference
54    /// path runs on the host (`Cpu`).
55    #[inline]
56    const fn execution_path(self) -> ExecutionPath {
57        match self {
58            Self::DeviceResident => ExecutionPath::GpuResidentFull,
59            Self::DeviceReupload => ExecutionPath::GpuReupload,
60            Self::CpuReference | Self::CpuArrow => ExecutionPath::Cpu,
61        }
62    }
63
64    /// Whether this mode's per-iterate operator apply (`H·z`) may run on the
65    /// device.
66    ///
67    /// `CpuReference` is the INDEPENDENT host oracle the parity harness divides
68    /// against, so its contraction must stay on the host: routing it to the
69    /// device too would make "device == CPU" compare the device against itself,
70    /// and would make its wall clock a device measurement wearing a CPU label.
71    #[inline]
72    const fn operator_on_device(self) -> bool {
73        match self {
74            Self::DeviceResident | Self::DeviceReupload => true,
75            Self::CpuReference | Self::CpuArrow => false,
76        }
77    }
78}
79use crate::arrow_schur::{ArrowSchurError, ArrowSchurSystem};
80
81/// SAE shape used by the resident inner-iteration workspace.
82///
83/// `p` is the target width and current shared-border width for this slice. The
84/// true SAE decoder has richer `(basis × output)` structure; slice 1 deliberately
85/// keeps that structure host-assembled into `row_cross_slabs` while preserving
86/// the qwen-scale target width in the Schur border.
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88pub struct DeviceResidentArrowShape {
89    pub n: usize,
90    pub p: usize,
91    pub basis_cols: usize,
92    pub d: usize,
93}
94
95impl DeviceResidentArrowShape {
96    #[inline]
97    pub const fn qwen_non_gating() -> Self {
98        Self {
99            n: 2_000,
100            p: 2_048,
101            basis_cols: 8,
102            d: 2,
103        }
104    }
105
106    /// Color-arm shape from the #1017 measured gap (n=180, p=5120, M≈9, K=1):
107    /// few rows, very wide border. The dense-Schur device path (cuSOLVER border
108    /// POTRF) handles the `p=5120` border that exceeds the fused-kernel `P_MAX`.
109    #[inline]
110    pub const fn color_arm() -> Self {
111        Self {
112            n: 180,
113            p: 5_120,
114            basis_cols: 9,
115            d: 2,
116        }
117    }
118
119    #[inline]
120    pub const fn target_len(self) -> usize {
121        self.n * self.p
122    }
123
124    #[inline]
125    pub const fn basis_len(self) -> usize {
126        self.n * self.basis_cols
127    }
128
129    #[inline]
130    pub const fn row_hessian_len(self) -> usize {
131        self.n * self.d * self.d
132    }
133
134    #[inline]
135    pub const fn row_cross_len(self) -> usize {
136        self.n * self.d * self.p
137    }
138
139    #[inline]
140    pub const fn row_gradient_len(self) -> usize {
141        self.n * self.d
142    }
143
144    #[inline]
145    pub const fn border_hessian_len(self) -> usize {
146        self.p * self.p
147    }
148}
149
150/// Host-fed row-block slabs for the first resident slice.
151///
152/// All matrices are row-major in host memory:
153/// * `row_hessian_slabs`: `n` slabs of shape `d × d`.
154/// * `row_cross_slabs`: `n` slabs of shape `d × p`.
155/// * `border_hessian`: one `p × p` shared block.
156#[derive(Clone, Debug)]
157pub struct DeviceResidentArrowSlabs {
158    pub row_hessian_slabs: Vec<f64>,
159    pub row_cross_slabs: Vec<f64>,
160    pub row_gradient_slabs: Vec<f64>,
161    pub border_hessian: Vec<f64>,
162    pub border_gradient: Vec<f64>,
163}
164
165/// Result of one resident SAE inner Newton iteration.
166#[derive(Clone, Debug)]
167pub struct DeviceResidentArrowStep {
168    pub delta_t: Array1<f64>,
169    pub delta_beta: Array1<f64>,
170    pub objective: f64,
171    pub gradient_norm: f64,
172    pub log_det_hessian: f64,
173    pub execution_path: ExecutionPath,
174}
175
176#[derive(Debug, Clone)]
177pub enum DeviceResidentArrowError {
178    Shape { reason: String },
179    Unavailable { reason: String },
180    Solve { reason: String },
181}
182
183impl std::fmt::Display for DeviceResidentArrowError {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        match self {
186            Self::Shape { reason } | Self::Unavailable { reason } | Self::Solve { reason } => {
187                f.write_str(reason)
188            }
189        }
190    }
191}
192
193impl std::error::Error for DeviceResidentArrowError {}
194
195#[cfg(target_os = "linux")]
196pub struct DeviceResidentArrowBuffers {
197    pub stream: std::sync::Arc<cudarc::driver::CudaStream>,
198    pub target_x_dev: cudarc::driver::CudaSlice<f64>,
199    pub basis_values_dev: cudarc::driver::CudaSlice<f64>,
200    pub gate_activations_dev: cudarc::driver::CudaSlice<f64>,
201    pub row_hessian_dev: cudarc::driver::CudaSlice<f64>,
202    pub row_cross_dev: cudarc::driver::CudaSlice<f64>,
203    pub row_gradient_dev: cudarc::driver::CudaSlice<f64>,
204    pub border_hessian_dev: cudarc::driver::CudaSlice<f64>,
205    pub border_gradient_dev: cudarc::driver::CudaSlice<f64>,
206    pub bytes: usize,
207    /// cuBLAS handle bound to `stream`, created ONCE with the resident buffers.
208    /// The per-iterate operator applies below reuse it; creating a handle per
209    /// apply would reintroduce exactly the per-iteration device setup cost that
210    /// residency exists to remove.
211    blas: cudarc::cublas::CudaBlas,
212    /// Persistent device scratch for the per-iterate operator apply. Held
213    /// behind a mutex because the apply runs through `&self` (the multiplexed
214    /// driver shares `&workspace` across a rayon scope); each workspace owns its
215    /// own stream, handle, and scratch, so the lock is uncontended within a fit
216    /// and prevents two applies on ONE workspace from aliasing the scratch.
217    operator_scratch: std::sync::Mutex<DeviceOperatorScratch>,
218}
219
220/// Device-side operand/result buffers for one resident operator apply.
221///
222/// These are the only allocations the per-iterate `H·z` contraction needs, and
223/// they are `O(n·d + p)` — the `O(n·d·p)` cross slab and the `O(p²)` border stay
224/// resident in [`DeviceResidentArrowBuffers`] and are never re-uploaded.
225#[cfg(target_os = "linux")]
226struct DeviceOperatorScratch {
227    t_dev: cudarc::driver::CudaSlice<f64>,
228    beta_dev: cudarc::driver::CudaSlice<f64>,
229    cross_t_dev: cudarc::driver::CudaSlice<f64>,
230    cross_beta_dev: cudarc::driver::CudaSlice<f64>,
231    border_beta_dev: cudarc::driver::CudaSlice<f64>,
232}
233
234/// One application of the resident bordered-quadratic operator at an iterate
235/// `z = (t, β)`.
236///
237/// These three products are the ONLY `O(n·d·p)` / `O(p²)` work the inner Newton
238/// loop needs at a point, and BOTH the residual gradient and the objective are
239/// algebraic combinations of them:
240///
241/// ```text
242///   cross_t[i·d+r] = Σ_j H_tβ^(i)[r,j] · β_j          (the (n·d)×p slab · β)
243///   cross_beta[j]  = Σ_i Σ_r H_tβ^(i)[r,j] · t_{i,r}  (that slab transposed · t)
244///   border_beta[j] = Σ_c H_ββ[j,c] · β_c              (the p×p border · β)
245/// ```
246///
247/// so
248///
249/// ```text
250///   r(z)_t[i,r] = (H_tt^(i) t_i)_r + cross_t[i·d+r] − g₀_t[i,r]
251///   r(z)_β[j]   = border_beta[j] + cross_beta[j] − g₀_β[j]
252///   φ(z)        = ½‖X‖² + ½(Σ_{i,r} t·((H_tt t) + 2·cross_t) + Σ_j β_j·border_beta[j])
253///                 − g₀ᵀz
254/// ```
255///
256/// The `H_tt` contribution is deliberately excluded: the per-row `d×d` blocks
257/// total `n·d²` entries (64 KiB at the qwen shape vs 64 MiB for the cross slab),
258/// so contracting them costs nothing and keeping them host-side avoids a kernel
259/// launch whose latency would exceed its arithmetic.
260#[derive(Clone, Debug)]
261struct ArrowOperatorApply {
262    cross_t: Vec<f64>,
263    cross_beta: Vec<f64>,
264    border_beta: Vec<f64>,
265}
266
267/// Upload-once workspace for the SAE data-fit Arrow-Schur inner iteration.
268pub struct DeviceResidentArrowWorkspace {
269    shape: DeviceResidentArrowShape,
270    target_x: Vec<f64>,
271    basis_values: Vec<f64>,
272    gate_activations: Vec<f64>,
273    slabs: DeviceResidentArrowSlabs,
274    #[cfg(target_os = "linux")]
275    device: Option<DeviceResidentArrowBuffers>,
276}
277
278impl DeviceResidentArrowWorkspace {
279    pub fn new(
280        shape: DeviceResidentArrowShape,
281        target_x: Vec<f64>,
282        basis_values: Vec<f64>,
283        gate_activations: Vec<f64>,
284        slabs: DeviceResidentArrowSlabs,
285    ) -> Result<Self, DeviceResidentArrowError> {
286        validate_shape(shape, &target_x, &basis_values, &gate_activations, &slabs)?;
287        #[cfg(target_os = "linux")]
288        let device =
289            upload_resident_buffers(shape, &target_x, &basis_values, &gate_activations, &slabs);
290        Ok(Self {
291            shape,
292            target_x,
293            basis_values,
294            gate_activations,
295            slabs,
296            #[cfg(target_os = "linux")]
297            device,
298        })
299    }
300
301    #[inline]
302    pub const fn shape(&self) -> DeviceResidentArrowShape {
303        self.shape
304    }
305
306    #[must_use]
307    pub fn device_resident(&self) -> bool {
308        #[cfg(target_os = "linux")]
309        {
310            self.device.is_some()
311        }
312        #[cfg(not(target_os = "linux"))]
313        {
314            false
315        }
316    }
317
318    #[must_use]
319    pub fn resident_device_bytes(&self) -> usize {
320        #[cfg(target_os = "linux")]
321        {
322            self.device.as_ref().map_or(0, |device| device.bytes)
323        }
324        #[cfg(not(target_os = "linux"))]
325        {
326            0
327        }
328    }
329
330    /// Opaque device-context identifier for telemetry: `1` when the resident
331    /// device buffers are live on this workspace, `0` when no device was bound.
332    /// Distinguishes "a device executed this fit" from "silent CPU fallback"
333    /// without leaking the cudarc handle.
334    #[must_use]
335    fn context_id(&self) -> usize {
336        usize::from(self.device_resident())
337    }
338
339    /// Bytes the re-uploading / frame-build path moves host→device for a full
340    /// `D`/`B`/`g`/border refresh, used to attribute H2D traffic in telemetry.
341    #[must_use]
342    fn frame_upload_bytes(&self) -> usize {
343        [
344            self.slabs.row_hessian_slabs.len(),
345            self.slabs.row_cross_slabs.len(),
346            self.slabs.row_gradient_slabs.len(),
347            self.slabs.border_hessian.len(),
348            self.slabs.border_gradient.len(),
349        ]
350        .into_iter()
351        .sum::<usize>()
352            * std::mem::size_of::<f64>()
353    }
354
355    #[must_use]
356    pub fn host_shadow_bytes(&self) -> usize {
357        [
358            self.target_x.len(),
359            self.basis_values.len(),
360            self.gate_activations.len(),
361            self.slabs.row_hessian_slabs.len(),
362            self.slabs.row_cross_slabs.len(),
363            self.slabs.row_gradient_slabs.len(),
364            self.slabs.border_hessian.len(),
365            self.slabs.border_gradient.len(),
366        ]
367        .into_iter()
368        .sum::<usize>()
369            * std::mem::size_of::<f64>()
370    }
371
372    /// Run one device-side Newton sequence. No CPU fallback is attempted here:
373    /// callers that want a reference path must call [`Self::cpu_reference_step`].
374    pub fn one_inner_iteration(
375        &self,
376        ridge_t: f64,
377        ridge_beta: f64,
378    ) -> Result<DeviceResidentArrowStep, DeviceResidentArrowError> {
379        if !self.device_resident() {
380            return Err(DeviceResidentArrowError::Unavailable {
381                reason: "SAE resident inner iteration unavailable: CUDA runtime did not admit the qwen-scale row-block workload".to_string(),
382            });
383        }
384        let sys = self.to_arrow_system();
385        let frame = crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
386            &sys, ridge_t, ridge_beta, None,
387        )
388        .map_err(map_gpu_error)?;
389        let g_t: Vec<f64> = sys
390            .rows
391            .iter()
392            .flat_map(|row| row.gt.iter().copied())
393            .collect();
394        let g_beta: Vec<f64> = sys.gb.iter().copied().collect();
395        // This is a SINGLE resident solve at one frozen gate/basis frame — the
396        // frame's constant Hessian factors are held resident and only the
397        // gradient is uploaded — so the truthful classifier is
398        // `GpuResidentLinearization`, not `GpuResidentFull` (which denotes the
399        // full multi-step device-resident inner Newton loop in `device_fit`).
400        frame
401            .solve_gradient(&g_t, &g_beta)
402            .map(|solution| self.finish_step(solution, ExecutionPath::GpuResidentLinearization))
403            .map_err(map_gpu_error)
404    }
405
406    /// CPU reference for parity harnesses. This path is explicit and is never
407    /// called from [`Self::one_inner_iteration`].
408    pub fn cpu_reference_step(
409        &self,
410        ridge_t: f64,
411        ridge_beta: f64,
412    ) -> Result<DeviceResidentArrowStep, DeviceResidentArrowError> {
413        let sys = self.to_arrow_system();
414        solve_arrow_newton_step_dense_reference(&sys, ridge_t, ridge_beta)
415            .map(|solution| self.finish_step(solution, ExecutionPath::Cpu))
416            .map_err(|reason| DeviceResidentArrowError::Solve { reason })
417    }
418
419    pub fn to_arrow_system(&self) -> ArrowSchurSystem {
420        let shape = self.shape;
421        let mut sys = ArrowSchurSystem::new(shape.n, shape.d, shape.p);
422        for i in 0..shape.n {
423            let h_base = i * shape.d * shape.d;
424            let b_base = i * shape.d * shape.p;
425            let g_base = i * shape.d;
426            for r in 0..shape.d {
427                for c in 0..shape.d {
428                    sys.rows[i].htt[[r, c]] =
429                        self.slabs.row_hessian_slabs[h_base + r * shape.d + c];
430                }
431                sys.rows[i].gt[r] = self.slabs.row_gradient_slabs[g_base + r];
432                for c in 0..shape.p {
433                    sys.rows[i].htbeta[[r, c]] =
434                        self.slabs.row_cross_slabs[b_base + r * shape.p + c];
435                }
436            }
437        }
438        for r in 0..shape.p {
439            sys.gb[r] = self.slabs.border_gradient[r];
440            for c in 0..shape.p {
441                sys.hbb[[r, c]] = self.slabs.border_hessian[r * shape.p + c];
442            }
443        }
444        sys.refresh_row_hessian_fingerprint();
445        sys
446    }
447
448    fn finish_step(
449        &self,
450        solution: crate::gpu_kernels::arrow_schur::ArrowSchurGpuSolution,
451        execution_path: ExecutionPath,
452    ) -> DeviceResidentArrowStep {
453        DeviceResidentArrowStep {
454            delta_t: solution.delta_t,
455            delta_beta: solution.delta_beta,
456            objective: 0.5 * squared_norm(&self.target_x),
457            gradient_norm: self.gradient_norm(),
458            log_det_hessian: solution.log_det_hessian,
459            execution_path,
460        }
461    }
462
463    fn gradient_norm(&self) -> f64 {
464        let row = squared_norm(&self.slabs.row_gradient_slabs);
465        let border = squared_norm(&self.slabs.border_gradient);
466        (row + border).sqrt()
467    }
468
469    // ---------------------------------------------------------------------
470    // Phase 3: full device-resident inner Newton loop (#1017).
471    //
472    // The resident slabs define a fixed bordered-quadratic data-fit objective
473    //     φ(z) = ½‖X‖² + ½ zᵀ H z − g₀ᵀ z,   z = (t, β),
474    // where `H` is the arrow-structured Hessian (per-row `H_tt`/`H_tβ` blocks
475    // plus the shared `H_ββ` border) and `g₀` is the base gradient assembled
476    // once at upload. This is the quadratic the SAE joint inner Newton actually
477    // minimises at a frozen gate/basis evaluation; the production driver
478    // (`LatentInnerSolver::solve`) re-linearises per outer evaluation, so a
479    // single resident frame is one such inner solve.
480    //
481    // The loop mirrors the production LM trust-region accept/reject exactly:
482    // at iterate `z` it forms the residual gradient `r(z) = H z − g₀`, takes
483    // the LM-damped arrow step (device or dense-reference), evaluates the trial
484    // objective, and accepts on the actual-vs-predicted reduction ratio. The
485    // iterate `(t, β)` and the per-step scalars (objective, gradient norm, ρ)
486    // are the ONLY host-side state; the heavy `O(n d³ + p³)` factor/solve stays
487    // on the resident buffers via `solve_arrow_newton_step`. For an exact
488    // quadratic the loop converges in one accepted step, but it exercises the
489    // full assemble→solve→objective→accept machinery and the scalar-only
490    // readback contract the production loop relies on.
491    // ---------------------------------------------------------------------
492
493    /// Run the full device-resident inner Newton loop. Routes the per-iteration
494    /// arrow solve through the GPU path; returns `Unavailable` when CUDA did not
495    /// admit the resident workload (callers wanting a CPU path use
496    /// [`Self::cpu_reference_fit`]).
497    pub fn device_fit(
498        &self,
499        opts: &DeviceResidentInnerOptions,
500    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
501        if !self.device_resident() {
502            return Err(DeviceResidentArrowError::Unavailable {
503                reason: "SAE resident inner loop unavailable: CUDA runtime did not admit the qwen-scale row-block workload".to_string(),
504            });
505        }
506        self.run_inner_loop(opts, InnerSolveMode::DeviceResident)
507    }
508
509    /// The #1017 residency baseline: run the SAME inner Newton loop but compute
510    /// each per-iterate arrow step through `solve_arrow_newton_step`, which
511    /// re-packs/re-uploads `D`/`B`/`g` and re-runs the per-row POTRF + border
512    /// Schur factor on EVERY iterate. This is the "current re-uploading path";
513    /// the bench divides [`Self::device_fit`] (resident) against it to isolate
514    /// the across-iteration residency speedup on one device, holding the host
515    /// control flow and the GPU factor kernels fixed.
516    pub fn device_reupload_fit(
517        &self,
518        opts: &DeviceResidentInnerOptions,
519    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
520        if !self.device_resident() {
521            return Err(DeviceResidentArrowError::Unavailable {
522                reason: "SAE re-uploading inner loop unavailable: CUDA runtime did not admit the row-block workload".to_string(),
523            });
524        }
525        self.run_inner_loop(opts, InnerSolveMode::DeviceReupload)
526    }
527
528    /// CPU dense-reference inner loop. Bit-for-bit the same host arithmetic as
529    /// [`Self::device_fit`] except the per-iteration arrow solve uses the dense
530    /// reference factorisation; the parity harness asserts the two agree.
531    ///
532    /// This is a correctness ORACLE, not a speed baseline: it factors the full
533    /// dense `(n·d+k) × (n·d+k)` joint Hessian, which is what makes it an
534    /// independent check of the arrow algebra, and also what makes its wall
535    /// clock meaningless as a CPU competitor. Use [`Self::cpu_arrow_fit`] for
536    /// the timing baseline.
537    pub fn cpu_reference_fit(
538        &self,
539        opts: &DeviceResidentInnerOptions,
540    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
541        self.run_inner_loop(opts, InnerSolveMode::CpuReference)
542    }
543
544    /// The honest CPU competitor: the SAME inner Newton loop with the
545    /// per-iterate step taken by the PRODUCTION structured Arrow-Schur solve on
546    /// the host (device policy off). This is what a CPU-only production host
547    /// runs, so it — not the dense oracle — is what a device speedup must be
548    /// divided against.
549    ///
550    /// `log_det_hessian` is reported as NaN for this mode: the production solve
551    /// entry returns the step and its PCG diagnostics, not the joint log
552    /// determinant, and fabricating one from a second factorisation would put
553    /// work in the baseline that the production path does not do.
554    pub fn cpu_arrow_fit(
555        &self,
556        opts: &DeviceResidentInnerOptions,
557    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
558        self.run_inner_loop(opts, InnerSolveMode::CpuArrow)
559    }
560
561    fn run_inner_loop(
562        &self,
563        opts: &DeviceResidentInnerOptions,
564        mode: InnerSolveMode,
565    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
566        let execution_path = mode.execution_path();
567        let n = self.shape.n;
568        let d = self.shape.d;
569        let p = self.shape.p;
570        let t_len = n * d;
571
572        // Resident iterate, host-side scalars only. The device buffers (X,
573        // slabs, border) never leave the device across iterations; only this
574        // O(t_len + p) iterate and the per-step reduction scalars cross back.
575        let mut t = vec![0.0_f64; t_len];
576        let mut beta = vec![0.0_f64; p];
577
578        let base = self.to_arrow_system();
579        let half_target_energy = 0.5 * squared_norm(&self.target_x);
580        // ONE residual system for the whole fit: the Hessian blocks are frozen
581        // for this frame, so only its gradients move (see `residual_into`).
582        let mut residual = self.to_arrow_system();
583        let mut accounting = ResidencyAccounting::default();
584
585        let mut ridge_t = opts.initial_ridge_t.max(0.0);
586        let mut ridge_beta = opts.initial_ridge_beta.max(0.0);
587        // #1017 Phase 3: when running on device, keep the resident Arrow frame
588        // (constant Hessian blocks + their factors) on the device across
589        // iterations. The frame bakes a fixed `(ridge_t, ridge_beta)` into the
590        // per-row and border Cholesky factors, so it is rebuilt only when the LM
591        // ridge changes (reject/shrink); every iteration that shares the cached
592        // ridge reuses the resident factors and uploads only the `O(n·d + p)`
593        // gradient. The CPU reference path keeps re-factoring per iterate so the
594        // parity harness compares residency against a fully independent solve.
595        let mut frames = SharedFrameState::default();
596        let mut current_apply = accounting.apply(self, mode, &t, &beta);
597        let mut current_objective =
598            self.objective_from_apply(&base, half_target_energy, &current_apply, &t, &beta);
599        let mut accepted_iters = 0_usize;
600        let mut total_iters = 0_usize;
601        let mut converged = false;
602        let mut last_step = DeviceResidentArrowStep {
603            delta_t: Array1::zeros(t_len),
604            delta_beta: Array1::zeros(p),
605            objective: current_objective,
606            gradient_norm: 0.0,
607            log_det_hessian: 0.0,
608            execution_path,
609        };
610
611        while total_iters < opts.max_iterations {
612            // Residual gradient r(z) = H z − g₀ becomes the system gradient,
613            // read off the operator apply already computed at this iterate.
614            self.residual_into(&mut residual, &base, &current_apply, &t);
615            let g_norm = arrow_system_gradient_norm(&residual);
616            let scale = 1.0 + iterate_norm(&t, &beta);
617            if g_norm / scale < opts.convergence_tolerance {
618                converged = true;
619                break;
620            }
621
622            let solve_start = std::time::Instant::now();
623            let solution = match mode {
624                InnerSolveMode::DeviceResident => {
625                    // #2539: one two-level frame for the whole loop. An unchanged
626                    // ridge reuses the ridge-keyed factors and uploads only the
627                    // `O(n·d + p)` gradient; a ridge change re-factors from the
628                    // resident base blocks instead of re-packing and re-uploading
629                    // the slabs. Either failure becomes a Solve error so the
630                    // LM-escalation arm below grows the ridge and retries,
631                    // identical to a per-iterate solve failure.
632                    self.resident_step(
633                        &mut frames,
634                        &mut accounting,
635                        &residual,
636                        ridge_t,
637                        ridge_beta,
638                    )
639                }
640                InnerSolveMode::DeviceReupload => {
641                    // #1017 residency baseline: re-upload D/B/g and re-factor on
642                    // every iterate. Same GPU factor kernels as the resident path,
643                    // minus the across-iteration buffer/factor reuse — so EVERY
644                    // iterate creates handles, factorizes, launches, and re-uploads
645                    // the full slabs.
646                    gam_gpu::profile::telemetry_record_handle_creation(self.context_id());
647                    gam_gpu::profile::telemetry_record_factorization();
648                    gam_gpu::profile::telemetry_record_h2d(self.frame_upload_bytes());
649                    gam_gpu::profile::telemetry_record_kernel_launch();
650                    gam_gpu::profile::telemetry_record_d2h(
651                        (n * d + p) * std::mem::size_of::<f64>(),
652                    );
653                    solve_arrow_newton_step(&residual, ridge_t, ridge_beta, None)
654                        .map_err(map_gpu_error)
655                }
656                InnerSolveMode::CpuReference => {
657                    solve_arrow_newton_step_dense_reference(&residual, ridge_t, ridge_beta)
658                        .map_err(|reason| DeviceResidentArrowError::Solve { reason })
659                }
660                InnerSolveMode::CpuArrow => {
661                    cpu_arrow_newton_step(&residual, ridge_t, ridge_beta)
662                }
663            };
664
665            accounting.solve_seconds += solve_start.elapsed().as_secs_f64();
666            let solution = match solution {
667                Ok(sol) => sol,
668                Err(DeviceResidentArrowError::Solve { .. })
669                | Err(DeviceResidentArrowError::Unavailable { .. }) => {
670                    // LM escalation: grow ridge, retry without consuming an
671                    // iteration. Mirrors the production per-row/Schur PD-failure
672                    // arm in `LatentInnerSolver::solve`.
673                    ridge_t = grow_ridge(ridge_t, opts.lm_grow);
674                    ridge_beta = grow_ridge(ridge_beta, opts.lm_grow);
675                    if ridge_t > opts.max_ridge || ridge_beta > opts.max_ridge {
676                        return Err(DeviceResidentArrowError::Solve {
677                            reason: format!(
678                                "SAE resident inner loop: LM ridge exceeded max ({:e}) at iter {total_iters}",
679                                opts.max_ridge
680                            ),
681                        });
682                    }
683                    total_iters += 1;
684                    continue;
685                }
686                Err(other) => return Err(other),
687            };
688
689            // Predicted reduction from the bare quadratic model on the residual
690            // system, identical formula to the production trust-region ratio.
691            let predicted_reduction = crate::arrow_schur::arrow_bare_quadratic_model_reduction(
692                &residual,
693                solution.delta_t.view(),
694                solution.delta_beta.view(),
695                ridge_t,
696                ridge_beta,
697            )
698            .map_err(|err| DeviceResidentArrowError::Solve {
699                reason: format!("SAE resident inner loop predicted-reduction failed: {err}"),
700            })?;
701
702            // Trial iterate.
703            let mut trial_t = t.clone();
704            let mut trial_beta = beta.clone();
705            for (slot, dv) in trial_t.iter_mut().zip(solution.delta_t.iter()) {
706                *slot += *dv;
707            }
708            for (slot, dv) in trial_beta.iter_mut().zip(solution.delta_beta.iter()) {
709                *slot += *dv;
710            }
711            let trial_apply = accounting.apply(self, mode, &trial_t, &trial_beta);
712            let trial_objective = self.objective_from_apply(
713                &base,
714                half_target_energy,
715                &trial_apply,
716                &trial_t,
717                &trial_beta,
718            );
719
720            // Trust-region gain-ratio noise floor keyed to the objective's own
721            // magnitude, mirroring the production `LatentInnerSolver` (#1127): the
722            // floor must be equivariant under a response rescaling `y → a·y` (the
723            // penalized objective and both reductions scale as `O(a²)`). The
724            // previous `.max(1.0)` absolute floor broke this — near a converged
725            // iterate it pinned the floor at `1e-14` while a genuine refining
726            // step's `predicted_reduction` was `O(a²)`, misclassifying the real
727            // step as numerical noise and stalling the inner solve at a
728            // non-stationary point. A perfectly converged objective
729            // (`current_objective == 0`) yields a `0` floor, so the
730            // `predicted_reduction > 0` branch still governs and no step is lost.
731            let objective_scale = current_objective.abs();
732            let noise_floor = objective_scale * 1e-14;
733            let actual_reduction = current_objective - trial_objective;
734            let rho = if predicted_reduction > noise_floor {
735                actual_reduction / predicted_reduction
736            } else if actual_reduction >= -noise_floor {
737                1.0
738            } else {
739                -1.0
740            };
741
742            if rho > 0.0 && trial_objective.is_finite() {
743                t = trial_t;
744                beta = trial_beta;
745                current_apply = trial_apply;
746                current_objective = trial_objective;
747                ridge_t = (ridge_t * opts.lm_shrink).max(0.0);
748                ridge_beta = (ridge_beta * opts.lm_shrink).max(0.0);
749                last_step = DeviceResidentArrowStep {
750                    delta_t: solution.delta_t,
751                    delta_beta: solution.delta_beta,
752                    objective: current_objective,
753                    gradient_norm: g_norm,
754                    log_det_hessian: solution.log_det_hessian,
755                    execution_path,
756                };
757                accepted_iters += 1;
758                total_iters += 1;
759            } else {
760                ridge_t = grow_ridge(ridge_t, opts.lm_grow);
761                ridge_beta = grow_ridge(ridge_beta, opts.lm_grow);
762                if ridge_t > opts.max_ridge || ridge_beta > opts.max_ridge {
763                    return Err(DeviceResidentArrowError::Solve {
764                        reason: format!(
765                            "SAE resident inner loop: LM rejected step until ridge exceeded max ({:e}) at iter {total_iters} (rho={rho:.3e})",
766                            opts.max_ridge
767                        ),
768                    });
769                }
770                total_iters += 1;
771            }
772        }
773
774        Ok(DeviceResidentInnerOutcome {
775            t: Array1::from_vec(t),
776            beta: Array1::from_vec(beta),
777            objective: current_objective,
778            gradient_norm: last_step.gradient_norm,
779            log_det_hessian: last_step.log_det_hessian,
780            iterations: total_iters,
781            accepted_iterations: accepted_iters,
782            converged,
783            execution_path,
784            residency: accounting.finish(),
785        })
786    }
787
788    // ---------------------------------------------------------------------
789    // Phase 3b: reuse the resident frame ACROSS OUTER iterations (#1017
790    // deliverable 3).
791    //
792    // The inner Newton loop above already keeps the resident Arrow frame
793    // (factored `D`/`B`/Schur) on the device across INNER iterations at a fixed
794    // ridge. The next residency tier is the OUTER loop: across consecutive outer
795    // evaluations the SAE Hessian operator is unchanged whenever the frozen
796    // gate/basis frame (hence `D = H_tt`, `B = H_tβ`, border `H_ββ`) does not
797    // move — only the base gradient `g₀` (the linearization point / target
798    // residual) changes. In that regime the `O(n·d³ + p³)` factor work and the
799    // dominant `O(n·d·p)` `D`/`B` upload need to happen ONCE for the whole outer
800    // sweep, not once per outer. `device_fit_outer_sequence` realizes that: it
801    // builds at most ONE resident frame for an unchanged operator and drives
802    // every outer's inner solve through it, re-uploading only the per-outer
803    // `O(n·d + p)` gradient. The per-outer parity oracle is an independent
804    // `device_fit` (fresh frame per outer); the two must agree because sharing
805    // the factor across outers skips only re-deriving operator-independent work.
806    // ---------------------------------------------------------------------
807
808    /// Apply the resident bordered-quadratic operator at `z = (t, β)`.
809    ///
810    /// This is the ONE `O(n·d·p + p²)` evaluation per visited point; the
811    /// residual gradient AND the objective are both read off the returned
812    /// products (see [`ArrowOperatorApply`]), so the loop below evaluates it
813    /// once per trial iterate instead of walking the cross slab three times.
814    ///
815    /// Routing is magic-by-default and shape-driven: the workspace is
816    /// device-resident exactly when `route_through_gpu` admitted the slab
817    /// upload at construction, so a below-break-even shape simply never has
818    /// device buffers and takes the host arm. A device apply that faults falls
819    /// back to the host arm and is reported once per process — never a silent
820    /// downgrade.
821    fn apply_operator(
822        &self,
823        on_device: bool,
824        t: &[f64],
825        beta: &[f64],
826    ) -> (ArrowOperatorApply, OperatorApplyCost) {
827        #[cfg(target_os = "linux")]
828        {
829            if on_device && self.device.is_some() {
830                match self.apply_operator_device(t, beta) {
831                    Some(apply) => {
832                        let moved = (t.len() + beta.len()) * std::mem::size_of::<f64>();
833                        let returned = (t.len() + 2 * beta.len()) * std::mem::size_of::<f64>();
834                        gam_gpu::profile::telemetry_record_h2d(moved);
835                        gam_gpu::profile::telemetry_record_kernel_launch();
836                        gam_gpu::profile::telemetry_record_d2h(returned);
837                        return (
838                            apply,
839                            OperatorApplyCost {
840                                on_device: true,
841                                host_to_device_bytes: moved,
842                                device_to_host_bytes: returned,
843                            },
844                        );
845                    }
846                    None => gam_gpu::engagement::note_route_engagement(
847        "gam-solve sae_resident inner step",
848        "CPU reference",
849                        false,
850                        "resident operator apply faulted on device; host contraction fallback",
851                    ),
852                }
853            }
854        }
855        // Reaching here with `on_device` set means the device arm was asked for
856        // and not taken: the workspace holds no device buffers (below the
857        // upload break-even, or a runtime that never admitted the slab), the
858        // apply faulted, or this target has no CUDA at all. All three are the
859        // downgrade the doc comment above promises is never silent, so report
860        // it through the same once-per-process channel the fault path uses.
861        // This also consumes `on_device` on every target, which is why the
862        // parameter does not need — and must not have — an underscore.
863        if on_device {
864            gam_gpu::engagement::note_route_engagement(
865        "gam-solve sae_resident inner step",
866        "CPU reference",
867                false,
868                "resident operator apply requested the device arm but the workspace is not \
869                 device-resident; host contraction",
870            );
871        }
872        (
873            self.apply_operator_host(t, beta),
874            OperatorApplyCost::default(),
875        )
876    }
877
878    /// Host arm of [`Self::apply_operator`]: two sequential sweeps over the
879    /// flat row-major cross slab plus one over the border.
880    ///
881    /// Reading the slabs FLAT is the point. The `ArrowSchurSystem` view of the
882    /// same data indexes `rows[i].htbeta[[r, j]]`, so accumulating
883    /// `Σ_i Σ_r H_tβ^(i)[r,j]·t` with `j` outermost walks `n` separate `d×p`
884    /// allocations with stride `p` — `n·d·p` cache-missing loads. The flat form
885    /// touches every byte exactly once in address order.
886    ///
887    /// Parallelism is deterministic by construction: the per-row `cross_t` and
888    /// per-border-row `border_beta` entries are independent, and the one real
889    /// reduction (`cross_beta`) is folded from fixed-size row chunks in chunk
890    /// order, so the result does not depend on the thread count or scheduling.
891    fn apply_operator_host(&self, t: &[f64], beta: &[f64]) -> ArrowOperatorApply {
892        let n = self.shape.n;
893        let d = self.shape.d;
894        let p = self.shape.p;
895        let cross = self.slabs.row_cross_slabs.as_slice();
896        let border = self.slabs.border_hessian.as_slice();
897
898        // Chunk width depends only on `n`, so the reduction order is identical
899        // on every host and at every thread count.
900        let chunk_rows = n.div_ceil(OPERATOR_MAX_ROW_CHUNKS).max(OPERATOR_MIN_ROW_CHUNK);
901        let parallel = n >= OPERATOR_PARALLEL_ROW_MIN && rayon::current_thread_index().is_none();
902
903        let mut cross_t = vec![0.0_f64; n * d];
904        let mut cross_beta = vec![0.0_f64; p];
905        let row_chunk = |rows: std::ops::Range<usize>,
906                         cross_t_chunk: &mut [f64],
907                         cross_beta_partial: &mut [f64]| {
908            for (local, i) in rows.enumerate() {
909                for r in 0..d {
910                    let base = (i * d + r) * p;
911                    let slab = &cross[base..base + p];
912                    let mut acc = 0.0_f64;
913                    for (j, &value) in slab.iter().enumerate() {
914                        acc += value * beta[j];
915                    }
916                    cross_t_chunk[local * d + r] = acc;
917                    let weight = t[i * d + r];
918                    for (j, &value) in slab.iter().enumerate() {
919                        cross_beta_partial[j] += value * weight;
920                    }
921                }
922            }
923        };
924
925        if parallel {
926            use rayon::prelude::*;
927            let partials: Vec<Vec<f64>> = cross_t
928                .par_chunks_mut(chunk_rows * d)
929                .enumerate()
930                .map(|(chunk_idx, cross_t_chunk)| {
931                    let start = chunk_idx * chunk_rows;
932                    let end = (start + chunk_rows).min(n);
933                    let mut partial = vec![0.0_f64; p];
934                    row_chunk(start..end, cross_t_chunk, &mut partial);
935                    partial
936                })
937                .collect();
938            // Fold in chunk order: bit-identical to the sequential arm's chunk
939            // sequence regardless of how the chunks were scheduled.
940            for partial in &partials {
941                for (slot, &value) in cross_beta.iter_mut().zip(partial.iter()) {
942                    *slot += value;
943                }
944            }
945        } else {
946            let mut chunk_start = 0_usize;
947            while chunk_start < n {
948                let chunk_end = (chunk_start + chunk_rows).min(n);
949                let mut partial = vec![0.0_f64; p];
950                let slice = &mut cross_t[chunk_start * d..chunk_end * d];
951                row_chunk(chunk_start..chunk_end, slice, &mut partial);
952                for (slot, value) in cross_beta.iter_mut().zip(partial) {
953                    *slot += value;
954                }
955                chunk_start = chunk_end;
956            }
957        }
958
959        let border_row = |r: usize| -> f64 {
960            let row = &border[r * p..(r + 1) * p];
961            let mut acc = 0.0_f64;
962            for (c, &value) in row.iter().enumerate() {
963                acc += value * beta[c];
964            }
965            acc
966        };
967        let border_beta: Vec<f64> = if parallel {
968            use rayon::prelude::*;
969            (0..p).into_par_iter().map(border_row).collect()
970        } else {
971            (0..p).map(border_row).collect()
972        };
973
974        ArrowOperatorApply {
975            cross_t,
976            cross_beta,
977            border_beta,
978        }
979    }
980
981    /// Device arm of [`Self::apply_operator`]: three cuBLAS `dgemv` launches
982    /// against the ALREADY-RESIDENT cross slab and border, moving only the
983    /// `O(n·d + p)` iterate up and the `O(n·d + p)` products back. The
984    /// `O(n·d·p)` and `O(p²)` operands never cross the bus after upload.
985    #[cfg(target_os = "linux")]
986    fn apply_operator_device(&self, t: &[f64], beta: &[f64]) -> Option<ArrowOperatorApply> {
987        use cudarc::cublas::sys::cublasOperation_t;
988        use cudarc::cublas::{Gemv, GemvConfig};
989
990        let device = self.device.as_ref()?;
991        let rows = self.shape.n.checked_mul(self.shape.d)?;
992        let p = self.shape.p;
993        let rows_i = i32::try_from(rows).ok()?;
994        let p_i = i32::try_from(p).ok()?;
995        let mut guard = device.operator_scratch.lock().ok()?;
996        let scratch = &mut *guard;
997        device.stream.memcpy_htod(t, &mut scratch.t_dev).ok()?;
998        device
999            .stream
1000            .memcpy_htod(beta, &mut scratch.beta_dev)
1001            .ok()?;
1002
1003        // `row_cross_dev` holds the `(n·d)×p` cross slab ROW-major, which cuBLAS
1004        // reads as the `p×(n·d)` COLUMN-major matrix `Aᵀ` with `lda = p`. So
1005        // `A·β` is the transposed product and `Aᵀ·t` the untransposed one — no
1006        // repacking, no transpose copy.
1007        let cross_t_cfg = GemvConfig::<f64> {
1008            trans: cublasOperation_t::CUBLAS_OP_T,
1009            m: p_i,
1010            n: rows_i,
1011            alpha: 1.0,
1012            lda: p_i,
1013            incx: 1,
1014            beta: 0.0,
1015            incy: 1,
1016        };
1017        // SAFETY: `row_cross_dev` is the live `p×(n·d)` column-major slab with
1018        // `lda = p` uploaded at construction and validated against the shape;
1019        // `beta_dev` holds `p` entries (the `m` operand length under OP_T) and
1020        // `cross_t_dev` holds `n·d` (the `n` result length), both unit-stride.
1021        unsafe {
1022            device.blas.gemv(
1023                cross_t_cfg,
1024                &device.row_cross_dev,
1025                &scratch.beta_dev,
1026                &mut scratch.cross_t_dev,
1027            )
1028        }
1029        .ok()?;
1030
1031        let cross_beta_cfg = GemvConfig::<f64> {
1032            trans: cublasOperation_t::CUBLAS_OP_N,
1033            m: p_i,
1034            n: rows_i,
1035            alpha: 1.0,
1036            lda: p_i,
1037            incx: 1,
1038            beta: 0.0,
1039            incy: 1,
1040        };
1041        // SAFETY: same slab as above; under OP_N the operand length is `n·d`
1042        // (`t_dev`) and the result length is `p` (`cross_beta_dev`), unit-stride.
1043        unsafe {
1044            device.blas.gemv(
1045                cross_beta_cfg,
1046                &device.row_cross_dev,
1047                &scratch.t_dev,
1048                &mut scratch.cross_beta_dev,
1049            )
1050        }
1051        .ok()?;
1052
1053        // `border_hessian_dev` is the `p×p` border ROW-major, i.e. `H_ββᵀ`
1054        // column-major with `lda = p`; the transposed GEMV recovers `H_ββ·β`.
1055        let border_cfg = GemvConfig::<f64> {
1056            trans: cublasOperation_t::CUBLAS_OP_T,
1057            m: p_i,
1058            n: p_i,
1059            alpha: 1.0,
1060            lda: p_i,
1061            incx: 1,
1062            beta: 0.0,
1063            incy: 1,
1064        };
1065        // SAFETY: `border_hessian_dev` is the live `p×p` slab with `lda = p`;
1066        // `beta_dev` and `border_beta_dev` each hold `p` unit-stride entries.
1067        unsafe {
1068            device.blas.gemv(
1069                border_cfg,
1070                &device.border_hessian_dev,
1071                &scratch.beta_dev,
1072                &mut scratch.border_beta_dev,
1073            )
1074        }
1075        .ok()?;
1076
1077        let cross_t = device.stream.clone_dtoh(&scratch.cross_t_dev).ok()?;
1078        let cross_beta = device.stream.clone_dtoh(&scratch.cross_beta_dev).ok()?;
1079        let border_beta = device.stream.clone_dtoh(&scratch.border_beta_dev).ok()?;
1080        Some(ArrowOperatorApply {
1081            cross_t,
1082            cross_beta,
1083            border_beta,
1084        })
1085    }
1086
1087    /// Bordered-quadratic objective `½‖X‖² + ½ zᵀ H z − g₀ᵀ z` at the iterate
1088    /// whose operator apply is `apply`. Only the `O(n·d²)` per-row `H_tt`
1089    /// contraction and `O(n·d + p)` dots remain here — the heavy products were
1090    /// already formed (on device when admitted) by [`Self::apply_operator`].
1091    fn objective_from_apply(
1092        &self,
1093        base: &ArrowSchurSystem,
1094        half_target_energy: f64,
1095        apply: &ArrowOperatorApply,
1096        t: &[f64],
1097        beta: &[f64],
1098    ) -> f64 {
1099        let n = self.shape.n;
1100        let d = self.shape.d;
1101        let p = self.shape.p;
1102        // quad = zᵀ H z, lin = g₀ᵀ z.
1103        let mut quad = 0.0_f64;
1104        let mut lin = 0.0_f64;
1105        for i in 0..n {
1106            let t_base = i * d;
1107            for r in 0..d {
1108                let mut htt_t = 0.0_f64;
1109                for c in 0..d {
1110                    htt_t += base.rows[i].htt[[r, c]] * t[t_base + c];
1111                }
1112                quad += t[t_base + r] * (htt_t + 2.0 * apply.cross_t[t_base + r]);
1113                lin += base.rows[i].gt[r] * t[t_base + r];
1114            }
1115        }
1116        for r in 0..p {
1117            quad += beta[r] * apply.border_beta[r];
1118            lin += base.gb[r] * beta[r];
1119        }
1120        half_target_energy + 0.5 * quad - lin
1121    }
1122
1123    /// Overwrite `sys`'s gradients with the residual `r(z) = H z − g₀` at the
1124    /// iterate whose operator apply is `apply`, leaving every Hessian block (and
1125    /// the row-Hessian fingerprint, which those blocks alone determine) intact.
1126    ///
1127    /// The system is built ONCE per fit and mutated in place from here on. The
1128    /// old per-iteration `to_arrow_system()` rebuild re-materialised all `n`
1129    /// `d×p` cross blocks and the `p×p` border — 96 MiB of allocation and copy
1130    /// at the qwen shape — and then re-hashed them through
1131    /// `refresh_row_hessian_fingerprint` (documented as "intentionally
1132    /// expensive"), every single iterate, to reproduce values that cannot move
1133    /// while the frame is frozen.
1134    fn residual_into(
1135        &self,
1136        sys: &mut ArrowSchurSystem,
1137        base: &ArrowSchurSystem,
1138        apply: &ArrowOperatorApply,
1139        t: &[f64],
1140    ) {
1141        let n = self.shape.n;
1142        let d = self.shape.d;
1143        let p = self.shape.p;
1144        for i in 0..n {
1145            let t_base = i * d;
1146            for r in 0..d {
1147                let mut hz = 0.0_f64;
1148                for c in 0..d {
1149                    hz += base.rows[i].htt[[r, c]] * t[t_base + c];
1150                }
1151                hz += apply.cross_t[t_base + r];
1152                sys.rows[i].gt[r] = hz - base.rows[i].gt[r];
1153            }
1154        }
1155        for r in 0..p {
1156            sys.gb[r] = apply.border_beta[r] + apply.cross_beta[r] - base.gb[r];
1157        }
1158    }
1159
1160    /// One per-iterate device-resident Newton solve through the two-level frame
1161    /// (#2539).
1162    ///
1163    /// `residual` carries the CONSTANT Hessian blocks and the iterate's gradient:
1164    /// `residual_into` above writes `gt`/`gb` only, so `H_tt`/`H_tβ`/`H_ββ` are
1165    /// fixed for the whole loop and nothing about them needs to cross the bus more
1166    /// than once. Routing:
1167    ///
1168    ///   * ridge unchanged since the last solve — reuse the ridge-keyed FACTORS
1169    ///     (`L_i`, `Y_i`, `L_S`) sitting on top of the resident base blocks and
1170    ///     upload only the `O(n·d + p)` gradient. No POTRF runs. This is the
1171    ///     whole no-rejection trajectory and it must stay free: the two-level
1172    ///     split exists precisely so adopting the base frame for ridge changes
1173    ///     does not put a factor chain back on the accepted step.
1174    ///   * ridge changed — re-factor from the already-resident base blocks. Only
1175    ///     the two ridge scalars, the re-diagonalised `D` (`n·d·d`) and the
1176    ///     gradient cross to the device, in place of a host re-pack plus a full
1177    ///     re-upload of the `n·d×p` slabs.
1178    ///   * base frame unavailable (no CUDA, or a matrix-free `H_ββ`/`H_tβ`
1179    ///     operator the dense device path does not admit) — fall back to the
1180    ///     pre-#2539 ridge-keyed rebuild, so a host that cannot hold the base
1181    ///     blocks behaves exactly as it did.
1182    ///
1183    /// A numerical refusal (`RidgeBumpRequired` / `SchurFactorFailed`) is returned
1184    /// rather than retried on the other arm: the caller's LM arm grows the ridge
1185    /// and retries, which is what a failed frame build already did.
1186    ///
1187    /// `accounting` receives the FRAME-BUILD half of this call's cost separately
1188    /// from the gradient solve. The issue's headline number is a split — "~277 of
1189    /// 505 ms is frame build" — and no counter on the fit could express it, so the
1190    /// only way to check the residency claim was an external profiler on a card
1191    /// nobody could rerun. The three build regions timed here (base-block upload,
1192    /// on-device re-factor, legacy host-rebuild) are exactly the work residency is
1193    /// supposed to remove.
1194    fn resident_step(
1195        &self,
1196        frames: &mut SharedFrameState,
1197        accounting: &mut ResidencyAccounting,
1198        residual: &ArrowSchurSystem,
1199        ridge_t: f64,
1200        ridge_beta: f64,
1201    ) -> Result<ArrowSchurGpuSolution, DeviceResidentArrowError> {
1202        let n = self.shape.n;
1203        let d = self.shape.d;
1204        let p = self.shape.p;
1205        let mut g_t = Vec::with_capacity(n * d);
1206        for row in &residual.rows {
1207            for &v in row.gt.iter() {
1208                g_t.push(v);
1209            }
1210        }
1211        let g_beta: Vec<f64> = residual.gb.iter().copied().collect();
1212        let gradient_bytes = (g_t.len() + g_beta.len()) * std::mem::size_of::<f64>();
1213        let readback_bytes = (n * d + p) * std::mem::size_of::<f64>();
1214
1215        // Level 1: the resident base blocks. Built at most once per loop.
1216        if !frames.base_declined && frames.base.is_none() {
1217            let build_start = std::time::Instant::now();
1218            let built =
1219                crate::gpu_kernels::arrow_schur::ResidentBaseArrowFrameHandle::new(residual, None);
1220            accounting.frame_build_seconds += build_start.elapsed().as_secs_f64();
1221            match built {
1222                Ok(base) => {
1223                    gam_gpu::profile::telemetry_record_handle_creation(self.context_id());
1224                    gam_gpu::profile::telemetry_record_h2d(self.frame_upload_bytes());
1225                    frames.base_builds += 1;
1226                    accounting.base_builds += 1;
1227                    frames.base = Some(base);
1228                }
1229                Err(err) => {
1230                    frames.base_declined = true;
1231                    gam_gpu::engagement::note_route_engagement(
1232        "gam-solve sae_resident inner step",
1233        "CPU reference",
1234                        false,
1235                        &format!(
1236                            "SAE base-resident frame declined; ridge changes fall back to the \
1237                             host rebuild: {}",
1238                            map_gpu_error(err)
1239                        ),
1240                    );
1241                }
1242            }
1243        }
1244        if let Some(base) = frames.base.as_ref() {
1245            // Level 2: the ridge-keyed factors on top of those blocks. A hit is a
1246            // gradient-only solve — the accepted-step path, which must not pay a
1247            // POTRF.
1248            let factors_match = frames
1249                .base_keyed
1250                .as_ref()
1251                .is_some_and(|factors| factors.matches_ridge(ridge_t, ridge_beta));
1252            if !factors_match {
1253                // The ridge moved. Drop the stale factors before building the new
1254                // ones so a failed factor cannot leave a key naming device state
1255                // that no longer matches it.
1256                frames.base_keyed = None;
1257                let refactor_start = std::time::Instant::now();
1258                let factored = base.factor_at(ridge_t, ridge_beta);
1259                accounting.frame_build_seconds += refactor_start.elapsed().as_secs_f64();
1260                let factors = factored.map_err(map_gpu_error)?;
1261                frames.base_refactors += 1;
1262                accounting.base_refactors += 1;
1263                gam_gpu::profile::telemetry_record_factorization();
1264                gam_gpu::profile::telemetry_record_h2d(n * d * d * std::mem::size_of::<f64>());
1265                frames.base_keyed = Some(factors);
1266            } else {
1267                frames.base_gradient_solves += 1;
1268                accounting.base_gradient_solves += 1;
1269            }
1270            match frames.base_keyed.as_ref() {
1271                Some(factors) => {
1272                    gam_gpu::profile::telemetry_record_h2d(gradient_bytes);
1273                    gam_gpu::profile::telemetry_record_kernel_launch();
1274                    gam_gpu::profile::telemetry_record_d2h(readback_bytes);
1275                    return base
1276                        .solve_with_factors(factors, &g_t, &g_beta)
1277                        .map_err(map_gpu_error);
1278                }
1279                None => {
1280                    return Err(DeviceResidentArrowError::Solve {
1281                        reason: "SAE two-level frame lost its ridge-keyed factors after building \
1282                                 them"
1283                            .to_string(),
1284                    });
1285                }
1286            }
1287        }
1288
1289        // No base frame on this host: the pre-#2539 ridge-keyed rebuild, with its
1290        // own same-ridge reuse so a CPU-declined host keeps the behaviour it had.
1291        let keyed_matches = frames
1292            .keyed
1293            .as_ref()
1294            .is_some_and(|(rt, rb, _)| *rt == ridge_t && *rb == ridge_beta);
1295        if keyed_matches {
1296            match frames.keyed.as_ref() {
1297                Some((_, _, frame)) => {
1298                    gam_gpu::profile::telemetry_record_h2d(gradient_bytes);
1299                    gam_gpu::profile::telemetry_record_kernel_launch();
1300                    gam_gpu::profile::telemetry_record_d2h(readback_bytes);
1301                    return frame.solve_gradient(&g_t, &g_beta).map_err(map_gpu_error);
1302                }
1303                None => {
1304                    return Err(DeviceResidentArrowError::Solve {
1305                        reason: "SAE two-level frame lost its ridge-keyed frame after matching it"
1306                            .to_string(),
1307                    });
1308                }
1309            }
1310        }
1311        frames.keyed = None;
1312        let rebuild_start = std::time::Instant::now();
1313        let rebuilt = crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
1314            residual, ridge_t, ridge_beta, None,
1315        );
1316        accounting.frame_build_seconds += rebuild_start.elapsed().as_secs_f64();
1317        match rebuilt {
1318            Ok(frame) => {
1319                frames.frame_builds += 1;
1320                accounting.frame_builds += 1;
1321                gam_gpu::profile::telemetry_record_handle_creation(self.context_id());
1322                gam_gpu::profile::telemetry_record_factorization();
1323                gam_gpu::profile::telemetry_record_h2d(self.frame_upload_bytes());
1324                frames.keyed = Some((ridge_t, ridge_beta, frame));
1325            }
1326            Err(err) => return Err(map_gpu_error(err)),
1327        }
1328        match frames.keyed.as_ref() {
1329            Some((_, _, frame)) => {
1330                gam_gpu::profile::telemetry_record_h2d(gradient_bytes);
1331                gam_gpu::profile::telemetry_record_kernel_launch();
1332                gam_gpu::profile::telemetry_record_d2h(readback_bytes);
1333                frame.solve_gradient(&g_t, &g_beta).map_err(map_gpu_error)
1334            }
1335            None => Err(DeviceResidentArrowError::Solve {
1336                reason: "SAE resident frame build reported success without storing a frame"
1337                    .to_string(),
1338            }),
1339        }
1340    }
1341}
1342
1343/// Upper bound on the number of row chunks the host operator apply folds. Fixes
1344/// the `cross_beta` reduction tree (and hence the exact floating-point result)
1345/// as a function of `n` alone, and bounds the partial-buffer memory at
1346/// `OPERATOR_MAX_ROW_CHUNKS · p` regardless of row count.
1347const OPERATOR_MAX_ROW_CHUNKS: usize = 256;
1348
1349/// Smallest row chunk worth handing to a worker: below this the per-chunk
1350/// `p`-wide partial buffer costs more than the rows it folds.
1351const OPERATOR_MIN_ROW_CHUNK: usize = 64;
1352
1353/// Row count below which the host operator apply stays sequential — the fan-out
1354/// and the per-chunk partial allocation dominate the contraction itself.
1355const OPERATOR_PARALLEL_ROW_MIN: usize = 256;
1356
1357/// Where one [`ArrowOperatorApply`] ran and what it moved across the bus.
1358#[derive(Clone, Copy, Debug, Default)]
1359struct OperatorApplyCost {
1360    on_device: bool,
1361    host_to_device_bytes: usize,
1362    device_to_host_bytes: usize,
1363}
1364
1365/// Running residency accounting for one inner Newton loop.
1366///
1367/// The fit reports its OWN split rather than leaving it to an external profiler:
1368/// #1017's flat tier landed the same discipline (`device_refresh_columns`), and
1369/// #2393's utilization gate is unfalsifiable without it. `operator_seconds`
1370/// covers the `O(n·d·p + p²)` contraction (host or device), `solve_seconds`
1371/// covers the arrow factor/solve, and the byte counters are the ONLY traffic the
1372/// resident loop generates after the one-time slab upload.
1373#[derive(Default)]
1374struct ResidencyAccounting {
1375    operator_applies: usize,
1376    operator_device_applies: usize,
1377    operator_seconds: f64,
1378    solve_seconds: f64,
1379    frame_build_seconds: f64,
1380    frame_builds: usize,
1381    base_builds: usize,
1382    base_refactors: usize,
1383    base_gradient_solves: usize,
1384    host_to_device_bytes: usize,
1385    device_to_host_bytes: usize,
1386}
1387
1388impl ResidencyAccounting {
1389    fn apply(
1390        &mut self,
1391        workspace: &DeviceResidentArrowWorkspace,
1392        mode: InnerSolveMode,
1393        t: &[f64],
1394        beta: &[f64],
1395    ) -> ArrowOperatorApply {
1396        let start = std::time::Instant::now();
1397        let (apply, cost) = workspace.apply_operator(mode.operator_on_device(), t, beta);
1398        self.operator_seconds += start.elapsed().as_secs_f64();
1399        self.operator_applies += 1;
1400        if cost.on_device {
1401            self.operator_device_applies += 1;
1402        }
1403        self.host_to_device_bytes += cost.host_to_device_bytes;
1404        self.device_to_host_bytes += cost.device_to_host_bytes;
1405        apply
1406    }
1407
1408    fn finish(self) -> ResidencyReport {
1409        ResidencyReport {
1410            operator_applies: self.operator_applies,
1411            operator_device_applies: self.operator_device_applies,
1412            operator_seconds: self.operator_seconds,
1413            solve_seconds: self.solve_seconds,
1414            frame_build_seconds: self.frame_build_seconds,
1415            frame_builds: self.frame_builds,
1416            base_builds: self.base_builds,
1417            base_refactors: self.base_refactors,
1418            base_gradient_solves: self.base_gradient_solves,
1419            operator_host_to_device_bytes: self.host_to_device_bytes,
1420            operator_device_to_host_bytes: self.device_to_host_bytes,
1421        }
1422    }
1423}
1424
1425/// Honest per-fit residency accounting, reported BY the fit (#1017/#2393).
1426///
1427/// A resident inner Newton is only resident if the per-iterate traffic is
1428/// `O(n·d + p)` and the `O(n·d·p)` operands stay put; these counters make that
1429/// claim checkable from a run's own output instead of from a profiler that a
1430/// GPU-less future session cannot rerun.
1431#[derive(Clone, Copy, Debug, Default, PartialEq)]
1432pub struct ResidencyReport {
1433    /// Operator applies (`H·z` contractions) the loop performed.
1434    pub operator_applies: usize,
1435    /// How many of those ran on the device.
1436    pub operator_device_applies: usize,
1437    /// Wall seconds inside the operator applies.
1438    pub operator_seconds: f64,
1439    /// Wall seconds inside the per-iterate arrow factor/solve. Includes
1440    /// [`Self::frame_build_seconds`]: the build happens inside the solve region,
1441    /// and subtracting gives the gradient-only remainder.
1442    pub solve_seconds: f64,
1443    /// Wall seconds of the above spent BUILDING frames rather than solving with
1444    /// them (#2539) — base-block upload, on-device re-factor, and the legacy
1445    /// host re-pack + full re-upload. Residency's whole claim is that this
1446    /// shrinks to one base build per loop; without the split the claim is
1447    /// checkable only from an external profiler on a card nobody can rerun.
1448    pub frame_build_seconds: f64,
1449    /// Host re-pack + full re-upload frame builds — the pre-#2539 path. `0` is
1450    /// the residency contract on a CUDA host; a nonzero count means the base
1451    /// frame was declined and every ridge change re-crossed the bus.
1452    pub frame_builds: usize,
1453    /// Base-block uploads. At most `1` per inner loop.
1454    pub base_builds: usize,
1455    /// On-device re-factors from the resident base blocks — one per ridge change.
1456    pub base_refactors: usize,
1457    /// Iterates served from already-built factors with no POTRF: gradient up,
1458    /// step down, nothing else.
1459    pub base_gradient_solves: usize,
1460    /// Host→device bytes the operator applies moved (the iterate only).
1461    pub operator_host_to_device_bytes: usize,
1462    /// Device→host bytes the operator applies moved (the products only).
1463    pub operator_device_to_host_bytes: usize,
1464}
1465
1466impl ResidencyReport {
1467
1468}
1469
1470/// Options for the device-resident inner Newton loop. Defaults mirror the
1471/// production [`crate::latent_inner::LatentInnerOptions`] trust-region
1472/// schedule so device and CPU paths run identical host-side control flow.
1473#[derive(Clone, Copy, Debug)]
1474pub struct DeviceResidentInnerOptions {
1475    pub max_iterations: usize,
1476    pub convergence_tolerance: f64,
1477    pub initial_ridge_t: f64,
1478    pub initial_ridge_beta: f64,
1479    pub lm_grow: f64,
1480    pub lm_shrink: f64,
1481    pub max_ridge: f64,
1482}
1483
1484impl Default for DeviceResidentInnerOptions {
1485    fn default() -> Self {
1486        Self {
1487            max_iterations: 16,
1488            convergence_tolerance: 1e-9,
1489            initial_ridge_t: 0.0,
1490            initial_ridge_beta: 0.0,
1491            lm_grow: 4.0,
1492            lm_shrink: 0.5,
1493            max_ridge: 1e9,
1494        }
1495    }
1496}
1497
1498/// Result of the full device-resident inner Newton loop.
1499#[derive(Clone, Debug)]
1500pub struct DeviceResidentInnerOutcome {
1501    pub t: Array1<f64>,
1502    pub beta: Array1<f64>,
1503    pub objective: f64,
1504    pub gradient_norm: f64,
1505    pub log_det_hessian: f64,
1506    pub iterations: usize,
1507    pub accepted_iterations: usize,
1508    pub converged: bool,
1509    pub execution_path: ExecutionPath,
1510    /// Where this fit's per-iterate work ran and what it moved (#2393).
1511    pub residency: ResidencyReport,
1512}
1513
1514/// Result of an across-outer resident sweep (`DeviceResidentArrowWorkspace::device_fit_outer_sequence`).
1515///
1516/// `outers` holds one inner-loop outcome per outer evaluation, in input order.
1517/// `frame_builds` is the total number of resident-frame (re)builds performed
1518/// across the whole sweep: for an unchanged operator with a well-posed ridge it
1519/// is exactly `1` (the across-outer amortization #1017 deliverable 3 buys —
1520/// factor once, reuse the device factors for every outer), regardless of how
1521/// many outers ran. A value `> 1` means a per-outer ridge escalation forced a
1522/// refactor, which the parity oracle still matches but which costs the
1523/// amortization for those outers.
1524#[derive(Clone, Debug)]
1525pub struct OuterSequenceOutcome {
1526    pub outers: Vec<DeviceResidentInnerOutcome>,
1527    pub frame_builds: usize,
1528    /// #2539: base-block uploads across the sweep — at most `1`, because the
1529    /// ridge-independent `D`/`B`/`H_ββ` blocks are the same system for every
1530    /// outer at an unchanged operator.
1531    pub base_builds: usize,
1532    /// #2539: on-device re-factors from the resident base blocks. This is the
1533    /// count that used to arrive as `frame_builds`: before the two-level split a
1534    /// ridge change re-packed the blocks on the host and re-uploaded them, so
1535    /// every one of these was a full rebuild.
1536    pub base_refactors: usize,
1537    /// #2539: solves that hit the ridge-keyed factors and therefore ran NO
1538    /// factor work at all — only the gradient crossed the bus.
1539    ///
1540    /// This is the counter that separates the two-level frame from a plain
1541    /// base-frame adoption. A base frame used on its own re-factors on every
1542    /// iterate, so this stays `0` and `base_refactors` equals the iterate count;
1543    /// with the factors cached on top, an unchanged-ridge iterate lands here
1544    /// instead. A trajectory whose ridge never moves must show
1545    /// `base_refactors == 1`.
1546    pub base_gradient_solves: usize,
1547}
1548
1549/// The two-level device frame carried through one inner Newton loop — and, for
1550/// `device_fit_outer_sequence`, across every outer in the sweep (#1017
1551/// deliverable 3, #2539).
1552///
1553/// `keyed` is the original ridge-keyed frame: one `(ridge_t, ridge_beta)` baked
1554/// into its factors, serving cheap no-POTRF re-solves for a fresh gradient at
1555/// that same ridge. `base` holds the ridge-INDEPENDENT blocks (`D = H_tt`,
1556/// `B = H_tβ`, border `H_ββ`) resident and re-factors on-device at any ridge, so
1557/// the `n·d×p` slabs cross the bus once per loop rather than once per ridge
1558/// change.
1559///
1560/// The counters are integers, not timings, which is what makes the amortization
1561/// falsifiable on a contended card:
1562///
1563///   * `frame_builds` — host re-pack + full re-upload frame builds. `1` on an
1564///     all-accept trajectory at `initial_ridge_t = 0`, and after #2539 it should
1565///     stay `1` even when the loop rejects, because a ridge change no longer
1566///     routes here.
1567///   * `base_builds` — base-block uploads. At most `1` per loop.
1568///   * `base_refactors` — on-device re-factors from the resident base blocks.
1569///     This is the count that used to be `frame_builds`.
1570#[derive(Default)]
1571struct SharedFrameState {
1572    keyed: Option<(
1573        f64,
1574        f64,
1575        crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle,
1576    )>,
1577    base: Option<crate::gpu_kernels::arrow_schur::ResidentBaseArrowFrameHandle>,
1578    /// Set once the base frame construction has been refused on this host, so a
1579    /// loop that cannot hold the base blocks stops re-attempting them on every
1580    /// ridge change and takes the pre-#2539 rebuild instead.
1581    base_declined: bool,
1582    /// The ridge-keyed factors living ON TOP of `base` (#2539). A same-ridge
1583    /// iterate re-solves against these and runs no POTRF; a ridge change
1584    /// replaces them from blocks that never leave the device.
1585    base_keyed: Option<crate::gpu_kernels::arrow_schur::ResidentBaseRidgeFactorsHandle>,
1586    frame_builds: usize,
1587    base_builds: usize,
1588    base_refactors: usize,
1589    base_gradient_solves: usize,
1590}
1591
1592/// One production-structured Arrow-Schur Newton step on the HOST.
1593///
1594/// `gpu_policy = Off` is what makes this a CPU baseline rather than a device
1595/// path wearing a CPU label: `solve_arrow_newton_step_core` will otherwise
1596/// consult `try_device_arrow_direct` / `maybe_inject_gpu_schur_matvec` and route
1597/// part of the solve to the device on a CUDA host, which would silently make the
1598/// "CPU" side of a speedup ratio partly a GPU measurement.
1599fn cpu_arrow_newton_step(
1600    sys: &ArrowSchurSystem,
1601    ridge_t: f64,
1602    ridge_beta: f64,
1603) -> Result<crate::gpu_kernels::arrow_schur::ArrowSchurGpuSolution, DeviceResidentArrowError> {
1604    let mut options = crate::arrow_schur::ArrowSolveOptions::direct();
1605    options.gpu_policy = gam_gpu::GpuPolicy::Off;
1606    let (delta_t, delta_beta, _diagnostics) = sys
1607        .solve_with_options(ridge_t, ridge_beta, &options)
1608        .map_err(|err| DeviceResidentArrowError::Solve {
1609            reason: format!("SAE CPU arrow baseline step failed: {err}"),
1610        })?;
1611    Ok(crate::gpu_kernels::arrow_schur::ArrowSchurGpuSolution {
1612        delta_t,
1613        delta_beta,
1614        log_det_hessian: f64::NAN,
1615    })
1616}
1617
1618fn grow_ridge(current: f64, grow: f64) -> f64 {
1619    if current == 0.0 { 1e-6 } else { current * grow }
1620}
1621
1622fn arrow_system_gradient_norm(sys: &ArrowSchurSystem) -> f64 {
1623    let mut acc = 0.0_f64;
1624    for row in &sys.rows {
1625        for &v in row.gt.iter() {
1626            acc += v * v;
1627        }
1628    }
1629    for &v in sys.gb.iter() {
1630        acc += v * v;
1631    }
1632    acc.sqrt()
1633}
1634
1635fn iterate_norm(t: &[f64], beta: &[f64]) -> f64 {
1636    (squared_norm(t) + squared_norm(beta)).sqrt()
1637}
1638
1639fn validate_shape(
1640    shape: DeviceResidentArrowShape,
1641    target_x: &[f64],
1642    basis_values: &[f64],
1643    gate_activations: &[f64],
1644    slabs: &DeviceResidentArrowSlabs,
1645) -> Result<(), DeviceResidentArrowError> {
1646    let checks = [
1647        ("target_x", target_x.len(), shape.target_len()),
1648        ("basis_values", basis_values.len(), shape.basis_len()),
1649        (
1650            "gate_activations",
1651            gate_activations.len(),
1652            shape.basis_len(),
1653        ),
1654        (
1655            "row_hessian_slabs",
1656            slabs.row_hessian_slabs.len(),
1657            shape.row_hessian_len(),
1658        ),
1659        (
1660            "row_cross_slabs",
1661            slabs.row_cross_slabs.len(),
1662            shape.row_cross_len(),
1663        ),
1664        (
1665            "row_gradient_slabs",
1666            slabs.row_gradient_slabs.len(),
1667            shape.row_gradient_len(),
1668        ),
1669        (
1670            "border_hessian",
1671            slabs.border_hessian.len(),
1672            shape.border_hessian_len(),
1673        ),
1674        ("border_gradient", slabs.border_gradient.len(), shape.p),
1675    ];
1676    for (label, got, want) in checks {
1677        if got != want {
1678            return Err(DeviceResidentArrowError::Shape {
1679                reason: format!(
1680                    "SAE resident workspace shape mismatch for {label}: got {got}, expected {want}"
1681                ),
1682            });
1683        }
1684    }
1685    if shape.n == 0 || shape.p == 0 || shape.d == 0 || shape.basis_cols == 0 {
1686        return Err(DeviceResidentArrowError::Shape {
1687            reason: "SAE resident workspace requires nonzero n, p, basis_cols, and d".to_string(),
1688        });
1689    }
1690    Ok(())
1691}
1692
1693#[cfg(target_os = "linux")]
1694fn upload_resident_buffers(
1695    shape: DeviceResidentArrowShape,
1696    target_x: &[f64],
1697    basis_values: &[f64],
1698    gate_activations: &[f64],
1699    slabs: &DeviceResidentArrowSlabs,
1700) -> Option<DeviceResidentArrowBuffers> {
1701    use gam_gpu::linalg_dispatch::{DispatchOp, route_through_gpu};
1702
1703    // Admission is the batched `d×d` POTRF over the n row blocks, with the
1704    // reduced-Schur GEMM `p × p × (n·d)` — the op the frame build is actually
1705    // dominated by — as the alternate route.
1706    //
1707    // Keying admission on the GEMM ALONE was tried and rejected on measurement
1708    // (#2393): the A10 sweep appeared to show the resident path losing at
1709    // p=128 (0.56× vs the production host solve), which would have justified a
1710    // narrower gate, but the whole deficit was the FIRST device call in the
1711    // process paying cuBLAS/context warm-up inside the timed region — 39.5 ms
1712    // at p=128 against 0.5–2.5 ms steady-state at every larger border. Warmed,
1713    // the device wins at every border width measured, so a gate that excluded
1714    // small `p` would have been tuned to a startup artifact.
1715    let runtime = route_through_gpu(DispatchOp::SmallDenseBatchedPotrf {
1716        p: shape.d,
1717        batch: shape.n,
1718    })
1719    .or_else(|| {
1720        route_through_gpu(DispatchOp::Gemm {
1721            m: shape.p,
1722            n: shape.p,
1723            k: shape.n * shape.d,
1724        })
1725    })?;
1726    let ctx = gam_gpu::device_runtime::cuda_context_for(runtime.device.ordinal)?;
1727    let stream = ctx.new_stream().ok()?;
1728    let target_x_dev = stream.clone_htod(target_x).ok()?;
1729    let basis_values_dev = stream.clone_htod(basis_values).ok()?;
1730    let gate_activations_dev = stream.clone_htod(gate_activations).ok()?;
1731    let row_hessian_dev = stream.clone_htod(&slabs.row_hessian_slabs).ok()?;
1732    let row_cross_dev = stream.clone_htod(&slabs.row_cross_slabs).ok()?;
1733    let row_gradient_dev = stream.clone_htod(&slabs.row_gradient_slabs).ok()?;
1734    let border_hessian_dev = stream.clone_htod(&slabs.border_hessian).ok()?;
1735    let border_gradient_dev = stream.clone_htod(&slabs.border_gradient).ok()?;
1736    // One cuBLAS handle + one set of O(n·d + p) scratch buffers per workspace,
1737    // created with the resident slabs and reused for every operator apply.
1738    let blas = cudarc::cublas::CudaBlas::new(stream.clone()).ok()?;
1739    let rows_times_d = shape.n.checked_mul(shape.d)?;
1740    let operator_scratch = std::sync::Mutex::new(DeviceOperatorScratch {
1741        t_dev: stream.alloc_zeros::<f64>(rows_times_d).ok()?,
1742        beta_dev: stream.alloc_zeros::<f64>(shape.p).ok()?,
1743        cross_t_dev: stream.alloc_zeros::<f64>(rows_times_d).ok()?,
1744        cross_beta_dev: stream.alloc_zeros::<f64>(shape.p).ok()?,
1745        border_beta_dev: stream.alloc_zeros::<f64>(shape.p).ok()?,
1746    });
1747    let bytes = [
1748        target_x.len(),
1749        basis_values.len(),
1750        gate_activations.len(),
1751        slabs.row_hessian_slabs.len(),
1752        slabs.row_cross_slabs.len(),
1753        slabs.row_gradient_slabs.len(),
1754        slabs.border_hessian.len(),
1755        slabs.border_gradient.len(),
1756    ]
1757    .into_iter()
1758    .sum::<usize>()
1759        * std::mem::size_of::<f64>();
1760    Some(DeviceResidentArrowBuffers {
1761        stream,
1762        target_x_dev,
1763        basis_values_dev,
1764        gate_activations_dev,
1765        row_hessian_dev,
1766        row_cross_dev,
1767        row_gradient_dev,
1768        border_hessian_dev,
1769        border_gradient_dev,
1770        bytes,
1771        blas,
1772        operator_scratch,
1773    })
1774}
1775
1776fn map_gpu_error(err: ArrowSchurGpuFailure) -> DeviceResidentArrowError {
1777    match err {
1778        ArrowSchurGpuFailure::Unavailable => DeviceResidentArrowError::Unavailable {
1779            reason: "SAE resident inner iteration unavailable after GPU admission".to_string(),
1780        },
1781        ArrowSchurGpuFailure::RidgeBumpRequired { row, bump } => DeviceResidentArrowError::Solve {
1782            reason: format!("SAE resident inner iteration row {row} requires ridge bump {bump:e}"),
1783        },
1784        ArrowSchurGpuFailure::SchurFactorFailed { reason } => {
1785            DeviceResidentArrowError::Solve { reason }
1786        }
1787        ArrowSchurGpuFailure::GpuRequiresDenseSystem {
1788            had_hbb_matvec,
1789            had_htbeta_matvec,
1790        } => DeviceResidentArrowError::Solve {
1791            reason: format!(
1792                "SAE resident inner iteration requires dense slabs; hbb_matvec={had_hbb_matvec} htbeta_matvec={had_htbeta_matvec}"
1793            ),
1794        },
1795    }
1796}
1797
1798fn squared_norm(values: &[f64]) -> f64 {
1799    values.iter().map(|v| v * v).sum()
1800}
1801
1802impl From<ArrowSchurError> for DeviceResidentArrowError {
1803    fn from(err: ArrowSchurError) -> Self {
1804        Self::Solve {
1805            reason: err.to_string(),
1806        }
1807    }
1808}
1809
1810/// Deterministic qwen-scale non-gating fixture for the resident harness.
1811pub fn qwen_non_gating_fixture() -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1812    qwen_non_gating_fixture_seeded(0x1017_0003_D3A1_5EED)
1813}
1814
1815/// Seeded variant of [`qwen_non_gating_fixture`]. Distinct seeds produce
1816/// distinct-but-well-conditioned resident frames, used to build independent
1817/// replicate fits for the stream-multiplexing parity harness.
1818pub fn qwen_non_gating_fixture_seeded(
1819    seed: u64,
1820) -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1821    fixture_for_shape_seeded(DeviceResidentArrowShape::qwen_non_gating(), seed)
1822}
1823
1824/// Deterministic color-arm-scale resident fixture (n=180, p=5120) for the
1825/// #1017 GPU wall-clock bench: few rows, very wide border — the shape where the
1826/// per-iterate re-upload + re-factor that across-iteration residency eliminates
1827/// dominates.
1828pub fn color_arm_fixture() -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1829    fixture_for_shape_seeded(DeviceResidentArrowShape::color_arm(), 0x1017_C010_2A12_5EED)
1830}
1831
1832/// Build a well-conditioned resident frame for any `d == 2` shape. Both the
1833/// qwen and color-arm fixtures share this body; the conditioning (strong row
1834/// `H_tt` diagonals, tiny cross blocks, diagonally-dominant border) keeps the
1835/// dense reference factorisation PD so the parity harness is meaningful.
1836fn fixture_for_shape_seeded(
1837    shape: DeviceResidentArrowShape,
1838    seed: u64,
1839) -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1840    if shape.d == 0 {
1841        return Err(DeviceResidentArrowError::Shape {
1842            reason: "fixture_for_shape_seeded requires d >= 1".to_string(),
1843        });
1844    }
1845    let d = shape.d;
1846    let mut rng = SplitMix64::new(seed);
1847    let mut target_x = vec![0.0_f64; shape.target_len()];
1848    for i in 0..shape.n {
1849        for j in 0..shape.p {
1850            let phase = ((i % 97) as f64) * 0.013 + ((j % 131) as f64) * 0.007;
1851            target_x[i * shape.p + j] = 0.02 * phase.sin() + 0.001 * rng.sample_signed();
1852        }
1853    }
1854    let mut basis_values = vec![0.0_f64; shape.basis_len()];
1855    let mut gate_activations = vec![1.0_f64; shape.basis_len()];
1856    for i in 0..shape.n {
1857        for a in 0..shape.basis_cols {
1858            let phase = ((i + 1) as f64) * ((a + 1) as f64) * 0.003;
1859            basis_values[i * shape.basis_cols + a] = phase.cos();
1860            gate_activations[i * shape.basis_cols + a] = 1.0;
1861        }
1862    }
1863    let mut row_hessian_slabs = vec![0.0_f64; shape.row_hessian_len()];
1864    let mut row_cross_slabs = vec![0.0_f64; shape.row_cross_len()];
1865    let mut row_gradient_slabs = vec![0.0_f64; shape.row_gradient_len()];
1866    for i in 0..shape.n {
1867        let mut basis_sum = 0.0_f64;
1868        for a in 0..shape.basis_cols {
1869            basis_sum +=
1870                basis_values[i * shape.basis_cols + a] * gate_activations[i * shape.basis_cols + a];
1871        }
1872        // Strongly diagonally-dominant d×d H_tt (row-major): diagonal ≈ 3, tiny
1873        // symmetric off-diagonals — PD for any d so the dense reference factors.
1874        let h_base = i * d * d;
1875        for r in 0..d {
1876            for c in 0..d {
1877                let v = if r == c {
1878                    3.0 + 0.01 * basis_sum.abs() + 0.1 * (r as f64)
1879                } else {
1880                    0.02 * (basis_sum + (r + c) as f64).sin() / (d as f64)
1881                };
1882                row_hessian_slabs[h_base + r * d + c] = v;
1883            }
1884        }
1885        // Symmetrize the off-diagonals exactly.
1886        for r in 0..d {
1887            for c in 0..r {
1888                let avg = 0.5
1889                    * (row_hessian_slabs[h_base + r * d + c]
1890                        + row_hessian_slabs[h_base + c * d + r]);
1891                row_hessian_slabs[h_base + r * d + c] = avg;
1892                row_hessian_slabs[h_base + c * d + r] = avg;
1893            }
1894        }
1895        // d×p cross block (row-major) and length-d gradient.
1896        let b_base = i * d * shape.p;
1897        let g_base = i * d;
1898        for r in 0..d {
1899            for j in 0..shape.p {
1900                let feature = ((j % 257) as f64) * 0.011;
1901                row_cross_slabs[b_base + r * shape.p + j] =
1902                    1.0e-4 * (basis_sum + r as f64).sin() * feature.cos();
1903            }
1904            row_gradient_slabs[g_base + r] = 0.01 * (basis_sum + r as f64).sin();
1905        }
1906    }
1907    let mut border_hessian = vec![0.0_f64; shape.border_hessian_len()];
1908    for r in 0..shape.p {
1909        border_hessian[r * shape.p + r] = 4.0;
1910        if r + 1 < shape.p {
1911            border_hessian[r * shape.p + r + 1] = 0.01;
1912            border_hessian[(r + 1) * shape.p + r] = 0.01;
1913        }
1914    }
1915    let mut border_gradient = vec![0.0_f64; shape.p];
1916    for j in 0..shape.p {
1917        border_gradient[j] = 0.001 * ((j % 193) as f64 * 0.017).sin();
1918    }
1919    DeviceResidentArrowWorkspace::new(
1920        shape,
1921        target_x,
1922        basis_values,
1923        gate_activations,
1924        DeviceResidentArrowSlabs {
1925            row_hessian_slabs,
1926            row_cross_slabs,
1927            row_gradient_slabs,
1928            border_hessian,
1929            border_gradient,
1930        },
1931    )
1932}
1933
1934/// One multiplexed resident fit: the workspace plus the inner-loop outcome.
1935pub struct MultiplexedFit {
1936    pub outcome: DeviceResidentInnerOutcome,
1937}
1938
1939/// Phase 4: run `workspaces.len()` independent device-resident inner fits that
1940/// share one device.
1941///
1942/// # Stream-multiplexing safety argument
1943///
1944/// Each fit calls [`DeviceResidentArrowWorkspace::device_fit`], whose per-row
1945/// arrow solve (`solve_arrow_newton_step`) acquires the **process-shared**
1946/// `Arc<CudaContext>` via `device_runtime::cuda_context_for` (a `Mutex`-guarded
1947/// `OnceLock` cache) and then creates its **own** `CudaStream` with its own
1948/// cuSOLVER/cuBLAS handles and its own device allocations. Distinct streams off
1949/// one shared context execute concurrently on the device; the only shared
1950/// mutable state — the context cache and cudarc's allocator — is internally
1951/// synchronised, and no two fits touch the same stream, handle, or buffer. So
1952/// independent fits are data-race-free and the device serialises only where the
1953/// hardware must (shared SMs / copy engines), which is exactly the throughput
1954/// multiplexing the issue's Phase 4 calls for.
1955///
1956/// Concurrency is driven through `run_topology_race_parallel` (bac4af426),
1957/// which already bounds nested Rayon so each fit's internal `par_iter`/faer
1958/// parallelism stays inside its per-fit thread budget rather than oversubscribing
1959/// the global pool. Results are returned in input order. A single A100 thus hosts
1960/// many color-/qwen-arm fits at once — the cross-fit batch where the 1e5–1e6×
1961/// race speedup materialises.
1962///
1963/// The process-wide typed GPU availability cache and per-ordinal context
1964/// cache are warmed by constructing the resident workspaces (each `new` calls
1965/// the same probe), so the per-fit calls inside the Rayon scope only *read* the
1966/// already-initialised `OnceLock`s — they never trigger a `get_or_init` whose
1967/// closure does nested parallel work, avoiding the OnceLock×Rayon deadlock.
1968pub fn run_resident_fits_multiplexed(
1969    workspaces: Vec<DeviceResidentArrowWorkspace>,
1970    opts: DeviceResidentInnerOptions,
1971) -> Result<Vec<Result<MultiplexedFit, DeviceResidentArrowError>>, String> {
1972    run_resident_fits_multiplexed_with(workspaces, opts, |workspace, opts| {
1973        workspace.device_fit(opts)
1974    })
1975}
1976
1977/// Multiplexing core parameterised over the per-fit runner, so the CPU-reference
1978/// path can exercise the exact same `run_topology_race_parallel` plumbing as the
1979/// device path in tests that run without CUDA.
1980fn run_resident_fits_multiplexed_with<Run>(
1981    workspaces: Vec<DeviceResidentArrowWorkspace>,
1982    opts: DeviceResidentInnerOptions,
1983    run_one: Run,
1984) -> Result<Vec<Result<MultiplexedFit, DeviceResidentArrowError>>, String>
1985where
1986    Run: Fn(
1987            &DeviceResidentArrowWorkspace,
1988            &DeviceResidentInnerOptions,
1989        ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError>
1990        + Sync,
1991{
1992    let rows = crate::topology_selector::run_topology_race_parallel(
1993        workspaces,
1994        move |workspace: DeviceResidentArrowWorkspace| {
1995            run_one(&workspace, &opts).map(|outcome| MultiplexedFit { outcome })
1996        },
1997    )?;
1998    Ok(rows.into_iter().map(|row| row.result).collect())
1999}
2000
2001/// Sequential reference for the multiplexing parity harness: the same fits run
2002/// one after another on the same shared device. Multiplexed results must be
2003/// bit-identical to this because each fit's arithmetic is independent of the
2004/// others — sharing the device changes only scheduling, never the numbers.
2005pub fn run_resident_fits_sequential(
2006    workspaces: &[DeviceResidentArrowWorkspace],
2007    opts: &DeviceResidentInnerOptions,
2008) -> Vec<Result<MultiplexedFit, DeviceResidentArrowError>> {
2009    workspaces
2010        .iter()
2011        .map(|workspace| {
2012            workspace
2013                .device_fit(opts)
2014                .map(|outcome| MultiplexedFit { outcome })
2015        })
2016        .collect()
2017}
2018
2019// ---------------------------------------------------------------------------
2020// Phase 4 variant sweep (#1017): the OLMo research battery's independent-fit
2021// matrix (K × topology × basis × layer/checkpoint) dispatched concurrently on
2022// one device.
2023//
2024// Each variant is a SEPARATE fit with its OWN resident frame: the per-fit
2025// arithmetic is independent of the others, so multiplexing them onto one a100
2026// changes only scheduling, never the numbers. This is the cross-fit batch where
2027// the issue's 1e5–1e6× race throughput materialises — and unlike per-fit
2028// across-iteration residency it needs NO fixed-quadratic inner loop, because the
2029// parallelism is BETWEEN fits, not within one.
2030// ---------------------------------------------------------------------------
2031
2032/// One independent fit in the battery's variant sweep. The battery maps each
2033/// (K, topology, basis, layer, checkpoint, seed) cell of its matrix to a
2034/// `SweepVariant`; `dim` carries the resident-frame shape that cell produces
2035/// after the host assembles its row/border slabs. Distinct `seed`s keep the
2036/// fits genuinely independent (no shared device buffer, handle, or stream).
2037#[derive(Clone, Copy, Debug)]
2038pub struct SweepVariant {
2039    /// Resident-frame shape for this variant's frozen gate/basis frame.
2040    pub dim: DeviceResidentArrowShape,
2041    /// Deterministic seed for this variant's fixture/frame.
2042    pub seed: u64,
2043}
2044
2045/// Throughput summary for a multiplexed variant sweep on one device.
2046#[derive(Clone, Copy, Debug)]
2047pub struct SweepThroughput {
2048    pub fits: usize,
2049    pub succeeded: usize,
2050    pub wall_seconds: f64,
2051    /// Fits completed per wall-clock second on the single shared device.
2052    pub fits_per_second: f64,
2053}
2054
2055/// Build the independent resident workspaces for a variant sweep. Each variant
2056/// gets its own well-conditioned `d == 2` frame (the host feeds real slabs in
2057/// production; here the deterministic fixture stands in for the parity/throughput
2058/// harness). Returns the workspaces in variant order.
2059pub fn build_sweep_workspaces(
2060    variants: &[SweepVariant],
2061) -> Result<Vec<DeviceResidentArrowWorkspace>, DeviceResidentArrowError> {
2062    variants
2063        .iter()
2064        .map(|v| fixture_for_shape_seeded(v.dim, v.seed))
2065        .collect()
2066}
2067
2068/// Dispatch a variant sweep concurrently on one device and measure cross-fit
2069/// throughput. Returns the per-variant outcomes (in variant order) and the
2070/// throughput summary (fits/sec on the single shared a100). Per-fit certified
2071/// parity is asserted by [`assert_sweep_parity_vs_sequential`].
2072pub fn run_variant_sweep_multiplexed(
2073    variants: &[SweepVariant],
2074    opts: DeviceResidentInnerOptions,
2075) -> Result<
2076    (
2077        Vec<Result<MultiplexedFit, DeviceResidentArrowError>>,
2078        SweepThroughput,
2079    ),
2080    String,
2081> {
2082    let workspaces = build_sweep_workspaces(variants).map_err(|e| e.to_string())?;
2083    run_battery_sweep_multiplexed(workspaces, opts)
2084}
2085
2086/// Production battery entry (#1017 Phase 4): dispatch CALLER-ASSEMBLED resident
2087/// workspaces concurrently on one device and measure cross-fit throughput.
2088///
2089/// This is the real-slab seam the OLMo battery uses: the host (pyffi) builds one
2090/// [`DeviceResidentArrowWorkspace`] per matrix cell from the cell's ACTUAL SAE
2091/// row_hessian/row_cross/border slabs via [`DeviceResidentArrowWorkspace::new`],
2092/// then hands the workspaces here. Unlike [`run_variant_sweep_multiplexed`]
2093/// (which builds frames from the deterministic harness fixture), this consumes
2094/// real frames, so the printed throughput is the battery's true fits/sec on one
2095/// device. Returns per-cell outcomes (in input order) + the throughput summary.
2096pub fn run_battery_sweep_multiplexed(
2097    workspaces: Vec<DeviceResidentArrowWorkspace>,
2098    opts: DeviceResidentInnerOptions,
2099) -> Result<
2100    (
2101        Vec<Result<MultiplexedFit, DeviceResidentArrowError>>,
2102        SweepThroughput,
2103    ),
2104    String,
2105> {
2106    let fits = workspaces.len();
2107    let start = std::time::Instant::now();
2108    let results = run_resident_fits_multiplexed(workspaces, opts)?;
2109    let wall_seconds = start.elapsed().as_secs_f64();
2110    let succeeded = results.iter().filter(|r| r.is_ok()).count();
2111    let throughput = SweepThroughput {
2112        fits,
2113        succeeded,
2114        wall_seconds,
2115        fits_per_second: (fits as f64) / wall_seconds.max(1e-9),
2116    };
2117    Ok((results, throughput))
2118}
2119
2120/// The OLMo battery's full color-arm variant matrix as [`SweepVariant`]s:
2121/// `K{1..=4} × topology{4} × basis{periodic, linear}` at the color-arm shape
2122/// (n=180, p=5120). `d` and `basis_cols` follow the intrinsic-rank convention
2123/// (periodic ⇒ d=2, basis_cols=8; linear ⇒ d=1, basis_cols=2). Exposed so the
2124/// pyffi battery seam can quote cross-fit throughput on the real shape matrix
2125/// (fixture frames) before the per-cell real-slab fits are wired through.
2126#[must_use]
2127pub fn color_arm_variant_matrix() -> Vec<SweepVariant> {
2128    let topologies = ["euclidean", "circle", "torus", "sphere"];
2129    let mut variants = Vec::with_capacity(4 * topologies.len() * 2);
2130    for k in 1..=4u64 {
2131        for (t_idx, _topology) in topologies.iter().enumerate() {
2132            // periodic (2 harmonics) and linear basis arms.
2133            for &(d, basis_cols, basis_tag) in &[(2usize, 8usize, 0u64), (1usize, 2usize, 1u64)] {
2134                let mut dim = DeviceResidentArrowShape::color_arm();
2135                dim.d = d;
2136                dim.basis_cols = basis_cols;
2137                let seed = 0x1017_C010_0000_0000 ^ (k << 16) ^ ((t_idx as u64) << 8) ^ basis_tag;
2138                variants.push(SweepVariant { dim, seed });
2139            }
2140        }
2141    }
2142    variants
2143}
2144
2145/// Certified per-fit parity for a variant sweep: the multiplexed (concurrent)
2146/// results must be bit-for-bit identical to the same fits run sequentially on
2147/// the same device, because independent fits' arithmetic does not depend on
2148/// scheduling. Returns the sequential throughput so the caller can report the
2149/// multiplex speedup (multiplexed fits/sec ÷ sequential fits/sec). Returns an
2150/// `Err` describing the first divergence so the harness fails loudly.
2151pub fn assert_sweep_parity_vs_sequential(
2152    variants: &[SweepVariant],
2153    opts: &DeviceResidentInnerOptions,
2154    multiplexed: &[Result<MultiplexedFit, DeviceResidentArrowError>],
2155) -> Result<SweepThroughput, String> {
2156    let workspaces = build_sweep_workspaces(variants).map_err(|e| e.to_string())?;
2157    let start = std::time::Instant::now();
2158    let sequential = run_resident_fits_sequential(&workspaces, opts);
2159    let wall_seconds = start.elapsed().as_secs_f64();
2160    if sequential.len() != multiplexed.len() {
2161        return Err(format!(
2162            "sweep parity: length mismatch seq={} mux={}",
2163            sequential.len(),
2164            multiplexed.len()
2165        ));
2166    }
2167    for (idx, (seq, mux)) in sequential.iter().zip(multiplexed.iter()).enumerate() {
2168        match (seq, mux) {
2169            (Ok(s), Ok(m)) => {
2170                if s.outcome.t.as_slice() != m.outcome.t.as_slice()
2171                    || s.outcome.beta.as_slice() != m.outcome.beta.as_slice()
2172                    || s.outcome.objective.to_bits() != m.outcome.objective.to_bits()
2173                {
2174                    return Err(format!(
2175                        "sweep parity: fit {idx} multiplexed result differs from sequential"
2176                    ));
2177                }
2178            }
2179            (Err(sequential), Err(multiplexed)) => {
2180                // Both paths refused. Parity is about agreement, and they agree
2181                // here, so this is not a parity failure - but a sweep where every
2182                // fit refuses would otherwise pass silently.
2183                log::debug!(
2184                    "sweep parity: fit {idx} refused on both paths (seq: {sequential}; mux: {multiplexed})"
2185                );
2186            }
2187            _ => {
2188                return Err(format!(
2189                    "sweep parity: fit {idx} success/failure disagrees seq-vs-mux"
2190                ));
2191            }
2192        }
2193    }
2194    let fits = variants.len();
2195    let succeeded = sequential.iter().filter(|r| r.is_ok()).count();
2196    Ok(SweepThroughput {
2197        fits,
2198        succeeded,
2199        wall_seconds,
2200        fits_per_second: (fits as f64) / wall_seconds.max(1e-9),
2201    })
2202}
2203
2204struct SplitMix64 {
2205    state: u64,
2206}
2207
2208impl SplitMix64 {
2209    const fn new(seed: u64) -> Self {
2210        Self { state: seed }
2211    }
2212
2213    fn next_u64(&mut self) -> u64 {
2214        gam_linalg::utils::splitmix64(&mut self.state)
2215    }
2216
2217    fn sample_signed(&mut self) -> f64 {
2218        let unit = (self.next_u64() >> 11) as f64 / ((1_u64 << 53) as f64);
2219        2.0 * unit - 1.0
2220    }
2221}
2222
2223#[cfg(test)]
2224mod tests {
2225    use super::*;
2226    use ndarray::Array2;
2227
2228    /// Build a small, strongly diagonally-dominant resident frame whose dense
2229    /// reference factorisation is well-conditioned. The objective minimiser is
2230    /// `z* = H^{-1} g₀`, which the inner loop must reach.
2231    fn small_fixture(seed: u64) -> DeviceResidentArrowWorkspace {
2232        // batch (n) = 8 clears the device dispatch floor
2233        // (`small_dense_batched_potrf_min_batch = 8`) so that on a CUDA host
2234        // `upload_resident_buffers` actually binds a device and
2235        // `device_resident()` is TRUE — otherwise the device-resident parity
2236        // branch of `device_resident_fit_matches_cpu_reference` is dead on real
2237        // GPU hardware (the route declines for batch < 8, so the test only ever
2238        // exercised the CPU-decline branch and never validated the device loop).
2239        let shape = DeviceResidentArrowShape {
2240            n: 8,
2241            p: 4,
2242            basis_cols: 2,
2243            d: 2,
2244        };
2245        let mut rng = SplitMix64::new(seed);
2246        let target_x = vec![0.0_f64; shape.target_len()];
2247        let basis_values = vec![0.5_f64; shape.basis_len()];
2248        let gate_activations = vec![1.0_f64; shape.basis_len()];
2249
2250        let mut row_hessian_slabs = vec![0.0_f64; shape.row_hessian_len()];
2251        let mut row_cross_slabs = vec![0.0_f64; shape.row_cross_len()];
2252        let mut row_gradient_slabs = vec![0.0_f64; shape.row_gradient_len()];
2253        for i in 0..shape.n {
2254            let h = i * shape.d * shape.d;
2255            row_hessian_slabs[h] = 5.0 + 0.1 * rng.sample_signed();
2256            row_hessian_slabs[h + 1] = 0.05 * rng.sample_signed();
2257            row_hessian_slabs[h + 2] = row_hessian_slabs[h + 1];
2258            row_hessian_slabs[h + 3] = 4.0 + 0.1 * rng.sample_signed();
2259            let b = i * shape.d * shape.p;
2260            for j in 0..shape.p {
2261                row_cross_slabs[b + j] = 0.01 * rng.sample_signed();
2262                row_cross_slabs[b + shape.p + j] = 0.01 * rng.sample_signed();
2263            }
2264            let g = i * shape.d;
2265            row_gradient_slabs[g] = rng.sample_signed();
2266            row_gradient_slabs[g + 1] = rng.sample_signed();
2267        }
2268        let mut border_hessian = vec![0.0_f64; shape.border_hessian_len()];
2269        for r in 0..shape.p {
2270            border_hessian[r * shape.p + r] = 6.0 + 0.1 * rng.sample_signed();
2271        }
2272        let border_gradient: Vec<f64> = (0..shape.p).map(|_| rng.sample_signed()).collect();
2273
2274        DeviceResidentArrowWorkspace::new(
2275            shape,
2276            target_x,
2277            basis_values,
2278            gate_activations,
2279            DeviceResidentArrowSlabs {
2280                row_hessian_slabs,
2281                row_cross_slabs,
2282                row_gradient_slabs,
2283                border_hessian,
2284                border_gradient,
2285            },
2286        )
2287        .expect("small resident fixture must validate")
2288    }
2289
2290    /// Dense `H z` for the resident frame (independent of the arrow path),
2291    /// used to confirm the inner-loop fixed point is the true stationary point.
2292    fn dense_hz(
2293        ws: &DeviceResidentArrowWorkspace,
2294        sys: &ArrowSchurSystem,
2295    ) -> (Array2<f64>, Array1<f64>) {
2296        let shape = ws.shape;
2297        let total = shape.n * shape.d + shape.p;
2298        let mut h = Array2::<f64>::zeros((total, total));
2299        let mut g0 = Array1::<f64>::zeros(total);
2300        for i in 0..shape.n {
2301            let base = i * shape.d;
2302            for r in 0..shape.d {
2303                for c in 0..shape.d {
2304                    h[[base + r, base + c]] = sys.rows[i].htt[[r, c]];
2305                }
2306                for c in 0..shape.p {
2307                    let v = sys.rows[i].htbeta[[r, c]];
2308                    h[[base + r, shape.n * shape.d + c]] = v;
2309                    h[[shape.n * shape.d + c, base + r]] = v;
2310                }
2311                g0[base + r] = sys.rows[i].gt[r];
2312            }
2313        }
2314        for r in 0..shape.p {
2315            for c in 0..shape.p {
2316                h[[shape.n * shape.d + r, shape.n * shape.d + c]] = sys.hbb[[r, c]];
2317            }
2318            g0[shape.n * shape.d + r] = sys.gb[r];
2319        }
2320        (h, g0)
2321    }
2322
2323    /// The fused operator apply must reproduce the dense `H·z` blockwise: it is
2324    /// the ONE contraction both the residual gradient and the objective are now
2325    /// read from, so an error here would corrupt every downstream quantity while
2326    /// still "converging".
2327    #[test]
2328    fn operator_apply_matches_dense_hessian_product() {
2329        let ws = small_fixture(0x2393_0A01);
2330        let sys = ws.to_arrow_system();
2331        let (h, _g0) = dense_hz(&ws, &sys);
2332        let shape = ws.shape;
2333        let t_len = shape.n * shape.d;
2334        let mut rng = SplitMix64::new(0x2393_0A02);
2335        let t: Vec<f64> = (0..t_len).map(|_| rng.sample_signed()).collect();
2336        let beta: Vec<f64> = (0..shape.p).map(|_| rng.sample_signed()).collect();
2337
2338        let apply = ws.apply_operator_host(&t, &beta);
2339
2340        // cross_t = H_tβ β (the t-rows of H·z minus the H_tt t part).
2341        let mut max_err = 0.0_f64;
2342        for row in 0..t_len {
2343            let mut expect = 0.0_f64;
2344            for (col, &b) in beta.iter().enumerate() {
2345                expect += h[[row, t_len + col]] * b;
2346            }
2347            max_err = max_err.max((expect - apply.cross_t[row]).abs());
2348        }
2349        // cross_beta = H_βt t, border_beta = H_ββ β.
2350        for col in 0..shape.p {
2351            let mut cross = 0.0_f64;
2352            for (row, &tv) in t.iter().enumerate() {
2353                cross += h[[t_len + col, row]] * tv;
2354            }
2355            max_err = max_err.max((cross - apply.cross_beta[col]).abs());
2356            let mut border = 0.0_f64;
2357            for (c, &b) in beta.iter().enumerate() {
2358                border += h[[t_len + col, t_len + c]] * b;
2359            }
2360            max_err = max_err.max((border - apply.border_beta[col]).abs());
2361        }
2362        assert!(
2363            max_err < 1e-12,
2364            "fused operator apply disagrees with the dense H·z blocks: max_abs_err={max_err:e}"
2365        );
2366    }
2367
2368    /// The residual written by `residual_into` must equal `H z − g₀` from the
2369    /// dense operator, and the objective read off the same apply must equal the
2370    /// dense bordered quadratic. This pins the algebra that replaced the
2371    /// per-iteration `to_arrow_system()` rebuild.
2372    #[test]
2373    fn residual_and_objective_from_apply_match_dense_quadratic() {
2374        let ws = small_fixture(0x2393_0B01);
2375        let base = ws.to_arrow_system();
2376        let (h, g0) = dense_hz(&ws, &base);
2377        let shape = ws.shape;
2378        let t_len = shape.n * shape.d;
2379        let total = t_len + shape.p;
2380        let mut rng = SplitMix64::new(0x2393_0B02);
2381        let t: Vec<f64> = (0..t_len).map(|_| rng.sample_signed()).collect();
2382        let beta: Vec<f64> = (0..shape.p).map(|_| rng.sample_signed()).collect();
2383        let z: Vec<f64> = t.iter().chain(beta.iter()).copied().collect();
2384
2385        let apply = ws.apply_operator_host(&t, &beta);
2386        let mut residual = ws.to_arrow_system();
2387        ws.residual_into(&mut residual, &base, &apply, &t);
2388
2389        let mut max_err = 0.0_f64;
2390        for row in 0..total {
2391            let mut hz = 0.0_f64;
2392            for (col, &value) in z.iter().enumerate() {
2393                hz += h[[row, col]] * value;
2394            }
2395            let want = hz - g0[row];
2396            let got = if row < t_len {
2397                residual.rows[row / shape.d].gt[row % shape.d]
2398            } else {
2399                residual.gb[row - t_len]
2400            };
2401            max_err = max_err.max((want - got).abs());
2402        }
2403        assert!(
2404            max_err < 1e-12,
2405            "residual_into disagrees with dense H z − g₀: max_abs_err={max_err:e}"
2406        );
2407
2408        let half_target_energy = 0.5 * squared_norm(&ws.target_x);
2409        let mut quad = 0.0_f64;
2410        let mut lin = 0.0_f64;
2411        for (row, &zr) in z.iter().enumerate() {
2412            let mut hz = 0.0_f64;
2413            for (col, &zc) in z.iter().enumerate() {
2414                hz += h[[row, col]] * zc;
2415            }
2416            quad += zr * hz;
2417            lin += g0[row] * zr;
2418        }
2419        let want = half_target_energy + 0.5 * quad - lin;
2420        let got = ws.objective_from_apply(&base, half_target_energy, &apply, &t, &beta);
2421        let rel = (want - got).abs() / want.abs().max(1.0);
2422        assert!(
2423            rel < 1e-12,
2424            "objective_from_apply disagrees with the dense quadratic: want={want:e} got={got:e} rel={rel:e}"
2425        );
2426    }
2427
2428    /// The structured CPU baseline must land on the SAME iterate as the dense
2429    /// oracle. Without this the honest speedup denominator could be fast for the
2430    /// wrong reason (a different, cheaper problem).
2431    #[test]
2432    fn cpu_arrow_baseline_matches_dense_reference() {
2433        let ws = small_fixture(0x2393_0C01);
2434        let opts = DeviceResidentInnerOptions::default();
2435        let dense = ws
2436            .cpu_reference_fit(&opts)
2437            .expect("dense reference fit must succeed on the PD fixture");
2438        let arrow = ws
2439            .cpu_arrow_fit(&opts)
2440            .expect("structured CPU baseline must succeed on the PD fixture");
2441        assert_eq!(arrow.execution_path, ExecutionPath::Cpu);
2442        assert_eq!(arrow.residency.operator_device_applies, 0);
2443        assert!(arrow.residency.operator_applies > 0);
2444        let scale = dense
2445            .t
2446            .iter()
2447            .chain(dense.beta.iter())
2448            .fold(1.0_f64, |m, &v| m.max(v.abs()));
2449        let mut max_rel = 0.0_f64;
2450        for (a, b) in dense.t.iter().zip(arrow.t.iter()) {
2451            max_rel = max_rel.max((a - b).abs() / scale);
2452        }
2453        for (a, b) in dense.beta.iter().zip(arrow.beta.iter()) {
2454            max_rel = max_rel.max((a - b).abs() / scale);
2455        }
2456        assert!(
2457            max_rel < 1e-9,
2458            "structured CPU baseline solves a different problem than the dense oracle: max_rel={max_rel:e}"
2459        );
2460    }
2461
2462    /// The host operator apply must be independent of the thread count: its one
2463    /// real reduction folds fixed-size row chunks in chunk order, so a 1-thread
2464    /// and an 8-thread run are bit-identical. A scheduling-dependent result here
2465    /// would move the criterion ranking across topology candidates (#1017's
2466    /// verification gate).
2467    #[test]
2468    fn host_operator_apply_is_bit_identical_across_thread_counts() {
2469        let shape = DeviceResidentArrowShape {
2470            n: 600,
2471            p: 12,
2472            basis_cols: 2,
2473            d: 2,
2474        };
2475        let ws = fixture_for_shape_seeded(shape, 0x2393_0D01)
2476            .expect("sweep-shaped fixture must validate");
2477        let t_len = shape.n * shape.d;
2478        let mut rng = SplitMix64::new(0x2393_0D02);
2479        let t: Vec<f64> = (0..t_len).map(|_| rng.sample_signed()).collect();
2480        let beta: Vec<f64> = (0..shape.p).map(|_| rng.sample_signed()).collect();
2481
2482        // Outside a rayon pool this row count takes the PARALLEL arm.
2483        let outside = ws.apply_operator_host(&t, &beta);
2484        assert!(
2485            shape.n >= OPERATOR_PARALLEL_ROW_MIN,
2486            "fixture must be large enough to take the parallel arm"
2487        );
2488        // `rayon::join` runs both closures on pool workers, so the nested-rayon
2489        // guard sends the apply down its SEQUENTIAL arm. Same chunk partition,
2490        // same fold order ⇒ the values must match EXACTLY, not approximately.
2491        let (inside, ()) = rayon::join(|| ws.apply_operator_host(&t, &beta), || ());
2492        assert_eq!(outside.cross_t, inside.cross_t);
2493        assert_eq!(outside.cross_beta, inside.cross_beta);
2494        assert_eq!(outside.border_beta, inside.border_beta);
2495    }
2496
2497    #[test]
2498    fn cpu_inner_loop_reaches_quadratic_minimiser() {
2499        let ws = small_fixture(0xABCD_0001);
2500        let opts = DeviceResidentInnerOptions::default();
2501        let outcome = ws.cpu_reference_fit(&opts).expect("cpu fit");
2502        assert!(
2503            outcome.converged,
2504            "inner loop must converge on a PD quadratic"
2505        );
2506
2507        // The stationary point satisfies H z* = g₀; verify the residual is zero.
2508        let base = ws.to_arrow_system();
2509        let (h, g0) = dense_hz(&ws, &base);
2510        let total = ws.shape.n * ws.shape.d + ws.shape.p;
2511        let mut z = Array1::<f64>::zeros(total);
2512        for r in 0..ws.shape.n * ws.shape.d {
2513            z[r] = outcome.t[r];
2514        }
2515        for c in 0..ws.shape.p {
2516            z[ws.shape.n * ws.shape.d + c] = outcome.beta[c];
2517        }
2518        let hz = h.dot(&z);
2519        let mut max_resid = 0.0_f64;
2520        for r in 0..total {
2521            max_resid = max_resid.max((hz[r] - g0[r]).abs());
2522        }
2523        assert!(
2524            max_resid < 1e-9,
2525            "inner loop fixed point must solve H z = g0; residual {max_resid:e}"
2526        );
2527    }
2528
2529    #[test]
2530    fn cpu_multiplex_matches_sequential_bit_identical() {
2531        let seeds = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
2532        let opts = DeviceResidentInnerOptions::default();
2533
2534        let seq_workspaces: Vec<_> = seeds.iter().map(|&s| small_fixture(s)).collect();
2535        let sequential: Vec<_> = seq_workspaces
2536            .iter()
2537            .map(|ws| ws.cpu_reference_fit(&opts).expect("seq cpu fit"))
2538            .collect();
2539
2540        let mux_workspaces: Vec<_> = seeds.iter().map(|&s| small_fixture(s)).collect();
2541        let multiplexed = run_resident_fits_multiplexed_with(mux_workspaces, opts, |ws, opts| {
2542            ws.cpu_reference_fit(opts)
2543        })
2544        .expect("multiplexed cpu fits");
2545
2546        assert_eq!(sequential.len(), multiplexed.len());
2547        for (seq, mux) in sequential.iter().zip(multiplexed.iter()) {
2548            let mux = mux.as_ref().expect("mux fit ok");
2549            // Independent fits: scheduling cannot change the numbers, so the
2550            // parallel result must be bit-for-bit identical to sequential.
2551            assert_eq!(seq.t.as_slice(), mux.outcome.t.as_slice());
2552            assert_eq!(seq.beta.as_slice(), mux.outcome.beta.as_slice());
2553            assert_eq!(seq.objective.to_bits(), mux.outcome.objective.to_bits());
2554        }
2555    }
2556
2557    /// #1017 Phase 3 residency parity. On a CUDA host the device-resident inner
2558    /// loop (`device_fit`, which keeps the Hessian factors on-device across
2559    /// iterations via `ResidentArrowFrameHandle`) must reach the same minimiser
2560    /// as the fully independent CPU dense-reference loop (`cpu_reference_fit`,
2561    /// which re-factors per iterate). On a CPU-only host the resident path must
2562    /// decline cleanly (`Unavailable`) rather than silently disagree, and the
2563    /// resident-frame handle construction must likewise decline — so the gate is
2564    /// meaningful on the build box and the wall-clock arm runs on the GPU node.
2565    #[test]
2566    fn device_resident_fit_matches_cpu_reference() {
2567        let ws = small_fixture(0x5AE_1017);
2568        let opts = DeviceResidentInnerOptions::default();
2569
2570        // CPU reference (re-factors per iterate) — always available.
2571        let cpu = ws.cpu_reference_fit(&opts).expect("cpu reference fit");
2572        assert!(cpu.converged, "cpu reference must converge on PD quadratic");
2573
2574        let base = ws.to_arrow_system();
2575
2576        println!(
2577            "DIAG_RESIDENT device_resident={} shape=({},{},{})",
2578            ws.device_resident(),
2579            ws.shape.n,
2580            ws.shape.d,
2581            ws.shape.p
2582        );
2583        if ws.device_resident() {
2584            // Resident device loop: factors stay on-device across iterations.
2585            let dev = ws.device_fit(&opts).expect("device resident fit");
2586            assert_eq!(
2587                dev.execution_path,
2588                ExecutionPath::GpuResidentFull,
2589                "device_fit must report the full device-resident execution path"
2590            );
2591            assert!(dev.converged, "device resident loop must converge");
2592
2593            // Certified-refinement parity (#1014): the resident path and the
2594            // independent CPU path solve the same quadratic, so their minimisers
2595            // agree to a tight relative tolerance. The resident path differs from
2596            // the reference only by SKIPPING re-derivation of g-independent
2597            // factor work, not by changing the arithmetic.
2598            let t_scale = cpu.t.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2599            let b_scale = cpu.beta.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2600            let mut max_rel = 0.0_f64;
2601            for (a, b) in dev.t.iter().zip(cpu.t.iter()) {
2602                max_rel = max_rel.max((a - b).abs() / t_scale);
2603            }
2604            for (a, b) in dev.beta.iter().zip(cpu.beta.iter()) {
2605                max_rel = max_rel.max((a - b).abs() / b_scale);
2606            }
2607            assert!(
2608                max_rel < 1e-9,
2609                "resident device fit must match CPU reference (rel {max_rel:e})"
2610            );
2611
2612            // The public single-iteration API must use the same resident-frame
2613            // mechanism as device_fit (constant factors held resident, only the
2614            // gradient uploaded), not the re-uploading arrow-Schur entry. Because
2615            // it is a SINGLE solve at one frozen frame, the truthful path is
2616            // `GpuResidentLinearization`, not the full inner-loop `GpuResidentFull`.
2617            let one = ws
2618                .one_inner_iteration(opts.initial_ridge_t, opts.initial_ridge_beta)
2619                .expect("resident one_inner_iteration");
2620            assert_eq!(
2621                one.execution_path,
2622                ExecutionPath::GpuResidentLinearization,
2623                "one_inner_iteration must report resident single-linearization residency"
2624            );
2625
2626            // The resident frame's single-gradient solve must also match a full
2627            // independent solve at the same gradient (the per-iterate contract).
2628            // `ResidentArrowFrameHandle` is UNINHABITED on CPU-only hosts, so a
2629            // `let … .expect()` binding marks everything after it unreachable
2630            // under `-D warnings`; the consuming assertions therefore live
2631            // inside the `Ok` arm (dead match arms are lint-exempt), exactly
2632            // like the production consumers.
2633            match crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
2634                &base,
2635                opts.initial_ridge_t,
2636                opts.initial_ridge_beta,
2637                None,
2638            ) {
2639                Err(err) => panic!("resident frame must build on CUDA host: {err:?}"),
2640                Ok(frame) => {
2641                    let g_t: Vec<f64> = base
2642                        .rows
2643                        .iter()
2644                        .flat_map(|r| r.gt.iter().copied())
2645                        .collect();
2646                    let g_beta: Vec<f64> = base.gb.iter().copied().collect();
2647                    let resident_sol = frame
2648                        .solve_gradient(&g_t, &g_beta)
2649                        .expect("resident single-gradient solve");
2650                    let full =
2651                        crate::gpu_kernels::arrow_schur::solve_arrow_newton_step_dense_reference(
2652                            &base,
2653                            opts.initial_ridge_t,
2654                            opts.initial_ridge_beta,
2655                        )
2656                        .expect("dense reference single solve");
2657                    let mut max_step_rel = 0.0_f64;
2658                    let step_scale = full
2659                        .delta_t
2660                        .iter()
2661                        .chain(full.delta_beta.iter())
2662                        .fold(1.0_f64, |m, &v| m.max(v.abs()));
2663                    for (a, b) in resident_sol.delta_t.iter().zip(full.delta_t.iter()) {
2664                        max_step_rel = max_step_rel.max((a - b).abs() / step_scale);
2665                    }
2666                    for (a, b) in resident_sol.delta_beta.iter().zip(full.delta_beta.iter()) {
2667                        max_step_rel = max_step_rel.max((a - b).abs() / step_scale);
2668                    }
2669                    assert!(
2670                        max_step_rel < 1e-9,
2671                        "resident solve_gradient must match full dense reference step \
2672                         (rel {max_step_rel:e})"
2673                    );
2674                }
2675            }
2676
2677            // The re-uploading GPU loop (residency baseline) must reach the same
2678            // minimiser as both the resident loop and the CPU reference.
2679            let reup = ws
2680                .device_reupload_fit(&opts)
2681                .expect("device re-uploading fit");
2682            assert_eq!(
2683                reup.execution_path,
2684                ExecutionPath::GpuReupload,
2685                "device_reupload_fit must report the re-uploading device path"
2686            );
2687            assert!(reup.converged, "re-uploading loop must converge");
2688            let mut max_reup_rel = 0.0_f64;
2689            for (a, b) in reup.t.iter().zip(cpu.t.iter()) {
2690                max_reup_rel = max_reup_rel.max((a - b).abs() / t_scale);
2691            }
2692            for (a, b) in reup.beta.iter().zip(cpu.beta.iter()) {
2693                max_reup_rel = max_reup_rel.max((a - b).abs() / b_scale);
2694            }
2695            assert!(
2696                max_reup_rel < 1e-9,
2697                "re-uploading GPU fit must match CPU reference (rel {max_reup_rel:e})"
2698            );
2699        } else {
2700            // The fixture is sized (batch = 8) to clear the device dispatch floor,
2701            // so on a host WITH a CUDA runtime `device_resident()` must be true and
2702            // we take the device branch above. Reaching this branch with a runtime
2703            // present means the device binding silently failed — which would mask a
2704            // real upload/dispatch fault behind the CPU-decline path (the
2705            // device-PCG skip-pass class, eee12f6b2). Fail loud unless this is a
2706            // genuinely CPU-only host.
2707            assert!(
2708                gam_gpu::device_runtime::GpuRuntime::resolve(gam_gpu::GpuPolicy::Auto)
2709                    .unwrap_or_else(|error| {
2710                        panic!("GPU probe fault in resident SAE engagement test: {error}")
2711                    })
2712                    .is_none(),
2713                "device_resident() is false on a host WITH a CUDA runtime present, \
2714                 despite a floor-clearing fixture (batch=8): the resident device \
2715                 buffers failed to bind — a real device fault, not a CPU-only skip."
2716            );
2717            // CPU-only host: the resident path must decline, not disagree.
2718            let dev = ws.device_fit(&opts);
2719            assert!(
2720                matches!(dev, Err(DeviceResidentArrowError::Unavailable { .. })),
2721                "device_fit must report Unavailable on a CPU-only host, got {dev:?}"
2722            );
2723            let reup = ws.device_reupload_fit(&opts);
2724            assert!(
2725                matches!(reup, Err(DeviceResidentArrowError::Unavailable { .. })),
2726                "device_reupload_fit must report Unavailable on a CPU-only host, got {reup:?}"
2727            );
2728            let frame = crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
2729                &base,
2730                opts.initial_ridge_t,
2731                opts.initial_ridge_beta,
2732                None,
2733            );
2734            assert!(
2735                frame.is_err(),
2736                "resident frame construction must decline on a CPU-only host"
2737            );
2738        }
2739    }
2740
2741    /// #1017 fit-path parity (CPU-runnable). The resident inner solve and the
2742    /// PRODUCTION arrow-Schur inner solve (`solve_arrow_newton_step_core`, the
2743    /// entry the SAE joint fit reaches through `solve_with_lm_escalation_inner`)
2744    /// must solve the SAME bordered-quadratic Newton system.
2745    ///
2746    /// This is the cross-implementation parity behind wiring the device seam into
2747    /// the SAE inner loop: `solve_arrow_newton_step_core` carries the #1017
2748    /// device-Schur seam (and falls through bit-identically to its CPU path off
2749    /// CUDA), and the resident workspace's `cpu_reference_fit` converges the same
2750    /// quadratic `φ(z) = ½‖X‖² + ½ zᵀH z − g₀ᵀ z`. The resident converged iterate
2751    /// `z*` is the stationary point `H z* = g₀`; the production arrow path solves
2752    /// the Newton system `H Δ = −g₀` from `z = 0`, so its step is
2753    /// `Δ = −H⁻¹ g₀ = −z*`. With `H` PD the exact relationship is therefore
2754    /// `Δ = −z*`; asserting it pins that routing the production inner solve through
2755    /// the device-aware `_core` (which a GPU host then offloads) solves the
2756    /// identical system the resident loop does. Runs on the CPU build box — no
2757    /// CUDA required.
2758    #[test]
2759    fn resident_inner_solve_matches_production_arrow_core() {
2760        use crate::arrow_schur::{ArrowSolveOptions, solve_arrow_newton_step_core};
2761
2762        let ws = small_fixture(0x1017_F17);
2763        let opts = DeviceResidentInnerOptions::default();
2764
2765        // Resident workspace converged fit (re-factoring CPU reference loop).
2766        let resident = ws.cpu_reference_fit(&opts).expect("resident cpu fit");
2767        assert!(
2768            resident.converged,
2769            "resident reference must converge on the PD quadratic"
2770        );
2771
2772        // Production arrow path: one Newton step on the same system from z = 0.
2773        // `_core` is the device-aware entry; on this CPU box it runs the dense
2774        // CPU solve, the exact path the GPU host would fall back to on decline.
2775        let sys = ws.to_arrow_system();
2776        let (delta_t, delta_beta, _diag) = solve_arrow_newton_step_core(
2777            &sys,
2778            opts.initial_ridge_t,
2779            opts.initial_ridge_beta,
2780            &ArrowSolveOptions::direct(),
2781        )
2782        .expect("production arrow-core solve");
2783
2784        // The Newton step from z = 0 is Δ = −H⁻¹g₀ = −z*, where z* is the resident
2785        // converged iterate (H z* = g₀, the invariant
2786        // `cpu_inner_loop_reaches_quadratic_minimiser` pins directly). With H PD
2787        // the relationship is exact, so Δ + z* = 0 to factorisation tolerance.
2788        let t_scale = resident.t.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2789        let b_scale = resident.beta.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2790        // #1399: report the t-block and beta-block mismatch SEPARATELY (not one
2791        // fused scalar). The two halves localise a divergence: a t-block-only gap
2792        // points at the per-row factor / row gradient assembly, a beta-block gap
2793        // at the border Schur path — turning the opaque overall rel into an
2794        // actionable signal for the resident-vs-production parity divergence.
2795        let mut max_rel_t = 0.0_f64;
2796        let mut worst_t: Option<(usize, f64, f64)> = None;
2797        for (i, (prod, res)) in delta_t.iter().zip(resident.t.iter()).enumerate() {
2798            let rel = (prod + res).abs() / t_scale;
2799            if rel > max_rel_t {
2800                max_rel_t = rel;
2801                worst_t = Some((i, *prod, *res));
2802            }
2803        }
2804        let mut max_rel_b = 0.0_f64;
2805        let mut worst_b: Option<(usize, f64, f64)> = None;
2806        for (i, (prod, res)) in delta_beta.iter().zip(resident.beta.iter()).enumerate() {
2807            let rel = (prod + res).abs() / b_scale;
2808            if rel > max_rel_b {
2809                max_rel_b = rel;
2810                worst_b = Some((i, *prod, *res));
2811            }
2812        }
2813        let max_rel = max_rel_t.max(max_rel_b);
2814        assert!(
2815            max_rel < 1e-9,
2816            "production arrow-core Newton step must be −(resident converged fit) on \
2817             the same quadratic; wiring the device seam into the SAE inner loop must \
2818             not change the system being solved. rel_t={max_rel_t:e} (worst {worst_t:?}: \
2819             Δt+t* must be 0), rel_beta={max_rel_b:e} (worst {worst_b:?}: Δβ+β* must \
2820             be 0). A t-only gap implicates the per-row factor / row-gradient \
2821             assembly; a β-only gap the border Schur path."
2822        );
2823    }
2824
2825    /// #1017 residency-isolating per-solve bench. A full-fit wall-clock bench
2826    /// runs an exact quadratic that converges
2827    /// in ONE Newton step, so the resident frame is built once and solved once —
2828    /// the across-iteration amortization (factor `D`/`B`/Schur once, reuse for
2829    /// every gradient) has nothing to amortize over and the measured speedup is
2830    /// only the single-solve `D`/`B` upload saving.
2831    ///
2832    /// This bench isolates the residency lever the way the production inner loop
2833    /// actually exercises it: at a frozen gate/basis frame the Hessian blocks are
2834    /// CONSTANT and the SAE inner Newton takes MANY gradient solves against them.
2835    /// It therefore times
2836    ///   * RESIDENT: build the [`crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle`]
2837    ///     ONCE, then `N` `solve_gradient` calls (upload only the `O(n·d + k)`
2838    ///     gradient per solve; no POTRF, no `D`/`B` re-upload);
2839    ///   * REUPLOAD: `N` `solve_arrow_newton_step` calls (re-pack/upload `D`/`B`/`g`
2840    ///     and re-run the per-row POTRF + border Schur factor every call).
2841    /// Both produce bit-identical steps; the ratio is the pure across-iteration
2842    /// residency speedup, which is what #1017 Phase 3 buys per inner iteration.
2843    /// `N` mirrors a realistic SAE inner-Newton iteration count. CPU-only hosts
2844    /// print a skip line. Run with `--nocapture`.
2845    #[test]
2846    fn gpu_residency_per_solve_bench() {
2847        use std::time::Instant;
2848        const N_SOLVES: usize = 24;
2849        for (label, ws) in [
2850            ("color_arm", super::color_arm_fixture()),
2851            ("qwen_non_gating", super::qwen_non_gating_fixture()),
2852        ] {
2853            let ws = ws.expect("bench fixture must validate");
2854            let base = ws.to_arrow_system();
2855            // A family of distinct gradients standing in for the per-iterate
2856            // residual r(z) = H z − g₀ the inner loop feeds. Distinct gradients
2857            // make the reupload path redo the (g-independent) factor work each
2858            // time — exactly the waste residency removes.
2859            let n = ws.shape.n;
2860            let d = ws.shape.d;
2861            let p = ws.shape.p;
2862            let gradients: Vec<(Vec<f64>, Vec<f64>)> = (0..N_SOLVES)
2863                .map(|s| {
2864                    let g_t: Vec<f64> =
2865                        (0..n * d).map(|i| ((i + s) as f64 * 0.001).sin()).collect();
2866                    let g_beta: Vec<f64> = (0..p)
2867                        .map(|j| ((j + 7 * s) as f64 * 0.0007).cos())
2868                        .collect();
2869                    (g_t, g_beta)
2870                })
2871                .collect();
2872
2873            if !ws.device_resident() {
2874                println!(
2875                    "[#1017 per-solve {label}] no CUDA device — {N_SOLVES} solves skipped; \
2876                     run on the GPU node for the across-iteration residency speedup"
2877                );
2878                continue;
2879            }
2880
2881            // Build the resident frame ONCE (its factor cost is the across-
2882            // iteration amortization the bench is measuring, so it is timed
2883            // separately from the per-solve loop).
2884            let t_build = Instant::now();
2885            // `ResidentArrowFrameHandle` is UNINHABITED on CPU-only hosts: a
2886            // `let … .expect()` binding marks everything after it unreachable
2887            // under `-D warnings`, so the bench body consumes the frame inside
2888            // the `Ok` arm — the same pattern as the production consumers.
2889            match crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
2890                &base, 0.0, 0.0, None,
2891            ) {
2892                Err(err) => panic!("resident frame must build on CUDA host: {err:?}"),
2893                Ok(frame) => {
2894                    let frame_build_ms = t_build.elapsed().as_secs_f64() * 1e3;
2895
2896                    // Warm-up: one solve on each path before timing so the residency
2897                    // ratio reflects steady-state per-iterate cost, not the one-time
2898                    // NVRTC/cuSOLVER handle init, module JIT, or first-touch device
2899                    // allocation (those are paid once per process, not per inner
2900                    // iteration). The production inner loop pays them once and then runs
2901                    // MANY solves, which is exactly the regime this assertion guards.
2902                    frame
2903                        .solve_gradient(&gradients[0].0, &gradients[0].1)
2904                        .expect("resident warm-up solve");
2905                    {
2906                        let mut sys = ws.to_arrow_system();
2907                        for (i, row) in sys.rows.iter_mut().enumerate() {
2908                            for r in 0..d {
2909                                row.gt[r] = gradients[0].0[i * d + r];
2910                            }
2911                        }
2912                        for (j, gb) in sys.gb.iter_mut().enumerate() {
2913                            *gb = gradients[0].1[j];
2914                        }
2915                        sys.refresh_row_hessian_fingerprint();
2916                        crate::gpu_kernels::arrow_schur::solve_arrow_newton_step(
2917                            &sys, 0.0, 0.0, None,
2918                        )
2919                        .expect("reupload warm-up solve");
2920                    }
2921
2922                    // RESIDENT: reuse the (already-built, already-warmed) frame for N
2923                    // gradient-only solves. Times ONLY the per-iterate gradient solves —
2924                    // upload `O(n·d + k)` gradient, run the cheap residual path, read
2925                    // back `δ`. No POTRF, no `D`/`B` re-upload.
2926                    let t_res = Instant::now();
2927                    let mut resident_steps = Vec::with_capacity(N_SOLVES);
2928                    for (g_t, g_beta) in &gradients {
2929                        resident_steps.push(
2930                            frame
2931                                .solve_gradient(g_t, g_beta)
2932                                .expect("resident solve_gradient"),
2933                        );
2934                    }
2935                    let resident_ms = t_res.elapsed().as_secs_f64() * 1e3;
2936
2937                    // REUPLOAD: N full solves, each re-uploading D/B/g and re-factoring.
2938                    let t_reup = Instant::now();
2939                    let mut reupload_steps = Vec::with_capacity(N_SOLVES);
2940                    for (g_t, g_beta) in &gradients {
2941                        let mut sys = ws.to_arrow_system();
2942                        for (i, row) in sys.rows.iter_mut().enumerate() {
2943                            for r in 0..d {
2944                                row.gt[r] = g_t[i * d + r];
2945                            }
2946                        }
2947                        for (j, gb) in sys.gb.iter_mut().enumerate() {
2948                            *gb = g_beta[j];
2949                        }
2950                        sys.refresh_row_hessian_fingerprint();
2951                        reupload_steps.push(
2952                            crate::gpu_kernels::arrow_schur::solve_arrow_newton_step(
2953                                &sys, 0.0, 0.0, None,
2954                            )
2955                            .expect("reupload solve_arrow_newton_step"),
2956                        );
2957                    }
2958                    let reupload_ms = t_reup.elapsed().as_secs_f64() * 1e3;
2959
2960                    // Parity: resident and reupload steps must be bit-identical (same
2961                    // factor kernels; residency only skips re-deriving g-independent work).
2962                    let mut max_rel = 0.0_f64;
2963                    for (rs, us) in resident_steps.iter().zip(reupload_steps.iter()) {
2964                        let scale = us
2965                            .delta_t
2966                            .iter()
2967                            .chain(us.delta_beta.iter())
2968                            .fold(1.0_f64, |m, &v| m.max(v.abs()));
2969                        for (a, b) in rs.delta_t.iter().zip(us.delta_t.iter()) {
2970                            max_rel = max_rel.max((a - b).abs() / scale);
2971                        }
2972                        for (a, b) in rs.delta_beta.iter().zip(us.delta_beta.iter()) {
2973                            max_rel = max_rel.max((a - b).abs() / scale);
2974                        }
2975                    }
2976
2977                    let resident_per_solve = resident_ms / N_SOLVES as f64;
2978                    let reupload_per_solve = reupload_ms / N_SOLVES as f64;
2979                    let residency_speedup = reupload_ms / resident_ms.max(1e-9);
2980                    println!(
2981                        "[#1017 per-solve {label}] N={N_SOLVES} frame_build={frame_build_ms:.2}ms \
2982                 resident={resident_ms:.2}ms ({resident_per_solve:.3}ms/solve, \
2983                 grad-upload + warm factors) reupload={reupload_ms:.2}ms \
2984                 ({reupload_per_solve:.3}ms/solve, N factors + N D/B uploads) \
2985                 residency_speedup={residency_speedup:.2}x parity_rel={max_rel:e}"
2986                    );
2987                    assert!(
2988                        max_rel < 1e-9,
2989                        "{label}: resident per-solve steps must match reupload (rel {max_rel:e})"
2990                    );
2991
2992                    // #1017 deliverable 2: the residency amortization must actually fire
2993                    // on hardware — reusing the resident factors across iterations has to
2994                    // be STRICTLY cheaper per solve than re-uploading D/B/g and
2995                    // re-factoring every iterate. This is the core perf claim, asserted
2996                    // (not merely printed) so a regression that silently re-uploads, or a
2997                    // dispatch change that drops the resident path, fails the gate on the
2998                    // A100 instead of slipping through as a slower-but-green run.
2999                    //
3000                    // The `color_arm` shape (n=180, p=5120) is the decisive case: the
3001                    // per-solve reupload pays a 5120-wide border Schur factor + the
3002                    // `O(n·d·p)` cross-block upload every iterate, while the resident path
3003                    // pays only the `O(n·d + p)` gradient transfer and two border TRSMs.
3004                    // We require a clear >1.5x margin there. The `qwen_non_gating` shape
3005                    // (p=2048) has a smaller border so its margin is thinner; we still
3006                    // require a genuine speedup (>1x) but do not over-tighten it.
3007                    let min_speedup = if label == "color_arm" { 1.5 } else { 1.0 };
3008                    assert!(
3009                        residency_speedup > min_speedup,
3010                        "{label}: across-iteration residency must beat per-solve re-upload \
3011                 (residency_speedup={residency_speedup:.3}x, required >{min_speedup}x; \
3012                 resident {resident_per_solve:.3}ms/solve vs reupload \
3013                 {reupload_per_solve:.3}ms/solve over N={N_SOLVES} solves) — the resident \
3014                 frame either silently re-uploaded D/B or the dispatch dropped the \
3015                 amortized factor path"
3016                    );
3017                }
3018            }
3019        }
3020    }
3021
3022    /// #1017 Phase 4 variant sweep: an OLMo-battery-shaped matrix of independent
3023    /// fits (here K{1..4} × 3 basis widths = 12 color-arm variants) dispatched
3024    /// concurrently on one device. This is the cross-fit throughput lever — the
3025    /// fits are independent, so multiplexing changes only scheduling.
3026    fn battery_variant_matrix() -> Vec<super::SweepVariant> {
3027        let mut variants = Vec::new();
3028        // K is the topology rank; the battery races K{1..4}. Each K × basis cell
3029        // is an independent fit. Color-arm border, varied basis_cols per cell.
3030        for k in 1..=4u64 {
3031            for basis_cols in [4usize, 8, 12] {
3032                let mut dim = DeviceResidentArrowShape::color_arm();
3033                dim.basis_cols = basis_cols;
3034                variants.push(super::SweepVariant {
3035                    dim,
3036                    seed: 0x1017_0040_0000_0000 ^ (k << 8) ^ (basis_cols as u64),
3037                });
3038            }
3039        }
3040        variants
3041    }
3042
3043    /// Same K{1..4} × 3-basis battery matrix as [`battery_variant_matrix`], on
3044    /// a small border. The multiplex parity property is SCHEDULING invariance
3045    /// — it is independent of `p` — while the CPU reference oracle factors the
3046    /// full dense joint Hessian with a naive scalar Cholesky, O((n·d + p)³)
3047    /// per fit. At the color-arm border (p = 5120) that oracle alone cost the
3048    /// suite 67 minutes at 0% GPU (#1017 datum, 2026-07-20); at p = 256 the
3049    /// identical property costs sub-second. The wide border stays exercised on
3050    /// the device by [`gpu_multiplex_throughput_bench`], which keeps the full
3051    /// color-arm matrix.
3052    fn battery_variant_matrix_cpu_gate() -> Vec<super::SweepVariant> {
3053        battery_variant_matrix()
3054            .into_iter()
3055            .map(|mut variant| {
3056                variant.dim.p = 256;
3057                variant
3058            })
3059            .collect()
3060    }
3061
3062    /// Phase-4 parity: the multiplexed sweep must be bit-identical to running the
3063    /// same fits sequentially (CPU reference path here so the gate runs on the
3064    /// build box; the device path is exercised by the throughput bench on the a100).
3065    #[test]
3066    fn variant_sweep_multiplex_matches_sequential() {
3067        let variants = battery_variant_matrix_cpu_gate();
3068        let opts = DeviceResidentInnerOptions::default();
3069
3070        // Multiplexed via the CPU-reference runner so the gate is meaningful
3071        // without CUDA, exercising the exact run_topology_race_parallel plumbing.
3072        let workspaces =
3073            super::build_sweep_workspaces(&variants).expect("sweep workspaces must build");
3074        let multiplexed =
3075            super::run_resident_fits_multiplexed_with(workspaces, opts, |ws, opts| {
3076                ws.cpu_reference_fit(opts)
3077            })
3078            .expect("multiplexed cpu sweep");
3079
3080        let seq_workspaces =
3081            super::build_sweep_workspaces(&variants).expect("sweep workspaces must build");
3082        let sequential: Vec<_> = seq_workspaces
3083            .iter()
3084            .map(|ws| ws.cpu_reference_fit(&opts))
3085            .collect();
3086
3087        assert_eq!(multiplexed.len(), sequential.len());
3088        for (idx, (mux, seq)) in multiplexed.iter().zip(sequential.iter()).enumerate() {
3089            let mux = &mux.as_ref().unwrap().outcome;
3090            let seq = seq.as_ref().unwrap();
3091            assert_eq!(
3092                mux.t.as_slice(),
3093                seq.t.as_slice(),
3094                "variant {idx}: multiplexed t differs from sequential"
3095            );
3096            assert_eq!(
3097                mux.beta.as_slice(),
3098                seq.beta.as_slice(),
3099                "variant {idx}: multiplexed beta differs from sequential"
3100            );
3101            assert_eq!(
3102                mux.objective.to_bits(),
3103                seq.objective.to_bits(),
3104                "variant {idx}: multiplexed objective differs from sequential"
3105            );
3106        }
3107    }
3108
3109}