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