1use crate::installers::common::{command_exists, DistroFacade};
12use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum MlStackComponent {
21 RocmConfig,
23 PyTorch,
25 OnnxRuntime,
27 MigraphX,
29 Megatron,
31 FlashAttention,
33 Rccl,
35 Mpi,
37}
38
39impl fmt::Display for MlStackComponent {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 MlStackComponent::RocmConfig => write!(f, "rocm_config"),
43 MlStackComponent::PyTorch => write!(f, "pytorch"),
44 MlStackComponent::OnnxRuntime => write!(f, "onnxruntime"),
45 MlStackComponent::MigraphX => write!(f, "migraphx"),
46 MlStackComponent::Megatron => write!(f, "megatron"),
47 MlStackComponent::FlashAttention => write!(f, "flash_attention"),
48 MlStackComponent::Rccl => write!(f, "rccl"),
49 MlStackComponent::Mpi => write!(f, "mpi"),
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum InstallMode {
57 All,
59 Selective,
61}
62
63#[derive(Debug, Clone)]
65pub struct MlStackConfig {
66 pub python_bin: String,
68 pub force_reinstall: bool,
70 pub dry_run: bool,
72 pub mode: InstallMode,
74 pub selected_components: Vec<MlStackComponent>,
76}
77
78impl Default for MlStackConfig {
79 fn default() -> Self {
80 Self {
81 python_bin: "python3".to_string(),
82 force_reinstall: false,
83 dry_run: false,
84 mode: InstallMode::All,
85 selected_components: vec![],
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct PipCommand {
93 pub program: String,
95 pub args: Vec<String>,
97}
98
99impl PipCommand {
100 pub fn to_command_string(&self) -> String {
102 if self.args.is_empty() {
103 self.program.clone()
104 } else {
105 format!("{} {}", self.program, self.args.join(" "))
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct SystemCommand {
113 pub program: String,
115 pub args: Vec<String>,
117}
118
119impl SystemCommand {
120 pub fn to_command_string(&self) -> String {
122 if self.args.is_empty() {
123 self.program.clone()
124 } else {
125 format!("{} {}", self.program, self.args.join(" "))
126 }
127 }
128}
129
130pub struct MlStackInstaller {
132 config: MlStackConfig,
133}
134
135impl MlStackInstaller {
136 pub fn new(config: MlStackConfig) -> Self {
138 Self { config }
139 }
140
141 pub fn with_defaults() -> Self {
143 Self::new(MlStackConfig::default())
144 }
145
146 pub fn install_order() -> Vec<MlStackComponent> {
162 vec![
163 MlStackComponent::RocmConfig,
164 MlStackComponent::PyTorch,
165 MlStackComponent::OnnxRuntime,
166 MlStackComponent::MigraphX,
167 MlStackComponent::Megatron,
168 MlStackComponent::FlashAttention,
169 MlStackComponent::Rccl,
170 MlStackComponent::Mpi,
171 ]
172 }
173
174 pub fn rocm_config_path(&self) -> String {
176 format!(
177 "{}/.rocmrc",
178 std::env::var("HOME").unwrap_or_else(|_| "/root".to_string())
179 )
180 }
181
182 pub fn build_migraphx_install_command(&self, distro: &DistroFacade) -> SystemCommand {
188 if distro.uses_apt() {
189 SystemCommand {
190 program: "sudo".to_string(),
191 args: vec![
192 "apt-get".to_string(),
193 "install".to_string(),
194 "-y".to_string(),
195 "migraphx".to_string(),
196 "python3-migraphx".to_string(),
197 ],
198 }
199 } else if distro.uses_dnf() {
200 SystemCommand {
201 program: "sudo".to_string(),
202 args: vec![
203 "dnf".to_string(),
204 "install".to_string(),
205 "-y".to_string(),
206 "migraphx".to_string(),
207 "python3-migraphx".to_string(),
208 ],
209 }
210 } else if distro.uses_pacman() {
211 SystemCommand {
212 program: "sudo".to_string(),
213 args: vec![
214 "pacman".to_string(),
215 "-S".to_string(),
216 "--noconfirm".to_string(),
217 "migraphx".to_string(),
218 "python-migraphx".to_string(),
219 ],
220 }
221 } else if distro.uses_zypper() {
222 SystemCommand {
223 program: "sudo".to_string(),
224 args: vec![
225 "zypper".to_string(),
226 "install".to_string(),
227 "-y".to_string(),
228 "migraphx".to_string(),
229 "python3-migraphx".to_string(),
230 ],
231 }
232 } else {
233 SystemCommand {
234 program: "sudo".to_string(),
235 args: vec![
236 "apt-get".to_string(),
237 "install".to_string(),
238 "-y".to_string(),
239 "migraphx".to_string(),
240 "python3-migraphx".to_string(),
241 ],
242 }
243 }
244 }
245
246 pub fn build_rccl_install_command(&self, distro: &DistroFacade) -> SystemCommand {
252 if distro.uses_apt() {
253 SystemCommand {
254 program: "sudo".to_string(),
255 args: vec![
256 "apt-get".to_string(),
257 "install".to_string(),
258 "-y".to_string(),
259 "rccl".to_string(),
260 ],
261 }
262 } else if distro.uses_dnf() {
263 SystemCommand {
264 program: "sudo".to_string(),
265 args: vec![
266 "dnf".to_string(),
267 "install".to_string(),
268 "-y".to_string(),
269 "rccl".to_string(),
270 ],
271 }
272 } else if distro.uses_pacman() {
273 SystemCommand {
274 program: "sudo".to_string(),
275 args: vec![
276 "pacman".to_string(),
277 "-S".to_string(),
278 "--noconfirm".to_string(),
279 "rccl".to_string(),
280 ],
281 }
282 } else {
283 SystemCommand {
284 program: "sudo".to_string(),
285 args: vec![
286 "apt-get".to_string(),
287 "install".to_string(),
288 "-y".to_string(),
289 "rccl".to_string(),
290 ],
291 }
292 }
293 }
294
295 pub fn build_mpi_install_command(&self, distro: &DistroFacade) -> SystemCommand {
301 if distro.uses_apt() {
302 SystemCommand {
303 program: "sudo".to_string(),
304 args: vec![
305 "apt-get".to_string(),
306 "install".to_string(),
307 "-y".to_string(),
308 "openmpi-bin".to_string(),
309 "libopenmpi-dev".to_string(),
310 ],
311 }
312 } else if distro.uses_dnf() {
313 SystemCommand {
314 program: "sudo".to_string(),
315 args: vec![
316 "dnf".to_string(),
317 "install".to_string(),
318 "-y".to_string(),
319 "openmpi".to_string(),
320 "openmpi-devel".to_string(),
321 ],
322 }
323 } else if distro.uses_pacman() {
324 SystemCommand {
325 program: "sudo".to_string(),
326 args: vec![
327 "pacman".to_string(),
328 "-S".to_string(),
329 "--noconfirm".to_string(),
330 "openmpi".to_string(),
331 ],
332 }
333 } else {
334 SystemCommand {
335 program: "sudo".to_string(),
336 args: vec![
337 "apt-get".to_string(),
338 "install".to_string(),
339 "-y".to_string(),
340 "openmpi-bin".to_string(),
341 "libopenmpi-dev".to_string(),
342 ],
343 }
344 }
345 }
346
347 pub fn build_megatron_clone_command(&self, target_dir: &str) -> SystemCommand {
353 SystemCommand {
354 program: "git".to_string(),
355 args: vec![
356 "clone".to_string(),
357 "https://github.com/NVIDIA/Megatron-LM.git".to_string(),
358 target_dir.to_string(),
359 ],
360 }
362 }
363
364 pub fn build_megatron_install_command(&self) -> PipCommand {
366 PipCommand {
367 program: self.config.python_bin.clone(),
368 args: vec![
369 "-m".to_string(),
370 "pip".to_string(),
371 "install".to_string(),
372 "--break-system-packages".to_string(),
373 "-e".to_string(),
374 ".".to_string(),
375 ],
376 }
377 }
378
379 pub fn prerequisite_commands(&self) -> &[&str] {
385 &["rocminfo", "python3", "pip3", "git", "cmake"]
386 }
387
388 pub fn check_prerequisite(&self, cmd: &str) -> bool {
390 command_exists(cmd)
391 }
392}
393
394#[cfg(test)]
399mod tests {
400 use super::*;
401 use crate::platform::detection::{DistroFamily, DistroInfo, PackageManager};
402
403 fn ubuntu_distro() -> DistroFacade {
404 DistroFacade::from_info(DistroInfo {
405 id: "ubuntu".to_string(),
406 name: "Ubuntu 24.04".to_string(),
407 version: "24.04".to_string(),
408 codename: "noble".to_string(),
409 family: DistroFamily::Debian,
410 pkg_manager: PackageManager::Apt,
411 id_like: "debian".to_string(),
412 })
413 }
414
415 fn fedora_distro() -> DistroFacade {
416 DistroFacade::from_info(DistroInfo {
417 id: "fedora".to_string(),
418 name: "Fedora 41".to_string(),
419 version: "41".to_string(),
420 codename: String::new(),
421 family: DistroFamily::Rhel,
422 pkg_manager: PackageManager::Dnf,
423 id_like: "fedora".to_string(),
424 })
425 }
426
427 fn arch_distro() -> DistroFacade {
428 DistroFacade::from_info(DistroInfo {
429 id: "arch".to_string(),
430 name: "Arch Linux".to_string(),
431 version: "rolling".to_string(),
432 codename: String::new(),
433 family: DistroFamily::Arch,
434 pkg_manager: PackageManager::Pacman,
435 id_like: "arch".to_string(),
436 })
437 }
438
439 #[test]
442 fn test_install_order() {
443 let order = MlStackInstaller::install_order();
444 assert_eq!(order.len(), 8);
445 assert_eq!(order[0], MlStackComponent::RocmConfig);
446 assert_eq!(order[1], MlStackComponent::PyTorch);
447 assert_eq!(order[7], MlStackComponent::Mpi);
448 }
449
450 #[test]
451 fn test_rocm_config_path() {
452 let installer = MlStackInstaller::with_defaults();
453 let path = installer.rocm_config_path();
454 assert!(path.ends_with("/.rocmrc"));
455 }
456
457 #[test]
458 fn test_migraphx_install_apt() {
459 let installer = MlStackInstaller::with_defaults();
460 let distro = ubuntu_distro();
461 let cmd = installer.build_migraphx_install_command(&distro);
462 assert!(cmd.args.contains(&"migraphx".to_string()));
463 assert!(cmd.args.contains(&"python3-migraphx".to_string()));
464 }
465
466 #[test]
467 fn test_migraphx_install_dnf() {
468 let installer = MlStackInstaller::with_defaults();
469 let distro = fedora_distro();
470 let cmd = installer.build_migraphx_install_command(&distro);
471 assert!(cmd.args.contains(&"migraphx".to_string()));
472 }
473
474 #[test]
475 fn test_migraphx_install_pacman() {
476 let installer = MlStackInstaller::with_defaults();
477 let distro = arch_distro();
478 let cmd = installer.build_migraphx_install_command(&distro);
479 assert!(cmd.args.contains(&"python-migraphx".to_string()));
480 }
481
482 #[test]
483 fn test_rccl_install_apt() {
484 let installer = MlStackInstaller::with_defaults();
485 let distro = ubuntu_distro();
486 let cmd = installer.build_rccl_install_command(&distro);
487 assert!(cmd.args.contains(&"rccl".to_string()));
488 }
489
490 #[test]
491 fn test_mpi_install_apt() {
492 let installer = MlStackInstaller::with_defaults();
493 let distro = ubuntu_distro();
494 let cmd = installer.build_mpi_install_command(&distro);
495 assert!(cmd.args.contains(&"openmpi-bin".to_string()));
496 assert!(cmd.args.contains(&"libopenmpi-dev".to_string()));
497 }
498
499 #[test]
500 fn test_mpi_install_dnf() {
501 let installer = MlStackInstaller::with_defaults();
502 let distro = fedora_distro();
503 let cmd = installer.build_mpi_install_command(&distro);
504 assert!(cmd.args.contains(&"openmpi".to_string()));
505 assert!(cmd.args.contains(&"openmpi-devel".to_string()));
506 }
507
508 #[test]
509 fn test_megatron_clone_command() {
510 let installer = MlStackInstaller::with_defaults();
511 let cmd = installer.build_megatron_clone_command("/tmp/Megatron-LM");
512 assert_eq!(cmd.program, "git");
513 assert!(cmd.args.contains(&"clone".to_string()));
514 assert!(cmd.args.iter().any(|a| a.contains("NVIDIA/Megatron-LM")));
515 assert!(cmd.args.contains(&"/tmp/Megatron-LM".to_string()));
516 }
517
518 #[test]
519 fn test_megatron_install_command() {
520 let installer = MlStackInstaller::with_defaults();
521 let cmd = installer.build_megatron_install_command();
522 assert_eq!(cmd.program, "python3");
523 assert!(cmd.args.contains(&"-e".to_string()));
524 assert!(cmd.args.contains(&".".to_string()));
525 }
526
527 #[test]
528 fn test_prerequisite_commands() {
529 let installer = MlStackInstaller::with_defaults();
530 let cmds = installer.prerequisite_commands();
531 assert!(cmds.contains(&"python3"));
532 assert!(cmds.contains(&"git"));
533 assert!(cmds.contains(&"cmake"));
534 }
535
536 #[test]
537 fn test_component_display() {
538 assert_eq!(format!("{}", MlStackComponent::PyTorch), "pytorch");
539 assert_eq!(format!("{}", MlStackComponent::RocmConfig), "rocm_config");
540 assert_eq!(format!("{}", MlStackComponent::Mpi), "mpi");
541 }
542}