use crate::installers::common::RocmEnv;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstallMethod {
Global,
Venv,
Auto,
}
impl fmt::Display for InstallMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InstallMethod::Global => write!(f, "global"),
InstallMethod::Venv => write!(f, "venv"),
InstallMethod::Auto => write!(f, "auto"),
}
}
}
#[derive(Debug, Clone)]
pub struct DeepSpeedConfig {
pub method: InstallMethod,
pub python_bin: String,
pub force_reinstall: bool,
pub dry_run: bool,
}
impl Default for DeepSpeedConfig {
fn default() -> Self {
Self {
method: InstallMethod::Auto,
python_bin: "python3".to_string(),
force_reinstall: false,
dry_run: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipCommand {
pub program: String,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
}
impl PipCommand {
pub fn to_command_string(&self) -> String {
let env_prefix = self
.env
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(" ");
let cmd = if self.args.is_empty() {
self.program.clone()
} else {
format!("{} {}", self.program, self.args.join(" "))
};
if env_prefix.is_empty() {
cmd
} else {
format!("{env_prefix} {cmd}")
}
}
}
pub struct DeepSpeedInstaller {
config: DeepSpeedConfig,
}
impl DeepSpeedInstaller {
pub fn new(config: DeepSpeedConfig) -> Self {
Self { config }
}
pub fn with_defaults() -> Self {
Self::new(DeepSpeedConfig::default())
}
pub fn rocm_build_env(&self, rocm_env: &RocmEnv) -> Vec<(String, String)> {
let rocm_path = rocm_env
.path()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "/opt/rocm".to_string());
vec![
("ROCM_HOME".to_string(), rocm_path.clone()),
("HIP_PATH".to_string(), rocm_path),
("HSA_OVERRIDE_GFX_VERSION".to_string(), "11.0.0".to_string()),
("PYTORCH_ROCM_ARCH".to_string(), "gfx1100".to_string()),
]
}
pub fn build_deps_install_command(&self) -> PipCommand {
let is_global = self.config.method == InstallMethod::Global
|| self.config.method == InstallMethod::Auto;
let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
if is_global {
args.push("--break-system-packages".to_string());
}
args.extend([
"packaging".to_string(),
"ninja".to_string(),
"pydantic".to_string(),
"jsonschema".to_string(),
"einops".to_string(),
]);
PipCommand {
program: self.config.python_bin.clone(),
args,
env: vec![],
}
}
pub fn build_install_command(&self) -> PipCommand {
let is_global = self.config.method == InstallMethod::Global
|| self.config.method == InstallMethod::Auto;
let mut args = vec!["-m".to_string(), "pip".to_string(), "install".to_string()];
if is_global {
args.push("--break-system-packages".to_string());
}
if self.config.force_reinstall {
args.push("--force-reinstall".to_string());
}
args.push("deepspeed".to_string());
args.push("einops".to_string());
PipCommand {
program: self.config.python_bin.clone(),
args,
env: vec![], }
}
pub fn build_no_deps_command(&self) -> PipCommand {
let mut args = vec![
"-m".to_string(),
"pip".to_string(),
"install".to_string(),
"--break-system-packages".to_string(),
"--no-deps".to_string(),
];
args.push("deepspeed".to_string());
PipCommand {
program: self.config.python_bin.clone(),
args,
env: vec![],
}
}
pub fn build_force_reinstall_command(&self) -> PipCommand {
let mut args = vec![
"-m".to_string(),
"pip".to_string(),
"install".to_string(),
"--break-system-packages".to_string(),
"--force-reinstall".to_string(),
];
args.push("deepspeed".to_string());
PipCommand {
program: self.config.python_bin.clone(),
args,
env: vec![],
}
}
pub fn required_dependencies(&self) -> &[&str] {
&[
"packaging",
"ninja",
"pydantic",
"jsonschema",
"einops",
"deepspeed",
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_build_install_command_global() {
let installer = DeepSpeedInstaller::new(DeepSpeedConfig {
method: InstallMethod::Global,
..Default::default()
});
let cmd = installer.build_install_command();
assert_eq!(cmd.program, "python3");
assert!(cmd.args.contains(&"-m".to_string()));
assert!(cmd.args.contains(&"pip".to_string()));
assert!(cmd.args.contains(&"install".to_string()));
assert!(cmd.args.contains(&"--break-system-packages".to_string()));
assert!(cmd.args.contains(&"deepspeed".to_string()));
assert!(cmd.args.contains(&"einops".to_string()));
}
#[test]
fn test_build_install_command_venv() {
let installer = DeepSpeedInstaller::new(DeepSpeedConfig {
method: InstallMethod::Venv,
..Default::default()
});
let cmd = installer.build_install_command();
assert!(!cmd.args.contains(&"--break-system-packages".to_string()));
assert!(cmd.args.contains(&"deepspeed".to_string()));
}
#[test]
fn test_build_install_command_force_reinstall() {
let installer = DeepSpeedInstaller::new(DeepSpeedConfig {
force_reinstall: true,
..Default::default()
});
let cmd = installer.build_install_command();
assert!(cmd.args.contains(&"--force-reinstall".to_string()));
}
#[test]
fn test_build_deps_command() {
let installer = DeepSpeedInstaller::with_defaults();
let cmd = installer.build_deps_install_command();
assert!(cmd.args.contains(&"packaging".to_string()));
assert!(cmd.args.contains(&"ninja".to_string()));
assert!(cmd.args.contains(&"pydantic".to_string()));
assert!(cmd.args.contains(&"jsonschema".to_string()));
assert!(cmd.args.contains(&"einops".to_string()));
}
#[test]
fn test_build_no_deps_command() {
let installer = DeepSpeedInstaller::with_defaults();
let cmd = installer.build_no_deps_command();
assert!(cmd.args.contains(&"--no-deps".to_string()));
assert!(cmd.args.contains(&"deepspeed".to_string()));
}
#[test]
fn test_build_force_reinstall_command() {
let installer = DeepSpeedInstaller::with_defaults();
let cmd = installer.build_force_reinstall_command();
assert!(cmd.args.contains(&"--force-reinstall".to_string()));
assert!(cmd.args.contains(&"deepspeed".to_string()));
}
#[test]
fn test_rocm_build_env() {
let installer = DeepSpeedInstaller::with_defaults();
let rocm_env = RocmEnv::from_known(Some(PathBuf::from("/opt/rocm")), "7.2.0".to_string());
let env = installer.rocm_build_env(&rocm_env);
assert!(env
.iter()
.any(|(k, v)| k == "ROCM_HOME" && v == "/opt/rocm"));
assert!(env.iter().any(|(k, v)| k == "HIP_PATH" && v == "/opt/rocm"));
assert!(env
.iter()
.any(|(k, v)| k == "HSA_OVERRIDE_GFX_VERSION" && v == "11.0.0"));
assert!(env
.iter()
.any(|(k, v)| k == "PYTORCH_ROCM_ARCH" && v == "gfx1100"));
}
#[test]
fn test_rocm_build_env_no_rocm() {
let installer = DeepSpeedInstaller::with_defaults();
let rocm_env = RocmEnv::none();
let env = installer.rocm_build_env(&rocm_env);
assert!(env
.iter()
.any(|(k, v)| k == "ROCM_HOME" && v == "/opt/rocm"));
}
#[test]
fn test_required_dependencies() {
let installer = DeepSpeedInstaller::with_defaults();
let deps = installer.required_dependencies();
assert!(deps.contains(&"deepspeed"));
assert!(deps.contains(&"einops"));
assert!(deps.contains(&"ninja"));
}
#[test]
fn test_command_string_format() {
let cmd = PipCommand {
program: "python3".to_string(),
args: vec![
"-m".to_string(),
"pip".to_string(),
"install".to_string(),
"deepspeed".to_string(),
],
env: vec![("ROCM_HOME".to_string(), "/opt/rocm".to_string())],
};
let s = cmd.to_command_string();
assert!(s.contains("ROCM_HOME=/opt/rocm"));
assert!(s.contains("python3 -m pip install deepspeed"));
}
}