1use std::{
4 ffi::c_void,
5 fmt,
6 path::{Path, PathBuf},
7 process::Command,
8 sync::Arc,
9};
10
11use libloading::Library;
12
13const HIP_NAMES: &[&str] = &["libamdhip64.so.7", "libamdhip64.so.6", "libamdhip64.so"];
14const ROCBLAS_NAMES: &[&str] = &[
15 "librocblas.so.5",
16 "librocblas.so.4",
17 "librocblas.so.0",
18 "librocblas.so",
19];
20const ROCBLASLT_NAMES: &[&str] = &["librocblaslt.so.0", "librocblaslt.so"];
21
22const HIP_SYMBOLS: &[&str] = &[
23 "hipInit",
24 "hipRuntimeGetVersion",
25 "hipGetDeviceCount",
26 "hipMalloc",
27 "hipFree",
28 "hipMemcpy",
29 "hipDeviceSynchronize",
30];
31const ROCBLAS_SYMBOLS: &[&str] = &[
32 "rocblas_create_handle",
33 "rocblas_destroy_handle",
34 "rocblas_sgemm",
35 "rocblas_gemm_ex",
36];
37const ROCBLASLT_SYMBOLS: &[&str] = &["rocblaslt_create_handle", "rocblaslt_destroy_handle"];
38
39#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct RocmSymbolEvidence {
42 pub name: String,
44 pub present: bool,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct RocmAbiEvidence {
51 pub hip_library: String,
53 pub rocblas_library: String,
55 pub rocblaslt_library: Option<String>,
57 pub hip_runtime_version: Option<i32>,
59 pub observed_gfx_targets: Vec<String>,
61 pub hip_symbols: Vec<RocmSymbolEvidence>,
63 pub rocblas_symbols: Vec<RocmSymbolEvidence>,
65 pub rocblaslt_symbols: Vec<RocmSymbolEvidence>,
67}
68
69impl RocmAbiEvidence {
70 pub fn is_complete(&self) -> bool {
72 cfg!(target_os = "linux")
73 && !self.observed_gfx_targets.is_empty()
74 && self.hip_symbols.iter().all(|symbol| symbol.present)
75 && self.rocblas_symbols.iter().all(|symbol| symbol.present)
76 }
77
78 pub fn supports_half_matmul(&self) -> bool {
80 self.rocblas_symbols
81 .iter()
82 .any(|symbol| symbol.name == "rocblas_gemm_ex" && symbol.present)
83 && !self.rocblaslt_symbols.is_empty()
84 && self.rocblaslt_symbols.iter().all(|symbol| symbol.present)
85 }
86}
87
88pub struct RocmLibrarySet {
90 evidence: RocmAbiEvidence,
91 hip: Library,
92 rocblas: Library,
93 rocblaslt: Option<Library>,
94}
95
96impl RocmLibrarySet {
97 fn new(
98 evidence: RocmAbiEvidence,
99 hip: Library,
100 rocblas: Library,
101 rocblaslt: Option<Library>,
102 ) -> Self {
103 Self {
104 evidence,
105 hip,
106 rocblas,
107 rocblaslt,
108 }
109 }
110
111 pub fn evidence(&self) -> &RocmAbiEvidence {
113 &self.evidence
114 }
115
116 pub fn handles(&self) -> (&Library, &Library, Option<&Library>) {
118 (&self.hip, &self.rocblas, self.rocblaslt.as_ref())
119 }
120
121 pub(crate) fn execution_handles(&self) -> (&Library, &Library) {
122 (&self.hip, &self.rocblas)
123 }
124}
125
126impl fmt::Debug for RocmLibrarySet {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 formatter
129 .debug_struct("RocmLibrarySet")
130 .field("evidence", &self.evidence)
131 .finish_non_exhaustive()
132 }
133}
134
135#[derive(Clone, Debug)]
137pub struct RocmRuntimeProbe {
138 pub runtime: Option<Arc<RocmLibrarySet>>,
140 pub evidence: Option<RocmAbiEvidence>,
142 pub diagnostics: Vec<String>,
144}
145
146impl RocmRuntimeProbe {
147 pub fn fake_present(evidence: RocmAbiEvidence) -> Self {
150 Self {
151 runtime: None,
152 evidence: Some(evidence),
153 diagnostics: Vec::new(),
154 }
155 }
156
157 pub fn is_available(&self) -> bool {
159 self.evidence
160 .as_ref()
161 .is_some_and(RocmAbiEvidence::is_complete)
162 }
163}
164
165#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct RocmLoadError {
168 pub message: String,
170}
171
172impl fmt::Display for RocmLoadError {
173 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
174 formatter.write_str(&self.message)
175 }
176}
177
178impl std::error::Error for RocmLoadError {}
179
180pub trait DynamicRocmLoader {
182 fn discover(&self) -> Result<RocmRuntimeProbe, RocmLoadError>;
184}
185
186#[derive(Clone, Debug)]
188pub struct RocmRuntimeLoader {
189 search_dirs: Vec<PathBuf>,
190 search_system: bool,
191}
192
193impl Default for RocmRuntimeLoader {
194 fn default() -> Self {
195 Self {
196 search_dirs: Vec::new(),
197 search_system: true,
198 }
199 }
200}
201
202impl RocmRuntimeLoader {
203 pub fn new() -> Self {
205 Self::default()
206 }
207
208 pub fn with_search_dirs(search_dirs: Vec<PathBuf>) -> Self {
210 Self {
211 search_dirs,
212 search_system: true,
213 }
214 }
215
216 pub fn with_search_dirs_only(search_dirs: Vec<PathBuf>) -> Self {
219 Self {
220 search_dirs,
221 search_system: false,
222 }
223 }
224}
225
226impl DynamicRocmLoader for RocmRuntimeLoader {
227 fn discover(&self) -> Result<RocmRuntimeProbe, RocmLoadError> {
228 let mut diagnostics = Vec::new();
229 if !cfg!(target_os = "linux") {
230 return Err(RocmLoadError {
231 message: "ROCm provider is supported only on Linux".to_owned(),
232 });
233 }
234 let (hip_name, hip) = self.open_first(HIP_NAMES, &mut diagnostics)?;
235 let (rocblas_name, rocblas) = self.open_first(ROCBLAS_NAMES, &mut diagnostics)?;
236 let rocblaslt = self.open_first(ROCBLASLT_NAMES, &mut diagnostics).ok();
237
238 let hip_symbols = symbol_evidence(&hip, HIP_SYMBOLS);
239 let rocblas_symbols = symbol_evidence(&rocblas, ROCBLAS_SYMBOLS);
240 let rocblaslt_symbols = rocblaslt
241 .as_ref()
242 .map(|(_, library)| symbol_evidence(library, ROCBLASLT_SYMBOLS))
243 .unwrap_or_default();
244 let hip_runtime_version = hip_runtime_version(&hip).ok();
245 let observed_gfx_targets = observed_gfx_targets();
246 let evidence = RocmAbiEvidence {
247 hip_library: hip_name,
248 rocblas_library: rocblas_name,
249 rocblaslt_library: rocblaslt.as_ref().map(|(name, _)| name.clone()),
250 hip_runtime_version,
251 observed_gfx_targets,
252 hip_symbols,
253 rocblas_symbols,
254 rocblaslt_symbols,
255 };
256 if !evidence.is_complete() {
257 return Ok(RocmRuntimeProbe {
258 runtime: None,
259 evidence: Some(evidence),
260 diagnostics,
261 });
262 }
263 let runtime = Arc::new(RocmLibrarySet::new(
264 evidence.clone(),
265 hip,
266 rocblas,
267 rocblaslt.map(|(_, library)| library),
268 ));
269 Ok(RocmRuntimeProbe {
270 runtime: Some(runtime),
271 evidence: Some(evidence),
272 diagnostics,
273 })
274 }
275}
276
277impl RocmRuntimeLoader {
278 fn open_first(
279 &self,
280 names: &[&str],
281 diagnostics: &mut Vec<String>,
282 ) -> Result<(String, Library), RocmLoadError> {
283 for name in candidate_paths(&self.search_dirs, names, self.search_system) {
284 match open_library(&name) {
285 Ok(library) => return Ok((name.display().to_string(), library)),
286 Err(error) => diagnostics.push(format!("{}: {error}", name.display())),
287 }
288 }
289 Err(RocmLoadError {
290 message: format!("ROCm library was not found; tried {}", names.join(", ")),
291 })
292 }
293}
294
295#[derive(Clone, Debug)]
297pub struct FakeRocmLoader {
298 probe: Result<RocmRuntimeProbe, RocmLoadError>,
299}
300
301impl FakeRocmLoader {
302 pub fn available() -> Self {
304 Self {
305 probe: Ok(RocmRuntimeProbe::fake_present(complete_fake_evidence())),
306 }
307 }
308
309 pub fn incomplete() -> Self {
311 let mut evidence = complete_fake_evidence();
312 if let Some(symbol) = evidence
313 .rocblas_symbols
314 .iter_mut()
315 .find(|symbol| symbol.name == "rocblas_sgemm")
316 {
317 symbol.present = false;
318 }
319 Self {
320 probe: Ok(RocmRuntimeProbe {
321 runtime: None,
322 evidence: Some(evidence),
323 diagnostics: vec!["missing rocblas_sgemm".to_owned()],
324 }),
325 }
326 }
327
328 pub fn without_rocblaslt() -> Self {
330 let mut evidence = complete_fake_evidence();
331 if let Some(symbol) = evidence
332 .rocblaslt_symbols
333 .iter_mut()
334 .find(|symbol| symbol.name == "rocblaslt_create_handle")
335 {
336 symbol.present = false;
337 }
338 Self {
339 probe: Ok(RocmRuntimeProbe {
340 runtime: None,
341 evidence: Some(evidence),
342 diagnostics: vec!["missing rocblaslt_create_handle".to_owned()],
343 }),
344 }
345 }
346
347 pub fn absent() -> Self {
349 Self {
350 probe: Err(RocmLoadError {
351 message: "ROCm runtime absent".to_owned(),
352 }),
353 }
354 }
355}
356
357impl DynamicRocmLoader for FakeRocmLoader {
358 fn discover(&self) -> Result<RocmRuntimeProbe, RocmLoadError> {
359 self.probe.clone()
360 }
361}
362
363pub fn discover_rocm_runtime() -> Result<RocmRuntimeProbe, RocmLoadError> {
365 RocmRuntimeLoader::new().discover()
366}
367
368fn complete_fake_evidence() -> RocmAbiEvidence {
369 RocmAbiEvidence {
370 hip_library: "fake-libamdhip64".to_owned(),
371 rocblas_library: "fake-librocblas".to_owned(),
372 rocblaslt_library: Some("fake-librocblaslt".to_owned()),
373 hip_runtime_version: Some(6_300_000),
374 observed_gfx_targets: vec!["gfx1103".to_owned()],
375 hip_symbols: HIP_SYMBOLS
376 .iter()
377 .map(|name| RocmSymbolEvidence {
378 name: (*name).to_owned(),
379 present: true,
380 })
381 .collect(),
382 rocblas_symbols: ROCBLAS_SYMBOLS
383 .iter()
384 .map(|name| RocmSymbolEvidence {
385 name: (*name).to_owned(),
386 present: true,
387 })
388 .collect(),
389 rocblaslt_symbols: ROCBLASLT_SYMBOLS
390 .iter()
391 .map(|name| RocmSymbolEvidence {
392 name: (*name).to_owned(),
393 present: true,
394 })
395 .collect(),
396 }
397}
398
399fn candidate_paths(search_dirs: &[PathBuf], names: &[&str], search_system: bool) -> Vec<PathBuf> {
400 let mut candidates = Vec::new();
401 for directory in search_dirs {
402 for name in names {
403 candidates.push(directory.join(name));
404 }
405 }
406 if search_system {
407 candidates.extend(names.iter().map(PathBuf::from));
408 }
409 candidates
410}
411
412fn symbol_evidence(library: &Library, names: &[&str]) -> Vec<RocmSymbolEvidence> {
413 names
414 .iter()
415 .map(|name| RocmSymbolEvidence {
416 name: (*name).to_owned(),
417 present: symbol_present(library, name),
418 })
419 .collect()
420}
421
422fn open_library(path: &Path) -> Result<Library, libloading::Error> {
423 unsafe { Library::new(path) }
425}
426
427fn symbol_present(library: &Library, name: &str) -> bool {
428 let mut bytes = name.as_bytes().to_vec();
429 bytes.push(0);
430 unsafe { library.get::<*mut c_void>(&bytes).is_ok() }
433}
434
435fn hip_runtime_version(library: &Library) -> Result<i32, RocmLoadError> {
436 type HipInit = unsafe extern "C" fn(u32) -> i32;
437 type HipRuntimeGetVersion = unsafe extern "C" fn(*mut i32) -> i32;
438 type HipGetDeviceCount = unsafe extern "C" fn(*mut i32) -> i32;
439 unsafe {
442 let hip_init = library
443 .get::<HipInit>(b"hipInit\0")
444 .map_err(|error| RocmLoadError {
445 message: error.to_string(),
446 })?;
447 let get_version = library
448 .get::<HipRuntimeGetVersion>(b"hipRuntimeGetVersion\0")
449 .map_err(|error| RocmLoadError {
450 message: error.to_string(),
451 })?;
452 let get_device_count = library
453 .get::<HipGetDeviceCount>(b"hipGetDeviceCount\0")
454 .map_err(|error| RocmLoadError {
455 message: error.to_string(),
456 })?;
457 let init_status = hip_init(0);
458 if init_status != 0 {
459 return Err(RocmLoadError {
460 message: format!("hipInit failed with status {init_status}"),
461 });
462 }
463 let mut device_count = 0;
464 let count_status = get_device_count(&mut device_count);
465 if count_status != 0 || device_count <= 0 {
466 return Err(RocmLoadError {
467 message: format!("hipGetDeviceCount failed with status {count_status}"),
468 });
469 }
470 let mut version = 0;
471 let version_status = get_version(&mut version);
472 if version_status != 0 {
473 return Err(RocmLoadError {
474 message: format!("hipRuntimeGetVersion failed with status {version_status}"),
475 });
476 }
477 Ok(version)
478 }
479}
480
481fn observed_gfx_targets() -> Vec<String> {
482 let Ok(output) = Command::new("rocm_agent_enumerator").output() else {
483 return Vec::new();
484 };
485 if !output.status.success() {
486 return Vec::new();
487 }
488 let Ok(stdout) = String::from_utf8(output.stdout) else {
489 return Vec::new();
490 };
491 let mut targets = stdout
492 .lines()
493 .map(str::trim)
494 .filter(|line| line.starts_with("gfx") && *line != "gfx000")
495 .map(ToOwned::to_owned)
496 .collect::<Vec<_>>();
497 targets.sort();
498 targets.dedup();
499 targets
500}