fix(uv): colocate the uv cache with the ROCm CLI data dir - #170
Conversation
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, a data dir moved via ROCM_CLI_DATA_DIR or --prefix), 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. Closes #160 Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
|
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.
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_DIRor--prefix— 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. Worth a maintainer sanity-check that this matches the intended meaning of the two directories.An existing
UV_CACHE_DIRwins. If one is already set, no pair is emitted and the child inherits the user's choice. This also keeps the e2e harness working, which setsUV_CACHE_DIRitself to share one cache across scenarios.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.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.uvprojects on the machine. Users who want the old sharing can setUV_CACHE_DIRthemselves; that escape hatch is deliberate but is not yet documented anywhere.$HOME/.cache/uvcontents are not migrated and nothing removes them. A migration or a one-line notice may be wanted — not included here.Tests
Three unit tests: the cache path is derived from the data dir (with
UV_HTTP_TIMEOUTstill present), an explicitUV_CACHE_DIRwins, and the derived path moves when the data dir moves.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 --workspaceon this branch shows two failures inproc_lifecyclethat are unrelated to it — see #168, fixed by #169. Everything else passes;cargo fmtandcargo clippy --workspace --all-targets -- -D warningsare clean.Fixes #160