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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! System-level detection that does NOT touch libtorch / CUDA.
//!
//! flodl's main CUDA APIs (e.g. [`crate::tensor::gpu_device_count`])
//! initialize libtorch on first call. Once libtorch latches onto a
//! device list, `CUDA_VISIBLE_DEVICES` is ignored and — critically for
//! cluster mode — the spawned children inherit a corrupted CUDA
//! context on heterogeneous-GPU rigs.
//!
//! [`detect_gpus`] avoids both problems by asking the vendor's own
//! stack (nvidia-smi for NVIDIA, the kernel's KFD topology for AMD)
//! with no GPU runtime initialized. Use this when you need GPU info for
//! pre-`Trainer::run` decisions (mode filtering, log banners,
//! CLI-flag validation) — see the "no CUDA before `Trainer::run`"
//! invariant in the [`crate::distributed::Trainer`] docs.
//!
//! For *runtime* GPU queries (after dispatch), the libtorch-backed
//! APIs in [`crate::tensor`] remain the right tool.
//!
//! Returns an empty `Vec` when the vendor tool is missing or fails —
//! callers can treat that as "no GPU visible" without a separate
//! "did we have a driver" branch.
//!
//! # Three questions, three entry points
//!
//! They are named apart on purpose; conflating them is a real bug in
//! every direction.
//!
//! | Question | Use |
//! |---|---|
//! | What is installed on this box? (provisioning: which libtorch to fetch) | [`survey`] / [`detect_gpus_physical`] |
//! | What will the runtime see? (masks applied, all vendors) | [`survey_visible`] |
//! | What can *this build* train on? (masks **and** vendor) | [`detect_gpus`] / [`survey_visible_for`] |
//!
//! The vendor axis exists because libtorch is built for exactly one GPU
//! backend, so on a mixed box the other vendor's cards are present,
//! healthy and unusable. See [`build_vendor`].
//!
//! # Where this lives
//!
//! The implementation is the dependency-free [`flodl_hw`] crate, which
//! `flodl-cli` also depends on. `fdl` needs the same answers *before
//! libtorch exists at all* (to pick which variant to install) and so
//! cannot depend on `flodl`; both used to carry a hand-synchronized copy
//! of this struct and this parser. This module is a re-export of the
//! single source, kept as `flodl::sys` because that is the published
//! path.
//!
//! # Spoofing hardware in tests
//!
//! [`ENV_TESTING_GPU_JSON`] replaces the whole sweep with a described
//! one, the sibling of `FLODL_TESTING_CLUSTER_JSON` for hardware rather
//! than topology. It is how a second GPU vendor's detection and routing
//! get tested on a machine that has none of that hardware:
//!
//! ```text
//! FLODL_TESTING_GPU_JSON='[{"vendor":"amd","arch":"gfx1030","vram_mb":16384}]' fdl test
//! ```
//!
//! `fdl` forwards it across the docker boundary. Visibility masks still
//! apply on top, and a malformed value panics rather than quietly
//! falling back to the real hardware. Full format in
//! [`flodl_hw::testing`].
pub use ;
/// The GPU vendor this build of `flodl` can address, or `None` for a
/// CPU-only build.
///
/// libtorch is built for exactly one GPU backend and both claim
/// `DeviceType::CUDA`, so the vendor is a **compile-time** property:
/// `libtorch_cuda.so` and `libtorch_hip.so` cannot coexist in a process,
/// as both register kernels against the same dispatch key. That is why
/// this reads cargo features rather than probing anything.
/// Enumerate the GPUs this build can actually train on: visibility masks
/// applied, then narrowed to [`build_vendor`].
///
/// The vendor filter is not cosmetic. A mixed AMD + NVIDIA host is an
/// ordinary machine (any box with an AMD APU and a discrete NVIDIA card
/// is one), and this count feeds the `>= 2` DDP auto-promote decision --
/// without the filter, a CUDA build on such a box spawns a rank for a
/// device it cannot address, and the failure surfaces far from its
/// cause. It also disambiguates indices, which are per-vendor ordinals.
///
/// A CPU-only build makes no vendor claim, so it filters nothing and
/// keeps reporting whatever is installed; nothing in that build will try
/// to place a rank on it.
///
/// Use [`survey_visible_for`] instead when the *reason* devices went
/// missing matters -- it carries the [`NoteKind::VendorMismatch`] note
/// that explains a zero count on a box whose hardware is the other
/// vendor's.