fix(uv): colocate the uv cache with the ROCm CLI data dir - #170
Conversation
|
The Failure signature on every attempt, unchanged: That is The same scenario failed and then passed on re-run, with no code change, on #166, #169 and #172 today. The per-PR failure count rose through the afternoon (1, then 2, then 3, then 5 here), which looks like a worsening runner or network condition rather than anything branch-specific. I have re-run this job five times and stopped there rather than keep consuming runner time. Could a maintainer re-run it, or advise if the Windows lane is known to be unhealthy right now? Everything else on this PR is green, and |
volen-silo
left a comment
There was a problem hiding this comment.
Reviewed at e10c649. The core mechanism is right and the execution is careful — root cause identified at the correct layer, measured evidence rather than assertion, every uv spawn site found and threaded (I checked all six independently), and a test seam that works around #![deny(unsafe_code)] making set_var unusable. The PR body also self-discloses its known gaps, which made this much faster to review. Two blocking items, one of them a two-minute edit.
Blocking
1. --prefix installs are not covered — and that's the mainline flow
crates/rocm-core/src/uv.rs:78 derives the cache from paths.data_dir unconditionally. But at apps/rocm/src/therock.rs:812:
let install_root = prefix.unwrap_or_else(|| managed_runtime_root(paths, "wheel", &runtime_key));With --prefix, install_root is a raw user path and data_dir is untouched. The two are never cross-checked, so the same-filesystem property this PR exists to establish does not hold for prefixed installs.
This isn't an edge case:
- The built-in assistant's system prompt (
apps/rocm/src/main.rs:8439) instructs the model to always obtain an install folder and pass--prefix, and forbids proposing an install command without one. Every assistant-driven SDK install goes down this path. - It cascades to ComfyUI —
apps/rocm/src/comfyui.rs:666roots the app atinstall_root.join("apps").join(APP_ID), so the uv install atcomfyui.rs:1426inherits the prefixed root. Two of the three uv install paths are affected. - One combination is a genuine regression.
ROCM_CLI_DATA_DIR=/mnt/bigplus--prefix $HOME/envs: before this PR the cache was~/.cache/uv, same filesystem as$HOME/envs, hardlinks worked. After, the cache is/mnt/big/uv-cacheand uv falls back to copying. The two knobs are independent and nothing validates one against the other.
There's also an existing convention pointing the other way. docs/testing.md:161-164 specifies a localized cache inside the selected ROCm runtime folder at <install-root>/pip-cache, "including both generated managed folders and explicit --prefix folders" — and docs/manual-testing.md:41-46 says the same in user terms. The documented intent is colocate-with-install-root, which covers --prefix; this PR colocates with data_dir, which doesn't. Either follow that convention or say why it's being changed.
The fix looks cheap, which is why I'm flagging it rather than deferring. The target root is already in scope at four of the five call sites: ensure_uv_venv (therock.rs:2218) takes install_root; run_uv_progress_command's only caller (therock.rs:922) has it as a local; run_uv_logged_command's only caller (comfyui.rs:350) has runtime.python; install_vllm_with_uv (engines/vllm/src/lib.rs:926) takes python: &Path. A uv_command_env_for_target(paths, target_root) is a signature change at four sites. ensure_managed_python (therock.rs:2747, :2760) genuinely doesn't fit — it installs an interpreter into uv's own global store — and should stay keyed off data_dir.
If you'd rather land the improvement now and handle --prefix separately, that's a reasonable call — but then it needs to be an explicit non-goal in the description with a follow-up issue, not silently implied as covered. Which brings me to:
2. The description and commit message state something factually wrong about --prefix
PR body and commit e10c649 both say:
a user who moved the data directory to a larger disk with
ROCM_CLI_DATA_DIRor--prefix
--prefix does not move the data directory. It relocates install_root for a single SDK install and leaves paths.data_dir — and therefore the uv cache — exactly where it was. Listing it among the fixed scenarios tells reviewers a case is handled when it isn't, and it's precisely the sentence that would stop the next reader from noticing finding 1. Free to correct in the body; the commit message needs an amend or a follow-up note.
Non-blocking
apps/rocm/src/main.rs:438-442— the uninstall flag help is now misleading.--keep-datasays "Keep app data such as logs, services, and engines" and--keep-cachesays "Keep caches."build_uninstall_planbuckets strictly bypaths.data_dir/paths.cache_dir, so after this PR--keep-cachehas zero effect on the uv cache while--keep-datasilently preserves what will often be the largest directory the product manages. A one-line amendment to--keep-datais close to required. (The broader data-vs-cache taxonomy issue pre-dates this PR —managed_pip_cache_dirwas already under the data root — so re-bucketing is a separate concern.)crates/rocm-core/src/uv.rs:78-81— the behavior the PR actually adds is untested. All three new tests call the privateuv_command_env_with_inherited_cachewith a pre-resolvedOption<&OsStr>, so the env read and the.filter(|v| !v.is_empty())are never exercised in either direction. Avoidingset_varis the right call, but the empty-string boundary is a pure function and could be tested as one.crates/rocm-core/src/uv.rs:440-448—command_env_cache_dir_follows_a_relocated_data_diris near-tautological.managed_uv_cache_diris an unbranchedroot.join("uv-cache"), so building twoAppPathswith differentdata_dirs and asserting the results differ only fails if the implementation ignorespathsentirely, which the preceding test already covers. It doesn't touchAppPaths::with_managed_rootorROCM_CLI_DATA_DIR, so the name overstates it.- Stale
$HOME/.cache/uv— you flag this as known. Worth noting there's house precedent for the light version:maybe_migrate_legacy_dashboard_config(main.rs:924-939) does one-shot legacy-location detection and prints a one-line notice. A notice would fit; a full cache migration seems neither needed nor advisable. crates/rocm-core/src/uv.rs:65— the escape hatch piggybacks on a third-party variable, against local convention. Every neighbouring override in this same module is namespaced:ROCM_CLI_UV_BINARY,ROCM_CLI_UV_VERSION,ROCM_CLI_UV_TIMEOUT_SECS.UV_CACHE_DIRis uv's generic variable, so a developer who has it exported for unrelated Python work silently opts out of the fix with no signal — indistinguishable from an accident. AROCM_CLI_UV_CACHE_DIR(keepingUV_CACHE_DIRinheritance for the e2e harness) would match the convention and let the CLI report that an override is active.MANIFEST.md:666-672— the uv section discloses that the uv binary is cached in the managed data directory but not the multi-GB wheel cache that now lives there. For a disclosure document that materially understates the footprint.
While you're here (pre-existing, but this PR is the natural place)
apps/rocm/src/main.rs:9411,:9423renderDownloads/cache: <value>fromactive_runtime_pip_cache_dir/setup_runtime_pip_cache_dir, which fall back to<install_root>/pip-cache. But nothing anywhere setsPIP_CACHE_DIRor--cache-dir, and every manifest constructor writespip_cache_dir: None. So the CLI tells the user downloads stay in their chosen ROCm folder while they actually go to the uv cache. Already wrong before this PR — but this is the PR that decides where downloads live, and fixing finding 1 by keying offinstall_rootwould make the existing message true.apps/rocm/src/therock.rs:3409—managed_uv_cache_defaults_inside_generated_runtime_folderonly assertsinstall_root.starts_with(&paths.data_dir)and never touchesmanaged_uv_cache_diroruv_command_env. It predates this PR, but its name now claims coverage of exactly what changed here. Repurposing it into a--prefixdivergence test would have caught finding 1.
Nit
crates/rocm-core/src/uv.rs:79 — the inherited-value check doesn't trim, while both siblings in the same file do (uv_version at :225, env_secs at :332). UV_CACHE_DIR=" " is treated as a deliberate override.
On the data_dir-over-cache_dir question you raised
Your reasoning holds. ROCM_CLI_CACHE_DIR can point at a different filesystem from the environments, which would reintroduce the bug, and AppPaths::with_managed_root already derives cache_dir from data_dir by default. The right axis to colocate on is the one the environments live on — which is also the argument for install_root in finding 1.
uv was never told where to cache, so its cache stayed at $HOME/.cache/uv while managed environments are created under the data dir. When the two are on different filesystems (split /home, container overlay, or a data dir moved to a larger disk via ROCM_CLI_DATA_DIR), uv cannot hardlink and silently copies every file, so each environment carries a full duplicate of the ROCm SDK and torch stack. Add managed_uv_cache_dir() alongside the other managed_*_dir helpers and set UV_CACHE_DIR from it in uv_command_env(), which now takes &AppPaths so the cache always follows the data dir. An UV_CACHE_DIR already present in the environment is a deliberate choice (the e2e harness sets one to share a cache across scenarios) and is left untouched. This colocates with the data directory only. --prefix relocates install_root for a single SDK install and leaves paths.data_dir untouched, so a --prefix on a different filesystem from the data dir is not covered here. Closes #160 Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Review follow-ups on the uv cache colocation. Namespace the escape hatch as ROCM_CLI_UV_CACHE_DIR, matching the sibling knobs in this module (ROCM_CLI_UV_BINARY, ROCM_CLI_UV_VERSION, ROCM_CLI_UV_TIMEOUT_SECS). A bare UV_CACHE_DIR exported for unrelated Python work would otherwise silently opt a user out of the fix with no signal. An ambient UV_CACHE_DIR is still honored so the e2e harness keeps working, but it now ranks below the namespaced variable, and UvCacheSource lets callers tell the three cases apart. Blank and whitespace-only values are ignored in both, as uv_version and env_secs already do. Cover the boundary the previous tests missed: the resolution is now a pure function exercised for unset, empty, whitespace-only, trimmed, inherited, and namespaced-override inputs, and the relocated-data-dir test goes through AppPaths::with_managed_root instead of two hand-built AppPaths. Correct the uninstall flag help: --keep-cache does not cover the uv cache, which lives under the data directory, and --keep-data therefore preserves what is usually the largest directory the CLI manages. Disclose that cache and its size in MANIFEST.md, which previously mentioned only the uv binary. Print a one-shot notice when a pre-colocation $HOME/.cache/uv is still on disk, following maybe_migrate_legacy_dashboard_config. Nothing is migrated or deleted: the cache is content-addressed and may be shared with unrelated uv projects. Document the --prefix gap in docs/manual-testing.md and pin it with a test, so the pip-cache convention there is not read as covering the uv cache. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
e10c649 to
35931cb
Compare
|
Thanks — this was a genuinely useful review; finding 1 turned out to be more interesting than either of us expected. Note on what changed since your review at 1.
|
|
CI status on Two self-hosted checks I can't drive from here:
Flagging rather than leaving a bare red X. Happy to re-push or re-run if anything here turns out to be real. |
Summary
Point
uvat a cache inside the ROCm CLI data directory, so the cache sits on the same filesystem as the environments it populates and hardlinking keeps working.Root cause
uv_command_env()set onlyUV_HTTP_TIMEOUT, neverUV_CACHE_DIR, so the cache stayed at$HOME/.cache/uvwhile managed environments were created under the data directory. When those are on different filesystems,uvcannot hardlink and falls back to copying every file — each environment then carries its own full copy of the ROCm SDK and the torch stack.This is not an exotic setup. It happens with a split
/home, a container overlay, or a user who moved the data directory to a larger disk withROCM_CLI_DATA_DIR— so the workaround for running out of space multiplied the space used.Measured with uv 0.9.30 on a host where
$HOME/.cacheis a separate mount, installing a small package and comparing inodes:A distinct inode with link count 1 — a copy, not a reflink or symlink. Setting
UV_LINK_MODE=hardlinkexplicitly does not help: the request is downgraded and the command still exits 0.uvdoes print a fallback warning, but two of the three install paths discard it — ComfyUI pipes stderr to a log file, and the vLLM install captures output with.output()and drops stderr on success.Technical decisions
Keyed off
data_dir, notcache_dir. A cache would naturally live in the cache directory, butROCM_CLI_CACHE_DIRcan point at a different filesystem from the environments, which would reintroduce the exact bug. Colocation with the environments is the property that matters here.The escape hatch is
ROCM_CLI_UV_CACHE_DIR, matching the sibling knobs in the same module (ROCM_CLI_UV_BINARY,ROCM_CLI_UV_VERSION,ROCM_CLI_UV_TIMEOUT_SECS). A bareUV_CACHE_DIRexported for unrelated Python work would otherwise silently opt a user out of the fix with no signal. An ambientUV_CACHE_DIRis still honored — the e2e harness sets one to share a cache across scenarios — but it ranks below the namespaced variable, andUvCacheSourcelets callers tell managed / override / inherited apart.A one-shot notice, not a migration. When a pre-colocation
$HOME/.cache/uvis still on disk, the CLI prints one notice and records that it did, followingmaybe_migrate_legacy_dashboard_config. Nothing is migrated or deleted: the cache is content-addressed and may be shared with unrelateduvprojects, so removing it is the user's call.Threading
&AppPathsthroughuv_command_envtouched the call sites intherock.rs,comfyui.rs, and the vLLM engine; all of them already hadpathsin scope, so no newAppPaths::discover()calls were introduced.Non-goal:
--prefixinstalls — #187This colocates with the data directory only.
--prefixrelocatesinstall_rootfor a single SDK install and leavespaths.data_dir— and therefore the uv cache — untouched. A--prefixon a different filesystem from the data directory still copies rather than hardlinks, and that case is not fixed here.It is deliberately out of scope because the obvious fix does not work. Threading the target root through and using
<install_root>/uv-cachebreaks every wheel install:uvmaterializesUV_CACHE_DIRon every invocation includinguv venv, anduv venvrefuses to create an environment in a directory that already exists. Verified:Since
install_rootis the venv directory, the cache cannot live inside it. (This is also why the existing<install-root>/pip-cacheconvention indocs/testing.mdworks for pip but not for uv: pip creates its cache during downloads, after the venv exists.) Choosing a different scheme is a design decision for maintainers, so it is tracked in #187 with the evidence and four options.In the meantime the gap is documented in
docs/manual-testing.md, pinned byuv_cache_does_not_follow_a_prefix_install_root, andROCM_CLI_UV_CACHE_DIRis a manual workaround.The related pre-existing
Downloads/cache:message — which reports<install_root>/pip-cachealthough nothing setsPIP_CACHE_DIR— is folded into #187, since whatever scheme resolves--prefixdecides what that message should say.Consequences worth noting
rocm uninstallnow covers the uv cache as a side effect, since it removes the data directory. That reclaims more space but makes uninstall slower. The--keep-data/--keep-cachehelp text has been corrected to say so:--keep-cachedoes not cover the uv cache.MANIFEST.mdnow discloses the wheel cache and its size, not just the uv binary.uvprojects on the machine. Users who want the old sharing can setROCM_CLI_UV_CACHE_DIR.Tests
Cache resolution is a pure function over the two override variables, exercised for unset, empty, whitespace-only, trimmed, inherited, and namespaced-override inputs, plus precedence between the two. The relocated-data-dir test goes through
AppPaths::with_managed_rootrather than two hand-builtAppPaths.uv_cache_does_not_follow_a_prefix_install_rootpins the #187 gap so it cannot regress silently in either direction.They exercise the pure helper rather than mutating the process environment, because the crate denies
unsafe_codeandstd::env::set_varisunsafein this edition. That also keeps them parallel-safe.CI note
cargo test --workspace --all-targets --no-fail-faston this branch shows two failures inproc_lifecyclethat are unrelated to it — see #168, fixed by #169. That file is not touched by this PR, and those are the only two failures in the workspace.cargo fmt --all --checkandcargo clippy --locked --workspace --all-targets -- -D warningsare clean.Fixes #160