ITADN

[Regression] `CollectivePipeliner` GPU pass present in JAX 0.4.38 is missing from JAX 0.10.1 — pipelining flags accepted but produce no pass stages; class symbols absent from binaries

#44228Openrsdcastro 创建于 2026-06-12
NVIDIA-GPU
R
rsdcastrocommented
## Summary The XLA collective pipeliner pass (`xla/service/collective_pipeliner.cc`) was present and runnable in the GPU plugin shipped with `jax==0.4.38` (confirmed via `strings`/`nm` of `jax_plugins/xla_cuda12/xla_cuda_plugin.so` — see "Cross-check on older JAX" below). In `jax==0.10.1` / `jaxlib==0.10.1` (current stable as of June 2026), the pass is no longer invoked on GPU regardless of which `--xla_gpu_enable_pipelined_*` flag is set: - `--xla_gpu_enable_pipelined_all_reduce=true` (and the `_all_gather` / `_reduce_scatter` variants) are accepted by the flag parser without error - HLO pass dumps (`XLA_FLAGS=--xla_dump_hlo_pass_re=.*`) contain **no `collective-pipeliner-forward` or `-backward` stages** in any compiled module - `nm -D` / `strings` on every `.so` shipped in `site-packages/jaxlib/` and `site-packages/jax_plugins/` shows **no `CollectivePipeliner` symbols** — the class is not linked into the binary - The umbrella flag `--xla_gpu_enable_pipelined_collectives=true` is rejected as "Unknown flag" by the parser - `--xla_gpu_run_post_layout_collective_pipeliner=true` is also rejected as "Unknown flag" (though it appears as a proto field name string in `libjax_common.so`) The pass is still referenced in XLA source (`xla/service/collective_pipeliner.cc`), so it exists upstream — but appears not built into the GPU plugin shipped with current JAX stable. The "Cross-check on older JAX" section below shows the same wheels installed via the same `pip install "jax[cuda12]==<version>"` command have the symbols in 0.4.38 but not in 0.10.1, narrowing the regression to a build-config change between those two releases. ## Environment | Component | Version | |---|---| | OS | CentOS Stream 9, Linux 6.13.2 x86_64 | | GPU | NVIDIA H100, driver 580.82.07 | | Python | 3.11.15 | | `jax` | 0.10.1 | | `jaxlib` | 0.10.1 | | `jax-cuda12-plugin` | 0.10.1 | | `jax-cuda12-pjrt` | 0.10.1 | | NCCL | 2.30.7 + CUDA 12.9 | | Hardware | 8× H100 single node | ## Reproduction ### Step 0 — fresh conda env (no environment contamination) ```bash conda create -n xla_repro_clean python=3.11 -y ~/.conda/envs/xla_repro_clean/bin/pip install --upgrade pip ~/.conda/envs/xla_repro_clean/bin/pip install "jax[cuda12]==0.10.1" ``` All subsequent commands use `~/.conda/envs/xla_repro_clean/bin/python` to guarantee a clean dependency stack. ### Step 1 — minimal repro script Save as `/tmp/repro.py` — Megatron-style TP-MLP scan that produces a `while` loop containing an `all-reduce`: ```python import jax import jax.numpy as jnp import numpy as np from jax.sharding import Mesh, PartitionSpec as P, NamedSharding from jax import lax assert jax.device_count() >= 8, f"need 8 devices, got {jax.device_count()}" devices = jax.devices()[:8] mesh = Mesh(np.array(devices).reshape(8), ("tp",)) D, D_FF, N_LAYERS, B, S = 4096, 16384, 12, 4, 128 DTYPE = jnp.bfloat16 x = jnp.ones((B, S, D), dtype=DTYPE) w_ups = jnp.ones((N_LAYERS, D, D_FF), dtype=DTYPE) w_downs = jnp.ones((N_LAYERS, D_FF, D), dtype=DTYPE) x_sh = NamedSharding(mesh, P(None, None, None)) wu_sh = NamedSharding(mesh, P(None, None, "tp")) # D_FF sharded on output wd_sh = NamedSharding(mesh, P(None, "tp", None)) # D_FF sharded on contracting x = jax.device_put(x, x_sh) w_ups = jax.device_put(w_ups, wu_sh) w_downs = jax.device_put(w_downs, wd_sh) def scan_body(x, ws): w_up, w_down = ws h = jax.nn.gelu(x @ w_up) @ w_down # produces all-reduce on contracting dim return h, None def model(x, w_ups, w_downs): y, _ = lax.scan(scan_body, x, (w_ups, w_downs)) return y f = jax.jit(model, in_shardings=(x_sh, wu_sh, wd_sh), out_shardings=x_sh) hlo = f.lower(x, w_ups, w_downs).compile().as_text() print(f"all-reduce: {hlo.count('all-reduce')}, while: {hlo.count('while(')}") y = f(x, w_ups, w_downs).block_until_ready() print("Done.", y.shape) ``` ### Step 2 — verify the compiled HLO has the right structure (`while` + `all-reduce`) ```bash ~/.conda/envs/xla_repro_clean/bin/python /tmp/repro.py ``` Output: ``` all-reduce: 8, while: 1 Done. (4, 128, 4096) ``` ### Step 3 — verify no pipeliner pass stages are dumped ```bash rm -rf /tmp/xla_dump && mkdir /tmp/xla_dump XLA_FLAGS="--xla_dump_to=/tmp/xla_dump --xla_dump_hlo_pass_re=.* \ --xla_gpu_enable_pipelined_all_reduce=true \ --xla_gpu_enable_pipelined_all_gather=true \ --xla_gpu_enable_pipelined_reduce_scatter=true" \ ~/.conda/envs/xla_repro_clean/bin/python /tmp/repro.py ls /tmp/xla_dump/ | grep -E 'collective-pipeliner-(forward|backward)' | wc -l # Expected: > 0 ; Actual: 0 ``` ### Step 4 — verify the `CollectivePipeliner` class is not linked into any `.so` ```bash find ~/.conda/envs/xla_repro_clean/lib/python3.11/site-packages -name "*.so" 2>/dev/null \ | while read f; do nm -D "$f" 2>/dev/null | grep -q CollectivePipeliner && echo "$f" done # Expected: at least one .so listed ; Actual: no output ``` ### Step 5 — verify the umbrella + post-layout flags are unrecognized ```bash XLA_FLAGS="--xla_gpu_enable_pipelined_collectives=true" \ ~/.conda/envs/xla_repro_clean/bin/python /tmp/repro.py 2>&1 | head -1 # Output: # F0612 08:30:24.243982 ...parse_flags_from_env.cc:234] Unknown flag in XLA_FLAGS: --xla_gpu_enable_pipelined_collectives=true XLA_FLAGS="--xla_gpu_run_post_layout_collective_pipeliner=true" \ ~/.conda/envs/xla_repro_clean/bin/python /tmp/repro.py 2>&1 | head -1 # Output: # F0612 08:36:33.970901 ...parse_flags_from_env.cc:234] Unknown flag in XLA_FLAGS: --xla_gpu_run_post_layout_collective_pipeliner=true ``` ### Single-GPU / no-GPU verification path The headline finding (pass missing from binary) can be verified by anyone without GPU access: ```bash conda create -n xla_repro_nogpu python=3.11 -y ~/.conda/envs/xla_repro_nogpu/bin/pip install "jax[cuda12]==0.10.1" SO=$(~/.conda/envs/xla_repro_nogpu/bin/python -c \ "import jaxlib, os; print(os.path.join(os.path.dirname(jaxlib.__file__), 'libjax_common.so'))") nm -D "$SO" | grep CollectivePipeliner # Expected: at least one symbol ; Actual: no output ``` (The CUDA12 wheel installs cleanly on hosts without GPUs; only Steps 2-3 require actual GPUs.) ## Expected vs Actual | Behavior | Expected | Actual | |---|---|---| | `collective-pipeliner-forward` stage appears in pass dumps | yes | no | | `collective-pipeliner-backward` stage appears in pass dumps | yes | no | | `CollectivePipeliner` class linked into binary | yes | no | | `--xla_gpu_enable_pipelined_collectives=true` accepted | yes (umbrella in newer XLA) | "Unknown flag" | | `--xla_gpu_enable_pipelined_all_reduce=true` triggers pipeliner | yes | accepted, no effect | ## Cross-check on older JAX Installed `jax==0.4.38` in a separate fresh env to verify the pass is supposed to exist. Confirmed `CollectivePipeliner` symbols **are** present in `jax_plugins/xla_cuda12/xla_cuda_plugin.so` in that version: ``` $ strings xla_cuda_plugin.so | grep -E "collective-pipeliner|CollectivePipeliner" collective-pipeliner-backward collective-pipeliner-forward collective-pipeliner-forwardsink N3xla19CollectivePipelinerE external/xla/xla/service/collective_pipeliner.cc ``` So the pass exists upstream and was present in earlier JAX versions. It appears to have been dropped from the build between 0.4.38 and 0.10.1. ## Questions 1. Is the `CollectivePipeliner` pass intentionally removed from the JAX-bundled XLA GPU build in 0.10.1? If so: - What is the recommended path for cross-iteration collective overlap on GPU? - Has the functionality been consolidated into another pass (e.g. latency-hiding-scheduler)? If so, please document. - Should the dead flags (`--xla_gpu_enable_pipelined_all_reduce` etc.) be removed or upgraded to errors to avoid confusion? 2. If unintentional, what build flag is needed to re-include the pass? `--xla_gpu_run_post_layout_collective_pipeliner` appears to be a proto field but not a CLI flag in this build. 3. Is the recommended way to get pipelining on GPU now via shardy/`shard_map` + manual primitives, or some other mechanism?
0 条评论