Skip to content

perf(compile): 13x faster V/U init compile + compile-time findings#941

Open
ocg-goodfire wants to merge 6 commits into
feature/jaxfrom
bridge/task-reduce-jax-compilation-time
Open

perf(compile): 13x faster V/U init compile + compile-time findings#941
ocg-goodfire wants to merge 6 commits into
feature/jaxfrom
bridge/task-reduce-jax-compilation-time

Conversation

@ocg-goodfire

Copy link
Copy Markdown
Collaborator

Description

Compile-time pass for the full-model runs (bridge task reduce-jax-compilation-time). Every new-config launch of the 32L Llama-8B decomposition paid a 3m31s jit_init_decomp_vu compile plus ~24 min of jit_step compile; this PR kills the first, gates incidental compile costs, and pins down (with measurements) where the second does and does not go.

Changes:

  • Stacked V/U init — 210.5s → 16.3s (12.9×) measured on GPU at the full 224-site/32L shape set. init_decomp_vu_placed now compiles two cheap stages: the RNG vmap-STACKED per V/U shape (2×n_shapes sharded jit outputs instead of 2×n_sites — the SPMD pass over a 448-output RNG graph was the multi-minute cost), then a trivial slice jit (stacked input donated) fans out to the per-site ZeRO-1 layout. Bit-identical to the previous jitted per-site init (vmap over the same per-site keys); test_sharding now pins exact equality against the jitted per-site reference. (Found in passing: the unjitted eager init_decomp_vu differs from ANY jitted init by ~1 ULP of XLA fusion — pre-existing, previously hidden by the test's allclose.)
  • HLO dump is now opt-in (ProfileConfig.hlo_dump, default off) — it cost compile time + ~100s of MB on every rank-0 run.
  • sbatch submissions scrub SLURM_*/SBATCH_* from the env — submitting from inside a SLURM allocation (agent sessions, dev pods) leaked SLURM_CPUS_PER_TASK into the new job, whose srun died instantly with "cpus-per-task set by two different environment variables" (the leak the btdr wrapper works around by hand).
  • Compile-probe generator regenerated against the current schema (it had bit-rotted: pre-discriminator ci_config, removed beta) and now derives from the production 32L yaml with per-arm cache isolation.
  • Docs: measured findings + smoke-config cache practice in param_decomp/CLAUDE.md.

Measured nulls / negative results (probe arms A–F, 8L 1-node slices of the production config, per-arm isolated caches):

  • xla_gpu_autotune_level 0/2/4: jit_step compile flat (71.3/71.8/74.4s vs baseline 71.3s) — with triton_gemm off, autotuning is a negligible slice.
  • Persistent autotune cache: already on by default — jax ≥0.10 auto-enables the XLA per-fusion autotune cache alongside the persistent compilation cache (arm A, baseline code, had 306 entries). A hand-wired duplicate was added and then reverted within this branch; the built-in also gates writes on rank 0 correctly.
  • jax_persistent_cache_enable_xla_caches='all' (kernel cache + parallel LLVM codegen): crashes at runtime on jax 0.10.1/B200 (CUDA_ERROR_NOT_FOUND: named symbol not found in jit_step). Documented as do-not-use.

Not addressed (the remaining ~24 min jit_step compile): it is graph size (the recon plan unrolls n_chunks full-model masked forwards+backwards python-side) × topology, not autotuning. The structural lever is a lax.scan over uniform recon chunks — scoped as a follow-up board task, core-trainer change.

Related Issue

Bridge task reduce-jax-compilation-time (see its log for the full probe data).

Motivation and Context

20–30 min before step 0 on every new full-model config made iteration painful. The persistent executable cache only hits on identical HLO, so every real config change paid full price.

How Has This Been Tested?

  • make test (458 passed) + make type clean, test_sharding at 1 and 4 sim devices.
  • Bit-identity of the new init vs the old jitted init pinned by test (exact array_equal, sharded + unsharded).
  • GPU A/B at full production site count (224 sites, 1 node, tp8): old 210.5s vs new 16.3s.
  • Probe arms A–D ran the production compile path end-to-end (10 steps, checkpoints, identical losses/step times across arms: 0.95–0.99 s/step, 119GB peak/rank).

Does this PR introduce a breaking change?

No. Init values are bit-identical; HLO dump moves behind runtime.launch_env.profile.hlo_dump: true.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CLGxUn9wtE3ce9XTbEJo9s

…O dump

Three compile-time cuts for the full-model runs (jit_init_decomp_vu was 3m31s
at 32L/dp128, jit_step ~24min; every new config pays both):

- init_decomp_vu_placed now compiles two cheap stages: the RNG runs
  vmap-STACKED per V/U shape (init_decomp_vu_stacked: 2 x n_shapes sharded jit
  outputs instead of 2 x n_sites), then a trivial slice jit (unstack_decomp_vu,
  stacked input donated) fans out to the per-site ZeRO-1 layout. BIT-identical
  to the previous jitted per-site init (vmap over the same per-site keys);
  test_sharding now pins exact equality against the jitted per-site reference
  (the unjitted eager reference differs from ANY jitted path by ~1 ULP of XLA
  fusion — pre-existing, previously hidden by allclose). CPU-sim compile at
  224 sites: 23.3s -> 4.3s.

- _enable_persistent_autotune_cache (lm/run.py): persist XLA's per-fusion GEMM
  autotuning to $PARAM_DECOMP_OUT_DIR/xla_autotune_cache via
  xla_gpu_per_fusion_autotune_cache_dir + AUTOTUNE_CACHE_MODE_UPDATE
  (setdefault: an explicit runtime.compiler_options wins). The executable
  cache only hits on identical HLO; this makes STRUCTURAL config changes skip
  re-benchmarking every GEMM. Rank-uniform flags keep the compile-cache key
  shared across ranks.

- HLO dump is now opt-in (ProfileConfig.hlo_dump, default off) — it cost
  compile time + ~100s of MB on every rank-0 run.
…L yaml

The generator hand-mirrored an old schema (pre-discriminated ci_config,
ImportanceMinimalityLoss.beta) and no longer validated. It now derives from
llama8b_full32L_seq512_b128_dp64.yaml, keeps arm-isolated persistent caches
via PARAM_DECOMP_OUT_DIR, and takes extra compiler_options k=v pairs for
autotune A/Bs.
Submitting from inside a SLURM allocation (agent sessions, dev pods) leaked
the caller's SLURM_CPUS_PER_TASK into the new job, whose srun then died with
'cpus-per-task set by two different environment variables' (the same leak the
btdr launch wrapper works around by hand). sbatch now submits with a clean
env so the job's SLURM state comes only from its own allocation.
…oes it

Compile-probe arms A–D showed jit_step compile flat across autotune levels
(~71s at 0/2/4), and arm A (baseline) already had a populated autotune cache:
jax >= 0.10 auto-enables the XLA per-fusion autotune cache alongside the
persistent compilation cache (jax_persistent_cache_enable_xla_caches default),
with the correct rank-0 UPDATE / rank-N READ gating. The hand wiring duplicated
that, less correctly (UPDATE on all ranks, flags in the compile-cache key).
Documented the built-in + the measured null on _enable_persistent_compilation_cache.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant