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_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 {
247 let is_global = self.config.method == InstallMethod::Global
248 || self.config.method == InstallMethod::Auto;
249
250 let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
251
252 if is_global {
253 args.push("--break-system-packages".to_string());
254 }
255
256 args.push("-e".to_string());
257 args.push(".".to_string());
258 args.push("--no-deps".to_string());
259
260 ShellCommand {
261 program: self.config.python_bin.clone(),
262 args,
263 env: vec![],
264 working_dir: Some(self.config.install_dir()),
265 }
266 }
267
268 pub fn build_dep_install_command(&self, package: &str) -> ShellCommand {
272 let is_global = self.config.method == InstallMethod::Global
273 || self.config.method == InstallMethod::Auto;
274
275 let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
276
277 if is_global {
278 args.push("--break-system-packages".to_string());
279 }
280
281 args.push(package.to_string());
282
283 ShellCommand {
284 program: self.config.python_bin.clone(),
285 args,
286 env: vec![],
287 working_dir: None,
288 }
289 }
290
291 pub fn build_requirements_install_command(&self, requirements_path: &str) -> ShellCommand {
293 let is_global = self.config.method == InstallMethod::Global
294 || self.config.method == InstallMethod::Auto;
295
296 let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
297
298 if is_global {
299 args.push("--break-system-packages".to_string());
300 }
301
302 args.push("-r".to_string());
303 args.push(requirements_path.to_string());
304
305 ShellCommand {
306 program: self.config.python_bin.clone(),
307 args,
308 env: vec![],
309 working_dir: None,
310 }
311 }
312
313 pub fn is_safe_package(&self, package_name: &str) -> bool {
323 !crate::installers::common::nvidia_blocklist::is_cuda_nvidia_package(package_name)
324 }
325
326 pub fn check_build_output(
332 &self,
333 stdout: &str,
334 stderr: &str,
335 exit_code: i32,
336 ) -> Result<(), String> {
337 if exit_code != 0 {
338 let error_patterns = [
339 "error:",
340 "Error:",
341 "FAILED:",
342 "fatal error:",
343 "undefined reference",
344 "No such file or directory",
345 "Permission denied",
346 "ImportError:",
347 "ModuleNotFoundError:",
348 ];
349
350 let combined = format!("{}\n{}", stdout, stderr);
351 let detected_errors: Vec<&str> = error_patterns
352 .iter()
353 .filter(|p| combined.contains(*p))
354 .copied()
355 .collect();
356
357 if detected_errors.is_empty() {
358 return Err(format!(
359 "Megatron operation failed with exit code {}",
360 exit_code
361 ));
362 }
363
364 return Err(format!(
365 "Megatron operation failed with exit code {}. Detected errors: {}",
366 exit_code,
367 detected_errors.join(", ")
368 ));
369 }
370
371 Ok(())
372 }
373}
374
375fn dirs_home() -> PathBuf {
377 std::env::var("HOME")
378 .map(PathBuf::from)
379 .unwrap_or_else(|_| PathBuf::from("/root"))
380}
381
382#[cfg(test)]
387mod tests {
388 use super::*;
389 use std::path::PathBuf;
390
391 #[test]
394 fn test_git_clone_command() {
395 let installer = MegatronInstaller::with_defaults();
396 let cmd = installer.build_git_clone_command();
397
398 assert_eq!(cmd.program, "git");
399 assert!(cmd.args.contains(&"clone".to_string()));
400 assert!(cmd.args.iter().any(|a| a.contains("NVIDIA/Megatron-LM")));
401 }
402
403 #[test]
404 fn test_git_clone_target_dir() {
405 let installer = MegatronInstaller::new(MegatronConfig {
406 install_dir: Some(PathBuf::from("/home/user/Megatron-LM")),
407 ..Default::default()
408 });
409 let cmd = installer.build_git_clone_command();
410 assert!(cmd
411 .args
412 .iter()
413 .any(|a| a.contains("/home/user/Megatron-LM")));
414 }
415
416 #[test]
417 fn test_git_clone_retries() {
418 let installer = MegatronInstaller::with_defaults();
419 assert_eq!(installer.max_clone_retries(), 3);
420 }
421
422 #[test]
425 fn test_pip_install_command() {
426 let installer = MegatronInstaller::new(MegatronConfig {
427 method: InstallMethod::Global,
428 ..Default::default()
429 });
430 let cmd = installer.build_pip_install_command();
431
432 assert_eq!(cmd.program, "python3");
433 assert!(cmd.args.contains(&"-m".to_string()));
434 assert!(cmd.args.contains(&"pip".to_string()));
435 assert!(cmd.args.contains(&"install".to_string()));
436 assert!(cmd.args.contains(&"--break-system-packages".to_string()));
437 assert!(cmd.args.contains(&"-e".to_string()));
438 assert!(cmd.args.contains(&".".to_string()));
439 assert!(cmd.args.contains(&"--no-deps".to_string()));
440 }
441
442 #[test]
443 fn test_pip_install_venv_no_break_system() {
444 let installer = MegatronInstaller::new(MegatronConfig {
445 method: InstallMethod::Venv,
446 ..Default::default()
447 });
448 let cmd = installer.build_pip_install_command();
449 assert!(!cmd.args.contains(&"--break-system-packages".to_string()));
450 }
451
452 #[test]
453 fn test_pip_install_working_dir() {
454 let installer = MegatronInstaller::new(MegatronConfig {
455 install_dir: Some(PathBuf::from("/home/user/Megatron-LM")),
456 ..Default::default()
457 });
458 let cmd = installer.build_pip_install_command();
459 assert_eq!(
460 cmd.working_dir,
461 Some(PathBuf::from("/home/user/Megatron-LM"))
462 );
463 }
464
465 #[test]
466 fn test_dep_install_command() {
467 let installer = MegatronInstaller::new(MegatronConfig {
468 method: InstallMethod::Global,
469 ..Default::default()
470 });
471 let cmd = installer.build_dep_install_command("numpy");
472 assert!(cmd.args.contains(&"numpy".to_string()));
473 assert!(cmd.args.contains(&"--break-system-packages".to_string()));
474 }
475
476 #[test]
477 fn test_requirements_install_command() {
478 let installer = MegatronInstaller::new(MegatronConfig {
479 method: InstallMethod::Global,
480 ..Default::default()
481 });
482 let cmd = installer.build_requirements_install_command("/tmp/req.txt");
483 assert!(cmd.args.contains(&"-r".to_string()));
484 assert!(cmd.args.contains(&"/tmp/req.txt".to_string()));
485 assert!(cmd.args.contains(&"--break-system-packages".to_string()));
486 }
487
488 #[test]
491 fn test_dependencies() {
492 let installer = MegatronInstaller::with_defaults();
493 let deps = installer.dependencies();
494 assert!(deps.contains(&"pytorch"));
495 assert!(deps.contains(&"mpi4py"));
496 }
497
498 #[test]
501 fn test_rocm_env() {
502 let installer = MegatronInstaller::with_defaults();
503 let rocm_env = RocmEnv::from_known(Some(PathBuf::from("/opt/rocm")), "7.2.0".to_string());
504 let env = installer.rocm_env(&rocm_env);
505 assert!(env.iter().any(|(k, v)| k == "AMD_LOG_LEVEL" && v == "0"));
506 assert!(env
507 .iter()
508 .any(|(k, v)| k == "HSA_OVERRIDE_GFX_VERSION" && v == "11.0.0"));
509 assert!(env
510 .iter()
511 .any(|(k, v)| k == "PYTORCH_ROCM_ARCH" && v == "gfx1100"));
512 assert!(env
513 .iter()
514 .any(|(k, v)| k == "ROCM_PATH" && v == "/opt/rocm"));
515 assert!(env
516 .iter()
517 .any(|(k, v)| k == "PYTORCH_ALLOC_CONF" && v == "expandable_segments:True"));
518 assert!(env.iter().any(|(k, _)| k == "HSA_TOOLS_LIB"));
519 }
520
521 #[test]
524 fn test_safe_package_allows_megatron_deps() {
525 let installer = MegatronInstaller::with_defaults();
526 assert!(installer.is_safe_package("numpy"));
528 assert!(installer.is_safe_package("regex"));
529 assert!(installer.is_safe_package("sentencepiece"));
530 assert!(installer.is_safe_package("pyyaml"));
531 assert!(installer.is_safe_package("transformers"));
532 assert!(installer.is_safe_package("tokenizers"));
533 assert!(installer.is_safe_package("tqdm"));
534 assert!(installer.is_safe_package("einops"));
535 assert!(installer.is_safe_package("packaging"));
536 assert!(installer.is_safe_package("omegaconf"));
537 }
538
539 #[test]
540 fn test_safe_package_blocks_cuda_nvidia() {
541 let installer = MegatronInstaller::with_defaults();
542 assert!(!installer.is_safe_package("torch"));
544 assert!(!installer.is_safe_package("torchvision"));
545 assert!(!installer.is_safe_package("torchaudio"));
546 assert!(!installer.is_safe_package("triton"));
547 assert!(!installer.is_safe_package("nvidia-cublas-cu12"));
548 assert!(!installer.is_safe_package("nvidia-cuda-runtime-cu12"));
549 assert!(!installer.is_safe_package("pytorch-cuda"));
550 assert!(!installer.is_safe_package("torch-cuda"));
551 assert!(!installer.is_safe_package("xformers"));
552 assert!(!installer.is_safe_package("cupy-cuda12x"));
553 }
554
555 #[test]
556 fn test_safe_package_with_version_spec() {
557 let installer = MegatronInstaller::with_defaults();
558 assert!(installer.is_safe_package("numpy>=1.21"));
559 assert!(installer.is_safe_package("packaging>=20.0"));
560 assert!(!installer.is_safe_package("torch>=2.0"));
561 }
562
563 #[test]
566 fn test_build_error_import() {
567 let installer = MegatronInstaller::with_defaults();
568 let result = installer.check_build_output("", "ImportError: No module named 'megatron'", 1);
569 assert!(result.is_err());
570 assert!(result.unwrap_err().contains("ImportError"));
571 }
572
573 #[test]
574 fn test_build_error_success() {
575 let installer = MegatronInstaller::with_defaults();
576 let result = installer.check_build_output("OK", "", 0);
577 assert!(result.is_ok());
578 }
579
580 #[test]
583 fn test_config_defaults() {
584 let config = MegatronConfig::default();
585 assert_eq!(config.method, InstallMethod::Auto);
586 assert_eq!(config.python_bin, "python3");
587 assert!(!config.force_reinstall);
588 assert!(!config.dry_run);
589 assert!(config.install_dir.is_none());
590 }
591
592 #[test]
593 fn test_config_install_dir_default() {
594 let config = MegatronConfig::default();
595 let dir = config.install_dir();
596 assert!(dir.to_string_lossy().contains("Megatron-LM"));
597 }
598
599 #[test]
600 fn test_config_fallback_dir() {
601 let config = MegatronConfig::default();
602 let fallback = config.fallback_install_dir();
603 assert!(fallback
604 .to_string_lossy()
605 .contains(".mlstack/src/Megatron-LM"));
606 }
607
608 #[test]
609 fn test_command_string_format() {
610 let cmd = ShellCommand {
611 program: "python3".to_string(),
612 args: vec![
613 "-m".to_string(),
614 "pip".to_string(),
615 "install".to_string(),
616 "numpy".to_string(),
617 ],
618 env: vec![("ROCM_PATH".to_string(), "/opt/rocm".to_string())],
619 working_dir: None,
620 };
621 let s = cmd.to_command_string();
622 assert!(s.contains("ROCM_PATH=/opt/rocm"));
623 assert!(s.contains("python3 -m pip install numpy"));
624 }
625}