Skip to content

refactor: version PyTorch directory structure for multi-version support#6091

Open
Eren-Jeager123 wants to merge 22 commits into
mainfrom
refactor/pytorch-versioned-structure
Open

refactor: version PyTorch directory structure for multi-version support#6091
Eren-Jeager123 wants to merge 22 commits into
mainfrom
refactor/pytorch-versioned-structure

Conversation

@Eren-Jeager123
Copy link
Copy Markdown
Contributor

@Eren-Jeager123 Eren-Jeager123 commented May 12, 2026

Summary

Restructure PyTorch to support maintaining multiple versions concurrently (1-year support window). Versioned directories isolate each version's build artifacts. Workflows are version-agnostic — no new workflow files needed when adding a future version.

1. Versioned directory structure

docker/pytorch/2.11/
├── Dockerfile.cuda
├── Dockerfile.cpu
├── versions-cuda.env
├── versions-cpu.env
├── cuda/pyproject.toml
├── cuda/uv.lock
├── cpu/pyproject.toml
└── cpu/uv.lock

Config files versioned by name: pytorch-2.11-ec2-cuda.yml, etc.
Shared scripts (scripts/pytorch/) and tests (test/pytorch/) stay in place.

2. PR workflows — configs matrix (no output clobbering)

gatekeeper → detect-versions → build-images / unit-test / sanity / security / telemetry / single-gpu / efa

Key design: detect-versions reads each version's config YAML and outputs a rich configs matrix where each entry carries all metadata fields. All downstream jobs iterate over this matrix and pass matrix.* fields directly to reusable workflows as individual inputs.

This solves the GitHub Actions limitation where matrix job outputs only expose the last-finishing leg's values — metadata travels with the matrix, not as job outputs. Reusable workflows are completely unmodified.

# detect-versions outputs:
#   configs: [{"version":"2.11", "framework":"pytorch_runtime", "framework_version":"2.11.0", ...}]

sanity-test:
  strategy:
    matrix:
      include: ${{ fromJson(needs.detect-versions.outputs.configs) }}
  uses: ./.github/workflows/reusable-sanity-tests.yml
  with:
    framework: ${{ matrix.framework }}
    framework-version: ${{ matrix.framework_version }}
    ...

Version detection combines both path patterns in a single grep to avoid missing versions:

git diff --name-only origin/main...HEAD \
  | grep -oP '(?:docker/pytorch/|pytorch-)\K[0-9]+\.[0-9]+'

3. Autorelease workflows — simplified dispatch

workflow_dispatch:
  inputs:
    version:
      description: "PyTorch version to release (e.g., 2.11)"

Human dispatch now accepts just the version string (e.g., 2.11) instead of a full config file path. Cron schedules map version → config file internally.

4. Dockerfile ARG for version paths

Dockerfiles use ARG DLC_PYTORCH_VERSION=2.11 instead of hardcoding:

COPY docker/pytorch/${DLC_PYTORCH_VERSION}/cuda/pyproject.toml /tmp/build/

5. Additional improvements

  • Unit tests extracted into separate job (parallel with sanity/security/telemetry)
  • upload_cached_wheels.sh accepts Dockerfile path as parameter
  • test_versions.py reads DLC_PYTORCH_VERSION env var to locate versioned env files
  • No changes to reusable workflows — zero blast radius to other frameworks

Adding PyTorch 2.12

  1. Copy docker/pytorch/2.11/docker/pytorch/2.12/, update version pins
  2. Create 4 config files (pytorch-2.12-{ec2,sagemaker}-{cuda,cpu}.yml)
  3. Autorelease: add one cron line + one case entry per workflow
  4. PR workflows: auto-detect from diff — no changes needed

