1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! Hardware detection for flodl: GPUs and host RAM, with **no libtorch
//! and no CUDA runtime**.
//!
//! One dependency, `serde_json`, and only to read
//! [`ENV_TESTING_GPU_JSON`]. It is free in practice: every consumer
//! (`flodl`, `flodl-cli`, `flodl-hf`) already depends on it directly,
//! so a workspace build and a `cargo install flodl-cli` compile exactly
//! zero extra crates for it.
//!
//! # Why this is its own crate
//!
//! Two consumers need the same answers and cannot share code any other
//! way:
//!
//! - **`flodl`** needs GPU identity *before* libtorch is initialized.
//! flodl's CUDA APIs (e.g. `flodl::tensor::gpu_device_count`)
//! initialize libtorch on first call, and once libtorch latches onto a
//! device list, `CUDA_VISIBLE_DEVICES` is ignored. Critically for
//! cluster mode, the launcher's spawned children then inherit a
//! corrupted CUDA context on heterogeneous-GPU rigs. See the
//! "no CUDA before `Trainer::run`" invariant.
//! - **`fdl`** (`flodl-cli`) needs the same answers *before libtorch
//! exists at all*, to decide which libtorch variant to download or
//! build. It therefore cannot depend on `flodl`, which would pull
//! `flodl-sys` and libtorch onto the install path.
//!
//! Both used to carry a hand-synchronized copy of the same struct and
//! the same parser, kept aligned by a comment. This crate is the single
//! source, and the one place a second GPU vendor is added.
//!
//! # Two enumerations, deliberately
//!
//! [`detect_gpus`] reports what the **runtime** will see (visibility
//! masks applied). [`detect_gpus_physical`] reports what is
//! **installed** (masks ignored). Provisioning decisions want the
//! physical set; runtime decisions want the visible set. Conflating them
//! is a real bug in both directions, so they are named apart rather than
//! separated by a boolean argument.
//!
//! # A sweep returns findings, it does not print them
//!
//! Both of the above are shorthands for [`survey`] / [`survey_visible`],
//! which return a [`GpuSurvey`]: the devices **plus** what the sweep
//! learned that a device list cannot express. An empty list has at least
//! four causes needing different responses (CPU-only box, driver present
//! but tool broken, card present but stack not installed, masked away),
//! and the one that matters most for a second vendor is the third: an
//! AMD card with no ROCm is a common state and the user needs told, with
//! an action.
//!
//! Detection therefore records [`SurveyNote`]s and lets the caller
//! decide what to surface, rather than `eprintln!`ing from inside a
//! library. [`GpuSurvey::require_devices`] turns an empty sweep into the
//! best available explanation, which is what a command with an explicit
//! GPU request (`--gpus all`) wants.
//!
//! # Vendor is not device
//!
//! [`GpuVendor`] is an identity. It is deliberately *not* the device
//! string a tensor library is handed: ROCm libtorch keeps `kCUDA`, so an
//! AMD device is `GpuVendor::Amd` here while still being addressed as
//! CUDA at the API surface. Vendor drives detection, diagnostics,
//! packaging and feature derivation; the API surface is a different
//! axis, and assuming they are the same one does not survive contact
//! with Intel (whose libtorch device type genuinely differs).
//!
//! # Spoofing hardware for tests
//!
//! [`ENV_TESTING_GPU_JSON`] replaces the whole sweep with a described
//! one, so a second vendor's detection, libtorch variant selection and
//! per-host build routing are testable on a machine that has none of
//! that hardware. See [`testing`] for the format.
//!
//! # Contract
//!
//! No thread is spawned and no GPU runtime is initialized. Every probe
//! is a filesystem read or a bounded subprocess, and each vendor's
//! subprocess is gated behind cheap filesystem checks, so a CPU-only
//! box spawns nothing at all. Absent hardware is never an error, so
//! callers need no "did we have a driver" branch. The one panic is a
//! malformed [`ENV_TESTING_GPU_JSON`], which is a developer error in a
//! deliberately-set variable.
pub use ;
pub use ;
pub use ;
pub use nvidia_driver_version;
pub use ;
pub use ENV_TESTING_GPU_JSON;
pub use ;