Skip to content

Commit 6513455

Browse files
authored
ci: run smoke tests across a python x torch version matrix (#32)
* ci: run smoke tests across a python x torch version matrix Smoke tests previously ran on one implicit torch version (whatever pyproject.toml's unpinned bound resolved to) across all supported python versions. Add torch_2_8/2_9/2_10/2_11 dependency groups (build-matched torchao/torchvision per pytorch/ao issue 2919) and a matrix.torch_group axis on the test-smoke CI job so each of the 4 torch minor versions is smoke tested independently, still covering all 3 python versions per job via the existing nox session. lowest_tested_torch/highest_tested_torch are renamed to torch_2_8/ torch_2_11 for a consistent naming scheme; the linux-tests/macos-tests full-suite jobs and their Makefile targets are unchanged. * fix: allow digits in AVAILABLE_GROUPS regex for torch_2_* group names setup_env.sh's group discovery used grep -E '^[a-z_-]+ = \[', which excludes digits and made torch_2_8/2_9/2_10/2_11 invisible to validate_groups — breaking test-highest-pytorch/test-lowest-pytorch with 'Unknown dependency group', caught by the fork CI run. * feat: make TORCH_GROUP the single source of truth for torch pinning setup_env.sh now reads TORCH_GROUP directly and folds it into every sync it runs, so make env/test/test-fast/docs/etc. all pin torch instead of floating on whatever pyproject.toml's plain bound resolves to. test-highest-pytorch/test-lowest-pytorch/env-highest-torch lock their group via an inline `export TORCH_GROUP=... &&` at the top of the recipe (rather than a hardcoded --with-torch_2_X flag), so their name stays a promise regardless of a command-line override — verified that GNU Make's own `override` directive achieves the same lock but trips up the mbake Makefile formatter's duplicate-target detector, which doesn't recognize that syntax. --with-torch_2_X stays as a guarded alternate path for direct, non-Make invocations of setup_env.sh; a guard now errors clearly if it disagrees with an explicitly-set TORCH_GROUP instead of surfacing a raw uv conflicting-groups resolver error. * refactor: read TORCH_GROUP directly in smoke tests, drop SMOKE_TEST_TORCH_GROUP Now that the Makefile exports TORCH_GROUP unconditionally, the smoke-test-specific SMOKE_TEST_TORCH_GROUP env var was redundant indirection. ci/nox/noxfile.py reads TORCH_GROUP directly, and `make test-smoke` no longer needs to translate the Makefile variable into a differently-named env var for the nox subprocess. * refactor(setup_env): drop bespoke TORCH_GROUP conflict guard The custom bash check that detected TORCH_GROUP disagreeing with an explicit --with-torch_2_X flag duplicated logic uv already provides: `uv sync --group torch_2_11 --group torch_2_8` fails on its own with a clear "Groups ... are incompatible with the conflicts: ..." error and non-zero exit. Simplifies the --all-groups exclusion loop and the plain --group append accordingly, removing the GROUP_REQUESTED/ TORCH_GROUP_ALREADY_SYNCED bookkeeping that only existed to feed it. * refactor(nox): drop unused default for TORCH_GROUP The Makefile always exports TORCH_GROUP before invoking nox, so the Python-side default was dead code for every make test-smoke run; kept only for a bare, non-Make nox invocation which isn't a supported entrypoint. * docs(Makefile): trim TORCH_GROUP comment The expanded rationale duplicated what's now self-evident from reading the three lock-target recipes directly. * fix(Makefile): narrow TORCH_GROUP override to the setup_env.sh call only `export TORCH_GROUP=torch_2_X && ...` set the variable for the whole recipe's shell, which happened to be harmless since nothing else in these three recipes reads TORCH_GROUP, but the intent was always to scope the override to just the one setup_env.sh invocation that needs it. Using `TORCH_GROUP=torch_2_X <command>` as an inline prefix does that precisely: it applies only to the first command in the `&&` chain, reverting to the Makefile's own default/command-line value for everything after. * refactor(Makefile): alias highest/lowest torch groups for maintainability Add HIGHEST_TORCH_GROUP/LOWEST_TORCH_GROUP variables and route the 4 places that hardcoded torch_2_11/torch_2_8 as a stand-in for "the extreme" (TORCH_GROUP's default, env-highest-torch, test-highest- pytorch, test-lowest-pytorch) through them instead. Addresses PR review feedback: bumping the project's torch version bounds now means changing 2 lines instead of manually finding every hardcoded reference. #32 (comment)
1 parent 56c4a36 commit 6513455

5 files changed

Lines changed: 115 additions & 51 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,23 @@ jobs:
3838
run: make check
3939

4040
test-smoke:
41-
name: Linux / make test-smoke
41+
name: Linux / make test-smoke / torch=${{ matrix.torch_version }}
4242
runs-on: ubuntu-latest
4343
timeout-minutes: 60
4444
env:
4545
INSTALL_PRECOMMIT: "false"
46+
strategy:
47+
fail-fast: false
48+
matrix:
49+
include:
50+
- torch_group: torch_2_8
51+
torch_version: "2.8"
52+
- torch_group: torch_2_9
53+
torch_version: "2.9"
54+
- torch_group: torch_2_10
55+
torch_version: "2.10"
56+
- torch_group: torch_2_11
57+
torch_version: "2.11"
4658
steps:
4759
- name: Check out repository
4860
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
@@ -51,7 +63,7 @@ jobs:
5163
with:
5264
enable-cache: false
5365
- name: Run `make test-smoke`
54-
run: make test-smoke
66+
run: make test-smoke TORCH_GROUP=${{ matrix.torch_group }}
5567

5668
test-tutorials:
5769
name: Linux / make test-tutorials

Makefile

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ VENV_HIGHEST_TORCH ?= .venv-highest-torch
5353
VENV_LOWEST_TORCH ?= .venv-lowest-torch
5454
VENV_TUTORIAL ?= .venv-tutorial
5555

56+
# The torch_2_* groups (pyproject.toml [dependency-groups]) currently at
57+
# each end of the supported range. Bump these two lines — nothing else —
58+
# when the project's torch version bounds change.
59+
HIGHEST_TORCH_GROUP := torch_2_11
60+
LOWEST_TORCH_GROUP := torch_2_8
61+
62+
# Torch dependency group (pyproject.toml [dependency-groups]) that every
63+
# environment-building target (env, test, test-smoke, docs, ...) pins to.
64+
TORCH_GROUP ?= $(HIGHEST_TORCH_GROUP)
65+
export TORCH_GROUP
66+
5667
# Documentation directory. Defaults to $(MAKEFILE_DIR)docs so the same recipe
5768
# works in both contexts:
5869
#
@@ -176,7 +187,7 @@ env: _maybe_patch_pyproject
176187

177188
# Set up development environment with latest supported PyTorch version
178189
env-highest-torch: _maybe_patch_pyproject
179-
@$(SETUP_ENV) --venv $(VENV_HIGHEST_TORCH) --python-version $(PYTHON_VERSION) --with-highest_tested_torch
190+
@TORCH_GROUP=$(HIGHEST_TORCH_GROUP) $(SETUP_ENV) --venv $(VENV_HIGHEST_TORCH) --python-version $(PYTHON_VERSION)
180191
@$(call write_active_venv,$(VENV_HIGHEST_TORCH))
181192

182193
# Set up environment for running tutorials (quantization notebook)
@@ -235,6 +246,7 @@ test-slow:
235246
@$(MAKE) test PYTEST_ARGS="--marker slow"
236247

237248
# Run smoke tests only (pass PYTEST_ARGS for custom flags, e.g., make test-smoke PYTEST_ARGS="--junitxml=results.xml").
249+
# Pass TORCH_GROUP to smoke test against a specific torch version (default: HIGHEST_TORCH_GROUP).
238250
test-smoke:
239251
@$(call use_env,VENV) && \
240252
echo "Running smoke tests..." && \
@@ -244,20 +256,20 @@ test-smoke:
244256
# Run tests on lowest supported PyTorch version (pass PYTEST_ARGS for custom flags)
245257
test-lowest-pytorch:
246258
@echo "Running tests on lowest PyTorch version supported..."
247-
@$(call use_env,VENV_LOWEST_TORCH,--with-lowest_tested_torch) && \
248-
echo "Testing with lowest supported PyTorch versions" && \
249-
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
250-
$(RUN_TESTS) $(PYTEST_ARGS) && \
251-
echo "All tests passed!"
259+
@TORCH_GROUP=$(LOWEST_TORCH_GROUP) $(call use_env,VENV_LOWEST_TORCH) && \
260+
echo "Testing with lowest supported PyTorch versions" && \
261+
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
262+
$(RUN_TESTS) $(PYTEST_ARGS) && \
263+
echo "All tests passed!"
252264

253265
# Run tests on highest supported PyTorch version (pass PYTEST_ARGS for custom flags)
254266
test-highest-pytorch:
255267
@echo "Running tests on highest PyTorch version supported..."
256-
@$(call use_env,VENV_HIGHEST_TORCH,--with-highest_tested_torch) && \
257-
echo "Testing with latest supported PyTorch versions" && \
258-
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
259-
$(RUN_TESTS) $(PYTEST_ARGS) && \
260-
echo "All tests passed!"
268+
@TORCH_GROUP=$(HIGHEST_TORCH_GROUP) $(call use_env,VENV_HIGHEST_TORCH) && \
269+
echo "Testing with latest supported PyTorch versions" && \
270+
uv run --no-sync --active python $(SCRIPTS)/make/log_versions.py && \
271+
$(RUN_TESTS) $(PYTEST_ARGS) && \
272+
echo "All tests passed!"
261273

262274
# Run tutorial notebook tests
263275
test-tutorials:

ci/nox/noxfile.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,14 @@
3434
options.default_venv_backend = "uv"
3535
options.error_on_missing_interpreters = True
3636

37+
TORCH_GROUP = os.environ.get("TORCH_GROUP")
3738

38-
@session(python=get_supported_python_versions(), uv_extras=["coreai"], uv_groups=["test"])
39+
40+
@session(
41+
python=get_supported_python_versions(),
42+
uv_extras=["coreai"],
43+
uv_groups=["test", TORCH_GROUP],
44+
)
3945
def smoke_tests(session: Session) -> None:
4046
"""Smoke test the package build and coreai_opt imports and basic functionality.
4147

pyproject.toml

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies = [
4646
# based on other package versions. We can either 1) add a stricter check to require the newer torchao
4747
# version for everyone, or 2) add a runtime check in src/coreai_opt/init.py to ensure torchao
4848
# version >= 0.15.0 for torch version >= 2.9.0. Opting option 1.
49-
# These torch versions must be in bounds of lowest_tested_torch and highest_tested_torch
49+
# These torch versions must be in bounds of torch_2_8, torch_2_9, torch_2_10, and torch_2_11
5050
"torch>=2.8.0,<=2.11.0",
5151
"torchao>=0.15.0,<=0.17.0",
5252
"tqdm>=4.65",
@@ -107,20 +107,6 @@ docs = [
107107
# This applies to the `coreml` group as well.
108108
coreai = [ "coreai-opt[coreai]" ]
109109
coreml = [ "coreai-opt[coreml]" ]
110-
# Used in CI to force latest mimimum supported torch version
111-
# These torch versions must be in bounds of torch versions listed in project dependencies
112-
highest_tested_torch = [
113-
"torch==2.11.0",
114-
"torchao==0.17.0",
115-
"torchvision==0.26.0",
116-
]
117-
# Used in CI to force lowest mimimum supported torch version
118-
# These torch versions must be in bounds of torch versions listed in project dependencies
119-
lowest_tested_torch = [
120-
"torch==2.8.0",
121-
"torchao==0.15.0",
122-
"torchvision==0.23.0",
123-
]
124110
pre-commit = [
125111
"bashate>=2.1.1",
126112
"darker>=3.0.0",
@@ -144,6 +130,27 @@ pre-commit = [
144130
"tomli-w>=1.0.0",
145131
]
146132
tamm-export = []
133+
# These torch versions must be in bounds of torch versions listed in project dependencies
134+
torch_2_10 = [
135+
"torch==2.10.0",
136+
"torchao==0.16.0",
137+
"torchvision==0.25.0",
138+
]
139+
torch_2_11 = [
140+
"torch==2.11.0",
141+
"torchao==0.17.0",
142+
"torchvision==0.26.0",
143+
]
144+
torch_2_8 = [
145+
"torch==2.8.0",
146+
"torchao==0.15.0",
147+
"torchvision==0.23.0",
148+
]
149+
torch_2_9 = [
150+
"torch==2.9.1",
151+
"torchao==0.15.0",
152+
"torchvision==0.24.1",
153+
]
147154
# Since torch and torchao are project dependencies, we need to include torchvision in dev
148155
# This allows standard `make test` to find torchvision
149156
torchvision = [
@@ -190,8 +197,10 @@ exclude-newer-package = { coreai-core = false, coreai-torch = false }
190197
# Declare conflicting groups so uv does not error
191198
conflicts = [
192199
[
193-
{ group = "highest_tested_torch" },
194-
{ group = "lowest_tested_torch" },
200+
{ group = "torch_2_8" },
201+
{ group = "torch_2_9" },
202+
{ group = "torch_2_10" },
203+
{ group = "torch_2_11" },
195204
],
196205
]
197206
[tool.uv.sources]

scripts/make/setup_env.sh

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ fi
5959
# - in_section && /^\[/: If we're in the section AND hit another section header (line starting with [)
6060
# * exit: Stop processing (we've left the dependency-groups section)
6161
# - in_section {print}: If we're in the section, print the line
62-
# 2. grep -E '^[a-z_-]+ = \[': Filter to lines that define groups
63-
# - ^[a-z_-]+: Group name at start of line (lowercase letters, hyphens, underscores)
62+
# 2. grep -E '^[a-z0-9_-]+ = \[': Filter to lines that define groups
63+
# - ^[a-z0-9_-]+: Group name at start of line (lowercase letters, digits, hyphens, underscores)
6464
# - = \[: Followed by space, equals sign, space, opening bracket
6565
# 3. cut -d' ' -f1: Extract just the group name
6666
# - -d' ': Use space as delimiter
@@ -75,7 +75,7 @@ AVAILABLE_GROUPS=$(
7575
in_section && /^\[/ { exit }
7676
in_section { print }
7777
' "$PYPROJECT_TOML" |
78-
grep -E '^[a-z_-]+ = \[' |
78+
grep -E '^[a-z0-9_-]+ = \[' |
7979
cut -d' ' -f1 |
8080
tr '\n' ' '
8181
)
@@ -109,7 +109,7 @@ ENSURE_MODE=false
109109

110110
# Groups excluded from --all-groups due to mutual conflicts in pyproject.toml.
111111
# tamm-export is omitted because it's opt-in only (never in default-groups or --all-groups).
112-
CONFLICTING_GROUPS=("highest_tested_torch" "lowest_tested_torch")
112+
CONFLICTING_GROUPS=("torch_2_8" "torch_2_9" "torch_2_10" "torch_2_11")
113113

114114
show_help() {
115115
echo "Usage: $0 [OPTIONS]"
@@ -127,6 +127,7 @@ show_help() {
127127
echo ""
128128
echo "Environment variables:"
129129
echo " VENV Virtual environment name, overrides --venv (default: .venv)"
130+
echo " TORCH_GROUP Torch dependency group to pin; source of truth for torch pinning"
130131
echo ""
131132
echo "Available dependency groups: $AVAILABLE_GROUPS"
132133
echo "Conflicting groups (excluded from --all-groups): ${CONFLICTING_GROUPS[*]}"
@@ -135,8 +136,8 @@ show_help() {
135136
echo " $0 --python-version 3.11 # Setup with dev group only"
136137
echo " $0 --python-version 3.11 --with-docs # Setup with dev and docs groups"
137138
echo " $0 --python-version 3.11 --all-groups # Setup with all non-conflicting groups"
138-
echo " $0 --python-version 3.11 --all-groups --with-highest_tested_torch # Setup with all groups and highest torch"
139-
echo " $0 --python-version 3.11 --all-groups --with-lowest_tested_torch # Setup with all groups and lowest torch"
139+
echo " TORCH_GROUP=torch_2_11 $0 --python-version 3.11 --all-groups # Setup with all groups and torch 2.11"
140+
echo " TORCH_GROUP=torch_2_8 $0 --python-version 3.11 --all-groups # Setup with all groups and torch 2.8"
140141
echo " $0 --python-version 3.11 --venv .venv-exp # Setup with custom venv name"
141142
echo " $0 --python-version 3.11 --with-docs --venv .venv-exp # Setup with docs group and custom venv name"
142143
echo " $0 --python-version 3.12 # Setup with Python 3.12"
@@ -222,6 +223,7 @@ validate_groups() {
222223
# Validate dependency group names early
223224
[[ ${#EXTRA_GROUPS[@]} -gt 0 ]] && validate_groups "${EXTRA_GROUPS[@]}"
224225
[[ ${#EXCLUDE_GROUPS[@]} -gt 0 ]] && validate_groups "${EXCLUDE_GROUPS[@]}"
226+
[[ -n "${TORCH_GROUP:-}" ]] && validate_groups "$TORCH_GROUP"
225227

226228
# Check for conflicts between --with-<group> and --without-<group>
227229
if [[ ${#EXTRA_GROUPS[@]} -gt 0 && ${#EXCLUDE_GROUPS[@]} -gt 0 ]]; then
@@ -267,24 +269,35 @@ if [[ "$ENSURE_MODE" == "true" ]] && [ -f "$VENV/bin/python" ]; then
267269
for GROUP in "${EXTRA_GROUPS[@]}"; do
268270
case "$GROUP" in
269271
docs) IMPORT_STMTS+="; import sphinx" ;;
270-
highest_tested_torch | lowest_tested_torch)
271-
IMPORT_STMTS+="; import torchao"
272-
EXPECTED_TORCH="$(group_torch_pin "$GROUP")"
273-
# These groups always pin torch, so an empty result means the
274-
# pyproject parse regressed — fail loudly instead of silently
275-
# skipping the version check (which would reintroduce the bug).
276-
if [[ -z "$EXPECTED_TORCH" ]]; then
277-
echo "Error: could not parse a torch pin for group '$GROUP' in $PYPROJECT_TOML" >&2
278-
exit 1
279-
fi
280-
IMPORT_STMTS+="; import torch; assert torch.__version__.split('+')[0] == '$EXPECTED_TORCH'"
281-
;;
282272
rio) IMPORT_STMTS+="; import turi_lightning" ;;
283273
tamm-export) IMPORT_STMTS+="; import tamm_export" ;;
284274
esac
285275
done
286276
fi
287277

278+
# TORCH_GROUP is the single source of truth for torch pinning; fall back
279+
# to a --with-torch_2_* flag for direct, non-Make invocations. Checked
280+
# unconditionally rather than only when present in EXTRA_GROUPS, since
281+
# TORCH_GROUP drives the sync regardless of what's in EXTRA_GROUPS.
282+
EXPECTED_GROUP="${TORCH_GROUP:-}"
283+
if [[ -z "$EXPECTED_GROUP" ]]; then
284+
for GROUP in "${EXTRA_GROUPS[@]:-}"; do
285+
[[ " ${CONFLICTING_GROUPS[*]} " == *" ${GROUP} "* ]] && EXPECTED_GROUP="$GROUP"
286+
done
287+
fi
288+
if [[ -n "$EXPECTED_GROUP" ]]; then
289+
IMPORT_STMTS+="; import torchao"
290+
EXPECTED_TORCH="$(group_torch_pin "$EXPECTED_GROUP")"
291+
# These groups always pin torch, so an empty result means the
292+
# pyproject parse regressed — fail loudly instead of silently
293+
# skipping the version check (which would reintroduce the bug).
294+
if [[ -z "$EXPECTED_TORCH" ]]; then
295+
echo "Error: could not parse a torch pin for '$EXPECTED_GROUP' in $PYPROJECT_TOML" >&2
296+
exit 1
297+
fi
298+
IMPORT_STMTS+="; import torch; assert torch.__version__.split('+')[0] == '$EXPECTED_TORCH'"
299+
fi
300+
288301
if "$VENV/bin/python" -c "$IMPORT_STMTS" 2>/dev/null; then
289302
exit 0
290303
fi
@@ -324,9 +337,13 @@ echo "[2/3] Installing dependencies..."
324337
SYNC_CMD=(uv sync --active)
325338
if [[ "$ALL_GROUPS" == "true" ]]; then
326339
SYNC_CMD+=(--all-groups)
327-
# Exclude conflicting groups unless explicitly requested via --with-*
340+
# Exclude conflicting torch groups other than the one TORCH_GROUP or
341+
# --with-<group> selects, so --all-groups doesn't try to sync every
342+
# torch_2_* group at once. If both TORCH_GROUP and --with-<other-group>
343+
# name different groups, neither gets excluded here and `uv sync` itself
344+
# rejects the combination via its own conflicting-groups resolution.
328345
for GROUP in "${CONFLICTING_GROUPS[@]}"; do
329-
if [[ ! " ${EXTRA_GROUPS[*]:-} " == *" ${GROUP} "* ]]; then
346+
if [[ "$GROUP" != "${TORCH_GROUP:-}" ]] && [[ ! " ${EXTRA_GROUPS[*]:-} " == *" ${GROUP} "* ]]; then
330347
SYNC_CMD+=(--no-group "$GROUP")
331348
fi
332349
done
@@ -335,6 +352,14 @@ elif [[ ${#EXTRA_GROUPS[@]} -gt 0 ]]; then
335352
SYNC_CMD+=(--group "$GROUP")
336353
done
337354
fi
355+
# TORCH_GROUP is the single source of truth for torch pinning: append it
356+
# unconditionally (already covered under --all-groups by not being
357+
# excluded above). A disagreeing --with-<other-torch-group> ends up as a
358+
# second, different --group flag here, which `uv sync` itself rejects via
359+
# its conflicting-groups resolution rather than a bespoke check.
360+
if [[ -n "${TORCH_GROUP:-}" && "$ALL_GROUPS" != "true" ]]; then
361+
SYNC_CMD+=(--group "$TORCH_GROUP")
362+
fi
338363
# Apply explicit group exclusions (e.g., --without-coreai)
339364
if [[ ${#EXCLUDE_GROUPS[@]} -gt 0 ]]; then
340365
for GROUP in "${EXCLUDE_GROUPS[@]}"; do

0 commit comments

Comments
 (0)