Test plan

  • PR workflow detects version from docker/pytorch/2.11/** and pytorch-2.11-* changes
  • Matrix builds all changed versions in parallel (CI tags include version — no collision)
  • Configs matrix passes correct metadata per version to reusable workflows
  • Unit tests run as separate job per version
  • Reusable workflows (vLLM, SGLang, Ray, Base) work unchanged
  • Autorelease workflow_dispatch with version input works
  • DLC_PYTORCH_VERSION build-arg resolves correctly in Dockerfile COPY paths

Move all PyTorch build artifacts under docker/pytorch/2.11/ to support
maintaining multiple PyTorch versions concurrently (1-year support
window per version).

Structure change:
  docker/pytorch/Dockerfile.cuda      → docker/pytorch/2.11/Dockerfile.cuda
  docker/pytorch/versions-cuda.env    → docker/pytorch/2.11/versions-cuda.env
  docker/pytorch/cuda/pyproject.toml  → docker/pytorch/2.11/cuda/pyproject.toml
  (same pattern for CPU)

  .github/config/image/pytorch-ec2-cuda.yml → pytorch-2.11-ec2-cuda.yml
  .github/workflows/pr-pytorch-ec2-cuda.yml → pr-pytorch-2.11-ec2-cuda.yml
  (same pattern for all 4 variants × PR + autorelease)

Adding PyTorch 2.12 when it releases means creating docker/pytorch/2.12/,
new configs, and new workflows — without touching 2.11.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eren-Jeager123 and others added 6 commits May 12, 2026 23:17
The versions env file moved from docker/pytorch/versions-cuda.env to
docker/pytorch/2.11/versions-cuda.env. Use glob to find the file under
any version directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test_versions.py now requires DLC_PYTORCH_VERSION env var (e.g., "2.11")
to locate the correct versions-{cuda,cpu}.env under the versioned
directory. All 4 PR workflows pass it via docker run -e.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same fix as the PR workflows — autorelease workflows also run unit
tests that need the versioned env file path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only unit tests (test_versions.py) need this env var. Removed from
single-GPU and multi-GPU test containers in EC2 CUDA workflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The script had a hardcoded docker/pytorch/Dockerfile path that no
longer exists after the versioned restructure. Accept the Dockerfile
path as a parameter and update all 3 callers to pass it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR workflows detect the PyTorch version from changed file paths
(docker/pytorch/X.Y/ or pytorch-X.Y-*.yml), falling back to
LATEST_PYTORCH_VERSION env var for shared file changes.

Autorelease workflows use multi-cron scheduling with a case mapping
from cron expression to version. Staggered 10 min apart per version.
Also supports workflow_dispatch with an explicit pytorch-version input.

Adding PyTorch 2.12: create docker/pytorch/2.12/ + config files, then
add one cron line + one case entry per autorelease workflow. No new
workflow files needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
version: ${{ steps.version.outputs.version }}
config-file: ${{ steps.version.outputs.config-file }}
steps:
- name: Determine PyTorch version
Copy link
Copy Markdown
Member

@sirutBuasai sirutBuasai May 19, 2026

Choose a reason for hiding this comment

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

Why do we need to determine pytorch version and then construct a config file name based off that? Why dont we simply use the config file as input, or simple do

case "$CRON" in
    "00 17 * * 1,3") CONF_FILE="<path_to_pt2.11_cpu>" ;;
    # "10 17 * * 1,3") CONF_FILE="<path_to_pt2.12_cpu>"  ;;
    *) echo "::error::Unknown cron: $CRON"; exit 1 ;;
esac

Then any usage of the actual Pytorch version field can just be derived from the config file using the output from load_config job.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point I forgot we can use the one from load_config

CONFIG_FILE: ".github/config/image/pytorch-ec2-cuda.yml"

jobs:
determine-version:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

CONFIG_FILE: ".github/config/image/pytorch-sagemaker-cpu.yml"

jobs:
determine-version:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

CONFIG_FILE: ".github/config/image/pytorch-sagemaker-cuda.yml"

jobs:
determine-version:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

run: |
VERSION=$(git diff --name-only origin/main...HEAD \
| grep -oP 'docker/pytorch/\K[0-9]+\.[0-9]+' \
| sort -u | head -1)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Multi-version PR silently tests only one version. so if we make a change for both PT 2.11 and PT 2.12, we would expect both jobs to run. The current logic only run one PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought that it's rare to change more than 1 version each time previously; Ya however I will change this.

# ============================================================
load-config:
needs: [gatekeeper]
needs: [gatekeeper, check-changes]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we should be able to parallelize the check-changes with load-config. Serializing this slows down the PR a little bit due to these two jobs being extremely small but the job startup each takes 30s

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Take a look at https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstrategymatrix matrix job submission. This would allow us to define a list of configurations that the workflow can in sets in parallel of each other.

https://github.com/aws/deep-learning-containers/blob/main/.github/workflows/dispatch-vllm-benchmark.yml is also another good example that we have that uses matrix run mechanism

- ".github/config/image/pytorch-ec2-cpu.yml"
- ".github/config/image/pytorch-*-ec2-cpu.yml"
- ".github/workflows/pr-pytorch-ec2-cpu.yml"
- "docker/pytorch/**"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is too broad, this means that a change in cuda dockerfile will trigger cpu as well

source docker/pytorch/versions-cpu.env
VERSION="${{ needs.check-changes.outputs.pytorch-version }}"
source docker/pytorch/${VERSION}/versions-cpu.env
CI_IMAGE_URI="${{ vars.CI_AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/ci:pytorch-cpu-runtime-pr-${{ github.event.pull_request.number }}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With matrix running, we'll need to add some kind of version string to the ci image. This is because if PT 2.11 and PT 2.12 runs at the same time, we run into naming collision

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

Comment thread docker/pytorch/2.11/Dockerfile.cuda Outdated
ENV PATH="/opt/venv/bin:${PATH}"

COPY docker/pytorch/cuda/pyproject.toml docker/pytorch/cuda/uv.lock /tmp/build/
COPY docker/pytorch/2.11/cuda/pyproject.toml docker/pytorch/2.11/cuda/uv.lock /tmp/build/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you're going to have to declare the version across the file, we should use ARG PYTORCH_VERSION=2.11 then use COPY .../${PYTORCH_VERSION}/...

Eren-Jeager123 and others added 9 commits May 19, 2026 19:56
Per team feedback: remove the redundant version output from the
determine-config job. Map cron directly to config file path. Derive
the docker directory version from load-config's framework-version
output (cut major.minor from "2.11.0" → "2.11").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses team feedback:
- Multi-version PRs now build ALL changed versions in parallel (not
  just the first detected one)
- Removed separate load-config job — config parsing inlined into
  build-images and detect-versions (eliminates 30s serialization)
- Uses strategy.matrix with fail-fast: false for parallel builds

Structure:
  gatekeeper → detect-versions → build-images (matrix) → test jobs

Only the latest version runs the full test suite (sanity, security,
telemetry, single-gpu). All versions validate that the build compiles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s outputs

Per team feedback: detect-versions should only detect versions and
path changes. Config values are now output by the build-images matrix
job (which already loads config per version). Downstream test jobs
reference build-images outputs instead.

Also removes the latest-version guard on unit tests — all matrix
versions now run unit tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oded paths

Per team feedback: Dockerfiles now use ARG DLC_PYTORCH_VERSION=2.11
for COPY paths instead of hardcoding "docker/pytorch/2.11/..."
throughout. Workflows pass --build-arg DLC_PYTORCH_VERSION to ensure
the value matches the matrix version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CUDA workflows only trigger on CUDA-related paths:
  docker/pytorch/*/Dockerfile.cuda, docker/pytorch/*/cuda/**, versions-cuda.env

CPU workflows only trigger on CPU-related paths:
  docker/pytorch/*/Dockerfile.cpu, docker/pytorch/*/cpu/**, versions-cpu.env

Previously all 4 workflows used docker/pytorch/** which meant a CUDA
Dockerfile change triggered the CPU workflow (and vice versa).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Docker requires ARGs to be re-declared after each FROM — global ARGs
are only available in FROM lines, not in stage instructions like COPY.
Without the re-declaration, DLC_PYTORCH_VERSION resolves to empty
string causing "not found" errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All test jobs (sanity, security, telemetry, single-gpu, efa, sagemaker)
now matrix over detected versions. Each version gets its own test run
with a constructed image-uri based on the version-specific CI tag.

GitHub Actions supports strategy.matrix on reusable workflow calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The load-config action installs yq but our inlined config step didn't.
yq is not available by default on CodeBuild runners, causing all config
outputs (framework, container-type, etc.) to be empty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eren-Jeager123 and others added 5 commits May 19, 2026 22:50
When a workflow is cancelled mid-EFA-test, the finally block may not
execute, leaking p4d.24xlarge instances and EIPs. This adds a
cleanup_stale_efa_instances() call at the start of each test run that
terminates instances tagged "CI-CD EFA efa-test" older than 4 hours
and releases orphaned EIPs.

Prevents: AddressLimitExceeded errors from accumulated leaked EIPs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs LD_LIBRARY_PATH, ofi-nccl lib presence, all_reduce_perf binary,
fi_info output, NCCL lib path, and full NCCL log on failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Solve the GitHub Actions matrix output clobbering problem without
modifying any reusable workflow. The detect-versions job now reads
each version's config YAML and outputs a rich configs matrix where
each entry carries all metadata fields (framework, python-version,
cuda-version, etc.).

Downstream test jobs iterate over this configs matrix and pass
matrix.* fields directly as individual inputs to the unmodified
reusable workflows. Each matrix leg carries its own correct metadata
— no dependency on build-images outputs.

Also extract unit-test from build-images into its own parallel job.
@Eren-Jeager123 Eren-Jeager123 force-pushed the refactor/pytorch-versioned-structure branch 6 times, most recently from 12bbb31 to 421e17c Compare May 22, 2026 16:14
Solve the matrix output clobbering problem without modifying any
reusable workflow:

- detect-versions: combine both path patterns (docker/pytorch/X.Y and
  pytorch-X.Y-*) in a single grep to avoid missing versions when both
  patterns are present in the same PR. Read each version's config YAML
  and output a rich configs matrix with all metadata fields per entry.

- Downstream test jobs (sanity, security, telemetry) iterate over the
  configs matrix and pass matrix.* fields directly as individual inputs
  to the unmodified reusable workflows.

- Extract unit-test from build-images into its own parallel job.

- Autorelease workflows: accept `version` (e.g., 2.11) instead of
  full config-file path for simpler manual dispatch.
@Eren-Jeager123 Eren-Jeager123 force-pushed the refactor/pytorch-versioned-structure branch from 421e17c to 668831f Compare May 22, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants