Skip to main content

ferrox_core/
bench_profile.rs

1//! Where a machine's measured bandwidth profile lives, and when it may
2//! be trusted.
3//!
4//! [`crate::qstar`] can turn a [`BandwidthProfile`] into a split
5//! policy, but only if something hands it one. This module is that
6//! something: it decides which file on disk *is* this machine's
7//! profile, reads it, and refuses it when it describes a different
8//! machine. Without it every deployment falls back to
9//! [`QStarPolicy::fixed_cap`]'s one-fetch-per-step default, which is
10//! safe and slow -- the whole point of benchmarking a host is that the
11//! result is then found again on the next run.
12//!
13//! # One file per GPU
14//!
15//! The profile is stored per **GPU UUID**, at
16//! `$XDG_CACHE_HOME/ferrox/benchbw/<uuid>.json`, not once per box.
17//! Bandwidth is a property of a *slot*, not of a machine: two identical
18//! cards in the same chassis routinely sit behind different links (x16
19//! off the CPU vs. x4 off the chipset), and the `q*` fraction that
20//! balances one of them starves the other. Machines with a single card
21//! and older benchmarks use the legacy `benchbw.json` next to it.
22//!
23//! # Lookup order, and the one place it stops
24//!
25//! An explicit path wins, then [`PROFILE_PATH_ENV`], then this card's
26//! `benchbw/<uuid>.json`, then the legacy `benchbw.json`.
27//!
28//! A candidate that is simply *absent* is skipped -- that is the whole
29//! reason the legacy file is in the list. A candidate that **exists but
30//! does not parse** is not skipped: the lookup returns [`None`] on the
31//! spot and the caller keeps its unbenchmarked default. Falling through
32//! would mean a truncated or half-written profile for the card in slot
33//! 1 silently promotes slot 0's numbers to describe slot 1, and a
34//! wrong fetch fraction is worse than no fetch fraction: it does not
35//! degrade to "a bit slower", it puts every decode step's misses on the
36//! wrong side of a link that cannot carry them. Corruption is a reason
37//! to stop, not a reason to guess.
38//!
39//! # Naming
40//!
41//! The environment variable is `FERROX_BENCHBW_PATH` and the cache
42//! directory is `ferrox/` (matching `ferrox-core`'s `registry_dir`);
43//! FreeToken spells the same two `FREETOKEN_BENCHBW_PATH` and
44//! `freetoken/`. The on-disk layout is otherwise identical --
45//! `benchbw/<uuid>.json` plus the legacy `benchbw.json`, same JSON
46//! document -- so a profile written by either tool is readable by the
47//! other once it is in the right directory.
48//!
49//! Ported 1:1 from FreeToken's `moe/bench_profile.py` (Apache-2.0); see
50//! `docs/THIRD_PARTY_NOTICES.md`.
51
52use std::path::{Path, PathBuf};
53
54use crate::qstar::{BandwidthProfile, MoeBackend, QStarPolicy};
55
56/// Overrides the whole lookup with one path. Empty means unset.
57pub const PROFILE_PATH_ENV: &str = "FERROX_BENCHBW_PATH";
58
59/// The per-GPU profile directory, under the cache directory.
60pub const PROFILE_SUBDIR: &str = "benchbw";
61
62/// The single-file profile that predates the per-GPU layout.
63pub const LEGACY_PROFILE_FILE: &str = "benchbw.json";
64
65/// The bench format key an engine quant name is measured under.
66///
67/// The benchmark keys its numbers by expert *format*, not by model,
68/// because the CPU-MoE-vs-PCIe-gather ratio the choice rides on is
69/// dominated by `(format, hardware)` -- so a profile taken on one
70/// workload transfers to any model with the same expert format on the
71/// same card. Most engine names are already the bench name; `mxfp4` is
72/// benched under its kernel's name, `mxfp4_triton`.
73///
74/// Anything not in the table is passed through unchanged, which is
75/// deliberate rather than lossy: an unmapped name finds no entry in the
76/// profile, the lookup yields [`None`], and the caller keeps its safe
77/// offload default. Only the offload-family formats that have a CPU MoE
78/// weight path can ever resolve to hybrid, and those are exactly the
79/// ones listed here.
80pub fn bench_format(quant_format: &str) -> &str {
81    match quant_format {
82        "nvfp4" => "nvfp4",
83        "ds_fp4" => "ds_fp4",
84        "mxfp4" => "mxfp4_triton",
85        "bf16" => "bf16",
86        "fp8_block" => "fp8_block",
87        other => other,
88    }
89}
90
91/// `$XDG_CACHE_HOME/ferrox`, else `$HOME/.cache/ferrox`, else a
92/// temporary directory.
93///
94/// The last fallback keeps a host with neither variable set (a bare
95/// service account, a container) from resolving profiles relative to
96/// the process's working directory; it matches `ferrox-core`'s
97/// `registry_dir`. A profile written there does not survive a reboot,
98/// which is the honest outcome when the machine has nowhere durable to
99/// put one.
100pub fn cache_dir() -> PathBuf {
101    std::env::var("XDG_CACHE_HOME")
102        .ok()
103        .filter(|s| !s.is_empty())
104        .map(PathBuf::from)
105        .or_else(|| {
106            std::env::var("HOME")
107                .ok()
108                .filter(|s| !s.is_empty())
109                .map(|h| PathBuf::from(h).join(".cache"))
110        })
111        .unwrap_or_else(std::env::temp_dir)
112        .join("ferrox")
113}
114
115/// The path [`PROFILE_PATH_ENV`] names, if it names one.
116///
117/// An empty value counts as unset, so `FERROX_BENCHBW_PATH=` in a unit
118/// file or a `docker run -e` with no value means "use the normal
119/// lookup" rather than "look for a file called nothing".
120pub fn env_profile_path() -> Option<PathBuf> {
121    std::env::var(PROFILE_PATH_ENV)
122        .ok()
123        .filter(|s| !s.is_empty())
124        .map(PathBuf::from)
125}
126
127/// `<cache_dir>/benchbw/<uuid>.json`, or the legacy
128/// `<cache_dir>/benchbw.json` when the card has no UUID.
129///
130/// Takes the cache directory rather than finding it, so the layout can
131/// be exercised without a home directory.
132pub fn default_profile_path_in(cache_dir: &Path, gpu_uuid: Option<&str>) -> PathBuf {
133    match gpu_uuid.filter(|u| !u.is_empty()) {
134        Some(uuid) => cache_dir.join(PROFILE_SUBDIR).join(format!("{uuid}.json")),
135        None => cache_dir.join(LEGACY_PROFILE_FILE),
136    }
137}
138
139/// [`default_profile_path_in`] under the real [`cache_dir`].
140pub fn default_profile_path(gpu_uuid: Option<&str>) -> PathBuf {
141    default_profile_path_in(&cache_dir(), gpu_uuid)
142}
143
144// ---- writing a profile ---------------------------------------------
145
146/// A measured (format, card) pair, before it becomes a profile entry.
147///
148/// All four bandwidths are `Option` for the same reason the profile's
149/// are: a run that could only measure one side has not measured the
150/// thing the fraction is made of, and a half-entry that reads as
151/// complete is worse than none.
152#[derive(Debug, Clone, Copy, Default, PartialEq)]
153pub struct Measured {
154    pub cpu_moe_gbs: Option<f64>,
155    pub pcie_gather_gbs: Option<f64>,
156    pub cpu_moe_overlap_gbs: Option<f64>,
157    pub pcie_gather_overlap_gbs: Option<f64>,
158}
159
160/// Why a measurement cannot become a profile.
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub enum NotMeasurable {
163    /// One side was measured and the other was not.
164    ///
165    /// The fraction is a RATIO of the two, so one number alone implies
166    /// nothing about the split -- and a profile carrying it would be
167    /// consulted by `policy_for` as though it did.
168    OnlyOneSide,
169    /// A bandwidth came back at or below zero, which is a failed
170    /// measurement rather than an infinitely slow link.
171    NotPositive,
172}
173
174impl std::fmt::Display for NotMeasurable {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        match self {
177            NotMeasurable::OnlyOneSide => write!(
178                f,
179                "only one side was measured; the fetch fraction is a ratio of \
180                 the two, so one number alone says nothing about the split"
181            ),
182            NotMeasurable::NotPositive => {
183                write!(
184                    f,
185                    "a bandwidth came back at or below zero, which is a failed measurement"
186                )
187            }
188        }
189    }
190}
191
192impl std::error::Error for NotMeasurable {}
193
194/// Turns a measurement into the entry a profile stores, deriving the
195/// verdict the reader consults.
196///
197/// `threshold` is the same one [`crate::qstar::recommend_backend`]
198/// applies, and the CONTENDED pair is preferred wherever it exists --
199/// standalone numbers assume each side owns the machine, and neither
200/// does once they run together, which is the whole reason the contended
201/// pair is measured at all.
202pub fn entry_from(
203    measured: &Measured,
204    threshold: f64,
205) -> Result<crate::qstar::KernelBandwidths, NotMeasurable> {
206    let positive = |v: Option<f64>| -> Result<Option<f64>, NotMeasurable> {
207        match v {
208            Some(x) if x > 0.0 && x.is_finite() => Ok(Some(x)),
209            Some(_) => Err(NotMeasurable::NotPositive),
210            None => Ok(None),
211        }
212    };
213    let cpu = positive(measured.cpu_moe_gbs)?;
214    let pcie = positive(measured.pcie_gather_gbs)?;
215    let cpu_ov = positive(measured.cpu_moe_overlap_gbs)?;
216    let pcie_ov = positive(measured.pcie_gather_overlap_gbs)?;
217
218    if cpu.is_some() != pcie.is_some() || cpu_ov.is_some() != pcie_ov.is_some() {
219        return Err(NotMeasurable::OnlyOneSide);
220    }
221    let (Some(cpu), Some(pcie)) = (cpu, pcie) else {
222        return Err(NotMeasurable::OnlyOneSide);
223    };
224
225    // The contended pair when it exists, exactly as `fetch_fraction`
226    // prefers it, so the verdict and the fraction cannot disagree about
227    // which numbers they came from.
228    let (verdict_cpu, verdict_pcie) = match (cpu_ov, pcie_ov) {
229        (Some(c), Some(p)) => (c, p),
230        _ => (cpu, pcie),
231    };
232    Ok(crate::qstar::KernelBandwidths {
233        cpu_moe_gbs: Some(cpu),
234        pcie_gather_gbs: Some(pcie),
235        cpu_moe_overlap_gbs: cpu_ov,
236        pcie_gather_overlap_gbs: pcie_ov,
237        recommended: Some(crate::qstar::recommend_backend(
238            verdict_cpu,
239            verdict_pcie,
240            threshold,
241        )),
242    })
243}
244
245/// Writes `profile` where [`read_profile`] will find it.
246///
247/// Through a temporary file and a rename, because a plain write is not
248/// atomic and a reader that catches a half-written profile gets serde's
249/// parse error -- which `read_profile` turns into NO profile, silently
250/// discarding a measurement the user did take.
251pub fn write_profile(path: &Path, profile: &BandwidthProfile) -> std::io::Result<()> {
252    if let Some(parent) = path.parent() {
253        std::fs::create_dir_all(parent)?;
254    }
255    let body = serde_json::to_vec_pretty(profile)
256        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
257    let tmp = path.with_extension("json.partial");
258    std::fs::write(&tmp, body)?;
259    std::fs::rename(&tmp, path)
260}
261
262/// The newest `benchbw/*.json` under `cache_dir`, else the legacy
263/// `benchbw.json`, else [`None`].
264///
265/// This is for reporting and for tools that want *a* profile without
266/// knowing which card they are asking about -- never for the serving
267/// path, which must go through [`usable_profile`] so that another
268/// card's numbers are refused rather than merely being the most recent.
269///
270/// Ties on modification time are broken by the greater file name, so
271/// the answer does not depend on directory order.
272pub fn latest_profile_path_in(cache_dir: &Path) -> Option<PathBuf> {
273    let mut found: Vec<(std::time::SystemTime, PathBuf)> = Vec::new();
274    if let Ok(entries) = std::fs::read_dir(cache_dir.join(PROFILE_SUBDIR)) {
275        for entry in entries.flatten() {
276            if !entry.file_name().to_string_lossy().ends_with(".json") {
277                continue;
278            }
279            let Ok(mtime) = entry.metadata().and_then(|m| m.modified()) else {
280                continue;
281            };
282            found.push((mtime, entry.path()));
283        }
284    }
285    if let Some((_, path)) = found.into_iter().max() {
286        return Some(path);
287    }
288    let legacy = default_profile_path_in(cache_dir, None);
289    legacy.is_file().then_some(legacy)
290}
291
292/// [`latest_profile_path_in`] under the real [`cache_dir`].
293pub fn latest_profile_path() -> Option<PathBuf> {
294    latest_profile_path_in(&cache_dir())
295}
296
297/// The profile document at `path`, or [`None`] when it is absent,
298/// unreadable, or not a profile.
299///
300/// This collapses "no file" and "bad file" into one answer, which is
301/// fine for a caller that only wants the document. The lookup itself
302/// must tell the two apart -- see the module docs -- so it does not use
303/// this.
304pub fn read_profile(path: &Path) -> Option<BandwidthProfile> {
305    match read_candidate(path) {
306        Candidate::Profile(profile) => Some(*profile),
307        _ => None,
308    }
309}
310
311/// What one candidate path turned out to be.
312enum Candidate {
313    /// No such file. Try the next candidate.
314    Missing,
315    /// The file is there but is not a profile. Stop.
316    Corrupt,
317    /// Boxed: a profile is several maps, and the other two variants are
318    /// empty.
319    Profile(Box<BandwidthProfile>),
320}
321
322/// Read one candidate, keeping "absent" and "present but broken"
323/// distinct.
324///
325/// Only [`std::io::ErrorKind::NotFound`] counts as absent. A permission
326/// error, a directory in the file's place, or a half-written file are
327/// all `Corrupt`: the profile *is* claimed by this path, we just cannot
328/// have it, and that is precisely the case where borrowing another
329/// card's file would be wrong.
330fn read_candidate(path: &Path) -> Candidate {
331    let body = match std::fs::read_to_string(path) {
332        Ok(body) => body,
333        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Candidate::Missing,
334        Err(_) => return Candidate::Corrupt,
335    };
336    match serde_json::from_str::<BandwidthProfile>(&body) {
337        Ok(profile) => Candidate::Profile(Box::new(profile)),
338        Err(_) => Candidate::Corrupt,
339    }
340}
341
342/// The candidate paths, in the order they are tried.
343///
344/// An explicit path is the *only* candidate: someone who named a file
345/// wants that file, and quietly serving a different one because theirs
346/// was missing would hide the typo.
347fn candidate_paths(cache_dir: &Path, path: Option<&Path>, gpu_uuid: Option<&str>) -> Vec<PathBuf> {
348    if let Some(explicit) = path {
349        return vec![explicit.to_path_buf()];
350    }
351    let mut candidates = Vec::with_capacity(2);
352    if gpu_uuid.is_some_and(|u| !u.is_empty()) {
353        candidates.push(default_profile_path_in(cache_dir, gpu_uuid));
354    }
355    candidates.push(default_profile_path_in(cache_dir, None));
356    candidates
357}
358
359/// The profile this machine may actually be served with, or [`None`].
360///
361/// [`None`] means one of three things, all of which the caller answers
362/// the same way -- keep the unbenchmarked default:
363///
364/// - no candidate file exists (nobody has benched this host);
365/// - a candidate exists and does not parse (see the module docs: the
366///   lookup stops there instead of falling through to another card's
367///   file);
368/// - the profile parsed but records a *different* GPU name, so its
369///   numbers describe hardware that is not in front of us.
370///
371/// Takes the cache directory rather than finding it, and takes the
372/// explicit path already resolved, so the whole rule is testable
373/// without a home directory, a GPU, or an environment variable.
374pub fn usable_profile_in(
375    cache_dir: &Path,
376    gpu_name: Option<&str>,
377    path: Option<&Path>,
378    gpu_uuid: Option<&str>,
379) -> Option<BandwidthProfile> {
380    let mut found: Option<BandwidthProfile> = None;
381    for candidate in candidate_paths(cache_dir, path, gpu_uuid) {
382        match read_candidate(&candidate) {
383            Candidate::Profile(profile) => {
384                found = Some(*profile);
385                break;
386            }
387            // Present and broken: do not borrow the next candidate's
388            // numbers for this card.
389            Candidate::Corrupt => return None,
390            Candidate::Missing => continue,
391        }
392    }
393    let profile = found?;
394    profile.matches_gpu(gpu_name).then_some(profile)
395}
396
397/// [`usable_profile_in`] under the real [`cache_dir`], with
398/// [`PROFILE_PATH_ENV`] standing in for an absent `path`.
399pub fn usable_profile(
400    gpu_name: Option<&str>,
401    path: Option<&Path>,
402    gpu_uuid: Option<&str>,
403) -> Option<BandwidthProfile> {
404    let from_env = path.is_none().then(env_profile_path).flatten();
405    let explicit = path.or(from_env.as_deref());
406    usable_profile_in(&cache_dir(), gpu_name, explicit, gpu_uuid)
407}
408
409/// The bench-recommended offload-family backend for `quant_format` on
410/// this card, or [`None`].
411///
412/// [`None`] means "no usable profile, or nothing measured for this
413/// format" -- not "offload". The caller keeps its own default, which is
414/// offload today; the distinction matters because a caller that had
415/// been told `Hybrid` by configuration should not be silently
416/// downgraded by a missing file.
417pub fn load_backend_recommendation(
418    quant_format: &str,
419    gpu_name: Option<&str>,
420    path: Option<&Path>,
421    gpu_uuid: Option<&str>,
422) -> Option<MoeBackend> {
423    usable_profile(gpu_name, path, gpu_uuid)?.backend_for(bench_format(quant_format))
424}
425
426/// The benched hybrid fetch fraction for `quant_format`, or [`None`].
427///
428/// The fraction itself -- contended pair first, standalone ratio as the
429/// fallback, clamped to `[0, 1]` -- is [`BandwidthProfile::fetch_fraction_for`];
430/// this only finds the file it comes from.
431pub fn load_hybrid_fetch_fraction(
432    quant_format: &str,
433    gpu_name: Option<&str>,
434    path: Option<&Path>,
435    gpu_uuid: Option<&str>,
436) -> Option<f64> {
437    usable_profile(gpu_name, path, gpu_uuid)?.fetch_fraction_for(bench_format(quant_format))
438}
439
440/// The split policy to serve `quant_format` with on this card.
441///
442/// Total, unlike the two loaders above: every path that ends in "we do
443/// not know" ends in the unbenchmarked default, a fixed cap of one
444/// fetch per layer per step.
445pub fn load_policy(
446    quant_format: &str,
447    gpu_name: Option<&str>,
448    path: Option<&Path>,
449    gpu_uuid: Option<&str>,
450) -> QStarPolicy {
451    match usable_profile(gpu_name, path, gpu_uuid) {
452        Some(profile) => profile.policy_for(bench_format(quant_format)),
453        None => QStarPolicy::fixed_cap(1),
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460    use std::sync::atomic::{AtomicUsize, Ordering};
461    use std::time::{Duration, SystemTime};
462
463    /// A unique directory under the system temp directory, removed when
464    /// the test ends. Nothing here may read the developer's home
465    /// directory or a real GPU.
466    struct TempDir {
467        path: PathBuf,
468    }
469
470    impl TempDir {
471        fn new(tag: &str) -> Self {
472            static COUNTER: AtomicUsize = AtomicUsize::new(0);
473            let n = COUNTER.fetch_add(1, Ordering::Relaxed);
474            let path = std::env::temp_dir().join(format!(
475                "ferrox-edge-bench-profile-{}-{tag}-{n}",
476                std::process::id()
477            ));
478            let _ = std::fs::remove_dir_all(&path);
479            std::fs::create_dir_all(&path).expect("temp dir is creatable");
480            TempDir { path }
481        }
482
483        fn path(&self) -> &Path {
484            &self.path
485        }
486    }
487
488    impl Drop for TempDir {
489        fn drop(&mut self) {
490            let _ = std::fs::remove_dir_all(&self.path);
491        }
492    }
493
494    fn write(path: &Path, body: &str) {
495        if let Some(parent) = path.parent() {
496            std::fs::create_dir_all(parent).expect("parent is creatable");
497        }
498        std::fs::write(path, body).expect("file is writable");
499    }
500
501    fn set_mtime(path: &Path, epoch_secs: u64) {
502        let file = std::fs::File::options()
503            .write(true)
504            .open(path)
505            .expect("file is openable");
506        file.set_modified(SystemTime::UNIX_EPOCH + Duration::from_secs(epoch_secs))
507            .expect("mtime is settable");
508    }
509
510    /// Overlapped 30/(30+90) = 0.25 for `nvfp4`, standalone 50/80 =
511    /// 0.625 for `mxfp4_triton`.
512    const CARD_PROFILE: &str = r#"{
513        "version": 4,
514        "gpu": {"name": "NVIDIA GeForce RTX 4090", "uuid": "GPU-slot0"},
515        "dtypes": {"nvfp4": "hybrid", "mxfp4_triton": "hybrid"},
516        "dtype_kernels": {
517            "nvfp4": {"cpu_moe_gbs": 100.0, "pcie_gather_gbs": 40.0,
518                      "cpu_moe_overlap_gbs": 90.0, "pcie_gather_overlap_gbs": 30.0},
519            "mxfp4_triton": {"cpu_moe_gbs": 80.0, "pcie_gather_gbs": 50.0}
520        }
521    }"#;
522
523    /// Deliberately different numbers from `CARD_PROFILE`: 20/(20+80) =
524    /// 0.2 for `nvfp4`, and `offload` where the card profile says
525    /// hybrid. Any test that accidentally reads this file instead of
526    /// the intended one sees it in the assertion.
527    const LEGACY_PROFILE: &str = r#"{
528        "version": 4,
529        "gpu": {"name": "NVIDIA GeForce RTX 4090", "uuid": "GPU-other"},
530        "dtypes": {"nvfp4": "offload"},
531        "dtype_kernels": {
532            "nvfp4": {"cpu_moe_overlap_gbs": 80.0, "pcie_gather_overlap_gbs": 20.0}
533        }
534    }"#;
535
536    #[test]
537    fn the_quant_name_maps_onto_the_bench_format_key() {
538        assert_eq!(bench_format("mxfp4"), "mxfp4_triton");
539        assert_eq!(bench_format("nvfp4"), "nvfp4");
540        assert_eq!(bench_format("ds_fp4"), "ds_fp4");
541        assert_eq!(bench_format("bf16"), "bf16");
542        assert_eq!(bench_format("fp8_block"), "fp8_block");
543    }
544
545    /// An unmapped quant is passed through, finds no entry, and so
546    /// leaves the caller on its safe offload default rather than
547    /// inheriting some other format's numbers.
548    #[test]
549    fn an_unmapped_quant_name_is_passed_through_and_finds_no_entry() {
550        assert_eq!(bench_format("q4_k_m"), "q4_k_m");
551        let dir = TempDir::new("unmapped");
552        let path = dir.path().join("profile.json");
553        write(&path, CARD_PROFILE);
554        assert_eq!(
555            load_hybrid_fetch_fraction("q4_k_m", None, Some(&path), None),
556            None
557        );
558        assert_eq!(
559            load_backend_recommendation("q4_k_m", None, Some(&path), None),
560            None
561        );
562        assert_eq!(
563            load_policy("q4_k_m", None, Some(&path), None),
564            QStarPolicy::fixed_cap(1)
565        );
566    }
567
568    /// Bandwidth is a property of a slot, so the file is keyed by GPU
569    /// UUID; only a card with no UUID lands on the legacy file.
570    #[test]
571    fn the_profile_path_is_one_file_per_gpu_uuid() {
572        let root = Path::new("/cache/ferrox");
573        assert_eq!(
574            default_profile_path_in(root, Some("GPU-slot0")),
575            Path::new("/cache/ferrox/benchbw/GPU-slot0.json")
576        );
577        assert_eq!(
578            default_profile_path_in(root, Some("GPU-slot1")),
579            Path::new("/cache/ferrox/benchbw/GPU-slot1.json")
580        );
581        assert_eq!(
582            default_profile_path_in(root, None),
583            Path::new("/cache/ferrox/benchbw.json")
584        );
585        assert_eq!(
586            default_profile_path_in(root, Some("")),
587            Path::new("/cache/ferrox/benchbw.json"),
588            "an empty uuid is no uuid"
589        );
590    }
591
592    #[test]
593    fn the_newest_per_gpu_profile_is_the_latest_one() {
594        let dir = TempDir::new("latest");
595        let older = dir.path().join(PROFILE_SUBDIR).join("GPU-slot0.json");
596        let newer = dir.path().join(PROFILE_SUBDIR).join("GPU-slot1.json");
597        write(&older, CARD_PROFILE);
598        write(&newer, CARD_PROFILE);
599        write(&dir.path().join(LEGACY_PROFILE_FILE), LEGACY_PROFILE);
600        set_mtime(&older, 1_700_000_000);
601        set_mtime(&newer, 1_700_000_100);
602        assert_eq!(latest_profile_path_in(dir.path()), Some(newer.clone()));
603        // Freshness is by mtime, not by name.
604        set_mtime(&older, 1_700_000_200);
605        assert_eq!(latest_profile_path_in(dir.path()), Some(older));
606    }
607
608    #[test]
609    fn the_legacy_file_answers_when_there_is_no_per_gpu_profile() {
610        let dir = TempDir::new("legacy-latest");
611        assert_eq!(
612            latest_profile_path_in(dir.path()),
613            None,
614            "an unbenched host has no profile at all"
615        );
616        let legacy = dir.path().join(LEGACY_PROFILE_FILE);
617        write(&legacy, LEGACY_PROFILE);
618        assert_eq!(latest_profile_path_in(dir.path()), Some(legacy));
619    }
620
621    /// **The rule this module exists for.** The per-card file is there
622    /// but unreadable, and a perfectly good legacy file sits next to
623    /// it. The lookup must return `None`, not the legacy profile: this
624    /// test fails if the code falls through to the legacy file, because
625    /// the legacy numbers (0.2, `offload`) are visibly different from
626    /// the card's (0.25, `hybrid`) and the assertions name `None`.
627    #[test]
628    fn a_corrupt_profile_for_this_card_is_not_replaced_by_the_legacy_file() {
629        let dir = TempDir::new("corrupt");
630        write(
631            &dir.path().join(PROFILE_SUBDIR).join("GPU-slot0.json"),
632            "{\"dtypes\": {\"nvfp4\": \"hyb",
633        );
634        write(&dir.path().join(LEGACY_PROFILE_FILE), LEGACY_PROFILE);
635        assert!(
636            usable_profile_in(dir.path(), None, None, Some("GPU-slot0")).is_none(),
637            "a half-written profile for this card must not borrow the legacy file"
638        );
639        // The legacy file really is usable on its own, so the `None`
640        // above is the corrupt-file rule and not an unreadable fixture.
641        assert!(usable_profile_in(dir.path(), None, None, None).is_some());
642    }
643
644    /// A file that parses as JSON but is not a profile document is
645    /// corrupt too, not empty.
646    #[test]
647    fn a_json_value_that_is_not_a_profile_counts_as_corrupt() {
648        let dir = TempDir::new("not-a-document");
649        write(
650            &dir.path().join(PROFILE_SUBDIR).join("GPU-slot0.json"),
651            "[1, 2, 3]",
652        );
653        write(&dir.path().join(LEGACY_PROFILE_FILE), LEGACY_PROFILE);
654        assert!(usable_profile_in(dir.path(), None, None, Some("GPU-slot0")).is_none());
655    }
656
657    /// A card that has never been benched is not an error: the legacy
658    /// file is exactly the fallback an absent candidate is meant to
659    /// reach.
660    #[test]
661    fn a_missing_per_gpu_profile_falls_through_to_the_legacy_file() {
662        let dir = TempDir::new("fallthrough");
663        write(&dir.path().join(LEGACY_PROFILE_FILE), LEGACY_PROFILE);
664        let profile = usable_profile_in(dir.path(), None, None, Some("GPU-slot0"))
665            .expect("the legacy file answers for an unbenched card");
666        assert_eq!(profile.fetch_fraction_for("nvfp4"), Some(0.2));
667    }
668
669    /// The per-card file wins over the legacy file when both are there.
670    #[test]
671    fn the_per_gpu_profile_wins_over_the_legacy_file() {
672        let dir = TempDir::new("per-gpu-wins");
673        write(
674            &dir.path().join(PROFILE_SUBDIR).join("GPU-slot0.json"),
675            CARD_PROFILE,
676        );
677        write(&dir.path().join(LEGACY_PROFILE_FILE), LEGACY_PROFILE);
678        let profile = usable_profile_in(dir.path(), None, None, Some("GPU-slot0"))
679            .expect("the card's own profile is usable");
680        assert_eq!(profile.fetch_fraction_for("nvfp4"), Some(0.25));
681        assert_eq!(profile.backend_for("nvfp4"), Some(MoeBackend::Hybrid));
682    }
683
684    /// Someone who named a file wants that file. A missing or broken
685    /// explicit path is not quietly replaced by a cached profile, which
686    /// would hide the typo behind plausible numbers.
687    #[test]
688    fn an_explicit_path_is_the_only_candidate_considered() {
689        let dir = TempDir::new("explicit");
690        write(
691            &dir.path().join(PROFILE_SUBDIR).join("GPU-slot0.json"),
692            CARD_PROFILE,
693        );
694        write(&dir.path().join(LEGACY_PROFILE_FILE), LEGACY_PROFILE);
695
696        let absent = dir.path().join("typo.json");
697        assert!(usable_profile_in(dir.path(), None, Some(&absent), Some("GPU-slot0")).is_none());
698
699        let broken = dir.path().join("broken.json");
700        write(&broken, "not json at all");
701        assert!(usable_profile_in(dir.path(), None, Some(&broken), Some("GPU-slot0")).is_none());
702    }
703
704    /// Bandwidths are hardware facts. A profile recorded on another
705    /// card is refused rather than approximated, even though it parses.
706    #[test]
707    fn a_profile_measured_on_another_card_is_ignored() {
708        let dir = TempDir::new("other-card");
709        let path = dir.path().join("profile.json");
710        write(&path, CARD_PROFILE);
711        assert!(usable_profile_in(
712            dir.path(),
713            Some("NVIDIA GeForce RTX 4090"),
714            Some(&path),
715            None
716        )
717        .is_some());
718        assert!(
719            usable_profile_in(
720                dir.path(),
721                Some("NVIDIA GeForce RTX 3060 Ti"),
722                Some(&path),
723                None
724            )
725            .is_none(),
726            "another card's bandwidths are worse than no bandwidths"
727        );
728    }
729
730    #[test]
731    fn the_loaders_resolve_the_quant_name_before_the_lookup() {
732        let dir = TempDir::new("loaders");
733        let path = dir.path().join("profile.json");
734        write(&path, CARD_PROFILE);
735        assert_eq!(
736            load_hybrid_fetch_fraction("mxfp4", None, Some(&path), None),
737            Some(0.625),
738            "mxfp4 is benched under mxfp4_triton"
739        );
740        assert_eq!(
741            load_backend_recommendation("mxfp4", None, Some(&path), None),
742            Some(MoeBackend::Hybrid)
743        );
744        assert_eq!(
745            load_hybrid_fetch_fraction("nvfp4", None, Some(&path), None),
746            Some(0.25)
747        );
748        assert_eq!(
749            load_policy("nvfp4", None, Some(&path), None),
750            QStarPolicy::from_fraction(0.25)
751        );
752    }
753
754    /// No profile is not a verdict: the loaders say `None` and the
755    /// caller keeps its own default, which `load_policy` spells out as
756    /// the one-fetch cap.
757    #[test]
758    fn the_loaders_return_none_without_a_usable_profile() {
759        let dir = TempDir::new("no-profile");
760        let absent = dir.path().join("nothing.json");
761        assert_eq!(
762            load_backend_recommendation("nvfp4", None, Some(&absent), None),
763            None
764        );
765        assert_eq!(
766            load_hybrid_fetch_fraction("nvfp4", None, Some(&absent), None),
767            None
768        );
769        assert_eq!(
770            load_policy("nvfp4", None, Some(&absent), None),
771            QStarPolicy::fixed_cap(1)
772        );
773    }
774
775    #[test]
776    fn reading_a_profile_yields_the_document_or_nothing() {
777        let dir = TempDir::new("read");
778        let path = dir.path().join("profile.json");
779        write(&path, CARD_PROFILE);
780        let profile = read_profile(&path).expect("the fixture parses");
781        assert_eq!(profile.gpu.uuid.as_deref(), Some("GPU-slot0"));
782        assert_eq!(read_profile(&dir.path().join("absent.json")), None);
783        assert_eq!(read_profile(dir.path()), None, "a directory is not a file");
784    }
785
786    /// The cache directory follows `ferrox`'s own layout, and never
787    /// resolves relative to the working directory.
788    #[test]
789    fn the_cache_directory_is_absolute_and_ends_in_ferrox() {
790        let dir = cache_dir();
791        assert!(dir.is_absolute(), "{dir:?}");
792        assert_eq!(dir.file_name().and_then(|n| n.to_str()), Some("ferrox"));
793    }
794
795    /// The fraction is a RATIO, so one side alone implies nothing about
796    /// the split -- and a profile carrying it would be consulted by
797    /// `policy_for` as though it did.
798    #[test]
799    fn one_side_measured_is_not_a_measurement() {
800        assert_eq!(
801            entry_from(
802                &Measured {
803                    cpu_moe_gbs: Some(50.0),
804                    ..Measured::default()
805                },
806                1.0
807            ),
808            Err(NotMeasurable::OnlyOneSide)
809        );
810        assert_eq!(
811            entry_from(
812                &Measured {
813                    pcie_gather_gbs: Some(20.0),
814                    ..Measured::default()
815                },
816                1.0
817            ),
818            Err(NotMeasurable::OnlyOneSide)
819        );
820        // Half a contended pair is the same failure: it is the pair
821        // that means something, not either half.
822        assert_eq!(
823            entry_from(
824                &Measured {
825                    cpu_moe_gbs: Some(50.0),
826                    pcie_gather_gbs: Some(20.0),
827                    cpu_moe_overlap_gbs: Some(30.0),
828                    pcie_gather_overlap_gbs: None,
829                },
830                1.0
831            ),
832            Err(NotMeasurable::OnlyOneSide)
833        );
834    }
835
836    /// A zero or negative bandwidth is a FAILED measurement, not an
837    /// infinitely slow link, and must not be written as though the
838    /// benchmark had succeeded.
839    #[test]
840    fn a_bandwidth_at_or_below_zero_is_a_failed_measurement() {
841        for bad in [0.0, -1.0, f64::NAN, f64::INFINITY] {
842            assert_eq!(
843                entry_from(
844                    &Measured {
845                        cpu_moe_gbs: Some(bad),
846                        pcie_gather_gbs: Some(20.0),
847                        ..Measured::default()
848                    },
849                    1.0
850                ),
851                Err(NotMeasurable::NotPositive),
852                "{bad} must not become a profile entry"
853            );
854        }
855    }
856
857    /// The verdict comes from the CONTENDED pair wherever it exists, so
858    /// it cannot disagree with the fraction about which numbers it came
859    /// from. Standalone numbers assume each side owns the machine, and
860    /// neither does once they run together -- which is the entire
861    /// reason the contended pair is measured.
862    #[test]
863    fn the_verdict_and_the_fraction_read_the_same_numbers() {
864        // Standalone says hybrid (cpu far outruns pcie); contended says
865        // offload, because under contention the CPU side collapses.
866        let measured = Measured {
867            cpu_moe_gbs: Some(100.0),
868            pcie_gather_gbs: Some(20.0),
869            cpu_moe_overlap_gbs: Some(10.0),
870            pcie_gather_overlap_gbs: Some(19.0),
871        };
872        let entry = entry_from(&measured, 1.0).expect("both sides measured");
873        assert_eq!(
874            entry.recommended,
875            Some(crate::qstar::MoeBackend::Offload),
876            "the contended pair is what the machine actually does"
877        );
878        // And the fraction the reader derives comes from the same pair.
879        let fraction = entry.fetch_fraction().expect("a pair exists");
880        let from_pair = crate::qstar::fetch_fraction_from_overlap(10.0, 19.0).unwrap();
881        assert!((fraction - from_pair).abs() < 1e-9);
882
883        // With no contended pair, the standalone numbers are all there
884        // is, and both halves fall back together.
885        let standalone = entry_from(
886            &Measured {
887                cpu_moe_gbs: Some(100.0),
888                pcie_gather_gbs: Some(20.0),
889                ..Measured::default()
890            },
891            1.0,
892        )
893        .unwrap();
894        assert_eq!(
895            standalone.recommended,
896            Some(crate::qstar::MoeBackend::Hybrid)
897        );
898    }
899
900    /// A reader that catches a half-written profile gets serde's parse
901    /// error, which `read_profile` turns into NO profile -- silently
902    /// discarding a measurement the user did take. So the write is
903    /// atomic, and nothing partial is ever left behind.
904    #[test]
905    fn a_profile_is_written_atomically_and_reads_back() {
906        let dir = std::env::temp_dir().join(format!(
907            "ferrox-benchbw-write-{}-{}",
908            std::process::id(),
909            line!()
910        ));
911        let _ = std::fs::remove_dir_all(&dir);
912        let path = default_profile_path_in(&dir, Some("GPU-abc"));
913
914        let mut profile = BandwidthProfile {
915            threshold: Some(1.0),
916            ..BandwidthProfile::default()
917        };
918        profile.gpu.name = Some("NVIDIA GeForce RTX 4090".to_string());
919        profile.gpu.uuid = Some("GPU-abc".to_string());
920        profile.dtype_kernels.insert(
921            "q4_k".to_string(),
922            entry_from(
923                &Measured {
924                    cpu_moe_gbs: Some(80.0),
925                    pcie_gather_gbs: Some(20.0),
926                    ..Measured::default()
927                },
928                1.0,
929            )
930            .unwrap(),
931        );
932
933        write_profile(&path, &profile).expect("writes");
934        let read = read_profile(&path).expect("reads back");
935        assert_eq!(read.gpu.uuid.as_deref(), Some("GPU-abc"));
936        assert_eq!(
937            read.dtype_kernels["q4_k"].recommended,
938            Some(crate::qstar::MoeBackend::Hybrid)
939        );
940
941        assert!(
942            std::fs::read_dir(path.parent().unwrap())
943                .unwrap()
944                .all(|e| !e
945                    .unwrap()
946                    .file_name()
947                    .to_string_lossy()
948                    .ends_with(".partial")),
949            "nothing partial may survive a completed write"
950        );
951
952        // The card it was taken on is what it is keyed to: another
953        // card's split is worse than no split.
954        assert!(read.matches_gpu(Some("NVIDIA GeForce RTX 4090")));
955        assert!(!read.matches_gpu(Some("NVIDIA GeForce RTX 3060 Ti")));
956
957        let _ = std::fs::remove_dir_all(&dir);
958    }
959}