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}