Repository navigation
Keep Inductor's compile cache under ~/.cache so restarts start warm - #819
Merged
Merged
Conversation
torch defaults TORCHINDUCTOR_CACHE_DIR to /tmp/torchinductor_<user>, which is lost with the container, on reboot and when /tmp is cleaned, so most restarts paid a full cold start (compiling and autotuning every capture bucket). mstar.engine now defaults it to ~/.cache/mstar/torchinductor (XDG-aware) before anything imports torch._dynamo, which writes torch's own default into the environment. An exported value wins, and a home that can't be written keeps torch's default. Docs: what to persist for warm restarts (serving.rst), the variable's row (environment_variables.rst), the XPU docker example mounts the cache, and the benchmarking skill says to use an empty cache dir for cold starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Automated review🟡 1 P2 on Description
What the review checked
Advisory, not a merge gate. Rules: |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stephen-dwq
approved these changes
Oct 24, 2026
Conflicts: - .claude/skills/benchmarking/SKILL.md: main's -O1/-O2 compile-level wording for trap 1, with this branch's persistent-cache sentence replacing the old "unverified" one. - docs/environment_variables.rst: kept both rows (main's MSTAR_COMPILE_LEVEL, this branch's TORCHINDUCTOR_CACHE_DIR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ache The 22-minute figure was -O2's; the default is -O1 (320 s, the page's own -O flag row). A process restart on one host already reused torch's /tmp cache, so the gain is for a new container or a reboot. (R1.2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CacheBase.get_system keys on the torch and Triton builds and, on CUDA and ROCm only, the GPU name and CUDA/HIP version; it doesn't cover ptxas or the driver, and on XPU it is empty. Now that the cache outlives a reboot, say how to break it (delete it, or torch's TORCH_COMPILE_CACHE_KEY_TAG), and keep the per-GPU-model advice to the backends that need it. (R1.4, R1.10) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The default moves compile I/O from node-local /tmp to home, and nothing prunes it. Say it is safe to delete, and when to export a node-local path instead (quota-limited or network homes, a volume shared across hosts). (R1.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image runs as root, so bind-mounting $HOME/.cache/mstar leaves root-owned entries a later native run can't write (or, if Docker creates the bind source, a root-owned directory the native run silently skips). A named volume keeps the container's cache to itself. (R1.7) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worker runs as root, so its default cache is /root/.cache/mstar, which was lost whenever compose recreated the container. (R1.9) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s torch's default mstar.engine's default resolved Path.home() outside its try, so a process with no HOME and no passwd entry failed `import mstar.engine` with RuntimeError, and a relative XDG_CACHE_HOME (or one with a literal ~) was used as is, against the XDG spec. The lookup was also the fourth hand-written copy of the cache root, each with different semantics. user_cache_dir() (mstar/utils/cache_home.py, standard library only, since mstar.engine calls it before importing torch) replaces all four, the engine catches RuntimeError too, and the conductor logs the cache directory it resolved once at startup. (R1.6, R1.12) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where a Triton kernel landed depended on what compiled first: Inductor's first kernel in a process writes TRITON_CACHE_DIR=<inductor>/triton/<dev> into the environment (CachingAutotuner.__init__), so raw Triton kernels compiled before it (Qwen3.5's GDN/FLA kernels) went to ~/.triton/cache, which the docs said ~/.cache held. Default TRITON_CACHE_DIR beside Inductor's cache, with the same exported-wins and unwritable-keeps-the- default rule, as vLLM does; triton_cache_dir() then sends Inductor's kernels there too. The docs list FlashInfer's own variable (FLASHINFER_WORKSPACE_BASE). (R1.3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…restarts A restart that finds a warm cache reloads the previous process's tuning (autotune_cache, and -O2's GEMM picks baked into the cached code), so "sample several processes" with one persisted cache per arm draws one pick N times, and one arm's lucky pick survives every restart. Say to share one cache across both arms so unchanged kernels keep their pick, and to give each boot fresh empty Inductor and Triton directories to resample or to measure a cold start (FlashInfer stays warm). Same-host restarts always reused /tmp. The env var row's cold-start recipe names both directories. (R1.1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orm env default `python -m mstar.integrations.dynamo` is a third process that can import torch._dynamo first; add it to the import-order test. And note in the docstring why the default can't live in Platform.process_env_defaults, which runs after torch._dynamo is imported. (R1.8) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benchmark/eval is shipped to eval hosts on its own (evalfw, PYTHONPATH set to that copy), so it can't import mstar; the import broke test_one_model_runs_both_arms_on_a_remote_slot_and_reports. The three mstar copies still share user_cache_dir. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
monkeypatch.delenv(raising=False) records nothing for an absent variable, so the values _default_compile_caches writes outlived the test in its xdist worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a sibling ~/.cache/mstar/triton, an exported TORCHINDUCTOR_CACHE_DIR (a node-local path, or an empty one for a cold start) no longer brought Triton's kernels with it, as torch's <inductor>/triton/<dev> did. Derive Triton's default from the resolved Inductor directory instead, so one variable moves everything again; an exported TRITON_CACHE_DIR still wins, and the no-home fallback leaves both unset. (R2.1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e (R2.2) The shared-cache advice was unmeasured, and a kernel the branch changes is still one draw per arm under it. Lead with what Qwen3-TTS showed (per-arm warm caches: fake -5.8% at c=1; fresh caches per boot: overlap), keep the shared cache only as a way to hold unchanged kernels fixed, and drop the unverified claim that same-host restarts always reused /tmp, which sat oddly beside the Whisper restart spread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…GPU model (R2.3) CacheBase.get_system's GPU name covers the FX graph cache and -O2's GEMM picks; -O1's coordinate-descent .best_config files are keyed on the kernel source, which embeds DeviceProperties (compute capability, SM count) but not the name, so an H100 and an H200 sharing a cache reuse each other's picks. On XPU get_system is empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (R2.4) Dynamo tracing and graph capture still run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the device (R3.1) FxGraphHashDetails and the -O1 .best_config key always hash torch_key(); only get_system() (Triton build, device, CUDA/HIP version) is empty off CUDA and ROCm, and on ROCm its device is the gfx arch, not the card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R3.2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dghosef
added a commit
that referenced
this pull request
Oct 28, 2026
Main since 73c6c17: #883 mstar_rust in the Linux wheels, #901 output spans (BatchedModelOutput.output_spans, NodeSubmodule.spanned_outputs, the worker's crop), #904 DiT seed noise drawn at ingest (on_request_added, NoiseStager deleted, to_device_nonblocking as one memcpy into pinned memory), #903 API-server torch pool from the CPU quota and pooled image encodes, #924 robotics harness, #819 Inductor cache under ~/.cache, #881 CommGroup.agree / JointGroups.agree (agree_across_ranks and flagged_on_any_rank deleted), #724 CUDA IPC KV in one 2D memcpy a page, #387 GLM-5.2 (MLA, fp8 MoE with NoAuxTcRouter, the DSA resource), #921 vjepa2 AC crop, #922 Cosmos3 contiguous positions, #923 dense attention falls back when fa3-fwd has no kernel for the GPU. Conflicts: - mstar/utils/h2d.py, to_device_nonblocking: the stack's signature (data: Tensor | Sequence, device, dtype=None) and DeviceCapability pin_memory gate, with main's tensor path (#904): a CPU tensor is written into a fresh pinned buffer with one memcpy rather than pin_memory()'s copy_ over the intra-op pool. Every CPU tensor now goes through a fresh buffer, so the stack's "an already-pinned source is read in place" caveat (R3.3) no longer applies and is dropped from the docstring. test_accelerator_graph_backend's pinning test checks the pinned torch.empty and its non-blocking copy instead of pin_memory(). - mstar/model/components/diffusion/denoise_loop.py: imports, both (main's copy, the stack's functools). Main code on a superseded path, ported here: - GLM-5.2 (glm52/submodules.py): its own check_stop / check_stop_batched (four arguments, step_walk, its own row-map loop) / _stops -> token_stop = TokenStop(decode_loop, new_token, eos_token_ids, sampler) with a _token_stops override for its two differences (a stop token at prefill ends the reply at the first decode step; max_tokens counts the prefill's token), and a check_stop override for that closing step, whose token postprocess drops. test_glm52_model's helper and test_model_kv_layouts' GLM cap test build the real submodule (over a stub language model) instead of object.__new__, which would leave token_stop unset. - PinnedStager.to_device (#387), a second "host values -> new device tensor" helper beside to_device_nonblocking: deleted with its test (test_pinned_stager_to_device.py); its callers, GLM-5.2's prefill seq_lens and the DSA resource's page tables, call to_device_nonblocking(list, device, dtype=torch.long). test_dsa_resource spies on that instead. The other files both sides touched (engine.py, accelerator_graph_runner.py, kv/manager.py, attn/base.py, ragged/base.py, communication.py, model/base.py, submodule_base.py, entrypoint.py, conductor.py, moe.py, image_io.py, vjepa2_model.py, docs, tests) auto-merged into disjoint hunks: per file the merged delta equals main's own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
This keeps Inductor's compile cache under
~/.cache, so a restart in a new container, after a reboot, or after/tmpis cleaned starts warm.By default torch puts
TORCHINDUCTOR_CACHE_DIRat/tmp/torchinductor_<user>. A process restart on the same host already reuses it, but the directory is lost with the container, on reboot, and when/tmpis cleaned. Then every capture bucket is compiled again and its GEMMs are autotuned again.How much that costs: at the default
-O1, Qwen3.5-0.8B on H200 takes about 320 s to start on an empty cache, and 1,294 s at-O2(#817). With this PR, a restart that loses/tmptakes 96–108 s instead (measured below).The weights (
~/.cache/huggingface) and FlashInfer's JIT builds (~/.cache/flashinfer) already live under~/.cache. Inductor's cache was the one piece that a~/.cachevolume didn't keep.How it works
mstar/engine/__init__.pysetsTORCHINDUCTOR_CACHE_DIRto<user cache dir>/mstar/torchinductorwhen it isn't already set, andTRITON_CACHE_DIRto<TORCHINDUCTOR_CACHE_DIR>/triton, so exporting the Inductor directory alone moves both. An exported value always wins.mstar/utils/cache_home.py::user_cache_dir():$XDG_CACHE_HOMEif it is an absolute path, else~/.cache. It replaces three hand-written XDG lookups (engine, omnilingual_asr, vjepa2). A relativeXDG_CACHE_HOMEis ignored, and with no usable home both variables stay unset, so torch's/tmpdefault applies and a compile never fails because of an unwritable cache directory.mstar.enginebecause importingtorch._dynamowrites torch's own/tmpdefault intoos.environ(torch/_dynamo/package.pycallscache_dir()at module level), so our default has to be set before that first import.mstar.engineis where every entry point first imports it.Platform.process_env_defaultsruns too late.Docs
docs/serving.rst, "Restarts and the compile cache": what to persist, what the cache key covers (torch/Triton versions and, at-O1, the source hash with compute capability and SM count, so keep one cache per GPU model; ptxas and the driver aren't in the key, so delete the directory or setTORCH_COMPILE_CACHE_KEY_TAGafter changing them), that the default grows in home and nothing prunes it, and to export a node-local path when home is on NFS or a quota.docs/environment_variables.rst: rows for both variables and their defaults.docker run(docs/installation.rst) and the Dynamo compose worker use a namedmstar-cachevolume..claude/skills/benchmarking/SKILL.md: a cross-process A/B or restart sampling uses a fresh, empty cache per boot, since a shared cache pins one arm's autotune picks.How it was tested
ruff check .3783c8890(merged with mainf417b902d) vs maintest_dump_stacks_retries_with_sudo_only_when_asked, a box-local py-spy install). None fail only on this PR.$HOME/.cache/mstar/torchinductorwith Triton under it; a second process hits the FX cache (1 hit, 0 misses); exportedTORCHINDUCTOR_CACHE_DIR,TRITON_CACHE_DIRandXDG_CACHE_HOMEeach win; an empty export counts as unset; a relative XDG is ignored; a read-only HOME (usernobody) falls back to/tmp/torchinductor_nobodyand the compile works; 8 concurrent compiles on one empty cache all exit 0 and a 9th process hits.Compile caches: Inductor ~/.cache/mstar/torchinductor, Triton .../torchinductor/tritonand left nothing in/tmp/torchinductor_*; main wrote to/tmp/torchinductor_root. No tracebacks, every request OK.Restart timing, Qwen3.5-0.8B at
-O1on 1 x H200 (PR3783c8890vs mainf417b902d). "Restart" wipes/tmp/torchinductor_*and keeps$HOME, which is what a new container loses:/health/tmp); with this PR it reused the 326 MB cache and wrote no new files.-O1compiles can differ on both arms (PR 3/4, main 1/4 identical), which is pre-existing.Not run: a restart that keeps
/tmp(same container), several servers sharing one cache, and Z-Image restarts.Not in this PR
torch.compiler.save_cache_artifacts), or a shared volume.-O2's extra compile is paid once per cache rather than once per start, which may be worth revisiting Reduce cold start time with broad compile tuning levels #817's choice of-O1as the default.Written with AI assistance (Claude Code); I've reviewed the change and the numbers above.
🤖 Generated with Claude Code