use crate::installers::common::RocmEnv;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TorchChannel {
Stable,
Latest,
Nightly,
}
impl fmt::Display for TorchChannel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TorchChannel::Stable => write!(f, "stable"),
TorchChannel::Latest => write!(f, "latest"),
TorchChannel::Nightly => write!(f, "nightly"),
}
}
}
impl TorchChannel {
pub fn from_choice(choice: u8) -> Option<Self> {
match choice {
1 => Some(TorchChannel::Stable),
2 => Some(TorchChannel::Latest),
3 => Some(TorchChannel::Nightly),
_ => None,
}
}
}
#[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 PyTorchConfig {
pub channel: TorchChannel,
pub method: InstallMethod,
pub python_bin: String,
pub force_reinstall: bool,
pub dry_run: bool,
}
impl Default for PyTorchConfig {
fn default() -> Self {
Self {
channel: TorchChannel::Latest,
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>,
}
impl PipCommand {
pub fn to_command_string(&self) -> String {
if self.args.is_empty() {
self.program.clone()
} else {
format!("{} {}", self.program, self.args.join(" "))
}
}
}
pub struct PyTorchInstaller {
config: PyTorchConfig,
}
impl PyTorchInstaller {
pub fn new(config: PyTorchConfig) -> Self {
Self { config }
}
pub fn with_defaults() -> Self {
Self::new(PyTorchConfig::default())
}
pub fn index_url_for_rocm(&self, rocm_mm: &str) -> String {
let mm: f32 = rocm_mm.parse().unwrap_or(7.2);
if mm >= 7.0 {
format!("https://repo.radeon.com/rocm/manylinux/rocm-rel-{rocm_mm}/")
} else if mm >= 6.4 {
format!("https://download.pytorch.org/whl/nightly/rocm{rocm_mm}")
} else if mm >= 6.3 {
format!("https://download.pytorch.org/whl/rocm{rocm_mm}")
} else if mm >= 6.0 {
"https://download.pytorch.org/whl/rocm6.2".to_string()
} else if mm >= 5.0 {
"https://download.pytorch.org/whl/rocm5.7".to_string()
} else {
"https://download.pytorch.org/whl/rocm6.3".to_string()
}
}
pub fn nightly_index_url(&self, rocm_mm: &str) -> String {
format!("https://download.pytorch.org/whl/nightly/rocm{rocm_mm}")
}
pub fn radeon_index_url(&self, rocm_mm: &str) -> String {
format!("https://repo.radeon.com/rocm/manylinux/rocm-rel-{rocm_mm}/")
}
pub fn fallback_index_url(&self) -> String {
"https://download.pytorch.org/whl/rocm7.0".to_string()
}
pub fn build_install_command(&self, rocm_mm: &str, use_uv: bool) -> PipCommand {
let index_url = self.index_url_for_rocm(rocm_mm);
let is_global = self.config.method == InstallMethod::Global
|| self.config.method == InstallMethod::Auto;
let effective_use_uv = use_uv && !is_global;
let mut args = Vec::new();
if effective_use_uv {
args.push("pip".to_string());
args.push("install".to_string());
} else {
args.push("-m".to_string());
args.push("pip".to_string());
args.push("install".to_string());
if is_global {
args.push("--break-system-packages".to_string());
}
}
args.push("--no-cache".to_string());
args.push("--index-url".to_string());
args.push(index_url);
args.push("torch".to_string());
args.push("torchvision".to_string());
args.push("torchaudio".to_string());
let program = if effective_use_uv {
"uv".to_string()
} else {
self.config.python_bin.clone()
};
PipCommand { program, args }
}
pub fn build_fallback_command(&self, use_uv: bool) -> PipCommand {
let is_global = self.config.method == InstallMethod::Global
|| self.config.method == InstallMethod::Auto;
let effective_use_uv = use_uv && !is_global;
let mut args = Vec::new();
if effective_use_uv {
args.push("pip".to_string());
args.push("install".to_string());
} else {
args.push("-m".to_string());
args.push("pip".to_string());
args.push("install".to_string());
if is_global {
args.push("--break-system-packages".to_string());
}
}
args.push("--no-cache".to_string());
args.push("--index-url".to_string());
args.push(self.fallback_index_url());
args.push("torch".to_string());
args.push("torchvision".to_string());
args.push("torchaudio".to_string());
let program = if effective_use_uv {
"uv".to_string()
} else {
self.config.python_bin.clone()
};
PipCommand { program, args }
}
pub fn build_common_deps_command(&self, use_uv: bool) -> PipCommand {
let is_global = self.config.method == InstallMethod::Global
|| self.config.method == InstallMethod::Auto;
let effective_use_uv = use_uv && !is_global;
let mut args = Vec::new();
if effective_use_uv {
args.push("pip".to_string());
args.push("install".to_string());
} else {
args.push("-m".to_string());
args.push("pip".to_string());
args.push("install".to_string());
args.push("--break-system-packages".to_string());
}
args.push("--no-cache-dir".to_string());
args.push("--upgrade".to_string());
args.push("torchsde".to_string());
args.push("sentencepiece".to_string());
let program = if effective_use_uv {
"uv".to_string()
} else {
self.config.python_bin.clone()
};
PipCommand { program, args }
}
pub fn rocm_env_exports(&self, rocm_env: &RocmEnv) -> Vec<(String, String)> {
let mut exports = vec![
("HSA_OVERRIDE_GFX_VERSION".to_string(), "11.0.0".to_string()),
("PYTORCH_ROCM_ARCH".to_string(), "gfx1100".to_string()),
(
"ROCM_PATH".to_string(),
rocm_env
.path()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "/opt/rocm".to_string()),
),
];
let rocm_lib = rocm_env.path().and_then(|p| {
let new_layout = p.join("lib/rocprofiler-sdk/librocprofiler-sdk-tool.so");
let old_layout = p.join("lib/librocprofiler-sdk-tool.so");
if new_layout.exists() {
Some(new_layout)
} else if old_layout.exists() {
Some(old_layout)
} else {
None
}
});
if let Some(lib) = rocm_lib {
exports.push((
"HSA_TOOLS_LIB".to_string(),
lib.to_string_lossy().to_string(),
));
} else {
exports.push(("HSA_TOOLS_LIB".to_string(), "0".to_string()));
}
exports
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_index_url_rocm_72() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("7.2");
assert_eq!(url, "https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/");
}
#[test]
fn test_index_url_rocm_70() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("7.0");
assert_eq!(url, "https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0/");
}
#[test]
fn test_index_url_rocm_64() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("6.4");
assert_eq!(url, "https://download.pytorch.org/whl/nightly/rocm6.4");
}
#[test]
fn test_index_url_rocm_63() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("6.3");
assert_eq!(url, "https://download.pytorch.org/whl/rocm6.3");
}
#[test]
fn test_index_url_rocm_62() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("6.2");
assert_eq!(url, "https://download.pytorch.org/whl/rocm6.2");
}
#[test]
fn test_index_url_rocm_57() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("5.7");
assert_eq!(url, "https://download.pytorch.org/whl/rocm5.7");
}
#[test]
fn test_index_url_fallback() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.index_url_for_rocm("4.0");
assert_eq!(url, "https://download.pytorch.org/whl/rocm6.3");
}
#[test]
fn test_nightly_index_url() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.nightly_index_url("7.2");
assert_eq!(url, "https://download.pytorch.org/whl/nightly/rocm7.2");
}
#[test]
fn test_radeon_index_url() {
let installer = PyTorchInstaller::with_defaults();
let url = installer.radeon_index_url("7.2");
assert_eq!(url, "https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/");
}
#[test]
fn test_build_install_command_uv_global_uses_pip() {
let installer = PyTorchInstaller::new(PyTorchConfig {
method: InstallMethod::Global,
..Default::default()
});
let cmd = installer.build_install_command("7.2", true);
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(&"--index-url".to_string()));
assert!(cmd.args.iter().any(|a| a.contains("rocm-rel-7.2")));
assert!(cmd.args.contains(&"torch".to_string()));
}
#[test]
fn test_build_install_command_pip() {
let installer = PyTorchInstaller::new(PyTorchConfig {
method: InstallMethod::Global,
python_bin: "python3".to_string(),
..Default::default()
});
let cmd = installer.build_install_command("6.4", false);
assert_eq!(cmd.program, "python3");
assert!(cmd.args.contains(&"-m".to_string()));
assert!(cmd.args.contains(&"pip".to_string()));
assert!(cmd.args.contains(&"--break-system-packages".to_string()));
assert!(cmd.args.iter().any(|a| a.contains("rocm6.4")));
}
#[test]
fn test_build_install_command_venv_no_break_system() {
let installer = PyTorchInstaller::new(PyTorchConfig {
method: InstallMethod::Venv,
..Default::default()
});
let cmd = installer.build_install_command("7.2", true);
assert!(!cmd.args.contains(&"--break-system-packages".to_string()));
assert!(!cmd.args.contains(&"--system".to_string()));
}
#[test]
fn test_build_fallback_command() {
let installer = PyTorchInstaller::new(PyTorchConfig {
method: InstallMethod::Global,
..Default::default()
});
let cmd = installer.build_fallback_command(true);
assert!(cmd.args.iter().any(|a| a.contains("rocm7.0")));
assert!(cmd.args.contains(&"torch".to_string()));
}
#[test]
fn test_build_common_deps_command() {
let installer = PyTorchInstaller::with_defaults();
let cmd = installer.build_common_deps_command(true);
assert!(cmd.args.contains(&"torchsde".to_string()));
assert!(cmd.args.contains(&"sentencepiece".to_string()));
}
#[test]
fn test_rocm_env_exports() {
let installer = PyTorchInstaller::with_defaults();
let rocm_env = RocmEnv::from_known(
Some(std::path::PathBuf::from("/opt/rocm")),
"7.2.0".to_string(),
);
let exports = installer.rocm_env_exports(&rocm_env);
assert!(exports
.iter()
.any(|(k, v)| k == "HSA_OVERRIDE_GFX_VERSION" && v == "11.0.0"));
assert!(exports
.iter()
.any(|(k, v)| k == "PYTORCH_ROCM_ARCH" && v == "gfx1100"));
assert!(exports
.iter()
.any(|(k, v)| k == "ROCM_PATH" && v == "/opt/rocm"));
assert!(exports.iter().any(|(k, _)| k == "HSA_TOOLS_LIB"));
}
#[test]
fn test_channel_from_choice() {
assert_eq!(TorchChannel::from_choice(1), Some(TorchChannel::Stable));
assert_eq!(TorchChannel::from_choice(2), Some(TorchChannel::Latest));
assert_eq!(TorchChannel::from_choice(3), Some(TorchChannel::Nightly));
assert_eq!(TorchChannel::from_choice(0), None);
}
}