-
Notifications
You must be signed in to change notification settings - Fork 10
fix(therock): resolve ROCm releases from the current multi-arch pip index #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
db42fd2
8c62a8b
50ecdbb
68934fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,12 @@ use anyhow::{Context, Result, bail}; | |
| use rocm_core::{ | ||
| AppPaths, ManagedToolConfig, RocmCliConfig, detect_host_gpu_diagnostics, | ||
| detect_host_therock_family, detect_managed_therock_family, disk_space, ensure_uv_binary, | ||
| known_therock_families, managed_tools_dir, normalize_runtime_path_for_host, | ||
| normalize_runtime_path_for_storage, normalize_runtime_path_text_for_host, | ||
| normalize_runtime_path_text_for_storage, normalize_therock_family, runtime_is_windows, | ||
| runtime_os_name, runtime_path_for_windows_child, runtime_path_list_split, | ||
| runtime_python_executable_in_env, unix_time_millis, uv_command_env, uv_pip_install_base, | ||
| uv_venv_args, verify_rsa_pkcs1_sha256_signature, | ||
| known_therock_families, known_therock_family_device_chips, managed_tools_dir, | ||
| normalize_runtime_path_for_host, normalize_runtime_path_for_storage, | ||
| normalize_runtime_path_text_for_host, normalize_runtime_path_text_for_storage, | ||
| normalize_therock_family, runtime_is_windows, runtime_os_name, runtime_path_for_windows_child, | ||
| runtime_path_list_split, runtime_python_executable_in_env, unix_time_millis, uv_command_env, | ||
| uv_pip_install_base, uv_venv_args, verify_rsa_pkcs1_sha256_signature, | ||
| }; | ||
| #[cfg(test)] | ||
| use rocm_core::{ | ||
|
|
@@ -826,6 +826,7 @@ fn install_wheel_runtime( | |
| "Found TheRock package family {} version {} with a matching PyTorch stack.", | ||
| resolution.family, resolution.latest_version | ||
| )); | ||
| let rocm_extras = therock_rocm_extras(&resolution.family, &resolution.index_url); | ||
| let runtime_key = runtime_key( | ||
| channel, | ||
| "wheel", | ||
|
|
@@ -880,11 +881,11 @@ fn install_wheel_runtime( | |
| let _ = writeln!( | ||
| output, | ||
| " package_specs: {}", | ||
| therock_pip_package_specs(&resolution.package_versions).join(" ") | ||
| therock_pip_package_specs(&resolution.package_versions, &rocm_extras).join(" ") | ||
| ); | ||
| let _ = writeln!( | ||
| output, | ||
| " package_policy: find the newest TheRock ROCm SDK version that has a matching PyTorch stack in the same index, then install pinned rocm[libraries,devel], torch, torchvision, and torchaudio versions in one uv transaction" | ||
| " package_policy: find the newest TheRock ROCm SDK version that has a matching PyTorch stack in the same index, then install pinned rocm (with any resolved device extras), torch, torchvision, and torchaudio versions in one uv transaction" | ||
| ); | ||
| if dry_run { | ||
| let env_python = venv_python_path(&install_root); | ||
|
|
@@ -893,7 +894,10 @@ fn install_wheel_runtime( | |
| if matches!(channel, TheRockChannel::Nightly) { | ||
| install_args.extend(["--prerelease".to_owned(), "allow".to_owned()]); | ||
| } | ||
| install_args.extend(therock_pip_package_specs(&resolution.package_versions)); | ||
| install_args.extend(therock_pip_package_specs( | ||
| &resolution.package_versions, | ||
| &rocm_extras, | ||
| )); | ||
| let venv_args = uv_venv_args(&python_launcher.executable, &install_root); | ||
| let venv_args_display = venv_args | ||
| .iter() | ||
|
|
@@ -933,15 +937,18 @@ fn install_wheel_runtime( | |
|
|
||
| progress_line(format!( | ||
| "Installing {} from {}", | ||
| therock_pip_package_specs(&resolution.package_versions).join(" "), | ||
| therock_pip_package_specs(&resolution.package_versions, &rocm_extras).join(" "), | ||
| resolution.index_url | ||
| )); | ||
| let mut install_args = uv_pip_install_base(&env_python); | ||
| install_args.extend(["--index-url".to_owned(), resolution.index_url.clone()]); | ||
| if matches!(channel, TheRockChannel::Nightly) { | ||
| install_args.extend(["--prerelease".to_owned(), "allow".to_owned()]); | ||
| } | ||
| install_args.extend(therock_pip_package_specs(&resolution.package_versions)); | ||
| install_args.extend(therock_pip_package_specs( | ||
| &resolution.package_versions, | ||
| &rocm_extras, | ||
| )); | ||
| run_uv_progress_command( | ||
| paths, | ||
| &uv, | ||
|
|
@@ -1012,15 +1019,42 @@ fn install_wheel_runtime( | |
| Ok(output) | ||
| } | ||
|
|
||
| fn therock_pip_package_specs(package_versions: &TheRockPipPackageVersions) -> Vec<String> { | ||
| fn therock_pip_package_specs( | ||
| package_versions: &TheRockPipPackageVersions, | ||
| rocm_extras: &str, | ||
| ) -> Vec<String> { | ||
| vec![ | ||
| format!("rocm[libraries,devel]=={}", package_versions.rocm), | ||
| format!("rocm[{rocm_extras}]=={}", package_versions.rocm), | ||
| format!("torch=={}", package_versions.torch), | ||
| format!("torchvision=={}", package_versions.torchvision), | ||
| format!("torchaudio=={}", package_versions.torchaudio), | ||
| ] | ||
| } | ||
|
|
||
| fn is_multi_arch_pip_index(index_url: &str) -> bool { | ||
| index_url.trim_end_matches('/') == THEROCK_RELEASE_PIP_MULTI_ARCH_INDEX_BASE | ||
| } | ||
|
|
||
| /// The `rocm[...]` extras to request for a resolved pip index. The classic | ||
| /// per-family index needs only the base `libraries,devel` extras; the flat | ||
| /// multi-arch index additionally needs an explicit `device-*` extra or no GPU | ||
| /// backend gets installed at all. | ||
| fn therock_rocm_extras(family: &str, index_url: &str) -> String { | ||
| let mut extras = "libraries,devel".to_owned(); | ||
| if !is_multi_arch_pip_index(index_url) { | ||
| return extras; | ||
| } | ||
| match known_therock_family_device_chips(family) { | ||
| Some(chips) => { | ||
| for chip in chips { | ||
| let _ = write!(extras, ",device-{chip}"); | ||
| } | ||
| } | ||
| None => extras.push_str(",device-all"), | ||
| } | ||
| extras | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fallback fires on every Instinct/datacenter part, and your own CI shows what it costs.
From job 99541673879 (MI300X, and then the post-install probe: So on a gfx943 host we download ~4.3 GiB to use one wheel, and the probe reports the wrong target family — There's no size guard either: Four of the six are enumerable right now, from the wheel names the same CI log lists: "gfx94X-dcgpu" => Some(&["gfx942"]),
"gfx950-dcgpu" => Some(&["gfx950"]),
"gfx101X-dgpu" => Some(&["gfx1010", "gfx1011", "gfx1012"]),
"gfx103X-dgpu" => Some(&["gfx1030", "gfx1031", "gfx1032", "gfx1033",
"gfx1034", "gfx1035", "gfx1036"]),I checked these against The better answer, if you're reconciling with #329 anyway, is #329's shape: derive the extra from the detected raw arch ( |
||
| } | ||
|
|
||
| fn quote_display_arg(value: &str) -> String { | ||
| if value.is_empty() | ||
| || value | ||
|
|
@@ -3368,9 +3402,12 @@ fn parse_version(value: &str) -> Option<ParsedVersion> { | |
|
|
||
| fn therock_index_urls(channel: TheRockChannel, family: &str) -> Vec<String> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the #329 collision you flagged — one detail worth pinning down before you agree a merge order, because it's not just a textual conflict. #329's replacement for this function still contains the bug: // pr/329 apps/rocm/src/therock.rs:3632-3638
TheRockChannel::Release => vec![
format!("{}/{family}", release_pip_index_base()),
format!("{}/{family}", release_pip_multi_arch_index_base()),
],Classic first, then the per-family multi-arch shape — byte-for-byte The other direction is the good news: #329's Whoever merges second should re-verify against #271's actual repro, not just resolve conflicts and trust the suite. |
||
| match channel { | ||
| // Multi-arch is flat (no per-family path segment) and is where AMD | ||
| // publishes current releases; try it first. The classic per-family | ||
| // index is kept as a fallback so older releases stay installable. | ||
| TheRockChannel::Release => vec![ | ||
| THEROCK_RELEASE_PIP_MULTI_ARCH_INDEX_BASE.to_owned(), | ||
| format!("{THEROCK_RELEASE_PIP_INDEX_BASE}/{family}"), | ||
| format!("{THEROCK_RELEASE_PIP_MULTI_ARCH_INDEX_BASE}/{family}"), | ||
| ], | ||
| TheRockChannel::Nightly => vec![format!("{THEROCK_NIGHTLY_PIP_INDEX_BASE}/{family}")], | ||
| } | ||
|
|
@@ -4065,7 +4102,7 @@ mod tests { | |
| torchaudio: "2.10.0+rocm7.13.0a20260513".to_owned(), | ||
| compatibility_key: "7.13.0a20260513".to_owned(), | ||
| }; | ||
| let package_specs = therock_pip_package_specs(&package_versions); | ||
| let package_specs = therock_pip_package_specs(&package_versions, "libraries,devel"); | ||
|
|
||
| assert_eq!( | ||
| package_specs, | ||
|
|
@@ -4078,6 +4115,51 @@ mod tests { | |
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn therock_index_urls_prefers_multi_arch_then_classic_for_release() { | ||
| let urls = therock_index_urls(TheRockChannel::Release, "gfx110X-all"); | ||
| assert_eq!( | ||
| urls, | ||
| vec![ | ||
| "https://repo.amd.com/rocm/whl-multi-arch".to_owned(), | ||
| "https://repo.amd.com/rocm/whl/gfx110X-all".to_owned(), | ||
| ] | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn therock_index_urls_nightly_unchanged() { | ||
| let urls = therock_index_urls(TheRockChannel::Nightly, "gfx110X-all"); | ||
| assert_eq!( | ||
| urls, | ||
| vec!["https://rocm.nightlies.amd.com/v2/gfx110X-all".to_owned()] | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn therock_rocm_extras_classic_index_is_unchanged() { | ||
| assert_eq!( | ||
| therock_rocm_extras("gfx110X-all", "https://repo.amd.com/rocm/whl/gfx110X-all"), | ||
| "libraries,devel" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn therock_rocm_extras_multi_arch_adds_exact_device_chips() { | ||
| assert_eq!( | ||
| therock_rocm_extras("gfx110X-all", "https://repo.amd.com/rocm/whl-multi-arch"), | ||
| "libraries,devel,device-gfx1100,device-gfx1101,device-gfx1102,device-gfx1103" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn therock_rocm_extras_multi_arch_falls_back_to_device_all_for_ambiguous_bucket() { | ||
| assert_eq!( | ||
| therock_rocm_extras("gfx90X-dcgpu", "https://repo.amd.com/rocm/whl-multi-arch"), | ||
| "libraries,devel,device-all" | ||
| ); | ||
| } | ||
|
|
||
| /// The downloaded archive is removed once it has been unpacked; keeping it | ||
| /// would double the disk cost of every installed SDK version. | ||
| #[test] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,3 +84,23 @@ Feature: Runtime configuration | |
| When the user tries to adopt the existing install | ||
| Then the adoption is refused | ||
| And the error explains which install types can be adopted | ||
|
|
||
| # Regression guard for the bug where the release-channel multi-arch pip index | ||
| # was queried with a per-family path segment (.../whl-multi-arch/{family}/), | ||
| # which 403s because that index is flat and 404/403-ed straight into the | ||
| # stale classic index every time, silently pinning every install to | ||
| # whatever version predated the migration. `--family` bypasses GPU | ||
| # auto-detection so this needs no GPU, and `--dry-run` resolves the real | ||
| # index without installing anything. | ||
| # | ||
| # `@nightly` for the same reason as scenario 8 above: this dry-run still | ||
| # resolves the real channel index over the network (dry-run only skips the | ||
| # venv/download, not index resolution), and the no-GPU mock lane's 64-way | ||
| # concurrency from that extra network work is what pushes | ||
| # `eai-7960-gen-tps-held-after-scrape-failure` and | ||
| # `eai-7960-gen-tps-expiry-boundary` past their validity window. Runs on the | ||
| # nightly lanes instead, where scenarios are serialized. | ||
| @id:runtime-install-sdk-release-index-shape @nightly | ||
| Scenario: 4 - Resolving the SDK from the release channel never uses the broken multi-arch URL shape | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This scenario issues |
||
| When the user dry-runs installing the SDK for a known family | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scenario number 4 is already taken — Nothing fails: the harness's uniqueness assert ( |
||
| Then the resolved package index is not the broken per-family multi-arch path | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -202,6 +202,15 @@ fn assert_engine_ready(world: &mut E2eWorld) { | |
| ); | ||
| } | ||
|
|
||
| #[when("the user dry-runs installing the SDK for a known family")] | ||
| async fn user_dry_runs_install_sdk_for_family(world: &mut E2eWorld) { | ||
| let stdout = crate::run_rocm_ok( | ||
| world, | ||
| &["install", "sdk", "--family", "gfx110X-all", "--dry-run"], | ||
| ); | ||
| world.cli_output = Some(stdout); | ||
| } | ||
|
|
||
| #[when("the user tries to adopt the existing install")] | ||
| async fn user_tries_adopt(world: &mut E2eWorld) { | ||
| let (stdout, stderr, rc) = crate::run_rocm( | ||
|
|
@@ -365,6 +374,15 @@ async fn assert_update_reports_freshness(world: &mut E2eWorld) { | |
| } | ||
| } | ||
|
|
||
| #[then("the resolved package index is not the broken per-family multi-arch path")] | ||
| async fn assert_index_not_broken_multi_arch(world: &mut E2eWorld) { | ||
| let stdout = world.cli_output.as_deref().expect("no dry-run output"); | ||
| assert!( | ||
| !stdout.contains("whl-multi-arch/gfx110X-all"), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assertion is satisfied on unpatched On The broken Asserting the positive fixes it, and it's a one-line change: assert!(
stdout.contains("index_url: https://repo.amd.com/rocm/whl-multi-arch\n"),
"release install did not resolve the flat multi-arch index:\n{stdout}"
);That fails on One edge worth naming: if the classic index ever stops resolving entirely, |
||
| "dry-run resolved the broken per-family multi-arch index shape:\n{stdout}" | ||
| ); | ||
| } | ||
|
|
||
| #[then("the adoption is refused")] | ||
| async fn assert_adoption_refused(world: &mut E2eWorld) { | ||
| let rc = world.cli_rc.expect("no command was run"); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shape change breaks a documented acceptance test that CI can't see.
scripts/therock_sdk_install_test.py— unchanged by this PR — asserts:After this change the release-channel output is
rocm[libraries,devel,device-gfx1151]==7.14.0, and"rocm[libraries,devel]=="is not a substring of that. The script's default is--channel release(:412), so every documented invocation indocs/testing.md:180/186/193/200anddocs/manual-testing.md:140fails on the first release run. It's manual-only — not inci.yml— so nothing catches it, and AGENTS.md §8 namesdocs/testing.mdchecks as part of the gate for touched behavior.The fix is small: make the constant a prefix,
"rocm[libraries,devel", and assert onassert_contains(install_output, THEROCK_SDK_PACKAGE_SPEC). The existingassert_not_contains(install_output, "rocm[devel]")negative still does its job.Same class of staleness in prose at
docs/testing.md:167, which lists the expected plan as "pinnedrocm[libraries,devel],torch,torchvision, andtorchaudioversions" — worth a sentence noting the release channel now addsdevice-*.