Skip to main content

fuel_dispatch/ranker/
cost_vector.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! `CostVector` — the per-path cost VECTOR the rankers produce, and
3//! the Pareto-dominance relation over it.
4//!
5//! Phase B PR-B1 of the "plan IS the graph" rebuild. This replaces the
6//! scalar `composite_ns` *sort key* with a structured vector, per
7//! `docs/architecture/04-optimization.md` §"Rankers and the cost
8//! model: a per-path cost vector, ranked" and decisions-log #7/#10.
9//!
10//! # The axes (kept low-dimensional and discrete on purpose)
11//!
12//! Keeping the ranker vector low-dimensional — ONE central time
13//! metric, memory as discrete tiers, discrete precision/accuracy — is
14//! what keeps the per-device Pareto frontier naturally small (~10²
15//! paths across a deep model) and lossless. See §"Bounding the
16//! frontier: Pareto per device + crowding cap".
17//!
18//! - **`time`** — ONE central wall-clock nanosecond metric. For B1
19//!   this is exactly the existing scalar:
20//!   `composite_ns(&static_cost).saturating_add(inbound_transfer_ns)`.
21//!   `t_min` ("fastest best case") is explicitly **NOT** an axis
22//!   (decisions-log #7/#10). The throughput-median-vs-latency-p99
23//!   *mode selection* the constitution describes is a later
24//!   refinement; for B1 there is one `time` number and the Judge's
25//!   distribution doesn't reach this struct yet.
26//! - **memory, per tier** — host-RAM and device-VRAM footprints
27//!   tracked separately (decisions-log #10: "memory as discrete
28//!   tiers"), attributed to the candidate's placement tier. Which
29//!   tier *binds* depends on the target machine, so they cannot be
30//!   collapsed into one number. Disk is reserved (the Layer-1
31//!   [`CostEstimate`] exposes no disk-footprint axis yet).
32//! - **`precision`** (digits) — derived from the candidate's
33//!   [`PrecisionGuarantee`]. Higher is better.
34//! - **`accuracy`** (ULP / rounding / monotonicity descriptor) —
35//!   derived from the candidate's [`PrecisionGuarantee`]. Higher is
36//!   better.
37//!
38//! # Orientation (documented once, used everywhere)
39//!
40//! Every axis is oriented so that **lower `time`, lower memory,
41//! higher `precision`, higher `accuracy` is better.** [`dominates`]
42//! and the tie-break order both read this orientation.
43//!
44//! [`dominates`]: CostVector::dominates
45//! [`CostEstimate`]: crate::fused::CostEstimate
46//! [`PrecisionGuarantee`]: crate::fused::PrecisionGuarantee
47
48use fuel_ir::DeviceLocation;
49
50use crate::fused::PrecisionGuarantee;
51
52use super::candidate::Candidate;
53use super::cost::{composite_ns, default_backend_rates};
54
55/// Discrete precision level in **decimal digits** — higher is better.
56///
57/// Derived from a [`PrecisionGuarantee`]'s relative-error bound when
58/// one is stated (`digits ≈ floor(-log10(max_relative))`), otherwise
59/// inferred from the strongest qualitative claim the guarantee makes.
60/// Kept as a small integer (not a continuous float) so the Pareto
61/// frontier stays discrete and small.
62pub type PrecisionDigits = u16;
63
64/// Discrete accuracy level — higher is better. Summarizes the
65/// guarantee's ULP / rounding / monotonicity character into one
66/// ordered descriptor so it can sit on the cost vector as a discrete
67/// axis. Higher means tighter-rounded / more deterministic.
68///
69/// The ladder is intentionally coarse (a handful of rungs), matching
70/// the constitution's "discrete accuracy levels."
71#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
72pub enum AccuracyClass {
73    /// No static bound and not bit-stable — the weakest claim
74    /// (e.g. scheduler-dependent subgroup reductions).
75    Unbounded,
76    /// Bit-stable on same hardware but no static ULP bound stated.
77    Stable,
78    /// A loose ULP bound is claimed (> 2 ULP) — vendor transcendental
79    /// territory.
80    BoundedLoose,
81    /// A tight ULP bound is claimed (1–2 ULP) — IEEE-754 elementwise.
82    BoundedTight,
83    /// Correctly-rounded: ULP = 0 (the reference grade).
84    CorrectlyRounded,
85}
86
87impl AccuracyClass {
88    /// Map to a small ordered integer (higher = better) for the
89    /// tie-break / dominance arithmetic.
90    pub fn rank(self) -> u8 {
91        match self {
92            AccuracyClass::Unbounded => 0,
93            AccuracyClass::Stable => 1,
94            AccuracyClass::BoundedLoose => 2,
95            AccuracyClass::BoundedTight => 3,
96            AccuracyClass::CorrectlyRounded => 4,
97        }
98    }
99}
100
101/// Per-tier memory footprint of a candidate's placement, in bytes.
102///
103/// Tracked per tier (not collapsed) because which tier binds depends
104/// on the target machine — a fast-but-VRAM-heavy path and a slow-but-
105/// host-light path are both legitimately Pareto-optimal. Disk is
106/// reserved for when the Layer-1 cost model grows a disk-footprint
107/// axis; today's [`CostEstimate`](crate::fused::CostEstimate) has none, so
108/// it stays zero.
109#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
110pub struct MemoryTiers {
111    /// Host-RAM footprint (bytes) attributed to a CPU-tier placement.
112    pub host_ram_bytes: u64,
113    /// Device-VRAM footprint (bytes) attributed to a GPU-tier
114    /// placement (CUDA / Vulkan / Metal).
115    pub device_vram_bytes: u64,
116}
117
118impl MemoryTiers {
119    /// Attribute `bytes` to the tier implied by `device`: GPU devices
120    /// land on VRAM, the CPU on host RAM. The Layer-1 cost model has
121    /// one footprint number (`bytes_moved`); placement decides which
122    /// tier it loads.
123    pub fn for_placement(device: DeviceLocation, bytes: u64) -> Self {
124        match device {
125            DeviceLocation::Cpu => MemoryTiers {
126                host_ram_bytes: bytes,
127                device_vram_bytes: 0,
128            },
129            DeviceLocation::Cuda { .. }
130            | DeviceLocation::Vulkan { .. }
131            | DeviceLocation::Metal { .. } => MemoryTiers {
132                host_ram_bytes: 0,
133                device_vram_bytes: bytes,
134            },
135        }
136    }
137
138    /// `true` iff `self` is no worse than `other` on every tier
139    /// (lower-or-equal bytes). Helper for [`CostVector::dominates`].
140    fn no_worse_than(&self, other: &MemoryTiers) -> bool {
141        self.host_ram_bytes <= other.host_ram_bytes
142            && self.device_vram_bytes <= other.device_vram_bytes
143    }
144
145    /// `true` iff `self` is strictly better than `other` on at least
146    /// one tier (strictly fewer bytes).
147    fn strictly_better_on_some(&self, other: &MemoryTiers) -> bool {
148        self.host_ram_bytes < other.host_ram_bytes
149            || self.device_vram_bytes < other.device_vram_bytes
150    }
151}
152
153/// The per-path cost vector the rankers produce. Pareto dominance
154/// (the per-device frontier in PR-B2) is over this whole vector;
155/// ties break **precision → accuracy → memory** (decisions-log #7).
156///
157/// Orientation: **lower `time`, lower `memory`, higher `precision`,
158/// higher `accuracy` is better.**
159#[derive(Copy, Clone, Debug, PartialEq, Eq)]
160pub struct CostVector {
161    /// ONE central wall-clock metric (ns). For B1: the old scalar
162    /// `composite_ns(static_cost) + inbound_transfer_ns`. Lower is
163    /// better. `t_min` is deliberately not an axis.
164    pub time: u64,
165    /// Per-tier memory footprint. Lower is better, per tier.
166    pub memory: MemoryTiers,
167    /// Numerical precision in decimal digits. Higher is better.
168    pub precision: PrecisionDigits,
169    /// Accuracy descriptor (ULP / rounding / monotonicity). Higher is
170    /// better.
171    pub accuracy: AccuracyClass,
172}
173
174impl CostVector {
175    /// Build a cost vector from a candidate.
176    ///
177    /// - `time` = [`composite_ns`]`(&static_cost) + inbound_transfer_ns`
178    ///   (saturating) — the exact B1 scalar.
179    /// - `memory` = the candidate's `static_cost.bytes_moved`
180    ///   attributed to the placement tier of `candidate.device`.
181    /// - `precision` / `accuracy` are derived from
182    ///   `candidate.precision`.
183    pub fn from_candidate(candidate: &Candidate) -> Self {
184        // The rank sees only `Candidate::backend` (no registered caps in
185        // hand), so it uses the per-backend throughput prior — kept
186        // consistent with the placement DP's authoritative caps figure
187        // via `default_backend_rates`.
188        let (compute_rate, mem_bandwidth) = default_backend_rates(candidate.backend);
189        let time = composite_ns(&candidate.static_cost, compute_rate, mem_bandwidth)
190            .saturating_add(candidate.inbound_transfer_ns);
191        let memory =
192            MemoryTiers::for_placement(candidate.device, candidate.static_cost.bytes_moved);
193        CostVector {
194            time,
195            memory,
196            precision: precision_digits(&candidate.precision),
197            accuracy: accuracy_class(&candidate.precision),
198        }
199    }
200
201    /// Pareto dominance over the whole vector.
202    ///
203    /// Returns `true` iff `self` is **no worse than `other` on every
204    /// axis** AND **strictly better on at least one** — the standard
205    /// Pareto relation. Per the documented orientation: lower `time`,
206    /// lower memory (per tier), higher `precision`, higher `accuracy`
207    /// is better.
208    ///
209    /// This is the relation PR-B2's per-device Pareto frontier will
210    /// filter on. It is intentionally *not* what selects the winner —
211    /// the winner stays time-first (see
212    /// [`super::alternative_set::AlternativeSet::rank_by_cost`]) so
213    /// realize behavior is preserved.
214    pub fn dominates(&self, other: &CostVector) -> bool {
215        let no_worse = self.time <= other.time
216            && self.memory.no_worse_than(&other.memory)
217            && self.precision >= other.precision
218            && self.accuracy >= other.accuracy;
219        if !no_worse {
220            return false;
221        }
222
223        self.time < other.time
224            || self.memory.strictly_better_on_some(&other.memory)
225            || self.precision > other.precision
226            || self.accuracy > other.accuracy
227    }
228
229    /// Total-order key that keeps the **winner time-first**: the
230    /// lowest-`time` candidate wins (preserving the old `composite_ns`
231    /// winner), with ties broken **precision → accuracy → memory**
232    /// (the constitution's order). Memory ties break on total bytes
233    /// (host + VRAM, saturating), lower first.
234    ///
235    /// Precision and accuracy are oriented "higher is better," so the
236    /// key negates them (via `Reverse`-style inversion) to sort
237    /// descending while `time` and memory sort ascending.
238    ///
239    /// Winner vs frontier: the winner (index 0 / arm-0) uses THIS
240    /// time-first order for realize-behavior preservation; the
241    /// *frontier retention* (PR-B2) will use [`Self::dominates`].
242    pub fn total_order_key(&self) -> (u64, u16, u8, u64) {
243        let total_memory = self
244            .memory
245            .host_ram_bytes
246            .saturating_add(self.memory.device_vram_bytes);
247        (
248            self.time,
249            // Higher precision should sort earlier → invert.
250            PrecisionDigits::MAX - self.precision,
251            // Higher accuracy should sort earlier → invert.
252            u8::MAX - self.accuracy.rank(),
253            total_memory,
254        )
255    }
256}
257
258/// Derive a discrete decimal-digit precision from a guarantee.
259///
260/// Priority:
261/// 1. A stated relative-error bound → `floor(-log10(max_relative))`
262///    (clamped to a sane ceiling); `max_relative == 0` means
263///    correctly-rounded → the ceiling.
264/// 2. ULP = 0 → correctly-rounded → the ceiling.
265/// 3. Bit-stable with no bound → a mid rung (deterministic but
266///    unquantified).
267/// 4. Otherwise (UNAUDITED / scheduler-dependent) → 0.
268///
269/// The ceiling (`MAX_DIGITS`) keeps the axis discrete and bounded;
270/// f64 carries ~15-16 significant digits, so 18 is a comfortable cap
271/// that never under-reports a correctly-rounded kernel.
272fn precision_digits(p: &PrecisionGuarantee) -> PrecisionDigits {
273    const MAX_DIGITS: PrecisionDigits = 18;
274    if let Some(rel) = p.max_relative {
275        if rel <= 0.0 {
276            return MAX_DIGITS;
277        }
278        let digits = (-rel.log10()).floor();
279        if digits.is_finite() && digits > 0.0 {
280            return (digits as u32).min(MAX_DIGITS as u32) as PrecisionDigits;
281        }
282        return 0;
283    }
284    if p.max_ulp == Some(0) {
285        return MAX_DIGITS;
286    }
287    if p.bit_stable_on_same_hardware {
288        // Deterministic but no quantified bound — a mid rung so a
289        // bit-stable kernel outranks an unaudited one without
290        // claiming reference-grade digits it never promised.
291        return MAX_DIGITS / 2;
292    }
293    0
294}
295
296/// Derive the discrete [`AccuracyClass`] from a guarantee's ULP /
297/// rounding / stability character.
298fn accuracy_class(p: &PrecisionGuarantee) -> AccuracyClass {
299    match p.max_ulp {
300        Some(0) => AccuracyClass::CorrectlyRounded,
301        Some(1..=2) => AccuracyClass::BoundedTight,
302        Some(_) => AccuracyClass::BoundedLoose,
303        None => {
304            if p.bit_stable_on_same_hardware {
305                AccuracyClass::Stable
306            } else {
307                AccuracyClass::Unbounded
308            }
309        }
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use crate::fused::{CostEstimate, PrecisionGuarantee};
317    use crate::kernel::{KernelCaps, OpParams};
318    use fuel_ir::probe::BackendId;
319    use fuel_ir::{DeviceLocation, Layout, Result};
320    use fuel_memory::Storage;
321    use std::sync::{Arc, RwLock};
322
323    fn noop(
324        _i: &[Arc<RwLock<Storage>>],
325        _o: &mut [Arc<RwLock<Storage>>],
326        _l: &[Layout],
327        _p: &OpParams,
328    ) -> Result<()> {
329        Ok(())
330    }
331
332    fn candidate(
333        device: DeviceLocation,
334        cost: CostEstimate,
335        inbound: u64,
336        precision: PrecisionGuarantee,
337    ) -> Candidate {
338        Candidate {
339            kernel: noop,
340            caps: KernelCaps::empty(),
341            backend: BackendId::Cpu,
342            device,
343            precision,
344            static_cost: cost,
345            inbound_transfer_ns: inbound,
346            op_params: OpParams::None,
347            coupling: Vec::new(),
348            kernel_source: "",
349            kernel_revision_hash: 0,
350        }
351    }
352
353    // ===== (a) CostVector is built correctly from a CostEstimate +
354    //            PrecisionGuarantee, axis by axis. =====
355
356    #[test]
357    fn cost_vector_time_axis_matches_old_scalar() {
358        // time == composite_ns(static_cost) + inbound_transfer_ns.
359        let cost = CostEstimate {
360            flops: 1000,
361            bytes_moved: 4000,
362            kernel_overhead_ns: 50,
363        };
364        let c = candidate(
365            DeviceLocation::Cpu,
366            cost,
367            777,
368            PrecisionGuarantee::REFERENCE,
369        );
370        let v = CostVector::from_candidate(&c);
371        // composite_ns: max(1000, 4000/4=1000) + 50 = 1050; + 777.
372        assert_eq!(v.time, 1050 + 777);
373    }
374
375    #[test]
376    fn cost_vector_memory_attributed_to_cpu_host_tier() {
377        let cost = CostEstimate {
378            flops: 0,
379            bytes_moved: 8192,
380            kernel_overhead_ns: 0,
381        };
382        let c = candidate(DeviceLocation::Cpu, cost, 0, PrecisionGuarantee::REFERENCE);
383        let v = CostVector::from_candidate(&c);
384        assert_eq!(v.memory.host_ram_bytes, 8192, "CPU loads host RAM");
385        assert_eq!(v.memory.device_vram_bytes, 0, "no VRAM on CPU");
386    }
387
388    #[test]
389    fn cost_vector_memory_attributed_to_gpu_vram_tier() {
390        let cost = CostEstimate {
391            flops: 0,
392            bytes_moved: 8192,
393            kernel_overhead_ns: 0,
394        };
395        let c = candidate(
396            DeviceLocation::Cuda { gpu_id: 0 },
397            cost,
398            0,
399            PrecisionGuarantee::REFERENCE,
400        );
401        let v = CostVector::from_candidate(&c);
402        assert_eq!(v.memory.device_vram_bytes, 8192, "GPU loads VRAM");
403        assert_eq!(v.memory.host_ram_bytes, 0, "no host RAM on GPU");
404    }
405
406    #[test]
407    fn cost_vector_precision_and_accuracy_from_guarantee() {
408        // REFERENCE: max_ulp Some(0), max_relative Some(0.0) →
409        // correctly-rounded + max digits.
410        let reference = candidate(
411            DeviceLocation::Cpu,
412            CostEstimate::default(),
413            0,
414            PrecisionGuarantee::REFERENCE,
415        );
416        let v = CostVector::from_candidate(&reference);
417        assert_eq!(v.accuracy, AccuracyClass::CorrectlyRounded);
418        assert_eq!(v.precision, 18, "ULP=0 / rel=0 → max digits");
419
420        // PRIMITIVE_DETERMINISTIC_CPU: bit-stable, no ULP bound →
421        // Stable accuracy, mid precision digits.
422        let prim = candidate(
423            DeviceLocation::Cpu,
424            CostEstimate::default(),
425            0,
426            PrecisionGuarantee::PRIMITIVE_DETERMINISTIC_CPU,
427        );
428        let v = CostVector::from_candidate(&prim);
429        assert_eq!(v.accuracy, AccuracyClass::Stable);
430        assert_eq!(v.precision, 9, "bit-stable-no-bound → mid rung");
431
432        // UNAUDITED: not bit-stable, no bound → weakest.
433        let unaudited = candidate(
434            DeviceLocation::Cpu,
435            CostEstimate::default(),
436            0,
437            PrecisionGuarantee::UNAUDITED,
438        );
439        let v = CostVector::from_candidate(&unaudited);
440        assert_eq!(v.accuracy, AccuracyClass::Unbounded);
441        assert_eq!(v.precision, 0);
442
443        // A loose ULP bound (4) → BoundedLoose.
444        let loose = PrecisionGuarantee {
445            bit_stable_on_same_hardware: true,
446            max_ulp: Some(4),
447            max_relative: None,
448            max_absolute: None,
449            notes: "loose",
450        };
451        let v = CostVector::from_candidate(&candidate(
452            DeviceLocation::Cpu,
453            CostEstimate::default(),
454            0,
455            loose,
456        ));
457        assert_eq!(v.accuracy, AccuracyClass::BoundedLoose);
458
459        // A tight ULP bound (1) → BoundedTight.
460        let tight = PrecisionGuarantee {
461            bit_stable_on_same_hardware: true,
462            max_ulp: Some(1),
463            max_relative: None,
464            max_absolute: None,
465            notes: "tight",
466        };
467        let v = CostVector::from_candidate(&candidate(
468            DeviceLocation::Cpu,
469            CostEstimate::default(),
470            0,
471            tight,
472        ));
473        assert_eq!(v.accuracy, AccuracyClass::BoundedTight);
474    }
475
476    // ===== (b) dominates: clearly-dominated pair → true; genuinely
477    //            non-dominated pair → false both ways. =====
478
479    #[test]
480    fn dominates_clearly_dominated_pair() {
481        // a: faster, less memory, higher precision/accuracy than b →
482        // a dominates b, and b does NOT dominate a.
483        let a = CostVector {
484            time: 100,
485            memory: MemoryTiers {
486                host_ram_bytes: 10,
487                device_vram_bytes: 0,
488            },
489            precision: 10,
490            accuracy: AccuracyClass::CorrectlyRounded,
491        };
492        let b = CostVector {
493            time: 200,
494            memory: MemoryTiers {
495                host_ram_bytes: 20,
496                device_vram_bytes: 0,
497            },
498            precision: 5,
499            accuracy: AccuracyClass::Stable,
500        };
501        assert!(a.dominates(&b), "a strictly better on every axis");
502        assert!(!b.dominates(&a), "dominated cannot dominate back");
503    }
504
505    #[test]
506    fn dominates_non_dominated_pair_is_false_both_ways() {
507        // Faster-but-lower-precision vs slower-but-higher-precision —
508        // a genuine tradeoff; neither dominates.
509        let fast_low_prec = CostVector {
510            time: 100,
511            memory: MemoryTiers::default(),
512            precision: 4,
513            accuracy: AccuracyClass::BoundedLoose,
514        };
515        let slow_high_prec = CostVector {
516            time: 500,
517            memory: MemoryTiers::default(),
518            precision: 12,
519            accuracy: AccuracyClass::CorrectlyRounded,
520        };
521        assert!(!fast_low_prec.dominates(&slow_high_prec));
522        assert!(!slow_high_prec.dominates(&fast_low_prec));
523    }
524
525    #[test]
526    fn dominates_equal_vectors_is_false() {
527        // Equal on all axes → no strict improvement → no dominance.
528        let a = CostVector {
529            time: 100,
530            memory: MemoryTiers {
531                host_ram_bytes: 5,
532                device_vram_bytes: 5,
533            },
534            precision: 7,
535            accuracy: AccuracyClass::Stable,
536        };
537        assert!(!a.dominates(&a));
538    }
539
540    #[test]
541    fn dominates_memory_tradeoff_across_tiers_is_non_dominated() {
542        // Same time/precision/accuracy, but one is lighter on host
543        // and heavier on VRAM and vice-versa → genuine cross-tier
544        // tradeoff, neither dominates.
545        let host_light = CostVector {
546            time: 100,
547            memory: MemoryTiers {
548                host_ram_bytes: 1,
549                device_vram_bytes: 100,
550            },
551            precision: 7,
552            accuracy: AccuracyClass::Stable,
553        };
554        let vram_light = CostVector {
555            time: 100,
556            memory: MemoryTiers {
557                host_ram_bytes: 100,
558                device_vram_bytes: 1,
559            },
560            precision: 7,
561            accuracy: AccuracyClass::Stable,
562        };
563        assert!(!host_light.dominates(&vram_light));
564        assert!(!vram_light.dominates(&host_light));
565    }
566
567    // ===== (c) tie-break precision → accuracy → memory among
568    //            equal-time candidates (via total_order_key). =====
569
570    #[test]
571    fn tie_break_precision_first_among_equal_time() {
572        let lower_prec = CostVector {
573            time: 100,
574            memory: MemoryTiers::default(),
575            precision: 5,
576            accuracy: AccuracyClass::CorrectlyRounded,
577        };
578        let higher_prec = CostVector {
579            time: 100,
580            memory: MemoryTiers::default(),
581            precision: 9,
582            accuracy: AccuracyClass::Unbounded,
583        };
584        // Higher precision sorts FIRST even though its accuracy is
585        // worse — precision dominates the tie-break.
586        assert!(
587            higher_prec.total_order_key() < lower_prec.total_order_key(),
588            "precision is the first tie-breaker",
589        );
590    }
591
592    #[test]
593    fn tie_break_accuracy_then_memory() {
594        // Equal time + precision → accuracy decides.
595        let lo_acc = CostVector {
596            time: 100,
597            memory: MemoryTiers::default(),
598            precision: 7,
599            accuracy: AccuracyClass::Stable,
600        };
601        let hi_acc = CostVector {
602            time: 100,
603            memory: MemoryTiers::default(),
604            precision: 7,
605            accuracy: AccuracyClass::CorrectlyRounded,
606        };
607        assert!(hi_acc.total_order_key() < lo_acc.total_order_key());
608
609        // Equal time + precision + accuracy → memory decides
610        // (lower total bytes first).
611        let heavy = CostVector {
612            time: 100,
613            memory: MemoryTiers {
614                host_ram_bytes: 1000,
615                device_vram_bytes: 0,
616            },
617            precision: 7,
618            accuracy: AccuracyClass::Stable,
619        };
620        let light = CostVector {
621            time: 100,
622            memory: MemoryTiers {
623                host_ram_bytes: 10,
624                device_vram_bytes: 0,
625            },
626            precision: 7,
627            accuracy: AccuracyClass::Stable,
628        };
629        assert!(light.total_order_key() < heavy.total_order_key());
630    }
631
632    #[test]
633    fn total_order_key_is_time_first() {
634        // Lower time wins regardless of every other axis — the
635        // realize-behavior-preserving contract.
636        let fast_but_worse = CostVector {
637            time: 100,
638            memory: MemoryTiers {
639                host_ram_bytes: u64::MAX,
640                device_vram_bytes: 0,
641            },
642            precision: 0,
643            accuracy: AccuracyClass::Unbounded,
644        };
645        let slow_but_better = CostVector {
646            time: 101,
647            memory: MemoryTiers::default(),
648            precision: 18,
649            accuracy: AccuracyClass::CorrectlyRounded,
650        };
651        assert!(
652            fast_but_worse.total_order_key() < slow_but_better.total_order_key(),
653            "time is the primary key",
654        );
655    }
656}