Skip to content

CI: dual-version PyTorch 2.9/2.12 containerized test pipeline - #55

Merged
jiahy0825 merged 45 commits into
mainfrom
ci/pytorch-2.12-dual-test
Aug 10, 2026
Merged

CI: dual-version PyTorch 2.9/2.12 containerized test pipeline#55
jiahy0825 merged 45 commits into
mainfrom
ci/pytorch-2.12-dual-test

Conversation

@cennn

@cennn cennn commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Motivation

MagiCompiler was previously validated only on PyTorch 2.9. PR #46 added PT 2.12 compatibility but lacked a continuous verification mechanism. This PR adds three things: containerized CI pipelines, a runtime compatibility layer, and version-aware tests.

Key Changes

1. Containerized dual-version CI

On PR and merge events, build Docker images and run the full test suite against both nvcr.io/nvidia/pytorch:25.10-py3 (PT 2.9) and 26.05-py3 (PT 2.12). A gate job ensures both versions pass before merge. CI style follows athena's containerized pipeline pattern.

2. AOT compilation compatibility layer

PT 2.12 renamed the AOT artifact class from CompileArtifacts to AOTCompiledFunction with different serialization APIs. New _aot_compat.py provides unified load_aot_artifacts() / save_aot_artifacts() / extract_aot_artifacts_from_fn() that branch on IS_PT_212 internally. TORCH_VERSION and IS_PT_212 are centralized in envs.py, eliminating 7 duplicate definitions across the codebase.

3. Version-aware tests

Several upstream behavioral changes in PT 2.12 require version-specific test expectations:

  • ND tiling: PT 2.12 Inductor generates program_id(2) references with max_tiles=3 on a 2D launch grid → fixed to max_tiles=2; perf threshold split by version (PT 2.9: 1.20x / PT 2.12: 1.05x)
  • filelock race: filelock>=3.19 calls unlink() before flock(LOCK_UN) on Docker overlayfs, raising FileNotFoundError → pin filelock<3.19
  • Symbolic unification: is_compiling() guard insufficient under PT 2.12 + magi_compile two-level compile → @torch.compiler.disable() wrapper added, with _pt29/_pt212 test variants and a negative test
  • FX serialization: PT 2.12 natively fixes view tensor fake_mode restoration and third-party op pickling → tests split into version variants
  • Unbacked symbol guard: PT 2.12 no longer raises guard errors on view(-1) with unbacked symbols → tests split accordingly

Other

  • Dockerfile simplified from ~140-line production build to ~35-line CI build (BASE_IMAGE parameterized, removed Flash Attention / CUTLASS CMake compilation)
  • Removed triton from requirements.txt and torchvision from requirements-test.txt (tightly coupled to torch version, provided by base image)
  • Docs (README / install.md / install.po) updated to list both PT 2.9.x and 2.12.x as supported

Fatemanx and others added 26 commits August 6, 2026 23:14
Introduce _aot_compat.py shim that abstracts away the AOT API
differences between PyTorch 2.9 (CompileArtifacts) and 2.12
(AOTCompiledFunction), so load/save/extract work on both versions.

Update magi_compiler_base.py to use the shim instead of calling
version-specific APIs directly.

Add pytest.mark.skipif decorators to tests that document
version-specific upstream behavior (skipped on 2.12 where
_OpPickleData no longer raises for unknown ops, skipped on 2.9
where upstream fixes landed in 2.12 only).

Tested in isolated containers:
  - nvcr.io/nvidia/pytorch:25.10-py3 (2.9): 61 passed, 3 skipped
  - nvcr.io/nvidia/pytorch:26.05-py3 (2.12): 61 passed, 3 skipped
Migrate from bare-metal runner CI to container-based testing,
following athena's _ci_pipeline.yml pattern for style consistency.

Changes:
- Dockerfile: parameterize BASE_IMAGE (default 25.10-py3), install
  MagiCompiler + test deps inside image, skip pinned triton to
  preserve each base image's built-in version
- _ci_pipeline.yml: reusable build+test workflow (build Docker image,
  push to CCR, run tests inside container)
- integration_test.yml: parallel matrix testing on PyTorch 2.9
  (nvcr.io/nvidia/pytorch:25.10-py3) and 2.12 (26.05-py3)
