rusty_stack/installers/components/
megatron.rs1use crate::installers::common::RocmEnv;
14use std::fmt;
15use std::path::PathBuf;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum InstallMethod {
24 Global,
26 Venv,
28 Auto,
30}
31
32impl fmt::Display for InstallMethod {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 InstallMethod::Global => write!(f, "global"),
36 InstallMethod::Venv => write!(f, "venv"),
37 InstallMethod::Auto => write!(f, "auto"),
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
44pub struct MegatronConfig {
45 pub method: InstallMethod,
47 pub python_bin: String,
49 pub force_reinstall: bool,
51 pub dry_run: bool,
53 pub install_dir: Option<PathBuf>,
55}
56
57impl Default for MegatronConfig {
58 fn default() -> Self {
59 Self {
60 method: InstallMethod::Auto,
61 python_bin: "python3".to_string(),
62 force_reinstall: false,
63 dry_run: false,
64 install_dir: None,
65 }
66 }
67}
68
69impl MegatronConfig {
70 pub fn install_dir(&self) -> PathBuf {
75 self.install_dir
76 .clone()
77 .unwrap_or_else(|| dirs_home().join("Megatron-LM"))
78 }
79
80 pub fn fallback_install_dir(&self) -> PathBuf {
82 dirs_home().join(".mlstack/src/Megatron-LM")
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ShellCommand {
89 pub program: String,
91 pub args: Vec<String>,
93 pub env: Vec<(String, String)>,
95 pub working_dir: Option<PathBuf>,
97}
98
99impl ShellCommand {
100 pub fn to_command_string(&self) -> String {
102 let env_prefix = self
103 .env
104 .iter()
105 .map(|(k, v)| format!("{k}={v}"))
106 .collect::<Vec<_>>()
107 .join(" ");
108 let cmd = if self.args.is_empty() {
109 self.program.clone()
110 } else {
111 format!("{} {}", self.program, self.args.join(" "))
112 };
113 if env_prefix.is_empty() {
114 cmd
115 } else {
116 format!("{env_prefix} {cmd}")
117 }
118 }
119}
120
121pub struct MegatronInstaller {
123 config: MegatronConfig,
124}
125
126impl MegatronInstaller {
127 pub fn new(config: MegatronConfig) -> Self {
129 Self { config }
130 }
131
132 pub fn with_defaults() -> Self {
134 Self::new(MegatronConfig::default())
135 }
136
137 pub fn dependencies(&self) -> &[&str] {
147 &["pytorch", "mpi4py"]
148 }
149
150 pub fn rocm_env(&self, rocm_env: &RocmEnv) -> Vec<(String, String)> {
166 let rocm_path = rocm_env
167 .path()
168 .map(|p| p.to_string_lossy().to_string())
169 .unwrap_or_else(|| "/opt/rocm".to_string());
170
171 let mut env = vec![
172 ("AMD_LOG_LEVEL".to_string(), "0".to_string()),
173 ("HSA_OVERRIDE_GFX_VERSION".to_string(), "11.0.0".to_string()),
174 ("PYTORCH_ROCM_ARCH".to_string(), "gfx1100".to_string()),
175 ("ROCM_PATH".to_string(), rocm_path.clone()),
176 (
177 "PYTORCH_CUDA_ALLOC_CONF".to_string(),
178 "expandable_segments:True".to_string(),
179 ),
180 ];
181
182 let profiler_lib = rocm_env.path().and_then(|p| {
185 let new_layout = p.join("lib/rocprofiler-sdk/librocprofiler-sdk-tool.so");
186 let old_layout = p.join("lib/librocprofiler-sdk-tool.so");
187 if new_layout.exists() {
188 Some(new_layout)
189 } else if old_layout.exists() {
190 Some(old_layout)
191 } else {
192 None
193 }
194 });
195 if let Some(lib) = profiler_lib {
196 env.push((
197 "HSA_TOOLS_LIB".to_string(),
198 lib.to_string_lossy().to_string(),
199 ));
200 } else {
201 env.push(("HSA_TOOLS_LIB".to_string(), "0".to_string()));
202 }
203
204 env
205 }
206
207 pub fn build_git_clone_command(&self) -> ShellCommand {
218 ShellCommand {
219 program: "git".to_string(),
220 args: vec![
221 "clone".to_string(),
222 "https://github.com/NVIDIA/Megatron-LM.git".to_string(),
223 self.config.install_dir().to_string_lossy().to_string(),
224 ],
225 env: vec![],
226 working_dir: None,
227 }
228 }
229
230 pub fn max_clone_retries(&self) -> u32 {
232 3
233 }
234
235 pub fn build_pip_install_command(&self) -> ShellCommand {
254 let is_global = self.config.method == InstallMethod::Global
255 || self.config.method == InstallMethod::Auto;
256
257 let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
258
259 if is_global {
260 args.push("--break-system-packages".to_string());
261 }
262
263 args.push("-e".to_string());
264 args.push(".".to_string());
265 args.push("--no-deps".to_string());
266
267 ShellCommand {
268 program: self.config.python_bin.clone(),
269 args,
270 env: vec![],
271 working_dir: Some(self.config.install_dir()),
272 }
273 }
274
275 pub fn build_dep_install_command(&self, package: &str) -> ShellCommand {
279 let is_global = self.config.method == InstallMethod::Global
280 || self.config.method == InstallMethod::Auto;
281
282 let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
283
284 if is_global {
285 args.push("--break-system-packages".to_string());
286 }
287
288 args.push(package.to_string());
289
290 ShellCommand {
291 program: self.config.python_bin.clone(),
292 args,
293 env: vec![],
294 working_dir: None,
295 }
296 }
297
298 pub fn build_requirements_install_command(&self, requirements_path: &str) -> ShellCommand {
300 let is_global = self.config.method == InstallMethod::Global
301 || self.config.method == InstallMethod::Auto;
302
303 let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
304
305 if is_global {
306 args.push("--break-system-packages".to_string());
307 }
308
309 args.push("-r".to_string());
310 args.push(requirements_path.to_string());
311
312 ShellCommand {
313 program: self.config.python_bin.clone(),
314 args,
315 env: vec![],
316 working_dir: None,
317 }
318 }
319
320 pub fn is_safe_package(&self, package_name: &str) -> bool {
330 !crate::installers::common::nvidia_blocklist::is_cuda_nvidia_package(package_name)
331 }
332
333 pub fn check_build_output(
339 &self,
340 stdout: &str,
341 stderr: &str,
342 exit_code: i32,
343 ) -> Result<(), String> {
344 if exit_code != 0 {
345 let error_patterns = [
346 "error:",
347 "Error:",
348 "FAILED:",
349 "fatal error:",
350 "undefined reference",
351 "No such file or directory",
352 "Permission denied",
353 "ImportError:",
354 "ModuleNotFoundError:",
355 ];
356
357 let combined = format!("{}\n{}", stdout, stderr);
358 let detected_errors: Vec<&str> = error_patterns
359 .iter()
360 .filter(|p| combined.contains(*p))
361 .copied()
362 .collect();
363
364 if detected_errors.is_empty() {
365 return Err(format!(
366 "Megatron operation failed with exit code {}",
367 exit_code
368 ));
369 }
370
371 return Err(format!(
372 "Megatron operation failed with exit code {}. Detected errors: {}",
373 exit_code,
374 detected_errors.join(", ")
375 ));
376 }
377
378 Ok(())
379 }
380}
381
382fn dirs_home() -> PathBuf {
384 std::env::var("HOME")
385 .map(PathBuf::from)
386 .unwrap_or_else(|_| PathBuf::from("/root"))
387}
388
389#[cfg(test)]
394mod tests {
395 use super::*;
396 use std::path::PathBuf;
397
398 #[test]
401 fn test_git_clone_command() {
402 let installer = MegatronInstaller::with_defaults();
403 let cmd = installer.build_git_clone_command();
404
405 assert_eq!(cmd.program, "git");
406 assert!(cmd.args.contains(&"clone".to_string()));
407 assert!(cmd.args.iter().any(|a| a.contains("NVIDIA/Megatron-LM")));
408 }
409
410 #[test]
411 fn test_git_clone_target_dir() {
412 let installer = MegatronInstaller::new(MegatronConfig {
413 install_dir: Some(PathBuf::from("/home/user/Megatron-LM")),
414 ..Default::default()
415 });
416 let cmd = installer.build_git_clone_command();
417 assert!(cmd
418 .args
419 .iter()
420 .any(|a| a.contains("/home/user/Megatron-LM")));
421 }
422
423 #[test]
424 fn test_git_clone_retries() {
425 let installer = MegatronInstaller::with_defaults();
426 assert_eq!(installer.max_clone_retries(), 3);
427 }
428
429 #[test]
432 fn test_pip_install_command() {
433 let installer = MegatronInstaller::new(MegatronConfig {
434 method: InstallMethod::Global,
435 ..Default::default()
436 });
437 let cmd = installer.build_pip_install_command();
438
439 assert_eq!(cmd.program, "python3");
440 assert!(cmd.args.contains(&"-m".to_string()));
441 assert!(cmd.args.contains(&"pip".to_string()));
442 assert!(cmd.args.contains(&"install".to_string()));
443 assert!(cmd.args.contains(&"--break-system-packages".to_string()));
444 assert!(cmd.args.contains(&"-e".to_string()));
445 assert!(cmd.args.contains(&".".to_string()));
446 assert!(cmd.args.contains(&"--no-deps".to_string()));
447 }
448
449 #[test]
450 fn test_pip_install_venv_no_break_system() {
451 let installer = MegatronInstaller::new(MegatronConfig {
452 method: InstallMethod::Venv,
453 ..Default::default()
454 });
455 let cmd = installer.build_pip_install_command();
456 assert!(!cmd.args.contains(&"--break-system-packages".to_string()));
457 }
458
459 #[test]
460 fn test_pip_install_working_dir() {
461 let installer = MegatronInstaller::new(MegatronConfig {
462 install_dir: Some(PathBuf::from("/home/user/Megatron-LM")),
463 ..Default::default()
464 });
465 let cmd = installer.build_pip_install_command();
466 assert_eq!(
467 cmd.working_dir,
468 Some(PathBuf::from("/home/user/Megatron-LM"))
469 );
470 }
471
472 #[test]
473 fn test_dep_install_command() {
474 let installer = MegatronInstaller::new(MegatronConfig {
475 method: InstallMethod::Global,
476 ..Default::default()
477 });
478 let cmd = installer.build_dep_install_command("numpy");
479 assert!(cmd.args.contains(&"numpy".to_string()));
480 assert!(cmd.args.contains(&"--break-system-packages".to_string()));
481 }
482
483 #[test]
484 fn test_requirements_install_command() {
485 let installer = MegatronInstaller::new(MegatronConfig {
486 method: InstallMethod::Global,
487 ..Default::default()
488 });
489 let cmd = installer.build_requirements_install_command("/tmp/req.txt");
490 assert!(cmd.args.contains(&"-r".to_string()));
491 assert!(cmd.args.contains(&"/tmp/req.txt".to_string()));
492 assert!(cmd.args.contains(&"--break-system-packages".to_string()));
493 }
494
495 #[test]
498 fn test_dependencies() {
499 let installer = MegatronInstaller::with_defaults();
500 let deps = installer.dependencies();
501 assert!(deps.contains(&"pytorch"));
502 assert!(deps.contains(&"mpi4py"));
503 }
504
505 #[test]
508 fn test_rocm_env() {
509 let installer = MegatronInstaller::with_defaults();
510 let rocm_env = RocmEnv::from_known(Some(PathBuf::from("/opt/rocm")), "7.2.0".to_string());
511 let env = installer.rocm_env(&rocm_env);
512 assert!(env.iter().any(|(k, v)| k == "AMD_LOG_LEVEL" && v == "0"));
513 assert!(env
514 .iter()
515 .any(|(k, v)| k == "HSA_OVERRIDE_GFX_VERSION" && v == "11.0.0"));
516 assert!(env
517 .iter()
518 .any(|(k, v)| k == "PYTORCH_ROCM_ARCH" && v == "gfx1100"));
519 assert!(env
520 .iter()
521 .any(|(k, v)| k == "ROCM_PATH" && v == "/opt/rocm"));
522 assert!(env
523 .iter()
524 .any(|(k, v)| k == "PYTORCH_CUDA_ALLOC_CONF" && v == "expandable_segments:True"));
525 assert!(env.iter().any(|(k, _)| k == "HSA_TOOLS_LIB"));
526 }
527
528 #[test]
531 fn test_safe_package_allows_megatron_deps() {
532 let installer = MegatronInstaller::with_defaults();
533 assert!(installer.is_safe_package("numpy"));
535 assert!(installer.is_safe_package("regex"));
536 assert!(installer.is_safe_package("sentencepiece"));
537 assert!(installer.is_safe_package("pyyaml"));
538 assert!(installer.is_safe_package("transformers"));
539 assert!(installer.is_safe_package("tokenizers"));
540 assert!(installer.is_safe_package("tqdm"));
541 assert!(installer.is_safe_package("einops"));
542 assert!(installer.is_safe_package("packaging"));
543 assert!(installer.is_safe_package("omegaconf"));
544 }
545
546 #[test]
547 fn test_safe_package_blocks_cuda_nvidia() {
548 let installer = MegatronInstaller::with_defaults();
549 assert!(!installer.is_safe_package("torch"));
551 assert!(!installer.is_safe_package("torchvision"));
552 assert!(!installer.is_safe_package("torchaudio"));
553 assert!(!installer.is_safe_package("triton"));
554 assert!(!installer.is_safe_package("nvidia-cublas-cu12"));
555 assert!(!installer.is_safe_package("nvidia-cuda-runtime-cu12"));
556 assert!(!installer.is_safe_package("pytorch-cuda"));
557 assert!(!installer.is_safe_package("torch-cuda"));
558 assert!(!installer.is_safe_package("xformers"));
559 assert!(!installer.is_safe_package("cupy-cuda12x"));
560 }
561
562 #[test]
563 fn test_safe_package_with_version_spec() {
564 let installer = MegatronInstaller::with_defaults();
565 assert!(installer.is_safe_package("numpy>=1.21"));
566 assert!(installer.is_safe_package("packaging>=20.0"));
567 assert!(!installer.is_safe_package("torch>=2.0"));
568 }
569
570 #[test]
573 fn test_build_error_import() {
574 let installer = MegatronInstaller::with_defaults();
575 let result = installer.check_build_output("", "ImportError: No module named 'megatron'", 1);
576 assert!(result.is_err());
577 assert!(result.unwrap_err().contains("ImportError"));
578 }
579
580 #[test]
581 fn test_build_error_success() {
582 let installer = MegatronInstaller::with_defaults();
583 let result = installer.check_build_output("OK", "", 0);
584 assert!(result.is_ok());
585 }
586
587 #[test]
590 fn test_config_defaults() {
591 let config = MegatronConfig::default();
592 assert_eq!(config.method, InstallMethod::Auto);
593 assert_eq!(config.python_bin, "python3");
594 assert!(!config.force_reinstall);
595 assert!(!config.dry_run);
596 assert!(config.install_dir.is_none());
597 }
598
599 #[test]
600 fn test_config_install_dir_default() {
601 let config = MegatronConfig::default();
602 let dir = config.install_dir();
603 assert!(dir.to_string_lossy().contains("Megatron-LM"));
604 }
605
606 #[test]
607 fn test_config_fallback_dir() {
608 let config = MegatronConfig::default();
609 let fallback = config.fallback_install_dir();
610 assert!(fallback
611 .to_string_lossy()
612 .contains(".mlstack/src/Megatron-LM"));
613 }
614
615 #[test]
616 fn test_command_string_format() {
617 let cmd = ShellCommand {
618 program: "python3".to_string(),
619 args: vec![
620 "-m".to_string(),
621 "pip".to_string(),
622 "install".to_string(),
623 "numpy".to_string(),
624 ],
625 env: vec![("ROCM_PATH".to_string(), "/opt/rocm".to_string())],
626 working_dir: None,
627 };
628 let s = cmd.to_command_string();
629 assert!(s.contains("ROCM_PATH=/opt/rocm"));
630 assert!(s.contains("python3 -m pip install numpy"));
631 }
632}