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, solve_arrow_newton_step, solve_arrow_newton_step_dense_reference,
14};
15use gam_problem::ExecutionPath;
16
17/// Per-iterate solve backend for the resident inner Newton loop.
18///
19/// All three modes run the IDENTICAL host control flow (`run_inner_loop`):
20/// residual-gradient assembly, LM trust-region accept/reject, ridge schedule.
21/// They differ ONLY in how the per-iterate arrow step is computed, which is
22/// exactly the residency lever #1017 measures:
23///
24/// * [`InnerSolveMode::DeviceResident`] — the Phase-3 fix: factor the constant
25///   Hessian blocks ONCE into a [`crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle`]
26///   and, every iterate, upload only the `O(n·d + k)` gradient and read back
27///   only `δ`. No per-solve D/B re-upload, no per-solve POTRF.
28/// * [`InnerSolveMode::DeviceReupload`] — the BEFORE path: call
29///   `solve_arrow_newton_step` per iterate, which re-packs and re-uploads
30///   `D`/`B`/`g` and re-runs the per-row POTRF + border Schur factor every call.
31///   This is the residency baseline the bench divides against.
32/// * [`InnerSolveMode::CpuReference`] — the dense f64 oracle (re-factors per
33///   iterate on the host), used for the correctness parity check.
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub enum InnerSolveMode {
36    DeviceResident,
37    DeviceReupload,
38    CpuReference,
39}
40
41impl InnerSolveMode {
42    /// Truthful [`ExecutionPath`] this solve mode realizes (issue #1017): the
43    /// resident loop keeps factors on-device (`GpuResidentFull`), the baseline
44    /// re-uploads/re-factors every iterate (`GpuReupload`), and the reference
45    /// path runs on the host (`Cpu`).
46    #[inline]
47    const fn execution_path(self) -> ExecutionPath {
48        match self {
49            Self::DeviceResident => ExecutionPath::GpuResidentFull,
50            Self::DeviceReupload => ExecutionPath::GpuReupload,
51            Self::CpuReference => ExecutionPath::Cpu,
52        }
53    }
54}
55use crate::arrow_schur::{ArrowSchurError, ArrowSchurSystem};
56
57/// SAE shape used by the resident inner-iteration workspace.
58///
59/// `p` is the target width and current shared-border width for this slice. The
60/// true SAE decoder has richer `(basis × output)` structure; slice 1 deliberately
61/// keeps that structure host-assembled into `row_cross_slabs` while preserving
62/// the qwen-scale target width in the Schur border.
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub struct DeviceResidentArrowShape {
65    pub n: usize,
66    pub p: usize,
67    pub basis_cols: usize,
68    pub d: usize,
69}
70
71impl DeviceResidentArrowShape {
72    #[inline]
73    pub const fn qwen_non_gating() -> Self {
74        Self {
75            n: 2_000,
76            p: 2_048,
77            basis_cols: 8,
78            d: 2,
79        }
80    }
81
82    /// Color-arm shape from the #1017 measured gap (n=180, p=5120, M≈9, K=1):
83    /// few rows, very wide border. The dense-Schur device path (cuSOLVER border
84    /// POTRF) handles the `p=5120` border that exceeds the fused-kernel `P_MAX`.
85    #[inline]
86    pub const fn color_arm() -> Self {
87        Self {
88            n: 180,
89            p: 5_120,
90            basis_cols: 9,
91            d: 2,
92        }
93    }
94
95    #[inline]
96    pub const fn target_len(self) -> usize {
97        self.n * self.p
98    }
99
100    #[inline]
101    pub const fn basis_len(self) -> usize {
102        self.n * self.basis_cols
103    }
104
105    #[inline]
106    pub const fn row_hessian_len(self) -> usize {
107        self.n * self.d * self.d
108    }
109
110    #[inline]
111    pub const fn row_cross_len(self) -> usize {
112        self.n * self.d * self.p
113    }
114
115    #[inline]
116    pub const fn row_gradient_len(self) -> usize {
117        self.n * self.d
118    }
119
120    #[inline]
121    pub const fn border_hessian_len(self) -> usize {
122        self.p * self.p
123    }
124}
125
126/// Host-fed row-block slabs for the first resident slice.
127///
128/// All matrices are row-major in host memory:
129/// * `row_hessian_slabs`: `n` slabs of shape `d × d`.
130/// * `row_cross_slabs`: `n` slabs of shape `d × p`.
131/// * `border_hessian`: one `p × p` shared block.
132#[derive(Clone, Debug)]
133pub struct DeviceResidentArrowSlabs {
134    pub row_hessian_slabs: Vec<f64>,
135    pub row_cross_slabs: Vec<f64>,
136    pub row_gradient_slabs: Vec<f64>,
137    pub border_hessian: Vec<f64>,
138    pub border_gradient: Vec<f64>,
139}
140
141/// Result of one resident SAE inner Newton iteration.
142#[derive(Clone, Debug)]
143pub struct DeviceResidentArrowStep {
144    pub delta_t: Array1<f64>,
145    pub delta_beta: Array1<f64>,
146    pub objective: f64,
147    pub gradient_norm: f64,
148    pub log_det_hessian: f64,
149    pub execution_path: ExecutionPath,
150}
151
152#[derive(Debug, Clone)]
153pub enum DeviceResidentArrowError {
154    Shape { reason: String },
155    Unavailable { reason: String },
156    Solve { reason: String },
157}
158
159impl std::fmt::Display for DeviceResidentArrowError {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        match self {
162            Self::Shape { reason } | Self::Unavailable { reason } | Self::Solve { reason } => {
163                f.write_str(reason)
164            }
165        }
166    }
167}
168
169impl std::error::Error for DeviceResidentArrowError {}
170
171#[cfg(target_os = "linux")]
172pub struct DeviceResidentArrowBuffers {
173    pub stream: std::sync::Arc<cudarc::driver::CudaStream>,
174    pub target_x_dev: cudarc::driver::CudaSlice<f64>,
175    pub basis_values_dev: cudarc::driver::CudaSlice<f64>,
176    pub gate_activations_dev: cudarc::driver::CudaSlice<f64>,
177    pub row_hessian_dev: cudarc::driver::CudaSlice<f64>,
178    pub row_cross_dev: cudarc::driver::CudaSlice<f64>,
179    pub row_gradient_dev: cudarc::driver::CudaSlice<f64>,
180    pub border_hessian_dev: cudarc::driver::CudaSlice<f64>,
181    pub border_gradient_dev: cudarc::driver::CudaSlice<f64>,
182    pub bytes: usize,
183}
184
185/// Upload-once workspace for the SAE data-fit Arrow-Schur inner iteration.
186pub struct DeviceResidentArrowWorkspace {
187    shape: DeviceResidentArrowShape,
188    target_x: Vec<f64>,
189    basis_values: Vec<f64>,
190    gate_activations: Vec<f64>,
191    slabs: DeviceResidentArrowSlabs,
192    #[cfg(target_os = "linux")]
193    device: Option<DeviceResidentArrowBuffers>,
194}
195
196impl DeviceResidentArrowWorkspace {
197    pub fn new(
198        shape: DeviceResidentArrowShape,
199        target_x: Vec<f64>,
200        basis_values: Vec<f64>,
201        gate_activations: Vec<f64>,
202        slabs: DeviceResidentArrowSlabs,
203    ) -> Result<Self, DeviceResidentArrowError> {
204        validate_shape(shape, &target_x, &basis_values, &gate_activations, &slabs)?;
205        #[cfg(target_os = "linux")]
206        let device =
207            upload_resident_buffers(shape, &target_x, &basis_values, &gate_activations, &slabs);
208        Ok(Self {
209            shape,
210            target_x,
211            basis_values,
212            gate_activations,
213            slabs,
214            #[cfg(target_os = "linux")]
215            device,
216        })
217    }
218
219    #[inline]
220    pub const fn shape(&self) -> DeviceResidentArrowShape {
221        self.shape
222    }
223
224    #[must_use]
225    pub fn device_resident(&self) -> bool {
226        #[cfg(target_os = "linux")]
227        {
228            self.device.is_some()
229        }
230        #[cfg(not(target_os = "linux"))]
231        {
232            false
233        }
234    }
235
236    #[must_use]
237    pub fn resident_device_bytes(&self) -> usize {
238        #[cfg(target_os = "linux")]
239        {
240            self.device.as_ref().map_or(0, |device| device.bytes)
241        }
242        #[cfg(not(target_os = "linux"))]
243        {
244            0
245        }
246    }
247
248    /// Opaque device-context identifier for telemetry: `1` when the resident
249    /// device buffers are live on this workspace, `0` when no device was bound.
250    /// Distinguishes "a device executed this fit" from "silent CPU fallback"
251    /// without leaking the cudarc handle.
252    #[must_use]
253    fn context_id(&self) -> usize {
254        usize::from(self.device_resident())
255    }
256
257    /// Bytes the re-uploading / frame-build path moves host→device for a full
258    /// `D`/`B`/`g`/border refresh, used to attribute H2D traffic in telemetry.
259    #[must_use]
260    fn frame_upload_bytes(&self) -> usize {
261        [
262            self.slabs.row_hessian_slabs.len(),
263            self.slabs.row_cross_slabs.len(),
264            self.slabs.row_gradient_slabs.len(),
265            self.slabs.border_hessian.len(),
266            self.slabs.border_gradient.len(),
267        ]
268        .into_iter()
269        .sum::<usize>()
270            * std::mem::size_of::<f64>()
271    }
272
273    #[must_use]
274    pub fn host_shadow_bytes(&self) -> usize {
275        [
276            self.target_x.len(),
277            self.basis_values.len(),
278            self.gate_activations.len(),
279            self.slabs.row_hessian_slabs.len(),
280            self.slabs.row_cross_slabs.len(),
281            self.slabs.row_gradient_slabs.len(),
282            self.slabs.border_hessian.len(),
283            self.slabs.border_gradient.len(),
284        ]
285        .into_iter()
286        .sum::<usize>()
287            * std::mem::size_of::<f64>()
288    }
289
290    /// Run one device-side Newton sequence. No CPU fallback is attempted here:
291    /// callers that want a reference path must call [`Self::cpu_reference_step`].
292    pub fn one_inner_iteration(
293        &self,
294        ridge_t: f64,
295        ridge_beta: f64,
296    ) -> Result<DeviceResidentArrowStep, DeviceResidentArrowError> {
297        if !self.device_resident() {
298            return Err(DeviceResidentArrowError::Unavailable {
299                reason: "SAE resident inner iteration unavailable: CUDA runtime did not admit the qwen-scale row-block workload".to_string(),
300            });
301        }
302        let sys = self.to_arrow_system();
303        let frame = crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
304            &sys, ridge_t, ridge_beta,
305        )
306        .map_err(map_gpu_error)?;
307        let g_t: Vec<f64> = sys
308            .rows
309            .iter()
310            .flat_map(|row| row.gt.iter().copied())
311            .collect();
312        let g_beta: Vec<f64> = sys.gb.iter().copied().collect();
313        // This is a SINGLE resident solve at one frozen gate/basis frame — the
314        // frame's constant Hessian factors are held resident and only the
315        // gradient is uploaded — so the truthful classifier is
316        // `GpuResidentLinearization`, not `GpuResidentFull` (which denotes the
317        // full multi-step device-resident inner Newton loop in `device_fit`).
318        frame
319            .solve_gradient(&g_t, &g_beta)
320            .map(|solution| self.finish_step(solution, ExecutionPath::GpuResidentLinearization))
321            .map_err(map_gpu_error)
322    }
323
324    /// CPU reference for parity harnesses. This path is explicit and is never
325    /// called from [`Self::one_inner_iteration`].
326    pub fn cpu_reference_step(
327        &self,
328        ridge_t: f64,
329        ridge_beta: f64,
330    ) -> Result<DeviceResidentArrowStep, DeviceResidentArrowError> {
331        let sys = self.to_arrow_system();
332        solve_arrow_newton_step_dense_reference(&sys, ridge_t, ridge_beta)
333            .map(|solution| self.finish_step(solution, ExecutionPath::Cpu))
334            .map_err(|reason| DeviceResidentArrowError::Solve { reason })
335    }
336
337    /// Production seam (#1017 Phase 3): one SAE data-fit inner Newton step under
338    /// the process-wide [`gam_gpu::GpuPolicy`] residency contract the caller passes
339    /// (`gam_gpu::global_policy()`). This is the entry a production inner Newton loop
340    /// calls per iterate; it does NOT touch the fitting loop itself — the caller
341    /// wires it (see the #1017 seam report).
342    ///
343    /// Break-even admission ("shapes clear the device threshold") is already
344    /// carried by [`Self::device_resident`]: the resident buffers upload only when
345    /// [`gam_gpu::linalg_dispatch::route_through_gpu`] admits the qwen-scale
346    /// row-block workload, so a below-break-even shape is simply not
347    /// device-resident. This method adds the mode lever and the typed fallback:
348    ///
349    /// * [`gam_gpu::GpuPolicy::Off`] — the dense CPU reference step; no device
350    ///   contact.
351    /// * [`gam_gpu::GpuPolicy::Auto`] — the resident device step when the workspace
352    ///   is device-resident, else the CPU reference; on a device-solve fault the
353    ///   fallback to the CPU reference is taken and logged ONCE per process (never
354    ///   a silent CPU downgrade). The resident step is a single frame-build +
355    ///   solve (no tile loop), so there is no unbounded async backlog to stall on
356    ///   silently — the #2227 "never silent" discipline here is the one-shot
357    ///   engagement warn plus the typed fault surface, not a per-tile heartbeat.
358    /// * [`gam_gpu::GpuPolicy::Required`] — the resident device step, or a typed
359    ///   [`DeviceResidentArrowError`] when the workspace is not device-resident or
360    ///   the solve faults (fails closed; never degrades to CPU).
361    pub fn inner_iteration_for_production(
362        &self,
363        mode: gam_gpu::GpuPolicy,
364        ridge_t: f64,
365        ridge_beta: f64,
366    ) -> Result<DeviceResidentArrowStep, DeviceResidentArrowError> {
367        match mode {
368            gam_gpu::GpuPolicy::Off => {
369                note_resident_engagement(false, "GpuPolicy::Off — CPU reference step");
370                self.cpu_reference_step(ridge_t, ridge_beta)
371            }
372            gam_gpu::GpuPolicy::Required => {
373                if !self.device_resident() {
374                    return Err(DeviceResidentArrowError::Unavailable {
375                        reason: format!(
376                            "SAE resident inner step GpuPolicy::Required: workspace is not \
377                             device-resident (the CUDA runtime did not admit shape n={} p={} d={} \
378                             at break-even); refusing to run on the CPU",
379                            self.shape.n, self.shape.p, self.shape.d
380                        ),
381                    });
382                }
383                note_resident_engagement(true, "GpuPolicy::Required — resident device step");
384                self.one_inner_iteration(ridge_t, ridge_beta)
385            }
386            gam_gpu::GpuPolicy::Auto => {
387                if !self.device_resident() {
388                    note_resident_engagement(
389                        false,
390                        "GpuPolicy::Auto — workspace not device-resident; CPU reference step",
391                    );
392                    return self.cpu_reference_step(ridge_t, ridge_beta);
393                }
394                match self.one_inner_iteration(ridge_t, ridge_beta) {
395                    Ok(step) => {
396                        note_resident_engagement(true, "GpuPolicy::Auto — resident device step");
397                        Ok(step)
398                    }
399                    Err(err) => {
400                        note_resident_engagement(
401                            false,
402                            &format!(
403                                "GpuPolicy::Auto — device solve fault, CPU reference fallback: {err}"
404                            ),
405                        );
406                        self.cpu_reference_step(ridge_t, ridge_beta)
407                    }
408                }
409            }
410        }
411    }
412
413    pub fn to_arrow_system(&self) -> ArrowSchurSystem {
414        let shape = self.shape;
415        let mut sys = ArrowSchurSystem::new(shape.n, shape.d, shape.p);
416        for i in 0..shape.n {
417            let h_base = i * shape.d * shape.d;
418            let b_base = i * shape.d * shape.p;
419            let g_base = i * shape.d;
420            for r in 0..shape.d {
421                for c in 0..shape.d {
422                    sys.rows[i].htt[[r, c]] =
423                        self.slabs.row_hessian_slabs[h_base + r * shape.d + c];
424                }
425                sys.rows[i].gt[r] = self.slabs.row_gradient_slabs[g_base + r];
426                for c in 0..shape.p {
427                    sys.rows[i].htbeta[[r, c]] =
428                        self.slabs.row_cross_slabs[b_base + r * shape.p + c];
429                }
430            }
431        }
432        for r in 0..shape.p {
433            sys.gb[r] = self.slabs.border_gradient[r];
434            for c in 0..shape.p {
435                sys.hbb[[r, c]] = self.slabs.border_hessian[r * shape.p + c];
436            }
437        }
438        sys.refresh_row_hessian_fingerprint();
439        sys
440    }
441
442    fn finish_step(
443        &self,
444        solution: crate::gpu_kernels::arrow_schur::ArrowSchurGpuSolution,
445        execution_path: ExecutionPath,
446    ) -> DeviceResidentArrowStep {
447        DeviceResidentArrowStep {
448            delta_t: solution.delta_t,
449            delta_beta: solution.delta_beta,
450            objective: 0.5 * squared_norm(&self.target_x),
451            gradient_norm: self.gradient_norm(),
452            log_det_hessian: solution.log_det_hessian,
453            execution_path,
454        }
455    }
456
457    fn gradient_norm(&self) -> f64 {
458        let row = squared_norm(&self.slabs.row_gradient_slabs);
459        let border = squared_norm(&self.slabs.border_gradient);
460        (row + border).sqrt()
461    }
462
463    // ---------------------------------------------------------------------
464    // Phase 3: full device-resident inner Newton loop (#1017).
465    //
466    // The resident slabs define a fixed bordered-quadratic data-fit objective
467    //     φ(z) = ½‖X‖² + ½ zᵀ H z − g₀ᵀ z,   z = (t, β),
468    // where `H` is the arrow-structured Hessian (per-row `H_tt`/`H_tβ` blocks
469    // plus the shared `H_ββ` border) and `g₀` is the base gradient assembled
470    // once at upload. This is the quadratic the SAE joint inner Newton actually
471    // minimises at a frozen gate/basis evaluation; the production driver
472    // (`LatentInnerSolver::solve`) re-linearises per outer evaluation, so a
473    // single resident frame is one such inner solve.
474    //
475    // The loop mirrors the production LM trust-region accept/reject exactly:
476    // at iterate `z` it forms the residual gradient `r(z) = H z − g₀`, takes
477    // the LM-damped arrow step (device or dense-reference), evaluates the trial
478    // objective, and accepts on the actual-vs-predicted reduction ratio. The
479    // iterate `(t, β)` and the per-step scalars (objective, gradient norm, ρ)
480    // are the ONLY host-side state; the heavy `O(n d³ + p³)` factor/solve stays
481    // on the resident buffers via `solve_arrow_newton_step`. For an exact
482    // quadratic the loop converges in one accepted step, but it exercises the
483    // full assemble→solve→objective→accept machinery and the scalar-only
484    // readback contract the production loop relies on.
485    // ---------------------------------------------------------------------
486
487    /// Run the full device-resident inner Newton loop. Routes the per-iteration
488    /// arrow solve through the GPU path; returns `Unavailable` when CUDA did not
489    /// admit the resident workload (callers wanting a CPU path use
490    /// [`Self::cpu_reference_fit`]).
491    pub fn device_fit(
492        &self,
493        opts: &DeviceResidentInnerOptions,
494    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
495        if !self.device_resident() {
496            return Err(DeviceResidentArrowError::Unavailable {
497                reason: "SAE resident inner loop unavailable: CUDA runtime did not admit the qwen-scale row-block workload".to_string(),
498            });
499        }
500        self.run_inner_loop(opts, InnerSolveMode::DeviceResident)
501    }
502
503    /// The #1017 residency baseline: run the SAME inner Newton loop but compute
504    /// each per-iterate arrow step through `solve_arrow_newton_step`, which
505    /// re-packs/re-uploads `D`/`B`/`g` and re-runs the per-row POTRF + border
506    /// Schur factor on EVERY iterate. This is the "current re-uploading path";
507    /// the bench divides [`Self::device_fit`] (resident) against it to isolate
508    /// the across-iteration residency speedup on one device, holding the host
509    /// control flow and the GPU factor kernels fixed.
510    pub fn device_reupload_fit(
511        &self,
512        opts: &DeviceResidentInnerOptions,
513    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
514        if !self.device_resident() {
515            return Err(DeviceResidentArrowError::Unavailable {
516                reason: "SAE re-uploading inner loop unavailable: CUDA runtime did not admit the row-block workload".to_string(),
517            });
518        }
519        self.run_inner_loop(opts, InnerSolveMode::DeviceReupload)
520    }
521
522    /// CPU dense-reference inner loop. Bit-for-bit the same host arithmetic as
523    /// [`Self::device_fit`] except the per-iteration arrow solve uses the dense
524    /// reference factorisation; the parity harness asserts the two agree.
525    pub fn cpu_reference_fit(
526        &self,
527        opts: &DeviceResidentInnerOptions,
528    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
529        self.run_inner_loop(opts, InnerSolveMode::CpuReference)
530    }
531
532    fn run_inner_loop(
533        &self,
534        opts: &DeviceResidentInnerOptions,
535        mode: InnerSolveMode,
536    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
537        let execution_path = mode.execution_path();
538        let n = self.shape.n;
539        let d = self.shape.d;
540        let p = self.shape.p;
541        let t_len = n * d;
542
543        // Resident iterate, host-side scalars only. The device buffers (X,
544        // slabs, border) never leave the device across iterations; only this
545        // O(t_len + p) iterate and the per-step reduction scalars cross back.
546        let mut t = vec![0.0_f64; t_len];
547        let mut beta = vec![0.0_f64; p];
548
549        let base = self.to_arrow_system();
550        let half_target_energy = 0.5 * squared_norm(&self.target_x);
551
552        let mut ridge_t = opts.initial_ridge_t.max(0.0);
553        let mut ridge_beta = opts.initial_ridge_beta.max(0.0);
554        // #1017 Phase 3: when running on device, keep the resident Arrow frame
555        // (constant Hessian blocks + their factors) on the device across
556        // iterations. The frame bakes a fixed `(ridge_t, ridge_beta)` into the
557        // per-row and border Cholesky factors, so it is rebuilt only when the LM
558        // ridge changes (reject/shrink); every iteration that shares the cached
559        // ridge reuses the resident factors and uploads only the `O(n·d + p)`
560        // gradient. The CPU reference path keeps re-factoring per iterate so the
561        // parity harness compares residency against a fully independent solve.
562        let mut resident_frame: Option<(
563            f64,
564            f64,
565            crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle,
566        )> = None;
567        let mut current_objective = self.objective_at(&base, half_target_energy, &t, &beta);
568        let mut accepted_iters = 0_usize;
569        let mut total_iters = 0_usize;
570        let mut converged = false;
571        let mut last_step = DeviceResidentArrowStep {
572            delta_t: Array1::zeros(t_len),
573            delta_beta: Array1::zeros(p),
574            objective: current_objective,
575            gradient_norm: 0.0,
576            log_det_hessian: 0.0,
577            execution_path,
578        };
579
580        while total_iters < opts.max_iterations {
581            // Residual gradient r(z) = H z − g₀ becomes the system gradient.
582            let residual = self.residual_system(&base, &t, &beta);
583            let g_norm = arrow_system_gradient_norm(&residual);
584            let scale = 1.0 + iterate_norm(&t, &beta);
585            if g_norm / scale < opts.convergence_tolerance {
586                converged = true;
587                break;
588            }
589
590            let solution = match mode {
591                InnerSolveMode::DeviceResident => {
592                    // Rebuild the resident frame only when the LM ridge changed; an
593                    // unchanged ridge reuses the resident factors. A build failure
594                    // becomes a Solve error so the LM-escalation arm below grows the
595                    // ridge and retries, identical to a per-iterate solve failure.
596                    let frame_matches = resident_frame
597                        .as_ref()
598                        .is_some_and(|(rt, rb, _)| *rt == ridge_t && *rb == ridge_beta);
599                    let mut frame_build_error: Option<DeviceResidentArrowError> = None;
600                    if !frame_matches {
601                        resident_frame = None;
602                        match crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
603                            &residual, ridge_t, ridge_beta,
604                        ) {
605                            Ok(frame) => {
606                                // Building a resident frame creates the device
607                                // stream/handles and runs the per-row POTRF +
608                                // border Schur factor once; record both so a
609                                // silent decline (no rebuild ⇒ no factor count)
610                                // is visible in the telemetry.
611                                gam_gpu::profile::telemetry_record_handle_creation(
612                                    self.context_id(),
613                                );
614                                gam_gpu::profile::telemetry_record_factorization();
615                                gam_gpu::profile::telemetry_record_h2d(self.frame_upload_bytes());
616                                resident_frame = Some((ridge_t, ridge_beta, frame));
617                            }
618                            Err(err) => frame_build_error = Some(map_gpu_error(err)),
619                        }
620                    }
621                    match resident_frame.as_ref() {
622                        Some((_, _, frame)) => {
623                            // Per-iterate gradient r(z) = (g_t rows, g_β), extracted
624                            // from the residual system the frame was built to match.
625                            let mut g_t = Vec::with_capacity(n * d);
626                            for row in &residual.rows {
627                                for &v in row.gt.iter() {
628                                    g_t.push(v);
629                                }
630                            }
631                            let g_beta: Vec<f64> = residual.gb.iter().copied().collect();
632                            // The resident solve uploads only the O(n·d + p)
633                            // gradient, launches the per-iterate solve kernel, and
634                            // reads back only δ.
635                            let grad_bytes =
636                                (g_t.len() + g_beta.len()) * std::mem::size_of::<f64>();
637                            gam_gpu::profile::telemetry_record_h2d(grad_bytes);
638                            gam_gpu::profile::telemetry_record_kernel_launch();
639                            gam_gpu::profile::telemetry_record_d2h(
640                                (n * d + p) * std::mem::size_of::<f64>(),
641                            );
642                            frame.solve_gradient(&g_t, &g_beta).map_err(map_gpu_error)
643                        }
644                        None => Err(frame_build_error.unwrap_or_else(|| {
645                            DeviceResidentArrowError::Solve {
646                                reason: "SAE resident frame build declined".to_string(),
647                            }
648                        })),
649                    }
650                }
651                InnerSolveMode::DeviceReupload => {
652                    // #1017 residency baseline: re-upload D/B/g and re-factor on
653                    // every iterate. Same GPU factor kernels as the resident path,
654                    // minus the across-iteration buffer/factor reuse — so EVERY
655                    // iterate creates handles, factorizes, launches, and re-uploads
656                    // the full slabs.
657                    gam_gpu::profile::telemetry_record_handle_creation(self.context_id());
658                    gam_gpu::profile::telemetry_record_factorization();
659                    gam_gpu::profile::telemetry_record_h2d(self.frame_upload_bytes());
660                    gam_gpu::profile::telemetry_record_kernel_launch();
661                    gam_gpu::profile::telemetry_record_d2h(
662                        (n * d + p) * std::mem::size_of::<f64>(),
663                    );
664                    solve_arrow_newton_step(&residual, ridge_t, ridge_beta).map_err(map_gpu_error)
665                }
666                InnerSolveMode::CpuReference => {
667                    solve_arrow_newton_step_dense_reference(&residual, ridge_t, ridge_beta)
668                        .map_err(|reason| DeviceResidentArrowError::Solve { reason })
669                }
670            };
671
672            let solution = match solution {
673                Ok(sol) => sol,
674                Err(DeviceResidentArrowError::Solve { .. })
675                | Err(DeviceResidentArrowError::Unavailable { .. }) => {
676                    // LM escalation: grow ridge, retry without consuming an
677                    // iteration. Mirrors the production per-row/Schur PD-failure
678                    // arm in `LatentInnerSolver::solve`.
679                    ridge_t = grow_ridge(ridge_t, opts.lm_grow);
680                    ridge_beta = grow_ridge(ridge_beta, opts.lm_grow);
681                    if ridge_t > opts.max_ridge || ridge_beta > opts.max_ridge {
682                        return Err(DeviceResidentArrowError::Solve {
683                            reason: format!(
684                                "SAE resident inner loop: LM ridge exceeded max ({:e}) at iter {total_iters}",
685                                opts.max_ridge
686                            ),
687                        });
688                    }
689                    total_iters += 1;
690                    continue;
691                }
692                Err(other) => return Err(other),
693            };
694
695            // Predicted reduction from the bare quadratic model on the residual
696            // system, identical formula to the production trust-region ratio.
697            let predicted_reduction = crate::arrow_schur::arrow_bare_quadratic_model_reduction(
698                &residual,
699                solution.delta_t.view(),
700                solution.delta_beta.view(),
701                ridge_t,
702                ridge_beta,
703            )
704            .map_err(|err| DeviceResidentArrowError::Solve {
705                reason: format!("SAE resident inner loop predicted-reduction failed: {err}"),
706            })?;
707
708            // Trial iterate.
709            let mut trial_t = t.clone();
710            let mut trial_beta = beta.clone();
711            for (slot, dv) in trial_t.iter_mut().zip(solution.delta_t.iter()) {
712                *slot += *dv;
713            }
714            for (slot, dv) in trial_beta.iter_mut().zip(solution.delta_beta.iter()) {
715                *slot += *dv;
716            }
717            let trial_objective =
718                self.objective_at(&base, half_target_energy, &trial_t, &trial_beta);
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_objective = trial_objective;
746                ridge_t = (ridge_t * opts.lm_shrink).max(0.0);
747                ridge_beta = (ridge_beta * opts.lm_shrink).max(0.0);
748                last_step = DeviceResidentArrowStep {
749                    delta_t: solution.delta_t,
750                    delta_beta: solution.delta_beta,
751                    objective: current_objective,
752                    gradient_norm: g_norm,
753                    log_det_hessian: solution.log_det_hessian,
754                    execution_path,
755                };
756                accepted_iters += 1;
757                total_iters += 1;
758            } else {
759                ridge_t = grow_ridge(ridge_t, opts.lm_grow);
760                ridge_beta = grow_ridge(ridge_beta, opts.lm_grow);
761                if ridge_t > opts.max_ridge || ridge_beta > opts.max_ridge {
762                    return Err(DeviceResidentArrowError::Solve {
763                        reason: format!(
764                            "SAE resident inner loop: LM rejected step until ridge exceeded max ({:e}) at iter {total_iters} (rho={rho:.3e})",
765                            opts.max_ridge
766                        ),
767                    });
768                }
769                total_iters += 1;
770            }
771        }
772
773        Ok(DeviceResidentInnerOutcome {
774            t: Array1::from_vec(t),
775            beta: Array1::from_vec(beta),
776            objective: current_objective,
777            gradient_norm: last_step.gradient_norm,
778            log_det_hessian: last_step.log_det_hessian,
779            iterations: total_iters,
780            accepted_iterations: accepted_iters,
781            converged,
782            execution_path,
783        })
784    }
785
786    // ---------------------------------------------------------------------
787    // Phase 3b: reuse the resident frame ACROSS OUTER iterations (#1017
788    // deliverable 3).
789    //
790    // The inner Newton loop above already keeps the resident Arrow frame
791    // (factored `D`/`B`/Schur) on the device across INNER iterations at a fixed
792    // ridge. The next residency tier is the OUTER loop: across consecutive outer
793    // evaluations the SAE Hessian operator is unchanged whenever the frozen
794    // gate/basis frame (hence `D = H_tt`, `B = H_tβ`, border `H_ββ`) does not
795    // move — only the base gradient `g₀` (the linearization point / target
796    // residual) changes. In that regime the `O(n·d³ + p³)` factor work and the
797    // dominant `O(n·d·p)` `D`/`B` upload need to happen ONCE for the whole outer
798    // sweep, not once per outer. `device_fit_outer_sequence` realizes that: it
799    // builds at most ONE resident frame for an unchanged operator and drives
800    // every outer's inner solve through it, re-uploading only the per-outer
801    // `O(n·d + p)` gradient. The per-outer parity oracle is an independent
802    // `device_fit` (fresh frame per outer); the two must agree because sharing
803    // the factor across outers skips only re-deriving operator-independent work.
804    // ---------------------------------------------------------------------
805
806    /// Run a sequence of outer evaluations that SHARE one resident frame when the
807    /// Hessian operator is unchanged across outers (#1017 deliverable 3).
808    ///
809    /// Each entry of `base_gradient_overrides` is one outer evaluation's base
810    /// gradient `(g_t rows: n·d, g_β: p)` — the only part of the bordered
811    /// quadratic that moves across outers at a frozen gate/basis frame. The
812    /// constant Hessian blocks ride the resident frame, which is built ONCE and
813    /// reused for every outer (frame builds are counted and returned so a caller
814    /// can assert the across-outer amortization actually fired: exactly one frame
815    /// build for an unchanged operator, regardless of how many outers run).
816    ///
817    /// Returns one [`DeviceResidentInnerOutcome`] per outer plus the number of
818    /// resident-frame builds performed across the whole sweep. On a CPU-only host
819    /// returns `Unavailable` (callers wanting a host path use
820    /// [`Self::cpu_reference_outer_sequence`]).
821    pub fn device_fit_outer_sequence(
822        &self,
823        base_gradient_overrides: &[(Vec<f64>, Vec<f64>)],
824        opts: &DeviceResidentInnerOptions,
825    ) -> Result<OuterSequenceOutcome, DeviceResidentArrowError> {
826        if !self.device_resident() {
827            return Err(DeviceResidentArrowError::Unavailable {
828                reason: "SAE outer-sequence residency unavailable: CUDA runtime did not admit the row-block workload".to_string(),
829            });
830        }
831        self.run_outer_sequence(
832            base_gradient_overrides,
833            opts,
834            InnerSolveMode::DeviceResident,
835        )
836    }
837
838    /// CPU-reference outer sequence: same host control flow as
839    /// [`Self::device_fit_outer_sequence`] but the per-iterate arrow solve uses
840    /// the dense reference factorisation. The parity harness asserts the device
841    /// across-outer sweep agrees with this per-outer-independent reference.
842    pub fn cpu_reference_outer_sequence(
843        &self,
844        base_gradient_overrides: &[(Vec<f64>, Vec<f64>)],
845        opts: &DeviceResidentInnerOptions,
846    ) -> Result<OuterSequenceOutcome, DeviceResidentArrowError> {
847        self.run_outer_sequence(base_gradient_overrides, opts, InnerSolveMode::CpuReference)
848    }
849
850    fn run_outer_sequence(
851        &self,
852        base_gradient_overrides: &[(Vec<f64>, Vec<f64>)],
853        opts: &DeviceResidentInnerOptions,
854        mode: InnerSolveMode,
855    ) -> Result<OuterSequenceOutcome, DeviceResidentArrowError> {
856        let n = self.shape.n;
857        let d = self.shape.d;
858        let p = self.shape.p;
859        let t_len = n * d;
860        let half_target_energy = 0.5 * squared_norm(&self.target_x);
861
862        // ONE resident frame for the whole sweep (device mode only). The operator
863        // is unchanged across outers — the frame bakes the constant `D`/`B`/Schur
864        // factors at `(initial_ridge_t, initial_ridge_beta)` once and every outer
865        // reuses it. A per-outer ridge escalation (PD failure) still rebuilds, but
866        // for a well-posed unchanged operator the build count stays at 1, which is
867        // the across-outer amortization this method delivers.
868        let mut shared = SharedFrameState::default();
869        let mut outcomes = Vec::with_capacity(base_gradient_overrides.len());
870
871        for (g_t_override, g_beta_override) in base_gradient_overrides {
872            if g_t_override.len() != t_len || g_beta_override.len() != p {
873                return Err(DeviceResidentArrowError::Shape {
874                    reason: format!(
875                        "outer-sequence gradient shape mismatch: g_t={} (want {t_len}), g_beta={} (want {p})",
876                        g_t_override.len(),
877                        g_beta_override.len()
878                    ),
879                });
880            }
881            // This outer's bordered quadratic: same Hessian blocks, base gradient
882            // swapped to this outer's `g₀`.
883            let mut base = self.to_arrow_system();
884            for (i, row) in base.rows.iter_mut().enumerate() {
885                for r in 0..d {
886                    row.gt[r] = g_t_override[i * d + r];
887                }
888            }
889            for (j, gb) in base.gb.iter_mut().enumerate() {
890                *gb = g_beta_override[j];
891            }
892            base.refresh_row_hessian_fingerprint();
893
894            let outcome = self.run_one_outer(&base, half_target_energy, opts, mode, &mut shared)?;
895            outcomes.push(outcome);
896        }
897
898        Ok(OuterSequenceOutcome {
899            outers: outcomes,
900            frame_builds: shared.frame_builds,
901        })
902    }
903
904    /// One outer evaluation's inner Newton loop, optionally reusing the frame
905    /// carried in `shared` across calls. Mirrors `run_inner_loop` but takes the
906    /// base system + the shared across-outer state so the caller can keep one
907    /// frame live for the whole sweep. `shared.frame_builds` is incremented every
908    /// time a frame is actually (re)built, so the caller can assert the
909    /// across-outer amortization fired.
910    fn run_one_outer(
911        &self,
912        base: &ArrowSchurSystem,
913        half_target_energy: f64,
914        opts: &DeviceResidentInnerOptions,
915        mode: InnerSolveMode,
916        shared: &mut SharedFrameState,
917    ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError> {
918        let execution_path = mode.execution_path();
919        let n = self.shape.n;
920        let d = self.shape.d;
921        let p = self.shape.p;
922        let t_len = n * d;
923
924        let mut t = vec![0.0_f64; t_len];
925        let mut beta = vec![0.0_f64; p];
926        let mut ridge_t = opts.initial_ridge_t.max(0.0);
927        let mut ridge_beta = opts.initial_ridge_beta.max(0.0);
928        let mut current_objective = self.objective_at(base, half_target_energy, &t, &beta);
929        let mut accepted_iters = 0_usize;
930        let mut total_iters = 0_usize;
931        let mut converged = false;
932        let mut last_gradient_norm = 0.0_f64;
933        let mut last_log_det = 0.0_f64;
934
935        while total_iters < opts.max_iterations {
936            let residual = self.residual_system(base, &t, &beta);
937            let g_norm = arrow_system_gradient_norm(&residual);
938            let scale = 1.0 + iterate_norm(&t, &beta);
939            if g_norm / scale < opts.convergence_tolerance {
940                converged = true;
941                break;
942            }
943
944            let solution = match mode {
945                InnerSolveMode::DeviceResident => {
946                    let frame_matches = shared
947                        .frame
948                        .as_ref()
949                        .is_some_and(|(rt, rb, _)| *rt == ridge_t && *rb == ridge_beta);
950                    let mut frame_build_error: Option<DeviceResidentArrowError> = None;
951                    if !frame_matches {
952                        shared.frame = None;
953                        match crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
954                            &residual, ridge_t, ridge_beta,
955                        ) {
956                            Ok(frame) => {
957                                shared.frame_builds += 1;
958                                gam_gpu::profile::telemetry_record_handle_creation(
959                                    self.context_id(),
960                                );
961                                gam_gpu::profile::telemetry_record_factorization();
962                                gam_gpu::profile::telemetry_record_h2d(self.frame_upload_bytes());
963                                shared.frame = Some((ridge_t, ridge_beta, frame));
964                            }
965                            Err(err) => frame_build_error = Some(map_gpu_error(err)),
966                        }
967                    }
968                    match shared.frame.as_ref() {
969                        Some((_, _, frame)) => {
970                            let mut g_t = Vec::with_capacity(n * d);
971                            for row in &residual.rows {
972                                for &v in row.gt.iter() {
973                                    g_t.push(v);
974                                }
975                            }
976                            let g_beta: Vec<f64> = residual.gb.iter().copied().collect();
977                            let grad_bytes =
978                                (g_t.len() + g_beta.len()) * std::mem::size_of::<f64>();
979                            gam_gpu::profile::telemetry_record_h2d(grad_bytes);
980                            gam_gpu::profile::telemetry_record_kernel_launch();
981                            gam_gpu::profile::telemetry_record_d2h(
982                                (n * d + p) * std::mem::size_of::<f64>(),
983                            );
984                            frame.solve_gradient(&g_t, &g_beta).map_err(map_gpu_error)
985                        }
986                        None => Err(frame_build_error.unwrap_or_else(|| {
987                            DeviceResidentArrowError::Solve {
988                                reason: "SAE resident frame build declined".to_string(),
989                            }
990                        })),
991                    }
992                }
993                InnerSolveMode::DeviceReupload => {
994                    solve_arrow_newton_step(&residual, ridge_t, ridge_beta).map_err(map_gpu_error)
995                }
996                InnerSolveMode::CpuReference => {
997                    solve_arrow_newton_step_dense_reference(&residual, ridge_t, ridge_beta)
998                        .map_err(|reason| DeviceResidentArrowError::Solve { reason })
999                }
1000            };
1001
1002            let solution = match solution {
1003                Ok(sol) => sol,
1004                Err(DeviceResidentArrowError::Solve { .. })
1005                | Err(DeviceResidentArrowError::Unavailable { .. }) => {
1006                    ridge_t = grow_ridge(ridge_t, opts.lm_grow);
1007                    ridge_beta = grow_ridge(ridge_beta, opts.lm_grow);
1008                    if ridge_t > opts.max_ridge || ridge_beta > opts.max_ridge {
1009                        return Err(DeviceResidentArrowError::Solve {
1010                            reason: format!(
1011                                "SAE outer-sequence inner loop: LM ridge exceeded max ({:e}) at iter {total_iters}",
1012                                opts.max_ridge
1013                            ),
1014                        });
1015                    }
1016                    total_iters += 1;
1017                    continue;
1018                }
1019                Err(other) => return Err(other),
1020            };
1021
1022            let predicted_reduction = crate::arrow_schur::arrow_bare_quadratic_model_reduction(
1023                &residual,
1024                solution.delta_t.view(),
1025                solution.delta_beta.view(),
1026                ridge_t,
1027                ridge_beta,
1028            )
1029            .map_err(|err| DeviceResidentArrowError::Solve {
1030                reason: format!("SAE outer-sequence predicted-reduction failed: {err}"),
1031            })?;
1032
1033            let mut trial_t = t.clone();
1034            let mut trial_beta = beta.clone();
1035            for (slot, dv) in trial_t.iter_mut().zip(solution.delta_t.iter()) {
1036                *slot += *dv;
1037            }
1038            for (slot, dv) in trial_beta.iter_mut().zip(solution.delta_beta.iter()) {
1039                *slot += *dv;
1040            }
1041            let trial_objective =
1042                self.objective_at(base, half_target_energy, &trial_t, &trial_beta);
1043
1044            let objective_scale = current_objective.abs();
1045            let noise_floor = objective_scale * 1e-14;
1046            let actual_reduction = current_objective - trial_objective;
1047            let rho = if predicted_reduction > noise_floor {
1048                actual_reduction / predicted_reduction
1049            } else if actual_reduction >= -noise_floor {
1050                1.0
1051            } else {
1052                -1.0
1053            };
1054
1055            if rho > 0.0 && trial_objective.is_finite() {
1056                t = trial_t;
1057                beta = trial_beta;
1058                current_objective = trial_objective;
1059                ridge_t = (ridge_t * opts.lm_shrink).max(0.0);
1060                ridge_beta = (ridge_beta * opts.lm_shrink).max(0.0);
1061                last_gradient_norm = g_norm;
1062                last_log_det = solution.log_det_hessian;
1063                accepted_iters += 1;
1064                total_iters += 1;
1065            } else {
1066                ridge_t = grow_ridge(ridge_t, opts.lm_grow);
1067                ridge_beta = grow_ridge(ridge_beta, opts.lm_grow);
1068                if ridge_t > opts.max_ridge || ridge_beta > opts.max_ridge {
1069                    return Err(DeviceResidentArrowError::Solve {
1070                        reason: format!(
1071                            "SAE outer-sequence inner loop: LM rejected step until ridge exceeded max ({:e}) at iter {total_iters} (rho={rho:.3e})",
1072                            opts.max_ridge
1073                        ),
1074                    });
1075                }
1076                total_iters += 1;
1077            }
1078        }
1079
1080        Ok(DeviceResidentInnerOutcome {
1081            t: Array1::from_vec(t),
1082            beta: Array1::from_vec(beta),
1083            objective: current_objective,
1084            gradient_norm: last_gradient_norm,
1085            log_det_hessian: last_log_det,
1086            iterations: total_iters,
1087            accepted_iterations: accepted_iters,
1088            converged,
1089            execution_path,
1090        })
1091    }
1092
1093    /// Bordered-quadratic objective `½‖X‖² + ½ zᵀ H z − g₀ᵀ z` at iterate
1094    /// `z = (t, β)`. Uses the resident arrow structure: per-row `H_tt`/`H_tβ`
1095    /// contractions plus the shared `H_ββ` border, then the linear `g₀ᵀ z`
1096    /// term. This is the reduction the device line search evaluates; on a CUDA
1097    /// host the `H z` contraction rides the same resident slabs (batched
1098    /// per-row GEMV + border GEMV), with only the final dot reduced to a scalar.
1099    fn objective_at(
1100        &self,
1101        base: &ArrowSchurSystem,
1102        half_target_energy: f64,
1103        t: &[f64],
1104        beta: &[f64],
1105    ) -> f64 {
1106        let n = self.shape.n;
1107        let d = self.shape.d;
1108        let p = self.shape.p;
1109        // quad = zᵀ H z, lin = g₀ᵀ z.
1110        let mut quad = 0.0_f64;
1111        let mut lin = 0.0_f64;
1112        // Per-row blocks: tᵢᵀ H_tt tᵢ + 2 tᵢᵀ H_tβ β contributes to quad; the
1113        // β border H_ββ is added once below.
1114        for i in 0..n {
1115            let t_base = i * d;
1116            for r in 0..d {
1117                // H_tt tᵢ row.
1118                let mut htt_t = 0.0_f64;
1119                for c in 0..d {
1120                    htt_t += base.rows[i].htt[[r, c]] * t[t_base + c];
1121                }
1122                // H_tβ β row.
1123                let mut htb_b = 0.0_f64;
1124                for c in 0..p {
1125                    htb_b += base.rows[i].htbeta[[r, c]] * beta[c];
1126                }
1127                quad += t[t_base + r] * (htt_t + 2.0 * htb_b);
1128                lin += base.rows[i].gt[r] * t[t_base + r];
1129            }
1130        }
1131        // β border: βᵀ H_ββ β and g_β ᵀ β.
1132        for r in 0..p {
1133            let mut hbb_b = 0.0_f64;
1134            for c in 0..p {
1135                hbb_b += base.hbb[[r, c]] * beta[c];
1136            }
1137            quad += beta[r] * hbb_b;
1138            lin += base.gb[r] * beta[r];
1139        }
1140        half_target_energy + 0.5 * quad - lin
1141    }
1142
1143    /// Build the residual arrow system at iterate `z`: same Hessian blocks as
1144    /// `base`, but the gradient set to `r(z) = H z − g₀`. The arrow solver
1145    /// solves `H δ = −gradient = −r(z) = g₀ − H z`, the Newton direction toward
1146    /// the quadratic's minimiser.
1147    fn residual_system(
1148        &self,
1149        base: &ArrowSchurSystem,
1150        t: &[f64],
1151        beta: &[f64],
1152    ) -> ArrowSchurSystem {
1153        let n = self.shape.n;
1154        let d = self.shape.d;
1155        let p = self.shape.p;
1156        // `ArrowSchurSystem` is not `Clone` (it carries matrix-free operator
1157        // closures whose sharing across a then-mutated system would be a
1158        // footgun), so own a fresh system built from the resident slabs rather
1159        // than cloning `base`. `to_arrow_system` reproduces the identical
1160        // Hessian blocks; we overwrite only the gradients below with the
1161        // residual `r(z) = H z − g₀`. The Hessian reads stay on `base` (bit-
1162        // identical to the fresh system's blocks).
1163        let mut sys = self.to_arrow_system();
1164        for i in 0..n {
1165            let t_base = i * d;
1166            for r in 0..d {
1167                let mut hz = 0.0_f64;
1168                for c in 0..d {
1169                    hz += base.rows[i].htt[[r, c]] * t[t_base + c];
1170                }
1171                for c in 0..p {
1172                    hz += base.rows[i].htbeta[[r, c]] * beta[c];
1173                }
1174                sys.rows[i].gt[r] = hz - base.rows[i].gt[r];
1175            }
1176        }
1177        for r in 0..p {
1178            let mut hz = 0.0_f64;
1179            // H_ββ β.
1180            for c in 0..p {
1181                hz += base.hbb[[r, c]] * beta[c];
1182            }
1183            // Σ_i (H_tβ^(i))ᵀ tᵢ contribution to the β-gradient.
1184            for i in 0..n {
1185                let t_base = i * d;
1186                for rr in 0..d {
1187                    hz += base.rows[i].htbeta[[rr, r]] * t[t_base + rr];
1188                }
1189            }
1190            sys.gb[r] = hz - base.gb[r];
1191        }
1192        sys.refresh_row_hessian_fingerprint();
1193        sys
1194    }
1195}
1196
1197/// Options for the device-resident inner Newton loop. Defaults mirror the
1198/// production [`crate::latent_inner::LatentInnerOptions`] trust-region
1199/// schedule so device and CPU paths run identical host-side control flow.
1200#[derive(Clone, Copy, Debug)]
1201pub struct DeviceResidentInnerOptions {
1202    pub max_iterations: usize,
1203    pub convergence_tolerance: f64,
1204    pub initial_ridge_t: f64,
1205    pub initial_ridge_beta: f64,
1206    pub lm_grow: f64,
1207    pub lm_shrink: f64,
1208    pub max_ridge: f64,
1209}
1210
1211impl Default for DeviceResidentInnerOptions {
1212    fn default() -> Self {
1213        Self {
1214            max_iterations: 16,
1215            convergence_tolerance: 1e-9,
1216            initial_ridge_t: 0.0,
1217            initial_ridge_beta: 0.0,
1218            lm_grow: 4.0,
1219            lm_shrink: 0.5,
1220            max_ridge: 1e9,
1221        }
1222    }
1223}
1224
1225/// Result of the full device-resident inner Newton loop.
1226#[derive(Clone, Debug)]
1227pub struct DeviceResidentInnerOutcome {
1228    pub t: Array1<f64>,
1229    pub beta: Array1<f64>,
1230    pub objective: f64,
1231    pub gradient_norm: f64,
1232    pub log_det_hessian: f64,
1233    pub iterations: usize,
1234    pub accepted_iterations: usize,
1235    pub converged: bool,
1236    pub execution_path: ExecutionPath,
1237}
1238
1239/// Result of an across-outer resident sweep ([`DeviceResidentArrowWorkspace::device_fit_outer_sequence`]).
1240///
1241/// `outers` holds one inner-loop outcome per outer evaluation, in input order.
1242/// `frame_builds` is the total number of resident-frame (re)builds performed
1243/// across the whole sweep: for an unchanged operator with a well-posed ridge it
1244/// is exactly `1` (the across-outer amortization #1017 deliverable 3 buys —
1245/// factor once, reuse the device factors for every outer), regardless of how
1246/// many outers ran. A value `> 1` means a per-outer ridge escalation forced a
1247/// refactor, which the parity oracle still matches but which costs the
1248/// amortization for those outers.
1249#[derive(Clone, Debug)]
1250pub struct OuterSequenceOutcome {
1251    pub outers: Vec<DeviceResidentInnerOutcome>,
1252    pub frame_builds: usize,
1253}
1254
1255/// Across-outer resident-frame state carried through a `device_fit_outer_sequence`
1256/// sweep. Holds the single resident frame (keyed by its `(ridge_t, ridge_beta)`)
1257/// reused across outers at an unchanged operator, plus the running count of frame
1258/// (re)builds so the caller can assert the across-outer amortization fired.
1259#[derive(Default)]
1260struct SharedFrameState {
1261    frame: Option<(
1262        f64,
1263        f64,
1264        crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle,
1265    )>,
1266    frame_builds: usize,
1267}
1268
1269/// One-shot engagement report for the #1017 production resident inner-step seam,
1270/// mirroring the sparse_dict routers' `note_route_engagement` (#1551 "GPU 0%"
1271/// class): a production run that silently declines the resident device path and
1272/// falls back to the CPU reference otherwise leaves no trace of why. Warns once
1273/// per category (engaged / declined) per process — the step is per-iterate, so an
1274/// unconditional line would flood the fit log.
1275fn note_resident_engagement(engaged: bool, detail: &str) {
1276    use std::sync::Once;
1277    static ENGAGED_ONCE: Once = Once::new();
1278    static DECLINED_ONCE: Once = Once::new();
1279    let once = if engaged {
1280        &ENGAGED_ONCE
1281    } else {
1282        &DECLINED_ONCE
1283    };
1284    once.call_once(|| {
1285        let verdict = if engaged {
1286            "device ENGAGED"
1287        } else {
1288            "device DECLINED - CPU reference"
1289        };
1290        log::warn!("[gam-solve sae_resident inner step] {verdict}: {detail}");
1291    });
1292}
1293
1294fn grow_ridge(current: f64, grow: f64) -> f64 {
1295    if current == 0.0 { 1e-6 } else { current * grow }
1296}
1297
1298fn arrow_system_gradient_norm(sys: &ArrowSchurSystem) -> f64 {
1299    let mut acc = 0.0_f64;
1300    for row in &sys.rows {
1301        for &v in row.gt.iter() {
1302            acc += v * v;
1303        }
1304    }
1305    for &v in sys.gb.iter() {
1306        acc += v * v;
1307    }
1308    acc.sqrt()
1309}
1310
1311fn iterate_norm(t: &[f64], beta: &[f64]) -> f64 {
1312    (squared_norm(t) + squared_norm(beta)).sqrt()
1313}
1314
1315fn validate_shape(
1316    shape: DeviceResidentArrowShape,
1317    target_x: &[f64],
1318    basis_values: &[f64],
1319    gate_activations: &[f64],
1320    slabs: &DeviceResidentArrowSlabs,
1321) -> Result<(), DeviceResidentArrowError> {
1322    let checks = [
1323        ("target_x", target_x.len(), shape.target_len()),
1324        ("basis_values", basis_values.len(), shape.basis_len()),
1325        (
1326            "gate_activations",
1327            gate_activations.len(),
1328            shape.basis_len(),
1329        ),
1330        (
1331            "row_hessian_slabs",
1332            slabs.row_hessian_slabs.len(),
1333            shape.row_hessian_len(),
1334        ),
1335        (
1336            "row_cross_slabs",
1337            slabs.row_cross_slabs.len(),
1338            shape.row_cross_len(),
1339        ),
1340        (
1341            "row_gradient_slabs",
1342            slabs.row_gradient_slabs.len(),
1343            shape.row_gradient_len(),
1344        ),
1345        (
1346            "border_hessian",
1347            slabs.border_hessian.len(),
1348            shape.border_hessian_len(),
1349        ),
1350        ("border_gradient", slabs.border_gradient.len(), shape.p),
1351    ];
1352    for (label, got, want) in checks {
1353        if got != want {
1354            return Err(DeviceResidentArrowError::Shape {
1355                reason: format!(
1356                    "SAE resident workspace shape mismatch for {label}: got {got}, expected {want}"
1357                ),
1358            });
1359        }
1360    }
1361    if shape.n == 0 || shape.p == 0 || shape.d == 0 || shape.basis_cols == 0 {
1362        return Err(DeviceResidentArrowError::Shape {
1363            reason: "SAE resident workspace requires nonzero n, p, basis_cols, and d".to_string(),
1364        });
1365    }
1366    Ok(())
1367}
1368
1369#[cfg(target_os = "linux")]
1370fn upload_resident_buffers(
1371    shape: DeviceResidentArrowShape,
1372    target_x: &[f64],
1373    basis_values: &[f64],
1374    gate_activations: &[f64],
1375    slabs: &DeviceResidentArrowSlabs,
1376) -> Option<DeviceResidentArrowBuffers> {
1377    use gam_gpu::linalg_dispatch::{DispatchOp, route_through_gpu};
1378
1379    let runtime = route_through_gpu(DispatchOp::SmallDenseBatchedPotrf {
1380        p: shape.d,
1381        batch: shape.n,
1382    })
1383    .or_else(|| {
1384        route_through_gpu(DispatchOp::Gemm {
1385            m: shape.p,
1386            n: shape.p,
1387            k: shape.n * shape.basis_cols,
1388        })
1389    })?;
1390    let ctx = gam_gpu::device_runtime::cuda_context_for(runtime.device.ordinal)?;
1391    let stream = ctx.new_stream().ok()?;
1392    let target_x_dev = stream.clone_htod(target_x).ok()?;
1393    let basis_values_dev = stream.clone_htod(basis_values).ok()?;
1394    let gate_activations_dev = stream.clone_htod(gate_activations).ok()?;
1395    let row_hessian_dev = stream.clone_htod(&slabs.row_hessian_slabs).ok()?;
1396    let row_cross_dev = stream.clone_htod(&slabs.row_cross_slabs).ok()?;
1397    let row_gradient_dev = stream.clone_htod(&slabs.row_gradient_slabs).ok()?;
1398    let border_hessian_dev = stream.clone_htod(&slabs.border_hessian).ok()?;
1399    let border_gradient_dev = stream.clone_htod(&slabs.border_gradient).ok()?;
1400    let bytes = [
1401        target_x.len(),
1402        basis_values.len(),
1403        gate_activations.len(),
1404        slabs.row_hessian_slabs.len(),
1405        slabs.row_cross_slabs.len(),
1406        slabs.row_gradient_slabs.len(),
1407        slabs.border_hessian.len(),
1408        slabs.border_gradient.len(),
1409    ]
1410    .into_iter()
1411    .sum::<usize>()
1412        * std::mem::size_of::<f64>();
1413    Some(DeviceResidentArrowBuffers {
1414        stream,
1415        target_x_dev,
1416        basis_values_dev,
1417        gate_activations_dev,
1418        row_hessian_dev,
1419        row_cross_dev,
1420        row_gradient_dev,
1421        border_hessian_dev,
1422        border_gradient_dev,
1423        bytes,
1424    })
1425}
1426
1427fn map_gpu_error(err: ArrowSchurGpuFailure) -> DeviceResidentArrowError {
1428    match err {
1429        ArrowSchurGpuFailure::Unavailable => DeviceResidentArrowError::Unavailable {
1430            reason: "SAE resident inner iteration unavailable after GPU admission".to_string(),
1431        },
1432        ArrowSchurGpuFailure::RidgeBumpRequired { row, bump } => DeviceResidentArrowError::Solve {
1433            reason: format!("SAE resident inner iteration row {row} requires ridge bump {bump:e}"),
1434        },
1435        ArrowSchurGpuFailure::SchurFactorFailed { reason } => {
1436            DeviceResidentArrowError::Solve { reason }
1437        }
1438        ArrowSchurGpuFailure::GpuRequiresDenseSystem {
1439            had_hbb_matvec,
1440            had_htbeta_matvec,
1441        } => DeviceResidentArrowError::Solve {
1442            reason: format!(
1443                "SAE resident inner iteration requires dense slabs; hbb_matvec={had_hbb_matvec} htbeta_matvec={had_htbeta_matvec}"
1444            ),
1445        },
1446    }
1447}
1448
1449fn squared_norm(values: &[f64]) -> f64 {
1450    values.iter().map(|v| v * v).sum()
1451}
1452
1453impl From<ArrowSchurError> for DeviceResidentArrowError {
1454    fn from(err: ArrowSchurError) -> Self {
1455        Self::Solve {
1456            reason: err.to_string(),
1457        }
1458    }
1459}
1460
1461/// Deterministic qwen-scale non-gating fixture for the resident harness.
1462pub fn qwen_non_gating_fixture() -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1463    qwen_non_gating_fixture_seeded(0x1017_0003_D3A1_5EED)
1464}
1465
1466/// Seeded variant of [`qwen_non_gating_fixture`]. Distinct seeds produce
1467/// distinct-but-well-conditioned resident frames, used to build independent
1468/// replicate fits for the stream-multiplexing parity harness.
1469pub fn qwen_non_gating_fixture_seeded(
1470    seed: u64,
1471) -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1472    fixture_for_shape_seeded(DeviceResidentArrowShape::qwen_non_gating(), seed)
1473}
1474
1475/// Deterministic color-arm-scale resident fixture (n=180, p=5120) for the
1476/// #1017 GPU wall-clock bench: few rows, very wide border — the shape where the
1477/// per-iterate re-upload + re-factor that across-iteration residency eliminates
1478/// dominates.
1479pub fn color_arm_fixture() -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1480    fixture_for_shape_seeded(DeviceResidentArrowShape::color_arm(), 0x1017_C010_2A12_5EED)
1481}
1482
1483/// Build a well-conditioned resident frame for any `d == 2` shape. Both the
1484/// qwen and color-arm fixtures share this body; the conditioning (strong row
1485/// `H_tt` diagonals, tiny cross blocks, diagonally-dominant border) keeps the
1486/// dense reference factorisation PD so the parity harness is meaningful.
1487fn fixture_for_shape_seeded(
1488    shape: DeviceResidentArrowShape,
1489    seed: u64,
1490) -> Result<DeviceResidentArrowWorkspace, DeviceResidentArrowError> {
1491    if shape.d == 0 {
1492        return Err(DeviceResidentArrowError::Shape {
1493            reason: "fixture_for_shape_seeded requires d >= 1".to_string(),
1494        });
1495    }
1496    let d = shape.d;
1497    let mut rng = SplitMix64::new(seed);
1498    let mut target_x = vec![0.0_f64; shape.target_len()];
1499    for i in 0..shape.n {
1500        for j in 0..shape.p {
1501            let phase = ((i % 97) as f64) * 0.013 + ((j % 131) as f64) * 0.007;
1502            target_x[i * shape.p + j] = 0.02 * phase.sin() + 0.001 * rng.sample_signed();
1503        }
1504    }
1505    let mut basis_values = vec![0.0_f64; shape.basis_len()];
1506    let mut gate_activations = vec![1.0_f64; shape.basis_len()];
1507    for i in 0..shape.n {
1508        for a in 0..shape.basis_cols {
1509            let phase = ((i + 1) as f64) * ((a + 1) as f64) * 0.003;
1510            basis_values[i * shape.basis_cols + a] = phase.cos();
1511            gate_activations[i * shape.basis_cols + a] = 1.0;
1512        }
1513    }
1514    let mut row_hessian_slabs = vec![0.0_f64; shape.row_hessian_len()];
1515    let mut row_cross_slabs = vec![0.0_f64; shape.row_cross_len()];
1516    let mut row_gradient_slabs = vec![0.0_f64; shape.row_gradient_len()];
1517    for i in 0..shape.n {
1518        let mut basis_sum = 0.0_f64;
1519        for a in 0..shape.basis_cols {
1520            basis_sum +=
1521                basis_values[i * shape.basis_cols + a] * gate_activations[i * shape.basis_cols + a];
1522        }
1523        // Strongly diagonally-dominant d×d H_tt (row-major): diagonal ≈ 3, tiny
1524        // symmetric off-diagonals — PD for any d so the dense reference factors.
1525        let h_base = i * d * d;
1526        for r in 0..d {
1527            for c in 0..d {
1528                let v = if r == c {
1529                    3.0 + 0.01 * basis_sum.abs() + 0.1 * (r as f64)
1530                } else {
1531                    0.02 * (basis_sum + (r + c) as f64).sin() / (d as f64)
1532                };
1533                row_hessian_slabs[h_base + r * d + c] = v;
1534            }
1535        }
1536        // Symmetrize the off-diagonals exactly.
1537        for r in 0..d {
1538            for c in 0..r {
1539                let avg = 0.5
1540                    * (row_hessian_slabs[h_base + r * d + c]
1541                        + row_hessian_slabs[h_base + c * d + r]);
1542                row_hessian_slabs[h_base + r * d + c] = avg;
1543                row_hessian_slabs[h_base + c * d + r] = avg;
1544            }
1545        }
1546        // d×p cross block (row-major) and length-d gradient.
1547        let b_base = i * d * shape.p;
1548        let g_base = i * d;
1549        for r in 0..d {
1550            for j in 0..shape.p {
1551                let feature = ((j % 257) as f64) * 0.011;
1552                row_cross_slabs[b_base + r * shape.p + j] =
1553                    1.0e-4 * (basis_sum + r as f64).sin() * feature.cos();
1554            }
1555            row_gradient_slabs[g_base + r] = 0.01 * (basis_sum + r as f64).sin();
1556        }
1557    }
1558    let mut border_hessian = vec![0.0_f64; shape.border_hessian_len()];
1559    for r in 0..shape.p {
1560        border_hessian[r * shape.p + r] = 4.0;
1561        if r + 1 < shape.p {
1562            border_hessian[r * shape.p + r + 1] = 0.01;
1563            border_hessian[(r + 1) * shape.p + r] = 0.01;
1564        }
1565    }
1566    let mut border_gradient = vec![0.0_f64; shape.p];
1567    for j in 0..shape.p {
1568        border_gradient[j] = 0.001 * ((j % 193) as f64 * 0.017).sin();
1569    }
1570    DeviceResidentArrowWorkspace::new(
1571        shape,
1572        target_x,
1573        basis_values,
1574        gate_activations,
1575        DeviceResidentArrowSlabs {
1576            row_hessian_slabs,
1577            row_cross_slabs,
1578            row_gradient_slabs,
1579            border_hessian,
1580            border_gradient,
1581        },
1582    )
1583}
1584
1585/// One multiplexed resident fit: the workspace plus the inner-loop outcome.
1586pub struct MultiplexedFit {
1587    pub outcome: DeviceResidentInnerOutcome,
1588}
1589
1590/// Phase 4: run `workspaces.len()` independent device-resident inner fits that
1591/// share one device.
1592///
1593/// # Stream-multiplexing safety argument
1594///
1595/// Each fit calls [`DeviceResidentArrowWorkspace::device_fit`], whose per-row
1596/// arrow solve (`solve_arrow_newton_step`) acquires the **process-shared**
1597/// `Arc<CudaContext>` via `device_runtime::cuda_context_for` (a `Mutex`-guarded
1598/// `OnceLock` cache) and then creates its **own** `CudaStream` with its own
1599/// cuSOLVER/cuBLAS handles and its own device allocations. Distinct streams off
1600/// one shared context execute concurrently on the device; the only shared
1601/// mutable state — the context cache and cudarc's allocator — is internally
1602/// synchronised, and no two fits touch the same stream, handle, or buffer. So
1603/// independent fits are data-race-free and the device serialises only where the
1604/// hardware must (shared SMs / copy engines), which is exactly the throughput
1605/// multiplexing the issue's Phase 4 calls for.
1606///
1607/// Concurrency is driven through [`run_topology_race_parallel`] (bac4af426),
1608/// which already bounds nested Rayon so each fit's internal `par_iter`/faer
1609/// parallelism stays inside its per-fit thread budget rather than oversubscribing
1610/// the global pool. Results are returned in input order. A single A100 thus hosts
1611/// many color-/qwen-arm fits at once — the cross-fit batch where the 1e5–1e6×
1612/// race speedup materialises.
1613///
1614/// The process-wide typed GPU availability cache and per-ordinal context
1615/// cache are warmed by constructing the resident workspaces (each `new` calls
1616/// the same probe), so the per-fit calls inside the Rayon scope only *read* the
1617/// already-initialised `OnceLock`s — they never trigger a `get_or_init` whose
1618/// closure does nested parallel work, avoiding the OnceLock×Rayon deadlock.
1619pub fn run_resident_fits_multiplexed(
1620    workspaces: Vec<DeviceResidentArrowWorkspace>,
1621    opts: DeviceResidentInnerOptions,
1622) -> Result<Vec<Result<MultiplexedFit, DeviceResidentArrowError>>, String> {
1623    run_resident_fits_multiplexed_with(workspaces, opts, |workspace, opts| {
1624        workspace.device_fit(opts)
1625    })
1626}
1627
1628/// Multiplexing core parameterised over the per-fit runner, so the CPU-reference
1629/// path can exercise the exact same `run_topology_race_parallel` plumbing as the
1630/// device path in tests that run without CUDA.
1631fn run_resident_fits_multiplexed_with<Run>(
1632    workspaces: Vec<DeviceResidentArrowWorkspace>,
1633    opts: DeviceResidentInnerOptions,
1634    run_one: Run,
1635) -> Result<Vec<Result<MultiplexedFit, DeviceResidentArrowError>>, String>
1636where
1637    Run: Fn(
1638            &DeviceResidentArrowWorkspace,
1639            &DeviceResidentInnerOptions,
1640        ) -> Result<DeviceResidentInnerOutcome, DeviceResidentArrowError>
1641        + Sync,
1642{
1643    let rows = crate::topology_selector::run_topology_race_parallel(
1644        workspaces,
1645        move |workspace: DeviceResidentArrowWorkspace| {
1646            run_one(&workspace, &opts).map(|outcome| MultiplexedFit { outcome })
1647        },
1648    )?;
1649    Ok(rows.into_iter().map(|row| row.result).collect())
1650}
1651
1652/// Sequential reference for the multiplexing parity harness: the same fits run
1653/// one after another on the same shared device. Multiplexed results must be
1654/// bit-identical to this because each fit's arithmetic is independent of the
1655/// others — sharing the device changes only scheduling, never the numbers.
1656pub fn run_resident_fits_sequential(
1657    workspaces: &[DeviceResidentArrowWorkspace],
1658    opts: &DeviceResidentInnerOptions,
1659) -> Vec<Result<MultiplexedFit, DeviceResidentArrowError>> {
1660    workspaces
1661        .iter()
1662        .map(|workspace| {
1663            workspace
1664                .device_fit(opts)
1665                .map(|outcome| MultiplexedFit { outcome })
1666        })
1667        .collect()
1668}
1669
1670// ---------------------------------------------------------------------------
1671// Phase 4 variant sweep (#1017): the OLMo research battery's independent-fit
1672// matrix (K × topology × basis × layer/checkpoint) dispatched concurrently on
1673// one device.
1674//
1675// Each variant is a SEPARATE fit with its OWN resident frame: the per-fit
1676// arithmetic is independent of the others, so multiplexing them onto one a100
1677// changes only scheduling, never the numbers. This is the cross-fit batch where
1678// the issue's 1e5–1e6× race throughput materialises — and unlike per-fit
1679// across-iteration residency it needs NO fixed-quadratic inner loop, because the
1680// parallelism is BETWEEN fits, not within one.
1681// ---------------------------------------------------------------------------
1682
1683/// One independent fit in the battery's variant sweep. The battery maps each
1684/// (K, topology, basis, layer, checkpoint, seed) cell of its matrix to a
1685/// `SweepVariant`; `dim` carries the resident-frame shape that cell produces
1686/// after the host assembles its row/border slabs. Distinct `seed`s keep the
1687/// fits genuinely independent (no shared device buffer, handle, or stream).
1688#[derive(Clone, Copy, Debug)]
1689pub struct SweepVariant {
1690    /// Resident-frame shape for this variant's frozen gate/basis frame.
1691    pub dim: DeviceResidentArrowShape,
1692    /// Deterministic seed for this variant's fixture/frame.
1693    pub seed: u64,
1694}
1695
1696/// Throughput summary for a multiplexed variant sweep on one device.
1697#[derive(Clone, Copy, Debug)]
1698pub struct SweepThroughput {
1699    pub fits: usize,
1700    pub succeeded: usize,
1701    pub wall_seconds: f64,
1702    /// Fits completed per wall-clock second on the single shared device.
1703    pub fits_per_second: f64,
1704}
1705
1706/// Build the independent resident workspaces for a variant sweep. Each variant
1707/// gets its own well-conditioned `d == 2` frame (the host feeds real slabs in
1708/// production; here the deterministic fixture stands in for the parity/throughput
1709/// harness). Returns the workspaces in variant order.
1710pub fn build_sweep_workspaces(
1711    variants: &[SweepVariant],
1712) -> Result<Vec<DeviceResidentArrowWorkspace>, DeviceResidentArrowError> {
1713    variants
1714        .iter()
1715        .map(|v| fixture_for_shape_seeded(v.dim, v.seed))
1716        .collect()
1717}
1718
1719/// Dispatch a variant sweep concurrently on one device and measure cross-fit
1720/// throughput. Returns the per-variant outcomes (in variant order) and the
1721/// throughput summary (fits/sec on the single shared a100). Per-fit certified
1722/// parity is asserted by [`assert_sweep_parity_vs_sequential`].
1723pub fn run_variant_sweep_multiplexed(
1724    variants: &[SweepVariant],
1725    opts: DeviceResidentInnerOptions,
1726) -> Result<
1727    (
1728        Vec<Result<MultiplexedFit, DeviceResidentArrowError>>,
1729        SweepThroughput,
1730    ),
1731    String,
1732> {
1733    let workspaces = build_sweep_workspaces(variants).map_err(|e| e.to_string())?;
1734    run_battery_sweep_multiplexed(workspaces, opts)
1735}
1736
1737/// Production battery entry (#1017 Phase 4): dispatch CALLER-ASSEMBLED resident
1738/// workspaces concurrently on one device and measure cross-fit throughput.
1739///
1740/// This is the real-slab seam the OLMo battery uses: the host (pyffi) builds one
1741/// [`DeviceResidentArrowWorkspace`] per matrix cell from the cell's ACTUAL SAE
1742/// row_hessian/row_cross/border slabs via [`DeviceResidentArrowWorkspace::new`],
1743/// then hands the workspaces here. Unlike [`run_variant_sweep_multiplexed`]
1744/// (which builds frames from the deterministic harness fixture), this consumes
1745/// real frames, so the printed throughput is the battery's true fits/sec on one
1746/// device. Returns per-cell outcomes (in input order) + the throughput summary.
1747pub fn run_battery_sweep_multiplexed(
1748    workspaces: Vec<DeviceResidentArrowWorkspace>,
1749    opts: DeviceResidentInnerOptions,
1750) -> Result<
1751    (
1752        Vec<Result<MultiplexedFit, DeviceResidentArrowError>>,
1753        SweepThroughput,
1754    ),
1755    String,
1756> {
1757    let fits = workspaces.len();
1758    let start = std::time::Instant::now();
1759    let results = run_resident_fits_multiplexed(workspaces, opts)?;
1760    let wall_seconds = start.elapsed().as_secs_f64();
1761    let succeeded = results.iter().filter(|r| r.is_ok()).count();
1762    let throughput = SweepThroughput {
1763        fits,
1764        succeeded,
1765        wall_seconds,
1766        fits_per_second: (fits as f64) / wall_seconds.max(1e-9),
1767    };
1768    Ok((results, throughput))
1769}
1770
1771/// The OLMo battery's full color-arm variant matrix as [`SweepVariant`]s:
1772/// `K{1..=4} × topology{4} × basis{periodic, linear}` at the color-arm shape
1773/// (n=180, p=5120). `d` and `basis_cols` follow the intrinsic-rank convention
1774/// (periodic ⇒ d=2, basis_cols=8; linear ⇒ d=1, basis_cols=2). Exposed so the
1775/// pyffi battery seam can quote cross-fit throughput on the real shape matrix
1776/// (fixture frames) before the per-cell real-slab fits are wired through.
1777#[must_use]
1778pub fn color_arm_variant_matrix() -> Vec<SweepVariant> {
1779    let topologies = ["euclidean", "circle", "torus", "sphere"];
1780    let mut variants = Vec::with_capacity(4 * topologies.len() * 2);
1781    for k in 1..=4u64 {
1782        for (t_idx, _topology) in topologies.iter().enumerate() {
1783            // periodic (2 harmonics) and linear basis arms.
1784            for &(d, basis_cols, basis_tag) in &[(2usize, 8usize, 0u64), (1usize, 2usize, 1u64)] {
1785                let mut dim = DeviceResidentArrowShape::color_arm();
1786                dim.d = d;
1787                dim.basis_cols = basis_cols;
1788                let seed = 0x1017_C010_0000_0000 ^ (k << 16) ^ ((t_idx as u64) << 8) ^ basis_tag;
1789                variants.push(SweepVariant { dim, seed });
1790            }
1791        }
1792    }
1793    variants
1794}
1795
1796/// Certified per-fit parity for a variant sweep: the multiplexed (concurrent)
1797/// results must be bit-for-bit identical to the same fits run sequentially on
1798/// the same device, because independent fits' arithmetic does not depend on
1799/// scheduling. Returns the sequential throughput so the caller can report the
1800/// multiplex speedup (multiplexed fits/sec ÷ sequential fits/sec). Returns an
1801/// `Err` describing the first divergence so the harness fails loudly.
1802pub fn assert_sweep_parity_vs_sequential(
1803    variants: &[SweepVariant],
1804    opts: &DeviceResidentInnerOptions,
1805    multiplexed: &[Result<MultiplexedFit, DeviceResidentArrowError>],
1806) -> Result<SweepThroughput, String> {
1807    let workspaces = build_sweep_workspaces(variants).map_err(|e| e.to_string())?;
1808    let start = std::time::Instant::now();
1809    let sequential = run_resident_fits_sequential(&workspaces, opts);
1810    let wall_seconds = start.elapsed().as_secs_f64();
1811    if sequential.len() != multiplexed.len() {
1812        return Err(format!(
1813            "sweep parity: length mismatch seq={} mux={}",
1814            sequential.len(),
1815            multiplexed.len()
1816        ));
1817    }
1818    for (idx, (seq, mux)) in sequential.iter().zip(multiplexed.iter()).enumerate() {
1819        match (seq, mux) {
1820            (Ok(s), Ok(m)) => {
1821                if s.outcome.t.as_slice() != m.outcome.t.as_slice()
1822                    || s.outcome.beta.as_slice() != m.outcome.beta.as_slice()
1823                    || s.outcome.objective.to_bits() != m.outcome.objective.to_bits()
1824                {
1825                    return Err(format!(
1826                        "sweep parity: fit {idx} multiplexed result differs from sequential"
1827                    ));
1828                }
1829            }
1830            (Err(_), Err(_)) => {}
1831            _ => {
1832                return Err(format!(
1833                    "sweep parity: fit {idx} success/failure disagrees seq-vs-mux"
1834                ));
1835            }
1836        }
1837    }
1838    let fits = variants.len();
1839    let succeeded = sequential.iter().filter(|r| r.is_ok()).count();
1840    Ok(SweepThroughput {
1841        fits,
1842        succeeded,
1843        wall_seconds,
1844        fits_per_second: (fits as f64) / wall_seconds.max(1e-9),
1845    })
1846}
1847
1848struct SplitMix64 {
1849    state: u64,
1850}
1851
1852impl SplitMix64 {
1853    const fn new(seed: u64) -> Self {
1854        Self { state: seed }
1855    }
1856
1857    fn next_u64(&mut self) -> u64 {
1858        gam_linalg::utils::splitmix64(&mut self.state)
1859    }
1860
1861    fn sample_signed(&mut self) -> f64 {
1862        let unit = (self.next_u64() >> 11) as f64 / ((1_u64 << 53) as f64);
1863        2.0 * unit - 1.0
1864    }
1865}
1866
1867#[cfg(test)]
1868mod tests {
1869    use super::*;
1870    use ndarray::Array2;
1871
1872    /// Build a small, strongly diagonally-dominant resident frame whose dense
1873    /// reference factorisation is well-conditioned. The objective minimiser is
1874    /// `z* = H^{-1} g₀`, which the inner loop must reach.
1875    fn small_fixture(seed: u64) -> DeviceResidentArrowWorkspace {
1876        // batch (n) = 8 clears the device dispatch floor
1877        // (`small_dense_batched_potrf_min_batch = 8`) so that on a CUDA host
1878        // `upload_resident_buffers` actually binds a device and
1879        // `device_resident()` is TRUE — otherwise the device-resident parity
1880        // branch of `device_resident_fit_matches_cpu_reference` is dead on real
1881        // GPU hardware (the route declines for batch < 8, so the test only ever
1882        // exercised the CPU-decline branch and never validated the device loop).
1883        let shape = DeviceResidentArrowShape {
1884            n: 8,
1885            p: 4,
1886            basis_cols: 2,
1887            d: 2,
1888        };
1889        let mut rng = SplitMix64::new(seed);
1890        let target_x = vec![0.0_f64; shape.target_len()];
1891        let basis_values = vec![0.5_f64; shape.basis_len()];
1892        let gate_activations = vec![1.0_f64; shape.basis_len()];
1893
1894        let mut row_hessian_slabs = vec![0.0_f64; shape.row_hessian_len()];
1895        let mut row_cross_slabs = vec![0.0_f64; shape.row_cross_len()];
1896        let mut row_gradient_slabs = vec![0.0_f64; shape.row_gradient_len()];
1897        for i in 0..shape.n {
1898            let h = i * shape.d * shape.d;
1899            row_hessian_slabs[h] = 5.0 + 0.1 * rng.sample_signed();
1900            row_hessian_slabs[h + 1] = 0.05 * rng.sample_signed();
1901            row_hessian_slabs[h + 2] = row_hessian_slabs[h + 1];
1902            row_hessian_slabs[h + 3] = 4.0 + 0.1 * rng.sample_signed();
1903            let b = i * shape.d * shape.p;
1904            for j in 0..shape.p {
1905                row_cross_slabs[b + j] = 0.01 * rng.sample_signed();
1906                row_cross_slabs[b + shape.p + j] = 0.01 * rng.sample_signed();
1907            }
1908            let g = i * shape.d;
1909            row_gradient_slabs[g] = rng.sample_signed();
1910            row_gradient_slabs[g + 1] = rng.sample_signed();
1911        }
1912        let mut border_hessian = vec![0.0_f64; shape.border_hessian_len()];
1913        for r in 0..shape.p {
1914            border_hessian[r * shape.p + r] = 6.0 + 0.1 * rng.sample_signed();
1915        }
1916        let border_gradient: Vec<f64> = (0..shape.p).map(|_| rng.sample_signed()).collect();
1917
1918        DeviceResidentArrowWorkspace::new(
1919            shape,
1920            target_x,
1921            basis_values,
1922            gate_activations,
1923            DeviceResidentArrowSlabs {
1924                row_hessian_slabs,
1925                row_cross_slabs,
1926                row_gradient_slabs,
1927                border_hessian,
1928                border_gradient,
1929            },
1930        )
1931        .expect("small resident fixture must validate")
1932    }
1933
1934    /// Dense `H z` for the resident frame (independent of the arrow path),
1935    /// used to confirm the inner-loop fixed point is the true stationary point.
1936    fn dense_hz(
1937        ws: &DeviceResidentArrowWorkspace,
1938        sys: &ArrowSchurSystem,
1939    ) -> (Array2<f64>, Array1<f64>) {
1940        let shape = ws.shape;
1941        let total = shape.n * shape.d + shape.p;
1942        let mut h = Array2::<f64>::zeros((total, total));
1943        let mut g0 = Array1::<f64>::zeros(total);
1944        for i in 0..shape.n {
1945            let base = i * shape.d;
1946            for r in 0..shape.d {
1947                for c in 0..shape.d {
1948                    h[[base + r, base + c]] = sys.rows[i].htt[[r, c]];
1949                }
1950                for c in 0..shape.p {
1951                    let v = sys.rows[i].htbeta[[r, c]];
1952                    h[[base + r, shape.n * shape.d + c]] = v;
1953                    h[[shape.n * shape.d + c, base + r]] = v;
1954                }
1955                g0[base + r] = sys.rows[i].gt[r];
1956            }
1957        }
1958        for r in 0..shape.p {
1959            for c in 0..shape.p {
1960                h[[shape.n * shape.d + r, shape.n * shape.d + c]] = sys.hbb[[r, c]];
1961            }
1962            g0[shape.n * shape.d + r] = sys.gb[r];
1963        }
1964        (h, g0)
1965    }
1966
1967    #[test]
1968    fn cpu_inner_loop_reaches_quadratic_minimiser() {
1969        let ws = small_fixture(0xABCD_0001);
1970        let opts = DeviceResidentInnerOptions::default();
1971        let outcome = ws.cpu_reference_fit(&opts).expect("cpu fit");
1972        assert!(
1973            outcome.converged,
1974            "inner loop must converge on a PD quadratic"
1975        );
1976
1977        // The stationary point satisfies H z* = g₀; verify the residual is zero.
1978        let base = ws.to_arrow_system();
1979        let (h, g0) = dense_hz(&ws, &base);
1980        let total = ws.shape.n * ws.shape.d + ws.shape.p;
1981        let mut z = Array1::<f64>::zeros(total);
1982        for r in 0..ws.shape.n * ws.shape.d {
1983            z[r] = outcome.t[r];
1984        }
1985        for c in 0..ws.shape.p {
1986            z[ws.shape.n * ws.shape.d + c] = outcome.beta[c];
1987        }
1988        let hz = h.dot(&z);
1989        let mut max_resid = 0.0_f64;
1990        for r in 0..total {
1991            max_resid = max_resid.max((hz[r] - g0[r]).abs());
1992        }
1993        assert!(
1994            max_resid < 1e-9,
1995            "inner loop fixed point must solve H z = g0; residual {max_resid:e}"
1996        );
1997    }
1998
1999    #[test]
2000    fn cpu_multiplex_matches_sequential_bit_identical() {
2001        let seeds = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
2002        let opts = DeviceResidentInnerOptions::default();
2003
2004        let seq_workspaces: Vec<_> = seeds.iter().map(|&s| small_fixture(s)).collect();
2005        let sequential: Vec<_> = seq_workspaces
2006            .iter()
2007            .map(|ws| ws.cpu_reference_fit(&opts).expect("seq cpu fit"))
2008            .collect();
2009
2010        let mux_workspaces: Vec<_> = seeds.iter().map(|&s| small_fixture(s)).collect();
2011        let multiplexed = run_resident_fits_multiplexed_with(mux_workspaces, opts, |ws, opts| {
2012            ws.cpu_reference_fit(opts)
2013        })
2014        .expect("multiplexed cpu fits");
2015
2016        assert_eq!(sequential.len(), multiplexed.len());
2017        for (seq, mux) in sequential.iter().zip(multiplexed.iter()) {
2018            let mux = mux.as_ref().expect("mux fit ok");
2019            // Independent fits: scheduling cannot change the numbers, so the
2020            // parallel result must be bit-for-bit identical to sequential.
2021            assert_eq!(seq.t.as_slice(), mux.outcome.t.as_slice());
2022            assert_eq!(seq.beta.as_slice(), mux.outcome.beta.as_slice());
2023            assert_eq!(seq.objective.to_bits(), mux.outcome.objective.to_bits());
2024        }
2025    }
2026
2027    /// #1017 Phase 3 residency parity. On a CUDA host the device-resident inner
2028    /// loop (`device_fit`, which keeps the Hessian factors on-device across
2029    /// iterations via `ResidentArrowFrameHandle`) must reach the same minimiser
2030    /// as the fully independent CPU dense-reference loop (`cpu_reference_fit`,
2031    /// which re-factors per iterate). On a CPU-only host the resident path must
2032    /// decline cleanly (`Unavailable`) rather than silently disagree, and the
2033    /// resident-frame handle construction must likewise decline — so the gate is
2034    /// meaningful on the build box and the wall-clock arm runs on the GPU node.
2035    #[test]
2036    fn device_resident_fit_matches_cpu_reference() {
2037        let ws = small_fixture(0x5AE_1017);
2038        let opts = DeviceResidentInnerOptions::default();
2039
2040        // CPU reference (re-factors per iterate) — always available.
2041        let cpu = ws.cpu_reference_fit(&opts).expect("cpu reference fit");
2042        assert!(cpu.converged, "cpu reference must converge on PD quadratic");
2043
2044        let base = ws.to_arrow_system();
2045
2046        println!(
2047            "DIAG_RESIDENT device_resident={} shape=({},{},{})",
2048            ws.device_resident(),
2049            ws.shape.n,
2050            ws.shape.d,
2051            ws.shape.p
2052        );
2053        if ws.device_resident() {
2054            // Resident device loop: factors stay on-device across iterations.
2055            let dev = ws.device_fit(&opts).expect("device resident fit");
2056            assert_eq!(
2057                dev.execution_path,
2058                ExecutionPath::GpuResidentFull,
2059                "device_fit must report the full device-resident execution path"
2060            );
2061            assert!(dev.converged, "device resident loop must converge");
2062
2063            // Certified-refinement parity (#1014): the resident path and the
2064            // independent CPU path solve the same quadratic, so their minimisers
2065            // agree to a tight relative tolerance. The resident path differs from
2066            // the reference only by SKIPPING re-derivation of g-independent
2067            // factor work, not by changing the arithmetic.
2068            let t_scale = cpu.t.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2069            let b_scale = cpu.beta.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2070            let mut max_rel = 0.0_f64;
2071            for (a, b) in dev.t.iter().zip(cpu.t.iter()) {
2072                max_rel = max_rel.max((a - b).abs() / t_scale);
2073            }
2074            for (a, b) in dev.beta.iter().zip(cpu.beta.iter()) {
2075                max_rel = max_rel.max((a - b).abs() / b_scale);
2076            }
2077            assert!(
2078                max_rel < 1e-9,
2079                "resident device fit must match CPU reference (rel {max_rel:e})"
2080            );
2081
2082            // The public single-iteration API must use the same resident-frame
2083            // mechanism as device_fit (constant factors held resident, only the
2084            // gradient uploaded), not the re-uploading arrow-Schur entry. Because
2085            // it is a SINGLE solve at one frozen frame, the truthful path is
2086            // `GpuResidentLinearization`, not the full inner-loop `GpuResidentFull`.
2087            let one = ws
2088                .one_inner_iteration(opts.initial_ridge_t, opts.initial_ridge_beta)
2089                .expect("resident one_inner_iteration");
2090            assert_eq!(
2091                one.execution_path,
2092                ExecutionPath::GpuResidentLinearization,
2093                "one_inner_iteration must report resident single-linearization residency"
2094            );
2095
2096            // The resident frame's single-gradient solve must also match a full
2097            // independent solve at the same gradient (the per-iterate contract).
2098            // `ResidentArrowFrameHandle` is UNINHABITED on CPU-only hosts, so a
2099            // `let … .expect()` binding marks everything after it unreachable
2100            // under `-D warnings`; the consuming assertions therefore live
2101            // inside the `Ok` arm (dead match arms are lint-exempt), exactly
2102            // like the production consumers.
2103            match crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
2104                &base,
2105                opts.initial_ridge_t,
2106                opts.initial_ridge_beta,
2107            ) {
2108                Err(err) => panic!("resident frame must build on CUDA host: {err:?}"),
2109                Ok(frame) => {
2110                    let g_t: Vec<f64> = base
2111                        .rows
2112                        .iter()
2113                        .flat_map(|r| r.gt.iter().copied())
2114                        .collect();
2115                    let g_beta: Vec<f64> = base.gb.iter().copied().collect();
2116                    let resident_sol = frame
2117                        .solve_gradient(&g_t, &g_beta)
2118                        .expect("resident single-gradient solve");
2119                    let full =
2120                        crate::gpu_kernels::arrow_schur::solve_arrow_newton_step_dense_reference(
2121                            &base,
2122                            opts.initial_ridge_t,
2123                            opts.initial_ridge_beta,
2124                        )
2125                        .expect("dense reference single solve");
2126                    let mut max_step_rel = 0.0_f64;
2127                    let step_scale = full
2128                        .delta_t
2129                        .iter()
2130                        .chain(full.delta_beta.iter())
2131                        .fold(1.0_f64, |m, &v| m.max(v.abs()));
2132                    for (a, b) in resident_sol.delta_t.iter().zip(full.delta_t.iter()) {
2133                        max_step_rel = max_step_rel.max((a - b).abs() / step_scale);
2134                    }
2135                    for (a, b) in resident_sol.delta_beta.iter().zip(full.delta_beta.iter()) {
2136                        max_step_rel = max_step_rel.max((a - b).abs() / step_scale);
2137                    }
2138                    assert!(
2139                        max_step_rel < 1e-9,
2140                        "resident solve_gradient must match full dense reference step \
2141                         (rel {max_step_rel:e})"
2142                    );
2143                }
2144            }
2145
2146            // The re-uploading GPU loop (residency baseline) must reach the same
2147            // minimiser as both the resident loop and the CPU reference.
2148            let reup = ws
2149                .device_reupload_fit(&opts)
2150                .expect("device re-uploading fit");
2151            assert_eq!(
2152                reup.execution_path,
2153                ExecutionPath::GpuReupload,
2154                "device_reupload_fit must report the re-uploading device path"
2155            );
2156            assert!(reup.converged, "re-uploading loop must converge");
2157            let mut max_reup_rel = 0.0_f64;
2158            for (a, b) in reup.t.iter().zip(cpu.t.iter()) {
2159                max_reup_rel = max_reup_rel.max((a - b).abs() / t_scale);
2160            }
2161            for (a, b) in reup.beta.iter().zip(cpu.beta.iter()) {
2162                max_reup_rel = max_reup_rel.max((a - b).abs() / b_scale);
2163            }
2164            assert!(
2165                max_reup_rel < 1e-9,
2166                "re-uploading GPU fit must match CPU reference (rel {max_reup_rel:e})"
2167            );
2168        } else {
2169            // The fixture is sized (batch = 8) to clear the device dispatch floor,
2170            // so on a host WITH a CUDA runtime `device_resident()` must be true and
2171            // we take the device branch above. Reaching this branch with a runtime
2172            // present means the device binding silently failed — which would mask a
2173            // real upload/dispatch fault behind the CPU-decline path (the
2174            // device-PCG skip-pass class, eee12f6b2). Fail loud unless this is a
2175            // genuinely CPU-only host.
2176            assert!(
2177                gam_gpu::device_runtime::GpuRuntime::resolve(gam_gpu::GpuPolicy::Auto)
2178                    .unwrap_or_else(|error| {
2179                        panic!("GPU probe fault in resident SAE engagement test: {error}")
2180                    })
2181                    .is_none(),
2182                "device_resident() is false on a host WITH a CUDA runtime present, \
2183                 despite a floor-clearing fixture (batch=8): the resident device \
2184                 buffers failed to bind — a real device fault, not a CPU-only skip."
2185            );
2186            // CPU-only host: the resident path must decline, not disagree.
2187            let dev = ws.device_fit(&opts);
2188            assert!(
2189                matches!(dev, Err(DeviceResidentArrowError::Unavailable { .. })),
2190                "device_fit must report Unavailable on a CPU-only host, got {dev:?}"
2191            );
2192            let reup = ws.device_reupload_fit(&opts);
2193            assert!(
2194                matches!(reup, Err(DeviceResidentArrowError::Unavailable { .. })),
2195                "device_reupload_fit must report Unavailable on a CPU-only host, got {reup:?}"
2196            );
2197            let frame = crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(
2198                &base,
2199                opts.initial_ridge_t,
2200                opts.initial_ridge_beta,
2201            );
2202            assert!(
2203                frame.is_err(),
2204                "resident frame construction must decline on a CPU-only host"
2205            );
2206        }
2207    }
2208
2209    /// #1017 fit-path parity (CPU-runnable). The resident inner solve and the
2210    /// PRODUCTION arrow-Schur inner solve (`solve_arrow_newton_step_core`, the
2211    /// entry the SAE joint fit reaches through `solve_with_lm_escalation_inner`)
2212    /// must solve the SAME bordered-quadratic Newton system.
2213    ///
2214    /// This is the cross-implementation parity behind wiring the device seam into
2215    /// the SAE inner loop: `solve_arrow_newton_step_core` carries the #1017
2216    /// device-Schur seam (and falls through bit-identically to its CPU path off
2217    /// CUDA), and the resident workspace's `cpu_reference_fit` converges the same
2218    /// quadratic `φ(z) = ½‖X‖² + ½ zᵀH z − g₀ᵀ z`. The resident converged iterate
2219    /// `z*` is the stationary point `H z* = g₀`; the production arrow path solves
2220    /// the Newton system `H Δ = −g₀` from `z = 0`, so its step is
2221    /// `Δ = −H⁻¹ g₀ = −z*`. With `H` PD the exact relationship is therefore
2222    /// `Δ = −z*`; asserting it pins that routing the production inner solve through
2223    /// the device-aware `_core` (which a GPU host then offloads) solves the
2224    /// identical system the resident loop does. Runs on the CPU build box — no
2225    /// CUDA required.
2226    #[test]
2227    fn resident_inner_solve_matches_production_arrow_core() {
2228        use crate::arrow_schur::{ArrowSolveOptions, solve_arrow_newton_step_core};
2229
2230        let ws = small_fixture(0x1017_F17);
2231        let opts = DeviceResidentInnerOptions::default();
2232
2233        // Resident workspace converged fit (re-factoring CPU reference loop).
2234        let resident = ws.cpu_reference_fit(&opts).expect("resident cpu fit");
2235        assert!(
2236            resident.converged,
2237            "resident reference must converge on the PD quadratic"
2238        );
2239
2240        // Production arrow path: one Newton step on the same system from z = 0.
2241        // `_core` is the device-aware entry; on this CPU box it runs the dense
2242        // CPU solve, the exact path the GPU host would fall back to on decline.
2243        let sys = ws.to_arrow_system();
2244        let (delta_t, delta_beta, _diag) = solve_arrow_newton_step_core(
2245            &sys,
2246            opts.initial_ridge_t,
2247            opts.initial_ridge_beta,
2248            &ArrowSolveOptions::direct(),
2249        )
2250        .expect("production arrow-core solve");
2251
2252        // The Newton step from z = 0 is Δ = −H⁻¹g₀ = −z*, where z* is the resident
2253        // converged iterate (H z* = g₀, the invariant
2254        // `cpu_inner_loop_reaches_quadratic_minimiser` pins directly). With H PD
2255        // the relationship is exact, so Δ + z* = 0 to factorisation tolerance.
2256        let t_scale = resident.t.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2257        let b_scale = resident.beta.iter().fold(1.0_f64, |m, &v| m.max(v.abs()));
2258        // #1399: report the t-block and beta-block mismatch SEPARATELY (not one
2259        // fused scalar). The two halves localise a divergence: a t-block-only gap
2260        // points at the per-row factor / row gradient assembly, a beta-block gap
2261        // at the border Schur path — turning the opaque overall rel into an
2262        // actionable signal for the resident-vs-production parity divergence.
2263        let mut max_rel_t = 0.0_f64;
2264        let mut worst_t: Option<(usize, f64, f64)> = None;
2265        for (i, (prod, res)) in delta_t.iter().zip(resident.t.iter()).enumerate() {
2266            let rel = (prod + res).abs() / t_scale;
2267            if rel > max_rel_t {
2268                max_rel_t = rel;
2269                worst_t = Some((i, *prod, *res));
2270            }
2271        }
2272        let mut max_rel_b = 0.0_f64;
2273        let mut worst_b: Option<(usize, f64, f64)> = None;
2274        for (i, (prod, res)) in delta_beta.iter().zip(resident.beta.iter()).enumerate() {
2275            let rel = (prod + res).abs() / b_scale;
2276            if rel > max_rel_b {
2277                max_rel_b = rel;
2278                worst_b = Some((i, *prod, *res));
2279            }
2280        }
2281        let max_rel = max_rel_t.max(max_rel_b);
2282        assert!(
2283            max_rel < 1e-9,
2284            "production arrow-core Newton step must be −(resident converged fit) on \
2285             the same quadratic; wiring the device seam into the SAE inner loop must \
2286             not change the system being solved. rel_t={max_rel_t:e} (worst {worst_t:?}: \
2287             Δt+t* must be 0), rel_beta={max_rel_b:e} (worst {worst_b:?}: Δβ+β* must \
2288             be 0). A t-only gap implicates the per-row factor / row-gradient \
2289             assembly; a β-only gap the border Schur path."
2290        );
2291    }
2292
2293    /// #1017 deliverable 3: across-OUTER residency. A sequence of outer
2294    /// evaluations whose Hessian operator is unchanged (only the base gradient
2295    /// moves) must share ONE resident frame — exactly one frame build for the
2296    /// whole sweep — and produce results bit-identical to per-outer-independent
2297    /// fits (each with a fresh frame). On a CPU-only host this asserts the
2298    /// reference path's outer-sequence wiring is consistent; on the A100 it
2299    /// proves the across-outer factor amortization fires AND stays exact.
2300    #[test]
2301    fn outer_sequence_reuses_frame_and_matches_independent() {
2302        let ws = super::color_arm_fixture().expect("color_arm fixture");
2303        let opts = DeviceResidentInnerOptions::default();
2304        let n = ws.shape.n;
2305        let d = ws.shape.d;
2306        let p = ws.shape.p;
2307
2308        // Three "outer" evaluations: same operator, distinct base gradients (the
2309        // moving linearization point). These stand in for consecutive outer REML
2310        // evaluations at a frozen gate/basis frame.
2311        let outers: Vec<(Vec<f64>, Vec<f64>)> = (0..3)
2312            .map(|s| {
2313                let g_t: Vec<f64> = (0..n * d)
2314                    .map(|i| 0.01 * (((i + 3 * s) as f64) * 0.002).sin())
2315                    .collect();
2316                let g_beta: Vec<f64> = (0..p)
2317                    .map(|j| 0.001 * (((j + 11 * s) as f64) * 0.0009).cos())
2318                    .collect();
2319                (g_t, g_beta)
2320            })
2321            .collect();
2322
2323        // Per-outer-independent reference (fresh frame each outer) via the CPU
2324        // path, which runs on any host.
2325        let independent = ws
2326            .cpu_reference_outer_sequence(&outers, &opts)
2327            .expect("cpu reference outer sequence");
2328        assert_eq!(independent.outers.len(), outers.len());
2329
2330        if ws.device_resident() {
2331            // Device across-outer sweep: ONE frame for all three outers.
2332            let shared = ws
2333                .device_fit_outer_sequence(&outers, &opts)
2334                .expect("device outer sequence");
2335            assert_eq!(
2336                shared.frame_builds,
2337                1,
2338                "across-outer residency must build the resident frame exactly once \
2339                 for an unchanged operator (got {} builds over {} outers) — a count \
2340                 > 1 means the frame was needlessly re-factored per outer",
2341                shared.frame_builds,
2342                outers.len()
2343            );
2344            // Bit-parity: sharing the factor across outers must not change the
2345            // numbers vs per-outer-independent device fits.
2346            for (idx, (sh, ind)) in shared
2347                .outers
2348                .iter()
2349                .zip(independent.outers.iter())
2350                .enumerate()
2351            {
2352                let scale = ind
2353                    .t
2354                    .iter()
2355                    .chain(ind.beta.iter())
2356                    .fold(1.0_f64, |m, &v| m.max(v.abs()));
2357                let mut max_rel = 0.0_f64;
2358                for (a, b) in sh.t.iter().zip(ind.t.iter()) {
2359                    max_rel = max_rel.max((a - b).abs() / scale);
2360                }
2361                for (a, b) in sh.beta.iter().zip(ind.beta.iter()) {
2362                    max_rel = max_rel.max((a - b).abs() / scale);
2363                }
2364                assert!(
2365                    max_rel < 1e-9,
2366                    "outer {idx}: across-outer-shared frame must match independent fit \
2367                     (rel {max_rel:e})"
2368                );
2369            }
2370            println!(
2371                "[#1017 outer-seq color_arm] outers={} frame_builds={} (across-outer factor \
2372                 amortized) parity<1e-9 OK",
2373                outers.len(),
2374                shared.frame_builds
2375            );
2376        } else {
2377            println!(
2378                "[#1017 outer-seq color_arm] no CUDA device — across-outer residency skipped; \
2379                 run on the GPU node to assert frame_builds==1 + device parity"
2380            );
2381        }
2382    }
2383
2384    /// #1017 residency-isolating per-solve bench. A full-fit wall-clock bench
2385    /// runs an exact quadratic that converges
2386    /// in ONE Newton step, so the resident frame is built once and solved once —
2387    /// the across-iteration amortization (factor `D`/`B`/Schur once, reuse for
2388    /// every gradient) has nothing to amortize over and the measured speedup is
2389    /// only the single-solve `D`/`B` upload saving.
2390    ///
2391    /// This bench isolates the residency lever the way the production inner loop
2392    /// actually exercises it: at a frozen gate/basis frame the Hessian blocks are
2393    /// CONSTANT and the SAE inner Newton takes MANY gradient solves against them.
2394    /// It therefore times
2395    ///   * RESIDENT: build the [`crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle`]
2396    ///     ONCE, then `N` `solve_gradient` calls (upload only the `O(n·d + k)`
2397    ///     gradient per solve; no POTRF, no `D`/`B` re-upload);
2398    ///   * REUPLOAD: `N` `solve_arrow_newton_step` calls (re-pack/upload `D`/`B`/`g`
2399    ///     and re-run the per-row POTRF + border Schur factor every call).
2400    /// Both produce bit-identical steps; the ratio is the pure across-iteration
2401    /// residency speedup, which is what #1017 Phase 3 buys per inner iteration.
2402    /// `N` mirrors a realistic SAE inner-Newton iteration count. CPU-only hosts
2403    /// print a skip line. Run with `--nocapture`.
2404    #[test]
2405    fn gpu_residency_per_solve_bench() {
2406        use std::time::Instant;
2407        const N_SOLVES: usize = 24;
2408        for (label, ws) in [
2409            ("color_arm", super::color_arm_fixture()),
2410            ("qwen_non_gating", super::qwen_non_gating_fixture()),
2411        ] {
2412            let ws = ws.expect("bench fixture must validate");
2413            let base = ws.to_arrow_system();
2414            // A family of distinct gradients standing in for the per-iterate
2415            // residual r(z) = H z − g₀ the inner loop feeds. Distinct gradients
2416            // make the reupload path redo the (g-independent) factor work each
2417            // time — exactly the waste residency removes.
2418            let n = ws.shape.n;
2419            let d = ws.shape.d;
2420            let p = ws.shape.p;
2421            let gradients: Vec<(Vec<f64>, Vec<f64>)> = (0..N_SOLVES)
2422                .map(|s| {
2423                    let g_t: Vec<f64> =
2424                        (0..n * d).map(|i| ((i + s) as f64 * 0.001).sin()).collect();
2425                    let g_beta: Vec<f64> = (0..p)
2426                        .map(|j| ((j + 7 * s) as f64 * 0.0007).cos())
2427                        .collect();
2428                    (g_t, g_beta)
2429                })
2430                .collect();
2431
2432            if !ws.device_resident() {
2433                println!(
2434                    "[#1017 per-solve {label}] no CUDA device — {N_SOLVES} solves skipped; \
2435                     run on the GPU node for the across-iteration residency speedup"
2436                );
2437                continue;
2438            }
2439
2440            // Build the resident frame ONCE (its factor cost is the across-
2441            // iteration amortization the bench is measuring, so it is timed
2442            // separately from the per-solve loop).
2443            let t_build = Instant::now();
2444            // `ResidentArrowFrameHandle` is UNINHABITED on CPU-only hosts: a
2445            // `let … .expect()` binding marks everything after it unreachable
2446            // under `-D warnings`, so the bench body consumes the frame inside
2447            // the `Ok` arm — the same pattern as the production consumers.
2448            match crate::gpu_kernels::arrow_schur::ResidentArrowFrameHandle::new(&base, 0.0, 0.0) {
2449                Err(err) => panic!("resident frame must build on CUDA host: {err:?}"),
2450                Ok(frame) => {
2451                    let frame_build_ms = t_build.elapsed().as_secs_f64() * 1e3;
2452
2453                    // Warm-up: one solve on each path before timing so the residency
2454                    // ratio reflects steady-state per-iterate cost, not the one-time
2455                    // NVRTC/cuSOLVER handle init, module JIT, or first-touch device
2456                    // allocation (those are paid once per process, not per inner
2457                    // iteration). The production inner loop pays them once and then runs
2458                    // MANY solves, which is exactly the regime this assertion guards.
2459                    frame
2460                        .solve_gradient(&gradients[0].0, &gradients[0].1)
2461                        .expect("resident warm-up solve");
2462                    {
2463                        let mut sys = ws.to_arrow_system();
2464                        for (i, row) in sys.rows.iter_mut().enumerate() {
2465                            for r in 0..d {
2466                                row.gt[r] = gradients[0].0[i * d + r];
2467                            }
2468                        }
2469                        for (j, gb) in sys.gb.iter_mut().enumerate() {
2470                            *gb = gradients[0].1[j];
2471                        }
2472                        sys.refresh_row_hessian_fingerprint();
2473                        crate::gpu_kernels::arrow_schur::solve_arrow_newton_step(&sys, 0.0, 0.0)
2474                            .expect("reupload warm-up solve");
2475                    }
2476
2477                    // RESIDENT: reuse the (already-built, already-warmed) frame for N
2478                    // gradient-only solves. Times ONLY the per-iterate gradient solves —
2479                    // upload `O(n·d + k)` gradient, run the cheap residual path, read
2480                    // back `δ`. No POTRF, no `D`/`B` re-upload.
2481                    let t_res = Instant::now();
2482                    let mut resident_steps = Vec::with_capacity(N_SOLVES);
2483                    for (g_t, g_beta) in &gradients {
2484                        resident_steps.push(
2485                            frame
2486                                .solve_gradient(g_t, g_beta)
2487                                .expect("resident solve_gradient"),
2488                        );
2489                    }
2490                    let resident_ms = t_res.elapsed().as_secs_f64() * 1e3;
2491
2492                    // REUPLOAD: N full solves, each re-uploading D/B/g and re-factoring.
2493                    let t_reup = Instant::now();
2494                    let mut reupload_steps = Vec::with_capacity(N_SOLVES);
2495                    for (g_t, g_beta) in &gradients {
2496                        let mut sys = ws.to_arrow_system();
2497                        for (i, row) in sys.rows.iter_mut().enumerate() {
2498                            for r in 0..d {
2499                                row.gt[r] = g_t[i * d + r];
2500                            }
2501                        }
2502                        for (j, gb) in sys.gb.iter_mut().enumerate() {
2503                            *gb = g_beta[j];
2504                        }
2505                        sys.refresh_row_hessian_fingerprint();
2506                        reupload_steps.push(
2507                            crate::gpu_kernels::arrow_schur::solve_arrow_newton_step(
2508                                &sys, 0.0, 0.0,
2509                            )
2510                            .expect("reupload solve_arrow_newton_step"),
2511                        );
2512                    }
2513                    let reupload_ms = t_reup.elapsed().as_secs_f64() * 1e3;
2514
2515                    // Parity: resident and reupload steps must be bit-identical (same
2516                    // factor kernels; residency only skips re-deriving g-independent work).
2517                    let mut max_rel = 0.0_f64;
2518                    for (rs, us) in resident_steps.iter().zip(reupload_steps.iter()) {
2519                        let scale = us
2520                            .delta_t
2521                            .iter()
2522                            .chain(us.delta_beta.iter())
2523                            .fold(1.0_f64, |m, &v| m.max(v.abs()));
2524                        for (a, b) in rs.delta_t.iter().zip(us.delta_t.iter()) {
2525                            max_rel = max_rel.max((a - b).abs() / scale);
2526                        }
2527                        for (a, b) in rs.delta_beta.iter().zip(us.delta_beta.iter()) {
2528                            max_rel = max_rel.max((a - b).abs() / scale);
2529                        }
2530                    }
2531
2532                    let resident_per_solve = resident_ms / N_SOLVES as f64;
2533                    let reupload_per_solve = reupload_ms / N_SOLVES as f64;
2534                    let residency_speedup = reupload_ms / resident_ms.max(1e-9);
2535                    println!(
2536                        "[#1017 per-solve {label}] N={N_SOLVES} frame_build={frame_build_ms:.2}ms \
2537                 resident={resident_ms:.2}ms ({resident_per_solve:.3}ms/solve, \
2538                 grad-upload + warm factors) reupload={reupload_ms:.2}ms \
2539                 ({reupload_per_solve:.3}ms/solve, N factors + N D/B uploads) \
2540                 residency_speedup={residency_speedup:.2}x parity_rel={max_rel:e}"
2541                    );
2542                    assert!(
2543                        max_rel < 1e-9,
2544                        "{label}: resident per-solve steps must match reupload (rel {max_rel:e})"
2545                    );
2546
2547                    // #1017 deliverable 2: the residency amortization must actually fire
2548                    // on hardware — reusing the resident factors across iterations has to
2549                    // be STRICTLY cheaper per solve than re-uploading D/B/g and
2550                    // re-factoring every iterate. This is the core perf claim, asserted
2551                    // (not merely printed) so a regression that silently re-uploads, or a
2552                    // dispatch change that drops the resident path, fails the gate on the
2553                    // A100 instead of slipping through as a slower-but-green run.
2554                    //
2555                    // The `color_arm` shape (n=180, p=5120) is the decisive case: the
2556                    // per-solve reupload pays a 5120-wide border Schur factor + the
2557                    // `O(n·d·p)` cross-block upload every iterate, while the resident path
2558                    // pays only the `O(n·d + p)` gradient transfer and two border TRSMs.
2559                    // We require a clear >1.5x margin there. The `qwen_non_gating` shape
2560                    // (p=2048) has a smaller border so its margin is thinner; we still
2561                    // require a genuine speedup (>1x) but do not over-tighten it.
2562                    let min_speedup = if label == "color_arm" { 1.5 } else { 1.0 };
2563                    assert!(
2564                        residency_speedup > min_speedup,
2565                        "{label}: across-iteration residency must beat per-solve re-upload \
2566                 (residency_speedup={residency_speedup:.3}x, required >{min_speedup}x; \
2567                 resident {resident_per_solve:.3}ms/solve vs reupload \
2568                 {reupload_per_solve:.3}ms/solve over N={N_SOLVES} solves) — the resident \
2569                 frame either silently re-uploaded D/B or the dispatch dropped the \
2570                 amortized factor path"
2571                    );
2572                }
2573            }
2574        }
2575    }
2576
2577    /// #1017 Phase 4 variant sweep: an OLMo-battery-shaped matrix of independent
2578    /// fits (here K{1..4} × 3 basis widths = 12 color-arm variants) dispatched
2579    /// concurrently on one device. This is the cross-fit throughput lever — the
2580    /// fits are independent, so multiplexing changes only scheduling.
2581    fn battery_variant_matrix() -> Vec<super::SweepVariant> {
2582        let mut variants = Vec::new();
2583        // K is the topology rank; the battery races K{1..4}. Each K × basis cell
2584        // is an independent fit. Color-arm border, varied basis_cols per cell.
2585        for k in 1..=4u64 {
2586            for basis_cols in [4usize, 8, 12] {
2587                let mut dim = DeviceResidentArrowShape::color_arm();
2588                dim.basis_cols = basis_cols;
2589                variants.push(super::SweepVariant {
2590                    dim,
2591                    seed: 0x1017_0040_0000_0000 ^ (k << 8) ^ (basis_cols as u64),
2592                });
2593            }
2594        }
2595        variants
2596    }
2597
2598    /// Same K{1..4} × 3-basis battery matrix as [`battery_variant_matrix`], on
2599    /// a small border. The multiplex parity property is SCHEDULING invariance
2600    /// — it is independent of `p` — while the CPU reference oracle factors the
2601    /// full dense joint Hessian with a naive scalar Cholesky, O((n·d + p)³)
2602    /// per fit. At the color-arm border (p = 5120) that oracle alone cost the
2603    /// suite 67 minutes at 0% GPU (#1017 datum, 2026-07-20); at p = 256 the
2604    /// identical property costs sub-second. The wide border stays exercised on
2605    /// the device by [`gpu_multiplex_throughput_bench`], which keeps the full
2606    /// color-arm matrix.
2607    fn battery_variant_matrix_cpu_gate() -> Vec<super::SweepVariant> {
2608        battery_variant_matrix()
2609            .into_iter()
2610            .map(|mut variant| {
2611                variant.dim.p = 256;
2612                variant
2613            })
2614            .collect()
2615    }
2616
2617    /// Phase-4 parity: the multiplexed sweep must be bit-identical to running the
2618    /// same fits sequentially (CPU reference path here so the gate runs on the
2619    /// build box; the device path is exercised by the throughput bench on the a100).
2620    #[test]
2621    fn variant_sweep_multiplex_matches_sequential() {
2622        let variants = battery_variant_matrix_cpu_gate();
2623        let opts = DeviceResidentInnerOptions::default();
2624
2625        // Multiplexed via the CPU-reference runner so the gate is meaningful
2626        // without CUDA, exercising the exact run_topology_race_parallel plumbing.
2627        let workspaces =
2628            super::build_sweep_workspaces(&variants).expect("sweep workspaces must build");
2629        let multiplexed =
2630            super::run_resident_fits_multiplexed_with(workspaces, opts, |ws, opts| {
2631                ws.cpu_reference_fit(opts)
2632            })
2633            .expect("multiplexed cpu sweep");
2634
2635        let seq_workspaces =
2636            super::build_sweep_workspaces(&variants).expect("sweep workspaces must build");
2637        let sequential: Vec<_> = seq_workspaces
2638            .iter()
2639            .map(|ws| ws.cpu_reference_fit(&opts))
2640            .collect();
2641
2642        assert_eq!(multiplexed.len(), sequential.len());
2643        for (idx, (mux, seq)) in multiplexed.iter().zip(sequential.iter()).enumerate() {
2644            let mux = &mux.as_ref().unwrap().outcome;
2645            let seq = seq.as_ref().unwrap();
2646            assert_eq!(
2647                mux.t.as_slice(),
2648                seq.t.as_slice(),
2649                "variant {idx}: multiplexed t differs from sequential"
2650            );
2651            assert_eq!(
2652                mux.beta.as_slice(),
2653                seq.beta.as_slice(),
2654                "variant {idx}: multiplexed beta differs from sequential"
2655            );
2656            assert_eq!(
2657                mux.objective.to_bits(),
2658                seq.objective.to_bits(),
2659                "variant {idx}: multiplexed objective differs from sequential"
2660            );
2661        }
2662    }
2663
2664    /// #1017 Phase 4 throughput bench. On a CUDA host this dispatches the battery
2665    /// variant matrix concurrently on one device, asserts per-fit certified
2666    /// parity vs sequential, and prints the cross-fit throughput (multiplexed
2667    /// fits/sec vs sequential fits/sec — the single-a100 race speedup). On a
2668    /// CPU-only host it prints a skip line. Run with `--nocapture`.
2669    #[test]
2670    fn gpu_multiplex_throughput_bench() {
2671        let variants = battery_variant_matrix();
2672        let opts = DeviceResidentInnerOptions::default();
2673
2674        let probe = super::build_sweep_workspaces(&variants).expect("sweep workspaces");
2675        let any_device = probe.iter().any(|w| w.device_resident());
2676        if !any_device {
2677            println!(
2678                "[#1017 mux-bench] no CUDA device — {} variants (K1..4 x 3 basis) \
2679                 skipped; run on the GPU node for cross-fit throughput",
2680                variants.len()
2681            );
2682            return;
2683        }
2684
2685        let (results, mux_tp) =
2686            super::run_variant_sweep_multiplexed(&variants, opts).expect("multiplexed sweep");
2687        let seq_tp = super::assert_sweep_parity_vs_sequential(&variants, &opts, &results)
2688            .expect("sweep parity vs sequential must hold");
2689        println!(
2690            "[#1017 mux-bench] fits={} succeeded={} multiplexed={:.3}s ({:.1} fits/s) \
2691             sequential={:.3}s ({:.1} fits/s) cross-fit-speedup={:.2}x",
2692            mux_tp.fits,
2693            mux_tp.succeeded,
2694            mux_tp.wall_seconds,
2695            mux_tp.fits_per_second,
2696            seq_tp.wall_seconds,
2697            seq_tp.fits_per_second,
2698            mux_tp.fits_per_second / seq_tp.fits_per_second.max(1e-9),
2699        );
2700        assert_eq!(
2701            mux_tp.succeeded, mux_tp.fits,
2702            "all battery variants must fit successfully on device"
2703        );
2704    }
2705}