- merge_test.yml: post-merge testing on both versions
- Add .dockerignore to exclude .git, __pycache__, build artifacts
- Replace --no-deps with proper dependency filtering: install all
  test deps except torchvision (requires torch>=2.12) and torchtitan
  (installed separately with --no-deps)
- Add graphviz system package required by tests
The self-hosted runner is registered with the label magi-compiler
(matching the original integration_test.yml), not magi_compiler_ci.
Update README.md badge and requirements, install.md, and zh_CN
locale to reflect dual PyTorch version support.
Move load_aot_artifacts, save_aot_artifacts, extract_aot_artifacts_from_fn
imports from inline (inside methods) to the top-level import block.
- test_compile_artifacts: split 4 tests into 8 (pt29/pt212 pairs) with
  version-appropriate assertions and skipif decorators
- test_piecewise_deferred_assert_scope: restore pt29 NameError expectation
  as xfail (incidentally fixed by later commits) + pt212 passes variant
- test_unbacked_symbol_guard: split legacy_view test into pt29 (expects
  guard error) and pt212 (compiles successfully)
- test_inductor_cache_reuse: skip entire class — expected autograd cache
  counters differ between PT 2.9 and 2.12, needs recalibration
The CI passes proxy via --build-arg but the Dockerfile read it from
--mount=type=secret, causing git fetch to hang without proxy access.
Switch to ARG http_proxy/https_proxy pattern matching athena.
… ffmpeg

Removed:
- flash-attention 3 build from source (~5min compile, zero test references)
- CUTLASS cmake build (headers-only clone is sufficient for EVT codegen)
- ffmpeg (no test references)
- build-essential, cmake, ninja-build system packages (not needed at runtime)
- syntax=docker/dockerfile:1.7 directive (no longer using --mount=type=secret)

Kept:
- CUTLASS headers clone (used by EVT-fusion codegen path)
- graphviz (required by depyf/test visualization)
- Merge all pip installs (upgrade + core deps + test deps) into one
  RUN before COPY source, so deps are cached on code-only changes
- Merge apt-get + CUTLASS clone into one system layer
- Only COPY . and pip install -e . re-run on code changes
The GitHub Actions container: directive fails on the magi-compiler
self-hosted runner (temp script mount issue). Switch to explicit
docker run which gives full control over mounts and avoids the
/__w/_temp path mapping problem.
The magi-compiler runner fails with container: (Docker-in-Docker temp
script mount issue). Switch test job to athena-new runner which is
known to support container: jobs correctly. Build job stays on
magi-compiler.
torchtitan==0.2.0 was installed with --no-deps to avoid pulling a newer
torch, but its transitive dependency tyro was missing, causing
test_fsdp_overlap_e2e tests to fail with ModuleNotFoundError.
athena-new runner is dedicated to athena CI and will not pick up
MagiCompiler jobs. Both build and test jobs now use magi-compiler.
- NCCL_NVLS_ENABLE=0: disable NVLink SHARP to avoid Fabric Manager
  errors in container environment (CUDA error 802)
- --shm-size=2g: ensure sufficient shared memory for multi-rank tests
- Prepare /tmp/torchinductor_root cache dir before tests to prevent
  filelock race conditions in PyTorch Inductor CPU vec ISA checks
The mkdir for /tmp/torchinductor_root was added speculatively to fix
FileNotFoundError in cpu_vec_isa.check_build(), but the issue is likely
runner-specific (only appeared on magi-compiler, not athena-new) and
this step does not address the root cause.

Keep NCCL_NVLS_ENABLE=0 which is the correct fix for NCCL 2.30+ in
containers without full Fabric Manager access.
ND tiling: PT 2.12 Inductor generates invalid 3D-grid reduction kernels
when max_tiles=3 — program_id(2) refers to a grid dimension that does
not exist in the launch config. Root cause is Inductor codegen, not
Triton (Triton 3.7 handles program_id(2) correctly when grid is 3D).
Cap max_tiles at 2 on PT >= 2.12; keep 3 on PT 2.9 where it works.
Verified: magi_compile + ND tiling workaround passes on both PT versions.

Conv perf: lower threshold from 1.20x to 1.05x to reduce CI flakiness
from GPU clock/thermal variance across runs.
Add three tests to test_nd_tiling_workaround.py:

