1use crate::installers::common::utils::{
18 command_exists, print_error, print_step, print_success, print_warning,
19};
20use std::fmt;
21
22#[derive(Debug, Clone)]
28pub struct GpuArchInfo {
29 pub gpu_arch: String,
31 pub hsa_override_gfx_version: String,
33}
34
35impl Default for GpuArchInfo {
36 fn default() -> Self {
37 Self {
38 gpu_arch: "gfx1100".to_string(),
39 hsa_override_gfx_version: "11.0.0".to_string(),
40 }
41 }
42}
43
44impl GpuArchInfo {
45 pub fn from_arch(arch: &str) -> Self {
49 let hsa_version = match arch {
50 "gfx1100" => "11.0.0",
51 "gfx1101" => "11.0.1",
52 "gfx1102" => "11.0.2",
53 "gfx1200" => "12.0.0",
54 "gfx1201" => "12.0.1",
55 "gfx1030" => "10.3.0",
56 "gfx1031" => "10.3.1",
57 "gfx1032" => "10.3.2",
58 _ => "11.0.0", };
60 Self {
61 gpu_arch: arch.to_string(),
62 hsa_override_gfx_version: hsa_version.to_string(),
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct GpuFilterResult {
70 pub discrete_gpu_indices: Vec<u32>,
72 pub hip_visible_devices: String,
74 pub gpu_count: usize,
76}
77
78#[derive(Debug, Clone)]
80pub struct SystemDependency {
81 pub package: &'static str,
83 pub description: &'static str,
85}
86
87impl SystemDependency {
88 pub fn required_packages() -> Vec<&'static str> {
92 vec![
93 "build-essential",
94 "cmake",
95 "git",
96 "python3-dev",
97 "python3-pip",
98 "libnuma-dev",
99 "pciutils",
100 "mesa-utils",
101 "clinfo",
102 ]
103 }
104}
105
106impl fmt::Display for SystemDependency {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 write!(f, "{} ({})", self.package, self.description)
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct DependencyCheckResult {
115 pub package: String,
117 pub installed: bool,
119 pub mapped_name: String,
121}
122
123#[derive(Debug, Clone)]
125pub struct EnvFileResult {
126 pub success: bool,
128 pub path: Option<String>,
130 pub errors: Vec<String>,
132}
133
134impl EnvFileResult {
135 pub fn success(path: String) -> Self {
137 Self {
138 success: true,
139 path: Some(path),
140 errors: Vec::new(),
141 }
142 }
143
144 pub fn failure(error: String) -> Self {
146 Self {
147 success: false,
148 path: None,
149 errors: vec![error],
150 }
151 }
152}
153
154pub fn detect_discrete_gpus() -> GpuFilterResult {
168 let mut discrete_indices: Vec<u32> = Vec::new();
169
170 let rocminfo_path = crate::installers::common::utils::resolve_rocminfo_path();
172 if command_exists(&rocminfo_path) || command_exists("rocminfo") {
173 discrete_indices = detect_gpus_from_rocminfo();
174 }
175
176 if discrete_indices.is_empty() {
178 discrete_indices = detect_gpus_from_lspci();
179 }
180
181 if discrete_indices.is_empty() {
183 discrete_indices = detect_gpus_from_render_nodes();
184 }
185
186 if discrete_indices.is_empty() {
188 discrete_indices.push(0);
189 }
190
191 let gpu_count = discrete_indices.len();
192 let hip_visible_devices = discrete_indices
193 .iter()
194 .map(|i| i.to_string())
195 .collect::<Vec<_>>()
196 .join(",");
197
198 GpuFilterResult {
199 discrete_gpu_indices: discrete_indices,
200 hip_visible_devices,
201 gpu_count,
202 }
203}
204
205fn detect_gpus_from_rocminfo() -> Vec<u32> {
207 let rocminfo_path = crate::installers::common::utils::resolve_rocminfo_path();
208 let output = match std::process::Command::new(&rocminfo_path).output() {
209 Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).to_string(),
210 _ => return Vec::new(),
211 };
212
213 let mut discrete_indices: Vec<u32> = Vec::new();
214 let mut current_gpu_id: Option<u32> = None;
215 let mut is_igpu = false;
216 let mut gpu_agent_counter = 0u32; for line in output.lines() {
219 let trimmed = line.trim();
220
221 if trimmed.starts_with("Agent") && trimmed.contains(char::is_numeric) {
223 }
225
226 if trimmed.starts_with("GPU ID:") {
227 if let Some(id) = current_gpu_id {
229 if !is_igpu && !discrete_indices.contains(&id) {
230 discrete_indices.push(id);
231 }
232 }
233
234 current_gpu_id = trimmed
236 .strip_prefix("GPU ID:")
237 .and_then(|s| s.trim().parse::<u32>().ok());
238 is_igpu = false;
239 } else if trimmed.starts_with("Marketing Name:") {
240 let marketing_name = trimmed
241 .strip_prefix("Marketing Name:")
242 .map(|s| s.trim())
243 .unwrap_or("");
244
245 if is_integrated_gpu_name(marketing_name) {
247 is_igpu = true;
248 }
249
250 if current_gpu_id.is_none() {
252 }
255 } else if trimmed.starts_with("Device Type:") {
256 let device_type = trimmed
257 .strip_prefix("Device Type:")
258 .map(|s| s.trim())
259 .unwrap_or("");
260
261 if device_type.contains("CPU") {
263 is_igpu = true;
264 }
265
266 if let Some(id) = current_gpu_id {
268 if !is_igpu && !discrete_indices.contains(&id) {
269 discrete_indices.push(id);
270 }
271 } else if device_type.contains("GPU") {
272 if !is_igpu && !discrete_indices.contains(&gpu_agent_counter) {
274 discrete_indices.push(gpu_agent_counter);
275 }
276 gpu_agent_counter += 1;
277 }
278 }
279 }
280
281 if let Some(id) = current_gpu_id {
283 if !is_igpu && !discrete_indices.contains(&id) {
284 discrete_indices.push(id);
285 }
286 }
287
288 discrete_indices.sort();
289 discrete_indices.dedup();
290 discrete_indices
291}
292
293fn detect_gpus_from_lspci() -> Vec<u32> {
295 if !command_exists("lspci") {
296 return Vec::new();
297 }
298
299 let output = match std::process::Command::new("lspci").output() {
300 Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).to_string(),
301 _ => return Vec::new(),
302 };
303
304 let mut discrete_count = 0u32;
305 for line in output.lines() {
306 let lower = line.to_lowercase();
307 if !((lower.contains("amd")
308 || lower.contains("radeon")
309 || lower.contains("advanced micro devices"))
310 && (lower.contains("vga") || lower.contains("3d") || lower.contains("display")))
311 {
312 continue;
313 }
314 if is_integrated_gpu_name(line) {
316 continue;
317 }
318 discrete_count += 1;
319 }
320
321 if discrete_count > 0 {
322 (0..discrete_count).collect()
323 } else {
324 Vec::new()
325 }
326}
327
328fn detect_gpus_from_render_nodes() -> Vec<u32> {
331 let render_entries: Vec<_> = std::fs::read_dir("/dev/dri")
332 .map(|entries| {
333 entries
334 .filter_map(|e| e.ok())
335 .filter(|e| e.file_name().to_string_lossy().starts_with("render"))
336 .collect()
337 })
338 .unwrap_or_default();
339
340 if render_entries.is_empty() {
341 return Vec::new();
342 }
343
344 let discrete_count = render_entries
346 .iter()
347 .filter(|entry| {
348 let name = entry.file_name().to_string_lossy().to_string();
349 let node_num: String = name
351 .trim_start_matches("render")
352 .chars()
353 .take_while(|c| c.is_ascii_digit())
354 .collect();
355 if node_num.is_empty() {
356 return true; }
358
359 let sysfs_path = format!("/sys/class/drm/card{}-render/device/uevent", node_num);
361 if let Ok(uevent) = std::fs::read_to_string(&sysfs_path) {
362 for line in uevent.lines() {
364 if line.starts_with("PCI_ID=") {
365 return true;
367 }
368 }
369 }
370
371 let card_sysfs = format!("/sys/class/drm/card{}/device/device", node_num);
373 if let Ok(pci_id) = std::fs::read_to_string(&card_sysfs) {
374 let pci_id = pci_id.trim();
375 if pci_id == "0x164e" {
377 return false; }
379 }
380
381 true })
383 .count();
384
385 if discrete_count > 0 {
386 (0..discrete_count as u32).collect()
387 } else if !render_entries.is_empty() {
388 (0..render_entries.len() as u32).collect()
390 } else {
391 Vec::new()
392 }
393}
394
395pub fn is_integrated_gpu_name(name: &str) -> bool {
400 let lower = name.to_lowercase();
401 lower.contains("raphael")
402 || lower.contains("integrated")
403 || lower.contains("igpu")
404 || lower.contains("ryzen") && !lower.contains("rx")
405 || lower == "amd radeon graphics"
406}
407
408pub fn detect_correct_gpu_arch(marketing_name: Option<&str>) -> GpuArchInfo {
421 let name = match marketing_name {
422 Some(n) => n,
423 None => return GpuArchInfo::default(),
424 };
425
426 let lower = name.to_lowercase();
427
428 if is_integrated_gpu_name(name) {
430 return GpuArchInfo::default();
431 }
432
433 let arch = if lower.contains("7900 xtx")
436 || lower.contains("7900xtx")
437 || lower.contains("7900 xt")
438 || lower.contains("7900xt")
439 || lower.contains("7900 gre")
440 || lower.contains("7900gre")
441 {
442 "gfx1100"
443 } else if lower.contains("7800 xt")
444 || lower.contains("7800xt")
445 || lower.contains("7800 gre")
446 || lower.contains("7800gre")
447 || lower.contains("7700 xt")
448 || lower.contains("7700xt")
449 {
450 "gfx1101"
451 } else if lower.contains("7600 xt") || lower.contains("7600xt") || lower.contains("7600") {
452 "gfx1102"
453 } else if lower.contains("9070 xt") || lower.contains("9070xt") {
454 "gfx1200"
455 } else {
456 "gfx1100" };
458
459 GpuArchInfo::from_arch(arch)
460}
461
462pub fn check_system_dependency(package: &str) -> DependencyCheckResult {
475 use crate::installers::common::distro::DistroFacade;
476 use crate::installers::common::package_mappings::map_package_name;
477
478 let distro = DistroFacade::detect();
479 let pkg_mgr = distro.package_manager();
480 let mapped = map_package_name(package, pkg_mgr);
481 let installed = check_package_installed(&mapped);
482
483 DependencyCheckResult {
484 package: package.to_string(),
485 installed,
486 mapped_name: mapped,
487 }
488}
489
490pub fn check_all_dependencies() -> Vec<DependencyCheckResult> {
494 SystemDependency::required_packages()
495 .into_iter()
496 .map(check_system_dependency)
497 .collect()
498}
499
500fn check_package_installed(package: &str) -> bool {
502 if command_exists("dpkg-query") {
504 let output = std::process::Command::new("dpkg-query")
505 .args(["-W", "-f=${Status}", package])
506 .output();
507 if let Ok(o) = output {
508 let stdout = String::from_utf8_lossy(&o.stdout);
509 if stdout.contains("install ok installed") {
510 return true;
511 }
512 }
513 }
514
515 if command_exists("rpm") {
517 let output = std::process::Command::new("rpm")
518 .args(["-q", package])
519 .output();
520 if let Ok(o) = output {
521 if o.status.success() {
522 return true;
523 }
524 }
525 }
526
527 if command_exists("pacman") {
529 let output = std::process::Command::new("pacman")
530 .args(["-Q", package])
531 .output();
532 if let Ok(o) = output {
533 if o.status.success() {
534 return true;
535 }
536 }
537 }
538
539 false
540}
541
542pub fn create_env_file(
555 hip_visible_devices: &str,
556 rocm_path: &str,
557 rocm_version: &str,
558 rocm_channel: &str,
559 gpu_arch: &str,
560 hsa_override_gfx_version: &str,
561 python_bin: &str,
562) -> EnvFileResult {
563 let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
564 let env_path = format!("{home}/.mlstack_env");
565 let fish_env_path = format!("{home}/.config/fish/conf.d/mlstack_env.fish");
566
567 let content = generate_env_file_content(
568 hip_visible_devices,
569 rocm_path,
570 rocm_version,
571 rocm_channel,
572 gpu_arch,
573 hsa_override_gfx_version,
574 python_bin,
575 );
576
577 if let Err(e) = std::fs::write(&env_path, &content) {
579 let msg = format!("Failed to write environment file: {e}");
580 print_error(&msg);
581 return EnvFileResult::failure(msg);
582 }
583
584 let fish_content = generate_fish_env_file_content(
586 hip_visible_devices,
587 rocm_path,
588 rocm_version,
589 rocm_channel,
590 gpu_arch,
591 hsa_override_gfx_version,
592 python_bin,
593 );
594 if let Some(parent) = std::path::Path::new(&fish_env_path).parent() {
596 let _ = std::fs::create_dir_all(parent);
597 }
598 if let Err(e) = std::fs::write(&fish_env_path, &fish_content) {
599 print_warning(&format!("Failed to write fish env file: {e}"));
601 } else {
602 print_success(&format!("Fish env file created: {fish_env_path}"));
603 }
604
605 print_success(&format!("Environment file created: {env_path}"));
606 EnvFileResult::success(env_path)
607}
608
609pub fn generate_env_file_content(
620 hip_visible_devices: &str,
621 rocm_path: &str,
622 rocm_version: &str,
623 rocm_channel: &str,
624 gpu_arch: &str,
625 hsa_override_gfx_version: &str,
626 python_bin: &str,
627) -> String {
628 let home = std::env::var("HOME").unwrap_or_else(|_| "$HOME".to_string());
629
630 format!(
631 r#"# ML Stack Environment File
632# Created by Enhanced ML Stack Environment Setup (Rust native)
633# Date: {date}
634
635# GPU Selection
636# Only set if not already set
637if [ -z "${{HIP_VISIBLE_DEVICES:-}}" ]; then export HIP_VISIBLE_DEVICES={hip_visible_devices}; fi
638if [ -z "${{CUDA_VISIBLE_DEVICES:-}}" ]; then export CUDA_VISIBLE_DEVICES={hip_visible_devices}; fi
639if [ -z "${{PYTORCH_ROCM_DEVICE:-}}" ]; then export PYTORCH_ROCM_DEVICE={hip_visible_devices}; fi
640
641# ROCm Settings
642# Only set if not already set
643if [ -z "${{ROCM_HOME:-}}" ]; then export ROCM_HOME={rocm_path}; fi
644if [ -z "${{CUDA_HOME:-}}" ]; then export CUDA_HOME={rocm_path}; fi
645if [ -z "${{ROCM_VERSION:-}}" ]; then export ROCM_VERSION={rocm_version}; fi
646if [ -z "${{ROCM_CHANNEL:-}}" ]; then export ROCM_CHANNEL={rocm_channel}; fi
647# GPU_ARCH is set based on detected hardware (corrected for rocminfo bugs)
648export GPU_ARCH={gpu_arch}
649
650# Path Settings - Hardcoded safe paths to prevent "command not found" errors
651export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:{rocm_path}/bin:{rocm_path}/hip/bin:$PATH"
652export LD_LIBRARY_PATH="$HOME/.mlstack/libmpi-compat:$HOME/.mlstack/libmpi-compat-user-$(id -u):{rocm_path}/lib:{rocm_path}/hip/lib:{rocm_path}/opencl/lib:${{LD_LIBRARY_PATH:-}}"
653
654# Performance Settings
655# HSA_OVERRIDE_GFX_VERSION is set based on detected GPU_ARCH
656export HSA_OVERRIDE_GFX_VERSION={hsa_override_gfx_version}
657export HSA_ENABLE_SDMA=0
658export GPU_MAX_HEAP_SIZE=100
659export GPU_MAX_ALLOC_PERCENT=100
660export HSA_TOOLS_LIB=/opt/rocm/lib/rocprofiler-sdk/librocprofiler-sdk-tool.so
661
662# MIOpen Settings
663# Only set if not already set
664if [ -z "${{MIOPEN_DEBUG_CONV_IMPLICIT_GEMM:-}}" ]; then export MIOPEN_DEBUG_CONV_IMPLICIT_GEMM=1; fi
665if [ -z "${{MIOPEN_FIND_MODE:-}}" ]; then export MIOPEN_FIND_MODE=3; fi
666if [ -z "${{MIOPEN_FIND_ENFORCE:-}}" ]; then export MIOPEN_FIND_ENFORCE=3; fi
667
668# PyTorch Settings
669# Only set if not already set
670if [ -z "${{TORCH_CUDA_ARCH_LIST:-}}" ]; then export TORCH_CUDA_ARCH_LIST="7.0;8.0;9.0"; fi
671# Use PYTORCH_ALLOC_CONF instead of deprecated PYTORCH_CUDA_ALLOC_CONF
672if [ -z "${{PYTORCH_ALLOC_CONF:-}}" ]; then export PYTORCH_ALLOC_CONF="max_split_size_mb:512"; fi
673if [ -z "${{PYTORCH_HIP_ALLOC_CONF:-}}" ]; then export PYTORCH_HIP_ALLOC_CONF="max_split_size_mb:512"; fi
674if [ -z "${{VLLM_WORKER_MULTIPROC_METHOD:-}}" ]; then export VLLM_WORKER_MULTIPROC_METHOD=spawn; fi
675if [ -z "${{VLLM_ROCM_USE_AITER:-}}" ]; then export VLLM_ROCM_USE_AITER=0; fi
676if [ -z "${{MLSTACK_TRITON_HOME:-}}" ]; then export MLSTACK_TRITON_HOME="$HOME/.cache/mlstack/triton"; fi
677if [ -z "${{TRITON_HOME:-}}" ]; then export TRITON_HOME="$MLSTACK_TRITON_HOME"; fi
678if [ -z "${{TRITON_CACHE_DIR:-}}" ]; then export TRITON_CACHE_DIR="$TRITON_HOME/cache"; fi
679if [ -z "${{TRITON_DUMP_DIR:-}}" ]; then export TRITON_DUMP_DIR="$TRITON_HOME/dump"; fi
680if [ -z "${{TRITON_OVERRIDE_DIR:-}}" ]; then export TRITON_OVERRIDE_DIR="$TRITON_HOME/override"; fi
681mkdir -p "$TRITON_CACHE_DIR" "$TRITON_DUMP_DIR" "$TRITON_OVERRIDE_DIR" 2>/dev/null || true
682
683# MPI Settings
684# Only set if not already set
685if [ -z "${{OMPI_MCA_opal_cuda_support:-}}" ]; then export OMPI_MCA_opal_cuda_support=true; fi
686if [ -z "${{OMPI_MCA_pml_ucx_opal_cuda_support:-}}" ]; then export OMPI_MCA_pml_ucx_opal_cuda_support=true; fi
687if [ -z "${{OMPI_MCA_btl_openib_allow_ib:-}}" ]; then export OMPI_MCA_btl_openib_allow_ib=true; fi
688if [ -z "${{OMPI_MCA_btl_openib_warn_no_device_params_found:-}}" ]; then export OMPI_MCA_btl_openib_warn_no_device_params_found=0; fi
689if [ -z "${{OMPI_MCA_coll_hcoll_enable:-}}" ]; then export OMPI_MCA_coll_hcoll_enable=0; fi
690if [ -z "${{OMPI_MCA_pml:-}}" ]; then export OMPI_MCA_pml=ucx; fi
691if [ -z "${{OMPI_MCA_osc:-}}" ]; then export OMPI_MCA_osc=ucx; fi
692if [ -z "${{OMPI_MCA_btl:-}}" ]; then export OMPI_MCA_btl=^openib,uct; fi
693# Prevent UCX ROCm memory registration segfault on multi-GPU systems
694export UCX_MEMTYPE_CACHE=no
695export UCX_LOG_LEVEL=error
696
697# Global flags for seamless pip installs (PEP 668 override)
698export PIP_BREAK_SYSTEM_PACKAGES=1
699export UV_PIP_BREAK_SYSTEM_PACKAGES=1
700export UV_SYSTEM_PYTHON=1
701
702# Python Interpreter
703# Canonical Python binary — all ML components install to this interpreter.
704# Ensures a single unified environment regardless of how many Pythons exist.
705export MLSTACK_PYTHON_BIN={python_bin}
706export UV_PYTHON={python_bin}
707
708# Flash Attention AMD
709export FLASH_ATTENTION_TRITON_AMD_ENABLE=TRUE
710
711# ONNX Runtime Settings
712# Only add if not already in PYTHONPATH
713if ! echo "${{PYTHONPATH:-}}" | grep -q "{home}/onnxruntime_build/onnxruntime/build/Linux/Release"; then
714 export PYTHONPATH={home}/onnxruntime_build/onnxruntime/build/Linux/Release:${{PYTHONPATH:-}}
715fi
716"#,
717 date = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
718 hip_visible_devices = hip_visible_devices,
719 rocm_path = rocm_path,
720 rocm_version = rocm_version,
721 rocm_channel = rocm_channel,
722 gpu_arch = gpu_arch,
723 hsa_override_gfx_version = hsa_override_gfx_version,
724 home = home,
725 python_bin = python_bin,
726 )
727}
728
729pub fn generate_fish_env_file_content(
736 hip_visible_devices: &str,
737 rocm_path: &str,
738 rocm_version: &str,
739 rocm_channel: &str,
740 gpu_arch: &str,
741 hsa_override_gfx_version: &str,
742 python_bin: &str,
743) -> String {
744 let home = std::env::var("HOME").unwrap_or_else(|_| "$HOME".to_string());
745
746 let onnx_path = format!("{home}/onnxruntime_build/onnxruntime/build/Linux/Release");
748
749 format!(
750 r##"# ML Stack Environment File — Fish Shell Native
751# Generated by Enhanced ML Stack Environment Setup (Rust native)
752# Date: {date}
753#
754# This file is auto-generated. Edit via rusty-stack's env setup, not by hand.
755
756# --- GPU Selection ---
757set -q HIP_VISIBLE_DEVICES; or set -gx HIP_VISIBLE_DEVICES {hip_visible_devices}
758set -q CUDA_VISIBLE_DEVICES; or set -gx CUDA_VISIBLE_DEVICES {hip_visible_devices}
759set -q PYTORCH_ROCM_DEVICE; or set -gx PYTORCH_ROCM_DEVICE {hip_visible_devices}
760
761# --- ROCm Settings ---
762set -q ROCM_HOME; or set -gx ROCM_HOME {rocm_path}
763set -q CUDA_HOME; or set -gx CUDA_HOME {rocm_path}
764set -q ROCM_VERSION; or set -gx ROCM_VERSION {rocm_version}
765set -q ROCM_CHANNEL; or set -gx ROCM_CHANNEL {rocm_channel}
766# GPU_ARCH is set based on detected hardware (corrected for rocminfo bugs)
767set -gx GPU_ARCH {gpu_arch}
768
769# --- Path Settings ---
770set -gx PATH /usr/local/bin /usr/bin /bin /usr/local/games /usr/games {rocm_path}/bin {rocm_path}/hip/bin $PATH
771set -gx LD_LIBRARY_PATH $HOME/.mlstack/libmpi-compat $HOME/.mlstack/libmpi-compat-user-(id -u) {rocm_path}/lib {rocm_path}/hip/lib {rocm_path}/opencl/lib $LD_LIBRARY_PATH
772
773# --- Performance Settings ---
774set -gx HSA_OVERRIDE_GFX_VERSION {hsa_override_gfx_version}
775set -gx HSA_ENABLE_SDMA 0
776set -gx GPU_MAX_HEAP_SIZE 100
777set -gx GPU_MAX_ALLOC_PERCENT 100
778set -gx HSA_TOOLS_LIB /opt/rocm/lib/rocprofiler-sdk/librocprofiler-sdk-tool.so
779
780# --- MIOpen Settings ---
781set -q MIOPEN_DEBUG_CONV_IMPLICIT_GEMM; or set -gx MIOPEN_DEBUG_CONV_IMPLICIT_GEMM 1
782set -q MIOPEN_FIND_MODE; or set -gx MIOPEN_FIND_MODE 3
783set -q MIOPEN_FIND_ENFORCE; or set -gx MIOPEN_FIND_ENFORCE 3
784
785# --- PyTorch Settings ---
786set -q TORCH_CUDA_ARCH_LIST; or set -gx TORCH_CUDA_ARCH_LIST "7.0;8.0;9.0"
787set -q PYTORCH_ALLOC_CONF; or set -gx PYTORCH_ALLOC_CONF "max_split_size_mb:512"
788set -q PYTORCH_HIP_ALLOC_CONF; or set -gx PYTORCH_HIP_ALLOC_CONF "max_split_size_mb:512"
789set -q VLLM_WORKER_MULTIPROC_METHOD; or set -gx VLLM_WORKER_MULTIPROC_METHOD spawn
790set -q VLLM_ROCM_USE_AITER; or set -gx VLLM_ROCM_USE_AITER 0
791set -q MLSTACK_TRITON_HOME; or set -gx MLSTACK_TRITON_HOME "$HOME/.cache/mlstack/triton"
792set -q TRITON_HOME; or set -gx TRITON_HOME $MLSTACK_TRITON_HOME
793set -q TRITON_CACHE_DIR; or set -gx TRITON_CACHE_DIR "$TRITON_HOME/cache"
794set -q TRITON_DUMP_DIR; or set -gx TRITON_DUMP_DIR "$TRITON_HOME/dump"
795set -q TRITON_OVERRIDE_DIR; or set -gx TRITON_OVERRIDE_DIR "$TRITON_HOME/override"
796mkdir -p $TRITON_CACHE_DIR $TRITON_DUMP_DIR $TRITON_OVERRIDE_DIR 2>/dev/null; or true
797
798# --- Global flags for seamless pip installs (PEP 668 override) ---
799set -q PIP_BREAK_SYSTEM_PACKAGES; or set -gx PIP_BREAK_SYSTEM_PACKAGES 1
800set -q UV_PIP_BREAK_SYSTEM_PACKAGES; or set -gx UV_PIP_BREAK_SYSTEM_PACKAGES 1
801set -gx UV_SYSTEM_PYTHON 1
802
803# --- Python Interpreter ---
804# Canonical Python binary — all ML components install to this interpreter.
805# Ensures a single unified environment regardless of how many Pythons exist.
806set -gx MLSTACK_PYTHON_BIN {python_bin}
807set -gx UV_PYTHON {python_bin}
808
809# --- Flash Attention AMD ---
810set -gx FLASH_ATTENTION_TRITON_AMD_ENABLE TRUE
811
812# --- MPI/UCX Settings ---
813set -q OMPI_MCA_opal_cuda_support; or set -gx OMPI_MCA_opal_cuda_support true
814set -q OMPI_MCA_pml_ucx_opal_cuda_support; or set -gx OMPI_MCA_pml_ucx_opal_cuda_support true
815set -q OMPI_MCA_btl_openib_allow_ib; or set -gx OMPI_MCA_btl_openib_allow_ib true
816set -q OMPI_MCA_btl_openib_warn_no_device_params_found; or set -gx OMPI_MCA_btl_openib_warn_no_device_params_found 0
817set -q OMPI_MCA_coll_hcoll_enable; or set -gx OMPI_MCA_coll_hcoll_enable 0
818set -q OMPI_MCA_pml; or set -gx OMPI_MCA_pml ucx
819set -q OMPI_MCA_osc; or set -gx OMPI_MCA_osc ucx
820set -q OMPI_MCA_btl; or set -gx OMPI_MCA_btl '^openib,uct'
821set -gx UCX_MEMTYPE_CACHE no
822set -gx UCX_LOG_LEVEL error
823
824# --- ONNX Runtime Settings ---
825if not string match -q '*{onnx_path}*' "$PYTHONPATH"
826 set -gx PYTHONPATH {onnx_path} $PYTHONPATH
827end
828"##,
829 date = chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
830 hip_visible_devices = hip_visible_devices,
831 rocm_path = rocm_path,
832 rocm_version = rocm_version,
833 rocm_channel = rocm_channel,
834 gpu_arch = gpu_arch,
835 hsa_override_gfx_version = hsa_override_gfx_version,
836 python_bin = python_bin,
837 onnx_path = onnx_path,
838 )
839}
840
841pub fn setup_environment() -> (EnvFileResult, GpuFilterResult, GpuArchInfo) {
859 print_step("Enhanced ML Stack Environment Setup");
860
861 print_step("Detecting AMD GPUs...");
863 let gpu_filter = detect_discrete_gpus();
864 if gpu_filter.gpu_count > 0 {
865 print_success(&format!(
866 "Detected {} discrete GPU(s): {}",
867 gpu_filter.gpu_count, gpu_filter.hip_visible_devices
868 ));
869 } else {
870 print_warning("No discrete GPUs detected, using defaults");
871 }
872
873 let rocm_env = crate::installers::common::rocm_env::RocmEnv::detect();
875 let rocm_path = rocm_env
876 .path()
877 .map(|p| p.to_string_lossy().to_string())
878 .unwrap_or_else(|| "/opt/rocm".to_string());
879 let rocm_version = if rocm_env.version().is_empty() {
880 "7.2.0".to_string()
881 } else {
882 rocm_env.version().to_string()
883 };
884 let rocm_channel = std::env::var("ROCM_CHANNEL").unwrap_or_else(|_| "latest".to_string());
885
886 if rocm_env.is_detected() {
887 print_success(&format!("ROCm {rocm_version} detected at {rocm_path}"));
888 } else {
889 print_warning("ROCm not detected, using default paths");
890 }
891
892 let gpu_arch_info = detect_gpu_arch_from_system();
894
895 print_step("Creating environment file...");
897 let python_bin = crate::platform::environment::resolve_canonical_python_bin();
898 let env_result = create_env_file(
899 &gpu_filter.hip_visible_devices,
900 &rocm_path,
901 &rocm_version,
902 &rocm_channel,
903 &gpu_arch_info.gpu_arch,
904 &gpu_arch_info.hsa_override_gfx_version,
905 &python_bin,
906 );
907
908 if env_result.success {
909 print_success("Environment setup complete");
910 }
911
912 (env_result, gpu_filter, gpu_arch_info)
913}
914
915fn detect_gpu_arch_from_system() -> GpuArchInfo {
917 let rocminfo_path = crate::installers::common::utils::resolve_rocminfo_path();
918 if !command_exists(&rocminfo_path) && !command_exists("rocminfo") {
919 return GpuArchInfo::default();
920 }
921
922 let output = match std::process::Command::new(&rocminfo_path).output() {
923 Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).to_string(),
924 _ => return GpuArchInfo::default(),
925 };
926
927 let mut marketing_name = String::new();
928
929 for line in output.lines() {
930 let trimmed = line.trim();
931 if trimmed.starts_with("Marketing Name:") {
932 let name = trimmed
933 .strip_prefix("Marketing Name:")
934 .map(|s| s.trim())
935 .unwrap_or("");
936
937 if is_integrated_gpu_name(name) {
939 continue;
940 }
941
942 if !name.is_empty() {
943 marketing_name = name.to_string();
944 break;
945 }
946 }
947 }
948
949 detect_correct_gpu_arch(if marketing_name.is_empty() {
950 None
951 } else {
952 Some(&marketing_name)
953 })
954}
955
956#[cfg(test)]
961mod tests {
962 use super::*;
963
964 #[test]
967 fn test_gpu_arch_info_default() {
968 let info = GpuArchInfo::default();
969 assert_eq!(info.gpu_arch, "gfx1100");
970 assert_eq!(info.hsa_override_gfx_version, "11.0.0");
971 }
972
973 #[test]
974 fn test_gpu_arch_info_from_arch_all_variants() {
975 let cases = vec![
976 ("gfx1100", "11.0.0"),
977 ("gfx1101", "11.0.1"),
978 ("gfx1102", "11.0.2"),
979 ("gfx1200", "12.0.0"),
980 ("gfx1201", "12.0.1"),
981 ("gfx1030", "10.3.0"),
982 ("gfx1031", "10.3.1"),
983 ("gfx1032", "10.3.2"),
984 ("gfx9999", "11.0.0"), ];
986
987 for (arch, expected_hsa) in cases {
988 let info = GpuArchInfo::from_arch(arch);
989 assert_eq!(info.gpu_arch, arch, "GPU arch mismatch for {arch}");
990 assert_eq!(
991 info.hsa_override_gfx_version, expected_hsa,
992 "HSA version mismatch for {arch}"
993 );
994 }
995 }
996
997 #[test]
1000 fn test_is_integrated_gpu_name_positive() {
1001 assert!(is_integrated_gpu_name("Raphael"));
1002 assert!(is_integrated_gpu_name("AMD Ryzen Graphics"));
1003 assert!(is_integrated_gpu_name("Integrated Graphics"));
1004 assert!(is_integrated_gpu_name("iGPU Device"));
1005 assert!(is_integrated_gpu_name("AMD Radeon Graphics"));
1006 }
1007
1008 #[test]
1009 fn test_is_integrated_gpu_name_negative() {
1010 assert!(!is_integrated_gpu_name("Radeon RX 7900 XTX"));
1011 assert!(!is_integrated_gpu_name("Radeon RX 7800 XT"));
1012 assert!(!is_integrated_gpu_name("Radeon RX 9070 XT"));
1013 assert!(!is_integrated_gpu_name("Unknown GPU"));
1014 }
1015
1016 #[test]
1017 fn test_is_integrated_gpu_name_case_insensitive() {
1018 assert!(is_integrated_gpu_name("raphael"));
1019 assert!(is_integrated_gpu_name("INTEGRATED"));
1020 assert!(is_integrated_gpu_name("AMD RADEON GRAPHICS"));
1021 }
1022
1023 #[test]
1026 fn test_detect_discrete_gpus_returns_result() {
1027 let result = detect_discrete_gpus();
1028 assert!(!result.hip_visible_devices.is_empty());
1030 assert!(result.gpu_count >= 1);
1031 }
1032
1033 #[test]
1034 fn test_detect_gpus_from_lspci_returns_vec() {
1035 let result = detect_gpus_from_lspci();
1037 let _ = result;
1039 }
1040
1041 #[test]
1042 fn test_detect_gpus_from_render_nodes_returns_vec() {
1043 let result = detect_gpus_from_render_nodes();
1044 let _ = result;
1045 }
1046
1047 #[test]
1050 fn test_detect_correct_gpu_arch_7900_xtx() {
1051 let info = detect_correct_gpu_arch(Some("Radeon RX 7900 XTX"));
1052 assert_eq!(info.gpu_arch, "gfx1100");
1053 assert_eq!(info.hsa_override_gfx_version, "11.0.0");
1054 }
1055
1056 #[test]
1057 fn test_detect_correct_gpu_arch_7800_xt() {
1058 let info = detect_correct_gpu_arch(Some("Radeon RX 7800 XT"));
1059 assert_eq!(info.gpu_arch, "gfx1101");
1060 }
1061
1062 #[test]
1063 fn test_detect_correct_gpu_arch_7600() {
1064 let info = detect_correct_gpu_arch(Some("Radeon RX 7600"));
1065 assert_eq!(info.gpu_arch, "gfx1102");
1066 }
1067
1068 #[test]
1069 fn test_detect_correct_gpu_arch_9070_xt() {
1070 let info = detect_correct_gpu_arch(Some("Radeon RX 9070 XT"));
1071 assert_eq!(info.gpu_arch, "gfx1200");
1072 }
1073
1074 #[test]
1075 fn test_detect_correct_gpu_arch_none() {
1076 let info = detect_correct_gpu_arch(None);
1077 assert_eq!(info.gpu_arch, "gfx1100"); }
1079
1080 #[test]
1081 fn test_detect_correct_gpu_arch_unknown() {
1082 let info = detect_correct_gpu_arch(Some("Unknown GPU Model"));
1083 assert_eq!(info.gpu_arch, "gfx1100"); }
1085
1086 #[test]
1087 fn test_detect_correct_gpu_arch_integrated_skipped() {
1088 let info = detect_correct_gpu_arch(Some("AMD Ryzen Graphics"));
1089 assert_eq!(info.gpu_arch, "gfx1100"); }
1091
1092 #[test]
1093 fn test_detect_correct_gpu_arch_case_insensitive() {
1094 let info = detect_correct_gpu_arch(Some("radeon rx 7900 xtx"));
1095 assert_eq!(info.gpu_arch, "gfx1100");
1096 }
1097
1098 #[test]
1101 fn test_system_dependency_required_packages() {
1102 let packages = SystemDependency::required_packages();
1103 assert!(!packages.is_empty());
1104 assert!(packages.contains(&"git"));
1105 assert!(packages.contains(&"cmake"));
1106 assert!(packages.contains(&"build-essential"));
1107 }
1108
1109 #[test]
1110 fn test_check_system_dependency_git() {
1111 let result = check_system_dependency("git");
1112 assert_eq!(result.package, "git");
1113 assert!(result.installed, "git should be installed");
1115 }
1116
1117 #[test]
1118 fn test_check_system_dependency_nonexistent() {
1119 let result = check_system_dependency("nonexistent-pkg-xyz-12345");
1120 assert!(!result.installed);
1121 }
1122
1123 #[test]
1124 fn test_check_all_dependencies_returns_results() {
1125 let results = check_all_dependencies();
1126 assert!(!results.is_empty(), "Should check at least one dependency");
1127 assert_eq!(results.len(), SystemDependency::required_packages().len());
1128 }
1129
1130 #[test]
1133 fn test_generate_env_file_has_header() {
1134 let content = generate_env_file_content(
1135 "0",
1136 "/opt/rocm",
1137 "7.2.0",
1138 "latest",
1139 "gfx1100",
1140 "11.0.0",
1141 "python3",
1142 );
1143 assert!(content.starts_with("# ML Stack Environment File"));
1144 }
1145
1146 #[test]
1147 fn test_generate_env_file_has_gpu_selection() {
1148 let content = generate_env_file_content(
1149 "0,1",
1150 "/opt/rocm",
1151 "7.2.0",
1152 "latest",
1153 "gfx1100",
1154 "11.0.0",
1155 "python3",
1156 );
1157 assert!(content.contains("HIP_VISIBLE_DEVICES"));
1158 assert!(content.contains("CUDA_VISIBLE_DEVICES"));
1159 assert!(content.contains("PYTORCH_ROCM_DEVICE"));
1160 assert!(content.contains("0,1"));
1161 }
1162
1163 #[test]
1164 fn test_generate_env_file_has_rocm_settings() {
1165 let content = generate_env_file_content(
1166 "0",
1167 "/opt/rocm",
1168 "7.2.0",
1169 "latest",
1170 "gfx1100",
1171 "11.0.0",
1172 "python3",
1173 );
1174 assert!(content.contains("ROCM_HOME"));
1175 assert!(content.contains("CUDA_HOME"));
1176 assert!(content.contains("ROCM_VERSION"));
1177 assert!(content.contains("ROCM_CHANNEL"));
1178 assert!(content.contains("GPU_ARCH"));
1179 assert!(content.contains("gfx1100"));
1180 }
1181
1182 #[test]
1183 fn test_generate_env_file_has_path_settings() {
1184 let content = generate_env_file_content(
1185 "0",
1186 "/opt/rocm",
1187 "7.2.0",
1188 "latest",
1189 "gfx1100",
1190 "11.0.0",
1191 "python3",
1192 );
1193 assert!(content.contains("PATH="));
1194 assert!(content.contains("LD_LIBRARY_PATH"));
1195 assert!(content.contains("/opt/rocm/bin"));
1196 }
1197
1198 #[test]
1199 fn test_generate_env_file_has_performance_settings() {
1200 let content = generate_env_file_content(
1201 "0",
1202 "/opt/rocm",
1203 "7.2.0",
1204 "latest",
1205 "gfx1100",
1206 "11.0.0",
1207 "python3",
1208 );
1209 assert!(content.contains("HSA_OVERRIDE_GFX_VERSION"));
1210 assert!(content.contains("HSA_ENABLE_SDMA"));
1211 assert!(content.contains("GPU_MAX_HEAP_SIZE"));
1212 assert!(content.contains("GPU_MAX_ALLOC_PERCENT"));
1213 assert!(content.contains("HSA_TOOLS_LIB"));
1214 }
1215
1216 #[test]
1217 fn test_generate_env_file_has_miopen_settings() {
1218 let content = generate_env_file_content(
1219 "0",
1220 "/opt/rocm",
1221 "7.2.0",
1222 "latest",
1223 "gfx1100",
1224 "11.0.0",
1225 "python3",
1226 );
1227 assert!(content.contains("MIOPEN_DEBUG_CONV_IMPLICIT_GEMM"));
1228 assert!(content.contains("MIOPEN_FIND_MODE"));
1229 assert!(content.contains("MIOPEN_FIND_ENFORCE"));
1230 }
1231
1232 #[test]
1233 fn test_generate_env_file_has_pytorch_settings() {
1234 let content = generate_env_file_content(
1235 "0",
1236 "/opt/rocm",
1237 "7.2.0",
1238 "latest",
1239 "gfx1100",
1240 "11.0.0",
1241 "python3",
1242 );
1243 assert!(content.contains("TORCH_CUDA_ARCH_LIST"));
1244 assert!(content.contains("PYTORCH_ALLOC_CONF"));
1245 assert!(content.contains("PYTORCH_HIP_ALLOC_CONF"));
1246 assert!(content.contains("VLLM_WORKER_MULTIPROC_METHOD"));
1247 }
1248
1249 #[test]
1250 fn test_generate_env_file_has_triton_settings() {
1251 let content = generate_env_file_content(
1252 "0",
1253 "/opt/rocm",
1254 "7.2.0",
1255 "latest",
1256 "gfx1100",
1257 "11.0.0",
1258 "python3",
1259 );
1260 assert!(content.contains("MLSTACK_TRITON_HOME"));
1261 assert!(content.contains("TRITON_HOME"));
1262 assert!(content.contains("TRITON_CACHE_DIR"));
1263 }
1264
1265 #[test]
1266 fn test_generate_env_file_has_mpi_settings() {
1267 let content = generate_env_file_content(
1268 "0",
1269 "/opt/rocm",
1270 "7.2.0",
1271 "latest",
1272 "gfx1100",
1273 "11.0.0",
1274 "python3",
1275 );
1276 assert!(content.contains("OMPI_MCA_opal_cuda_support"));
1277 assert!(content.contains("OMPI_MCA_pml"));
1278 assert!(content.contains("OMPI_MCA_osc"));
1279 assert!(content.contains("OMPI_MCA_btl"));
1280 }
1281
1282 #[test]
1283 fn test_generate_env_file_has_onnx_runtime() {
1284 let content = generate_env_file_content(
1285 "0",
1286 "/opt/rocm",
1287 "7.2.0",
1288 "latest",
1289 "gfx1100",
1290 "11.0.0",
1291 "python3",
1292 );
1293 assert!(content.contains("PYTHONPATH"));
1294 assert!(content.contains("onnxruntime"));
1295 }
1296
1297 #[test]
1298 fn test_generate_env_file_uses_guard_syntax() {
1299 let content = generate_env_file_content(
1300 "0",
1301 "/opt/rocm",
1302 "7.2.0",
1303 "latest",
1304 "gfx1100",
1305 "11.0.0",
1306 "python3",
1307 );
1308 assert!(content.contains("if [ -z \"${HIP_VISIBLE_DEVICES:-}\" ]"));
1310 assert!(content.contains("if [ -z \"${ROCM_HOME:-}\" ]"));
1311 }
1312
1313 #[test]
1314 fn test_generate_env_file_gpu_arch_not_guarded() {
1315 let content = generate_env_file_content(
1317 "0",
1318 "/opt/rocm",
1319 "7.2.0",
1320 "latest",
1321 "gfx1100",
1322 "11.0.0",
1323 "python3",
1324 );
1325 assert!(content.contains("export GPU_ARCH=gfx1100"));
1326 }
1327
1328 #[test]
1329 fn test_generate_env_file_custom_values() {
1330 let content = generate_env_file_content(
1331 "0,1",
1332 "/opt/rocm-7.0",
1333 "7.0.0",
1334 "legacy",
1335 "gfx1030",
1336 "10.3.0",
1337 "python3",
1338 );
1339 assert!(content.contains("0,1"));
1340 assert!(content.contains("/opt/rocm-7.0"));
1341 assert!(content.contains("7.0.0"));
1342 assert!(content.contains("legacy"));
1343 assert!(content.contains("gfx1030"));
1344 assert!(content.contains("10.3.0"));
1345 }
1346
1347 #[test]
1350 fn test_create_env_file_creates_file_and_fish() {
1351 let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
1352 let _env_path = tmp_dir.path().join(".mlstack_env");
1353 let _fish_env_path = tmp_dir.path().join(".config/fish/conf.d/mlstack_env.fish");
1354
1355 let original_home = std::env::var("HOME").ok();
1357 std::env::set_var("HOME", tmp_dir.path());
1358
1359 let result = create_env_file(
1360 "0",
1361 "/opt/rocm",
1362 "7.2.0",
1363 "latest",
1364 "gfx1100",
1365 "11.0.0",
1366 "python3",
1367 );
1368
1369 if let Some(home) = original_home {
1371 std::env::set_var("HOME", home);
1372 } else {
1373 std::env::remove_var("HOME");
1374 }
1375
1376 assert!(result.success, "create_env_file should succeed");
1377 assert!(result.path.is_some());
1378
1379 let path = result.path.unwrap();
1381 let content = std::fs::read_to_string(&path).expect("Should be able to read env file");
1382 assert!(content.contains("HIP_VISIBLE_DEVICES"));
1383 assert!(content.contains("ROCM_HOME"));
1384 assert!(content.contains("PIP_BREAK_SYSTEM_PACKAGES=1"));
1385 assert!(content.contains("UV_SYSTEM_PYTHON=1"));
1386 }
1387
1388 #[test]
1391 fn test_generate_fish_env_file_has_fish_syntax() {
1392 let content = generate_fish_env_file_content(
1393 "0",
1394 "/opt/rocm",
1395 "7.2.0",
1396 "latest",
1397 "gfx1100",
1398 "11.0.0",
1399 "python3",
1400 );
1401 assert!(!content.contains("if [ -z"));
1403 assert!(!content.contains("; fi"));
1404 assert!(!content.contains("export "));
1405 assert!(!content.contains("${"));
1406 assert!(content.contains("set -gx"));
1408 assert!(content.contains("set -q"));
1409 assert!(content.contains("; or set -gx"));
1410 }
1411
1412 #[test]
1413 fn test_generate_fish_env_file_has_required_vars() {
1414 let content = generate_fish_env_file_content(
1415 "0,1",
1416 "/opt/rocm",
1417 "7.2.0",
1418 "latest",
1419 "gfx1100",
1420 "11.0.0",
1421 "python3",
1422 );
1423 assert!(content.contains("HIP_VISIBLE_DEVICES"));
1424 assert!(content.contains("ROCM_HOME"));
1425 assert!(content.contains("ROCM_VERSION"));
1426 assert!(content.contains("GPU_ARCH"));
1427 assert!(content.contains("HSA_OVERRIDE_GFX_VERSION"));
1428 assert!(content.contains("PIP_BREAK_SYSTEM_PACKAGES"));
1429 assert!(content.contains("UV_PIP_BREAK_SYSTEM_PACKAGES"));
1430 assert!(content.contains("UV_SYSTEM_PYTHON"));
1431 assert!(content.contains("FLASH_ATTENTION_TRITON_AMD_ENABLE"));
1432 assert!(content.contains("0,1"));
1433 }
1434
1435 #[test]
1436 fn test_generate_fish_env_file_has_no_bash_patterns() {
1437 let content = generate_fish_env_file_content(
1438 "0",
1439 "/opt/rocm",
1440 "7.2.0",
1441 "latest",
1442 "gfx1100",
1443 "11.0.0",
1444 "python3",
1445 );
1446 assert!(!content.contains("if ! "));
1448 assert!(!content.contains("fi\n"));
1449 assert!(!content.contains("; then"));
1450 }
1451
1452 #[test]
1453 fn test_generate_bash_env_file_has_pip_break_system_packages() {
1454 let content = generate_env_file_content(
1455 "0",
1456 "/opt/rocm",
1457 "7.2.0",
1458 "latest",
1459 "gfx1100",
1460 "11.0.0",
1461 "python3",
1462 );
1463 assert!(content.contains("export PIP_BREAK_SYSTEM_PACKAGES=1"));
1464 assert!(content.contains("export UV_PIP_BREAK_SYSTEM_PACKAGES=1"));
1465 assert!(content.contains("export UV_SYSTEM_PYTHON=1"));
1466 assert!(content.contains("export FLASH_ATTENTION_TRITON_AMD_ENABLE=TRUE"));
1467 }
1468
1469 #[test]
1472 fn test_setup_environment_returns_results() {
1473 let (env_result, gpu_result, gpu_arch) = setup_environment();
1474 let _ = env_result.success;
1476 assert!(!gpu_result.hip_visible_devices.is_empty());
1477 assert!(!gpu_arch.gpu_arch.is_empty());
1478 }
1479}