Skip to content

Commit 491e1ca

Browse files
Gasoonjiagasoonjia
authored andcommitted
[executorch][ci] Split unittest-cuda into kernel and system shards
unittest-cuda has timed out at 90 min on every main run since a04a9f9 made the SDPA autotune tests ~6.5 min slower; the job was already at 83-89 min before that. Split it into a matrix: - kernel: Triton kernel and autotune tests (SDPA, tq4 SDPA, int4 matmul, int5 large GEMM, fused MoE, deltanet, topk, autotune inputs/launch params, mutation cache). ~42 min of pytest. - system: everything else under backends/cuda/tests and passes/tests (lowering passes, op dispatch, export/partitioner/runtime), plus the pybindings device tests, C++ shim tests, and Qwen 3.5 MoE / Gemma 4 31B model tests. ~28 min of pytest. The system shard runs the test directories with the kernel and quantized GEMM files ignored, so new tests default to it. Test plan: CI on this PR; both unittest-cuda shards should finish well under the 90 min timeout.
1 parent 8d9eec3 commit 491e1ca

1 file changed

Lines changed: 89 additions & 48 deletions

File tree

‎.github/workflows/cuda.yml‎

Lines changed: 89 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ jobs:
467467
"${RUNNER_ARTIFACT_DIR}/native-sm86-on-sm80.log"
468468
469469
unittest-cuda:
470-
name: unittest-cuda
470+
name: unittest-cuda (${{ matrix.shard }})
471471
needs: [changed-files, run-decision]
472472
# This job runs the pybindings device test, so it also has to fire on the
473473
# two pybindings paths the workflow filter lists.
@@ -482,6 +482,13 @@ jobs:
482482
contains(needs.changed-files.outputs.changed-files, 'extension/pybindings') ||
483483
contains(needs.changed-files.outputs.changed-files, 'runtime/__init__.py') ||
484484
needs.run-decision.outputs.is-full-run == 'true'
485+
# kernel: Triton kernels and their autotuning. system: lowering passes, op
486+
# dispatch, export/runtime, and model tests. Together they do not fit the
487+
# timeout.
488+
strategy:
489+
fail-fast: false
490+
matrix:
491+
shard: [kernel, system]
485492
uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main
486493
permissions:
487494
id-token: write
@@ -495,6 +502,37 @@ jobs:
495502
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
496503
script: |
497504
set -eux
505+
SHARD="${{ matrix.shard }}"
506+
507+
# The system shard runs everything under backends/cuda/tests that is not
508+
# listed here, so a new test lands there unless it is added to this list.
509+
KERNEL_TESTS=(
510+
backends/cuda/tests/test_triton_sdpa.py
511+
backends/cuda/tests/test_triton_sdpa_splitk.py
512+
backends/cuda/tests/test_triton_sdpa_nan.py
513+
backends/cuda/tests/test_tq4_sdpa.py
514+
backends/cuda/tests/test_int4_matmul.py
515+
backends/cuda/tests/test_int5_large_quantized_gemm.py
516+
backends/cuda/tests/test_quantized_gemm_family.py
517+
backends/cuda/tests/test_fused_moe.py
518+
backends/cuda/tests/test_fused_deltanet_decode.py
519+
backends/cuda/tests/test_chunk_gated_delta_rule.py
520+
backends/cuda/tests/test_topk.py
521+
backends/cuda/tests/test_autotune_inputs.py
522+
backends/cuda/tests/test_autotune_launch_params.py
523+
backends/cuda/tests/test_triton_mutation_cache.py
524+
)
525+
# These run in unittest-cuda-quantized-gemm.
526+
QUANTIZED_GEMM_TESTS=(
527+
backends/cuda/tests/test_int4_quantized_gemm.py
528+
backends/cuda/tests/test_int4_large_quantized_gemm.py
529+
backends/cuda/tests/test_int5_quantized_gemm.py
530+
backends/cuda/tests/test_int6_quantized_gemm.py
531+
backends/cuda/tests/test_int6_large_quantized_gemm.py
532+
backends/cuda/tests/test_int8_quantized_gemm.py
533+
backends/cuda/tests/test_cuda_graph_timing.py
534+
)
535+
498536
# Install executorch in editable mode so custom op libs land in-tree
499537
bash ./install_executorch.sh
500538
@@ -503,64 +541,67 @@ jobs:
503541
conda install -y -c conda-forge 'libstdcxx-ng>=12'
504542
export LD_LIBRARY_PATH=/opt/conda/lib:$LD_LIBRARY_PATH
505543
506-
# The pybindings loader only asks for device memory on a build that has
507-
# a device allocator registered, so these tests cannot run in the CPU
508-
# jobs. They need the install above and nothing built below, so run them
509-
# here and fail before the long builds.
510-
#
511-
# unittest exits 0 when a test skips, and these tests skip when the CUDA
512-
# backend is missing or no GPU is visible. Both are the prerequisites
513-
# this job exists to provide, so treat a skip as a failure rather than
514-
# let a packaging regression pass silently. `tee` would otherwise hide
515-
# the real exit status behind its own, so read it back explicitly.
516-
set +e
517-
python -m unittest -v \
518-
executorch.extension.pybindings.test.test_pybindings.PybindingsTest.test_device_planned_method_allocates_on_the_device \
519-
executorch.extension.pybindings.test.test_pybindings.PybindingsTest.test_unplanned_device_output_is_device_memory \
520-
2>&1 | tee /tmp/pybindings_device_test.log
521-
pybindings_device_status=${PIPESTATUS[0]}
522-
set -e
523-
if [ "${pybindings_device_status}" -ne 0 ]; then
524-
echo "::error::a pybindings device test failed"
525-
exit "${pybindings_device_status}"
526-
fi
527-
if grep -qE "^OK \(skipped=|skipped=[1-9]" /tmp/pybindings_device_test.log; then
528-
echo "::error::a pybindings device test skipped in the CUDA job, which means the CUDA backend or a visible GPU is missing"
529-
exit 1
544+
if [ "${SHARD}" = "system" ]; then
545+
# The pybindings loader only asks for device memory on a build that has
546+
# a device allocator registered, so these tests cannot run in the CPU
547+
# jobs. They need the install above and nothing built below, so run them
548+
# here and fail before the long builds.
549+
#
550+
# unittest exits 0 when a test skips, and these tests skip when the CUDA
551+
# backend is missing or no GPU is visible. Both are the prerequisites
552+
# this job exists to provide, so treat a skip as a failure rather than
553+
# let a packaging regression pass silently. `tee` would otherwise hide
554+
# the real exit status behind its own, so read it back explicitly.
555+
set +e
556+
python -m unittest -v \
557+
executorch.extension.pybindings.test.test_pybindings.PybindingsTest.test_device_planned_method_allocates_on_the_device \
558+
executorch.extension.pybindings.test.test_pybindings.PybindingsTest.test_unplanned_device_output_is_device_memory \
559+
2>&1 | tee /tmp/pybindings_device_test.log
560+
pybindings_device_status=${PIPESTATUS[0]}
561+
set -e
562+
if [ "${pybindings_device_status}" -ne 0 ]; then
563+
echo "::error::a pybindings device test failed"
564+
exit "${pybindings_device_status}"
565+
fi
566+
if grep -qE "^OK \(skipped=|skipped=[1-9]" /tmp/pybindings_device_test.log; then
567+
echo "::error::a pybindings device test skipped in the CUDA job, which means the CUDA backend or a visible GPU is missing"
568+
exit 1
569+
fi
530570
fi
531571
532572
# Build ExecuTorch with CUDA support
533573
cmake --workflow --preset llm-release-cuda
534574
535-
# Build and run CUDA shim tests (C++)
536-
pushd backends/cuda/runtime/shims/tests
537-
cmake --workflow --preset default
538-
popd
575+
if [ "${SHARD}" = "system" ]; then
576+
# Build and run CUDA shim tests (C++)
577+
pushd backends/cuda/runtime/shims/tests
578+
cmake --workflow --preset default
579+
popd
580+
fi
539581
540582
# Install flash-linear-attention for chunk_gated_delta_rule triton kernel tests
541583
pip install "flash-linear-attention==0.4.2"
542584
543-
# Build executor_runner (needed by CUDA backend e2e tests)
585+
# Build executor_runner (needed by CUDA backend e2e tests in both shards)
544586
cmake --build cmake-out --target executor_runner
545587
546-
# Run CUDA backend Python tests. The quantized GEMM kernel suites run
547-
# in unittest-cuda-quantized-gemm; together they do not fit this
548-
# job's timeout.
549-
python -m pytest backends/cuda/tests backends/cuda/passes/tests -v -o "addopts=" \
550-
--ignore=backends/cuda/tests/test_int4_quantized_gemm.py \
551-
--ignore=backends/cuda/tests/test_int4_large_quantized_gemm.py \
552-
--ignore=backends/cuda/tests/test_int5_quantized_gemm.py \
553-
--ignore=backends/cuda/tests/test_int6_quantized_gemm.py \
554-
--ignore=backends/cuda/tests/test_int6_large_quantized_gemm.py \
555-
--ignore=backends/cuda/tests/test_int8_quantized_gemm.py \
556-
--ignore=backends/cuda/tests/test_cuda_graph_timing.py
557-
558-
# Run Qwen 3.5 MoE tests (quantize roundtrip + TurboQuant KV cache + sampler)
559-
python -m pytest examples/models/qwen3_5_moe/test_quantize_roundtrip.py examples/models/qwen3_5_moe/test_turboquant.py examples/models/qwen3_5_moe/test_sampler.py -v -o "addopts="
560-
561-
# Run Gemma 4 31B tests (quant unit tests + pipeline integration tests)
562-
pip install gguf
563-
python -m pytest examples/models/gemma4_31b/tests/ --ignore=examples/models/gemma4_31b/tests/test_mlx_pipeline.py -v -o "addopts="
588+
if [ "${SHARD}" = "kernel" ]; then
589+
python -m pytest "${KERNEL_TESTS[@]}" -v -o "addopts=" --durations=20
590+
else
591+
IGNORES=()
592+
for t in "${KERNEL_TESTS[@]}" "${QUANTIZED_GEMM_TESTS[@]}"; do
593+
IGNORES+=("--ignore=${t}")
594+
done
595+
python -m pytest backends/cuda/tests backends/cuda/passes/tests -v -o "addopts=" --durations=20 \
596+
"${IGNORES[@]}"
597+
598+
# Run Qwen 3.5 MoE tests (quantize roundtrip + TurboQuant KV cache + sampler)
599+
python -m pytest examples/models/qwen3_5_moe/test_quantize_roundtrip.py examples/models/qwen3_5_moe/test_turboquant.py examples/models/qwen3_5_moe/test_sampler.py -v -o "addopts="
600+
601+
# Run Gemma 4 31B tests (quant unit tests + pipeline integration tests)
602+
pip install gguf
603+
python -m pytest examples/models/gemma4_31b/tests/ --ignore=examples/models/gemma4_31b/tests/test_mlx_pipeline.py -v -o "addopts="
604+
fi
564605
565606
unittest-cuda-muse-glimmer-batching:
566607
name: unittest-cuda-muse-glimmer-batching

0 commit comments

Comments
 (0)