- test_max_tiles_3_crashes_on_pt212: reproduces the PT 2.12 Inductor bug
  where max_tiles=3 + tile_reductions=True generates a Triton kernel
  referencing program_id(2) on a 2D launch grid (assert the error)
- test_max_tiles_2_compiles_successfully: verifies the fix (max_tiles=2)
  compiles correctly on all PT versions
- test_nd_tiling_pass_uses_safe_max_tiles_on_pt212: verifies the pass
  itself picks max_tiles=2 on PT >= 2.12

Also update _assert_injected to be version-aware (expect max_tiles=2 on
PT 2.12 instead of 3).
…2.12

- Add Prepare environment step (mkdir -p /tmp/torchinductor_root) to CI
  test job, fixing 6 InductorError: FileNotFoundError on early tests
- Skip test_nd_tiling_workaround_speedup on PT >= 2.12 because max_tiles=2
  (required to avoid Inductor codegen bug) reduces tiling granularity
PT 2.12 Inductor generates different scheduler node counts across ranks
for the same all_gather graph, causing cross-rank key-set mismatch and
analytical fallback (ratio=0.34 vs 0.5-2.0 tolerance).
PT 2.12's cpu_vec_isa.check_build() spawns a subprocess to dlopen a
test .so, protected by a filelock under /tmp/torchinductor_root.
In fresh containers the probe can fail with FileNotFoundError during
lock release — a known upstream issue (pytorch/pytorch#183515,
pytorch/pytorch#134667).

The official workaround is TORCHINDUCTOR_VEC_ISA_OK=1, which tells
Inductor to assume AVX support without running the probe at all.
This replaces the previous mkdir + Python warmup hack.
@cennn
cennn force-pushed the ci/pytorch-2.12-dual-test branch from e4c2a37 to 1b8fbd2 Compare August 9, 2026 06:55
cenn added 3 commits August 9, 2026 18:46
TORCHINDUCTOR_VEC_ISA_OK=1 only short-circuits VecISA.__bool__impl,
but VecAVX512 subclass __bool__ still calls check_build() for BF16
extension, hitting the same filelock race on /tmp.

Two-pronged fix:
- TORCHINDUCTOR_CACHE_DIR=/app/.inductor_cache (stable, not /tmp tmpfs)
- Pre-test warmup step runs pick_vec_isa() to populate cache once
Instead of env-var workarounds (TORCHINDUCTOR_CACHE_DIR, VEC_ISA_OK),
cache check_build() results in-memory via conftest.py — equivalent to
upstream PR #181617's .load_ok markers which PT 2.12 lacks.

- Patch VecISA.check_build with dict-based memoization
- Warm up all ISA probes once at collection time via pick_vec_isa()
- Remove TORCHINDUCTOR_VEC_ISA_OK and TORCHINDUCTOR_CACHE_DIR from CI
- Remove the separate warmup step (conftest handles it in-process)
…r_cache/

Root cause: the autouse cleanup_cache fixture called shutil.rmtree on the
entire cache_root_dir (~/.cache/magi_compiler/), which includes Inductor's
inductor_cache/ subdirectory.  Inductor's async compiler holds FileLock
objects inside that directory; deleting the lock file while still held
causes FileNotFoundError on fcntl.flock(fd, LOCK_UN) in Linux overlayfs
containers (the exact CI failure on PT 2.12).

Fix: only delete MagiCompiler-owned subdirs (magi_cache/, magi_depyf/),
leaving inductor_cache/ untouched so async lock release succeeds.

Also adds test_cleanup_filelock_race.py with minimal reproduction and fix
verification.
cenn and others added 15 commits August 9, 2026 21:24
Add copyright header, remove unused import, fix black formatting.
Root cause: Docker overlayfs can cause fcntl.flock(fd, LOCK_UN) to raise
FileNotFoundError on unlinked inodes.  This affects every Inductor
filelock path (ISA probe via cpu_vec_isa.check_build, code cache via
codecache.load_async, async compile).

Fix: monkey-patch filelock._unix.UnixFileLock._release in conftest.py
to catch FileNotFoundError and safely close the fd.  This is more robust
than previous attempts (ISA caching, cache dir relocation) because it
covers ALL filelock code paths.
The selective cleanup (preserving inductor_cache/) caused matmul epilogue
fusion tests to fail due to stale Inductor cache state between tests.

Restore the original shutil.rmtree(cache_root_dir) behavior to ensure
clean state per test, while keeping the filelock._release patch to
handle the overlayfs FileNotFoundError that rmtree triggers.

Also fix test_cleanup_filelock_race.py cleanup to use shutil.rmtree
instead of os.unlink (lock file may already be gone on overlayfs).
Add links to filelock#494, #495, #513 and pytorch#134384 explaining
why _release() raises FileNotFoundError on overlayfs and why the
monkey-patch is needed (upstream fixed _acquire but not _release).
filelock >= 3.19 calls unlink() in _release() before flock(LOCK_UN),
which raises FileNotFoundError on Docker overlayfs (the CI container
filesystem).  Versions < 3.19 explicitly preserve lock files on release.

This replaces the previous monkey-patch approach with a clean dependency
pin — no runtime code changes needed.

Ref: tox-dev/filelock#494
Move TORCH_VERSION / IS_PT_212 from scattered per-file definitions
into magi_compiler.utils.envs, eliminating 7 duplicate copies.

Also restore test_symbolic_unification.py to original is_compiling()
guard form and split bad_order cache check into _pt29 (u0/u1/u2)
and _pt212 (s-prefixed symbols) variants, verified via experiment.
PT 2.12 + magi_compile two-level compile: is_compiling() guard
is insufficient to prevent symbolic unification when first call
has zero-token modalities. Add ModalityDispatcherMockV2Fixed
using @torch.compiler.disable() wrapper.

- test_bad_order_pt29: OuterModel (is_compiling guard) + u-symbol cache check
- test_bad_order_pt212: OuterModelFixed (@torch.compiler.disable)
- test_is_compiling_guard_insufficient_pt212: negative test asserting
  the is_compiling() approach fails on PT 2.12

Verified on both PT 2.9 (cenn-3) and PT 2.12 (dev__260804).
PT 2.12 torch.compile baseline is faster, narrowing the channels-last
speedup from ~1.2x to ~1.1x. Use IS_PT_212 to keep the original 1.20
threshold on PT 2.9 and relax to 1.05 on PT 2.12.

Measured on H100 (3 runs each):
  PT 2.9:  1.23x, 1.24x, 1.27x  → threshold 1.20 (unchanged)
  PT 2.12: 1.09x, 1.10x, 1.11x  → threshold 1.05
Revert training autograd_miss to 1 (the correct PT 2.9 value).
Replace unconditional @pytest.mark.skip with skipif(IS_PT_212)
so PT 2.9 runs the test normally.
Document the three behavioral differences between PyTorch versions
in the module docstring and at each split point:
1. View tensor bad reducer (Patch A)
2. Third-party op serialization (Patch B+C)
3. Unknown op handling (Patch C)
torchtitan==0.2.0 dependencies (including torchdata) resolve cleanly
on both PT 2.9 and 2.12 NVIDIA base images — verified with dry-run.
No need for --no-deps + manual tyro install.
triton and torchvision are tightly coupled to the torch version and
must be pre-installed (e.g. via NVIDIA NGC base image). Pinning them
in requirements causes version conflicts on dual-version CI:
- triton==3.7.1 replaces NVIDIA-patched builds
- torchvision==0.27.1 pulls in torch==2.12.1, destroying PT 2.9 env

Dockerfile now does a straightforward pip install of both requirements
files with no grep filters.
@cennn cennn changed the title Support PyTorch 2.9/2.12 dual-version AOT compatibility [WIP] CI: dual-version PyTorch 2.9/2.12 containerized test pipeline Aug 10, 2026
The test only verifies that magi_compile training runs and updates
parameters — no need for a 1024-hidden, 4096-seqlen model.
Reduce to tiny config (hidden=64, seq=32, batch=2) to use <100 MiB
instead of multiple GiB.

@jiahy0825 jiahy0825 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jiahy0825 jiahy0825 changed the title [WIP] CI: dual-version PyTorch 2.9/2.12 containerized test pipeline CI: dual-version PyTorch 2.9/2.12 containerized test pipeline Aug 10, 2026
@jiahy0825
jiahy0825 merged commit b5ba173 into main Aug 10, 2026
6 checks passed
@jiahy0825
jiahy0825 deleted the ci/pytorch-2.12-dual-test branch August 10, 2026 07:52
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.

3 participants