Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .buildkite/amd/scripts/bootstrap-amd-omni.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ resolve_test_specs() {
if ! selected_specs=$(python3 "$AMD_SUITE_SELECTOR" \
--branch "$BUILDKITE_BRANCH" \
--labels "$PR_LABELS" \
--debug-test-yaml "${DEBUG_TEST_YAML:-}"); then
--debug-test-yaml "${DEBUG_TEST_YAML:-}" \
--nightly "$NIGHTLY"); then
exit 1
fi

Expand All @@ -80,6 +81,12 @@ filter_test_specs_by_skip_ci() {
case "$suite_spec" in
READY_TESTS:*) level="l2" ;;
MERGE_TESTS:*) level="l3" ;;
NIGHTLY_TESTS:*)
# Explicit PR and scheduled nightly selections must survive
# L2/L3 diff gating, including docs-only main commits.
runnable_specs+=("$suite_spec")
continue
;;
*)
echo "ERROR: unknown AMD test suite spec '$suite_spec'" >&2
exit 1
Expand All @@ -101,7 +108,7 @@ filter_test_specs_by_skip_ci() {
":memo: CI skipped — docs or pytest skip-mark changes only" \
--style "info" 2>/dev/null || true
fi
echo "No AMD L2/L3 suites remain after skip-ci filtering."
echo "No AMD suites remain after skip-ci filtering."
exit 0
fi
}
Expand Down Expand Up @@ -219,8 +226,8 @@ if [[ $BUILDKITE_BRANCH == "main" ]]; then
file_diff=$(get_diff_main)
fi

# Resolve PR tier labels before skip-ci so ready and merge suites can be
# filtered independently for CI-YAML-only changes.
# Resolve PR tier labels before skip-ci so ready/merge suites can be filtered
# independently while an explicitly selected nightly suite passes through.
resolve_test_specs
filter_test_specs_by_skip_ci

Expand Down
33 changes: 20 additions & 13 deletions .buildkite/amd/scripts/select_test_suites.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Select AMD L2/L3 suites after Buildkite has created an AMD build.
"""Select AMD ready/merge/nightly suites after Buildkite creates a build.

This repository-side selector does not decide which GitHub label events start
the external ``vllm-omni-amd-ci`` pipeline. Its Buildkite PR build condition
Expand All @@ -17,6 +17,7 @@
SUITE_SPECS = {
"ready": "READY_TESTS:test-amd-ready.yml",
"merge": "MERGE_TESTS:test-amd-merge.yml",
"nightly": "NIGHTLY_TESTS:test-amd-nightly.yml",
}


Expand All @@ -28,7 +29,7 @@ def _parse_debug_suites(value: str) -> tuple[str, ...]:
seen: set[str] = set()
for suite in suites:
if suite not in SUITE_SPECS:
raise ValueError(f"DEBUG_TEST_YAML entries must be 'merge' or 'ready', got {suite!r}")
raise ValueError(f"DEBUG_TEST_YAML entries must be 'merge', 'nightly', or 'ready', got {suite!r}")
if suite in seen:
raise ValueError(f"duplicate DEBUG_TEST_YAML suite {suite!r}")
seen.add(suite)
Expand All @@ -40,18 +41,27 @@ def select_amd_test_suites(
branch: str,
labels: Iterable[str],
debug_test_yaml: str = "",
nightly: bool = False,
) -> tuple[str, ...]:
"""Return ordered suite names while preserving legacy unlabeled PRs."""

if debug_test_yaml:
return _parse_debug_suites(debug_test_yaml)
if branch == "main":
return ("merge",)
return ("nightly",) if nightly else ("merge",)

label_set = {label.strip() for label in labels if label.strip()}
selected = tuple(suite for label, suite in (("ready", "ready"), ("merge-test", "merge")) if label in label_set)
# AMD historically uploaded L2 for every PR build. Keep that behavior for
# triggers such as amd-test while adding tier selection for ready/merge-test.
selected = tuple(
suite
for label, suite in (
("ready", "ready"),
("merge-test", "merge"),
("nightly-test", "nightly"),
)
if label in label_set
)
# AMD historically uploaded the ready suite for every admitted PR build.
# Preserve that fallback when no explicit tier label is present.
return selected or ("ready",)


Expand All @@ -60,6 +70,7 @@ def main() -> int:
parser.add_argument("--branch", required=True)
parser.add_argument("--labels", default="")
parser.add_argument("--debug-test-yaml", default="")
parser.add_argument("--nightly", type=int, choices=(0, 1), default=0)
args = parser.parse_args()

labels = tuple(args.labels.splitlines())
Expand All @@ -68,19 +79,15 @@ def main() -> int:
branch=args.branch,
labels=labels,
debug_test_yaml=args.debug_test_yaml,
nightly=bool(args.nightly),
)
except ValueError as exc:
parser.error(str(exc))

label_set = {label.strip() for label in labels if label.strip()}
if not args.debug_test_yaml and args.branch != "main" and not ({"ready", "merge-test"} & label_set):
print(
"No AMD L2/L3 tier label found; preserving the legacy ready-suite fallback.",
file=sys.stderr,
)
if "nightly-test" in label_set:
if args.branch != "main" and not args.debug_test_yaml and not ({"ready", "merge-test", "nightly-test"} & label_set):
print(
"AMD nightly-test has no suite yet and does not affect L2/L3 selection.",
"No AMD ready/merge/nightly label found; preserving the legacy ready-suite fallback.",
file=sys.stderr,
)

Expand Down
66 changes: 66 additions & 0 deletions .buildkite/amd/test-amd-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# AMD MI300 `full_model` nightly coverage during burn-in.
# All jobs are non-blocking until the lane has stable runtime evidence.
Comment thread
andyluo7 marked this conversation as resolved.
# Historical build #11646 was red in Function Expansion and Accuracy (one
# async_chunk similarity mismatch; Seed-TTS WER 0.376480 > 0.35).

env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60

steps:
# MI325 is the repository's legacy ROCm pytest marker. The AMD template maps
# agent_pool to the current MI300 Buildkite queues independently of that mark.
- group: ":full_moon: Qwen3-Omni MI300 Nightly"
steps:
- label: "Qwen3-Omni Function Expansion"
agent_pool: mi300_2
depends_on: amd-build
mirror_hardwares: [amdproduction]
grade: NonBlocking
timeout_in_minutes: 180
commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- pytest -s -v tests/e2e/online_serving/test_qwen3_omni_expansion.py -m "full_model and rocm and MI325 and cards_2" --run-level "full_model"

- label: "Qwen3-Omni Accuracy"
agent_pool: mi300_2
depends_on: amd-build
mirror_hardwares: [amdproduction]
grade: NonBlocking
timeout_in_minutes: 180
artifact_paths:
- tests/e2e/accuracy/qwen3_omni/results/qwen_omni_acc/*.json
commands:
- export SEED_TTS_WER_EVAL=1
# PyTorch ROCm uses the torch.cuda device namespace.
- export SEED_TTS_EVAL_DEVICE=cuda:1
- pytest -s -v tests/e2e/accuracy/qwen3_omni/test_qwen3_omni.py -m "full_model and rocm and MI325 and cards_2" --run-level "full_model"
# Native tests run from the image copy under /app/vllm-omni, while
# Buildkite's artifact hook searches the checkout workspace.
- |
if [[ "$$PWD" != "$${BUILDKITE_BUILD_CHECKOUT_PATH:?}" ]]; then
artifact_dir="$${BUILDKITE_BUILD_CHECKOUT_PATH}/tests/e2e/accuracy/qwen3_omni/results/qwen_omni_acc"
mkdir -p "$$artifact_dir"
cp -v tests/e2e/accuracy/qwen3_omni/results/qwen_omni_acc/*.json "$$artifact_dir/"
fi

- label: "Qwen3-Omni Documentation Examples"
agent_pool: mi300_2
depends_on: amd-build
mirror_hardwares: [amdproduction]
grade: NonBlocking
timeout_in_minutes: 120
commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- pytest -s -v tests/examples/online_serving/test_qwen3_omni.py -m "full_model and rocm and MI325 and cards_2" --run-level "full_model"

- label: "Qwen3-Omni AITER-on Smoke"
agent_pool: mi300_2
depends_on: amd-build
mirror_hardwares: [amdproduction]
grade: NonBlocking
timeout_in_minutes: 90
commands:
- export VLLM_ROCM_USE_AITER=1
- pytest -s -v tests/e2e/online_serving/test_qwen3_omni_expansion.py::test_text_to_text_audio_001 -m "full_model and rocm and MI325 and cards_2" --run-level "full_model"
1 change: 1 addition & 0 deletions .buildkite/common/scripts/skip_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
L45_YAML_FILES: dict[str, str] = {
".buildkite/cuda/test-nightly.yml": "cuda",
".buildkite/cuda/test-weekly.yml": "cuda",
".buildkite/amd/test-amd-nightly.yml": "amd",
".buildkite/npu/test-npu-nightly.yml": "npu",
}

Expand Down
14 changes: 8 additions & 6 deletions docs/contributing/ci/ci_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Canonical layout (prefer these paths for new changes):
├── amd/
│ ├── test-amd-ready.yml # L2 job definitions (template input)
│ ├── test-amd-merge.yml # L3 job definitions
│ ├── test-amd-nightly.yml # Nightly full-model jobs (experimental)
│ ├── test-template-amd-omni.j2 # Renders final pipeline.yaml
│ └── scripts/
│ ├── bootstrap-amd-omni.sh # Entry: skip-ci → Jinja → upload
Expand Down Expand Up @@ -64,7 +65,7 @@ Canonical layout (prefer these paths for new changes):
| -------- | ---------------- | -------------- | ---------------- | -------------------- |
| **CUDA** | `cuda/pipeline.yml` | `test-ready.yml`, `test-merge.yml`, `test-nightly.yml`, `test-weekly.yml` | `upload_pipeline.py --upload` (expands uploader-only keys) | omit `mirror_hardwares` (from `-m`) / preset string + `MIRROR_HW` |
| **NPU** | `npu/pipeline-npu.yml` | `test-npu-ready.yml`, `test-npu-nightly.yml` | `upload_pipeline.py --upload` | `mirror_hardwares: a2b3_npu_1` / `a2b3_npu_4` / `a3_npu_2` |
| **AMD** | `amd/scripts/bootstrap-amd-omni.sh` | `test-amd-ready.yml`, `test-amd-merge.yml` | Jinja (`test-template-amd-omni.j2`) → `pipeline upload` | `agent_pool` + `mirror_hardwares: [amdproduction]` (array, template filter) |
| **AMD** | `amd/scripts/bootstrap-amd-omni.sh` | `test-amd-ready.yml`, `test-amd-merge.yml`, `test-amd-nightly.yml` | Jinja (`test-template-amd-omni.j2`) → `pipeline upload` | `agent_pool` + `mirror_hardwares: [amdproduction]` (array, template filter) |
| **Intel** | `intel/scripts/bootstrap-intel-omni.sh` | `intel/pipeline-intel.yml` (steps inline) | Direct `pipeline upload` | Inline `agents.queue` on each step |

## Platform configuration style
Expand Down Expand Up @@ -171,9 +172,9 @@ Canonical layout (prefer these paths for new changes):

**Bootstrap:** [`amd/scripts/bootstrap-amd-omni.sh`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/amd/scripts/bootstrap-amd-omni.sh)—skip-ci, diff filtering, Jinja render, then `buildkite-agent pipeline upload`.

**Test YAML (data):** `amd/test-amd-ready.yml` (L2 / `ready`), `test-amd-merge.yml` (L3 / `merge-test` and `main`). On a PR carrying both labels, AMD combines both suites behind one image build. PR builds without either tier label retain the legacy L2 fallback for compatibility with the `amd-test` trigger. `DEBUG_TEST_YAML` remains an explicit override. AMD has no L4 file yet, so `nightly-test` does not select an additional suite.
**Test YAML (data):** `amd/test-amd-ready.yml` (L2 / `ready`), `test-amd-merge.yml` (L3 / `merge-test` and ordinary `main`), and experimental `test-amd-nightly.yml` (`full_model` / `nightly-test` or `main` with `NIGHTLY=1`). Multiple PR tier labels combine their suites behind one image build. PR builds that reach the bootstrap without a tier label retain the legacy ready-suite fallback. `DEBUG_TEST_YAML` accepts `ready`, `merge`, and `nightly` as an explicit override. During nightly burn-in every AMD nightly leaf is non-blocking.

**Trigger boundary:** AMD label handling has two layers. First, the external `vllm-omni-amd-ci` Buildkite pipeline condition decides whether a GitHub label event creates a build. Only after that build starts does this repository's bootstrap inspect all current PR labels and select L2, L3, or both. Repository-side selection therefore cannot make a `merge-test` event start AMD CI by itself. The external condition must admit both `ready` and `merge-test`, while preserving the legacy `amd-test` trigger and non-PR `main` / scheduled builds. For PR builds without a `DEBUG_TEST_YAML` override, an unreadable label set fails the bootstrap instead of silently selecting the wrong tier. Debug overrides skip label lookup and are validated directly; invalid values, including whitespace-only values, fail the bootstrap. Keep `nightly-test` out of the AMD condition until an AMD L4 suite exists; otherwise it would start a build without providing nightly coverage.
**Trigger boundary:** AMD label handling has two layers. First, the external `vllm-omni-amd-ci` Buildkite pipeline condition decides whether a GitHub label event creates a build. Only after that build starts does this repository's bootstrap inspect all current PR labels and select the ready, merge, nightly, or combined suites. Repository-side selection therefore cannot start AMD CI by itself. AMD-only nightly validation uses both `amd-test` and `nightly-test`: the external condition should accept an event for either label only when the complete PR label set contains both. `amd-test` admits the AMD pipeline, while `nightly-test` makes the bootstrap select `NIGHTLY_TESTS:test-amd-nightly.yml`. Existing `ready` / `merge-test` behavior and non-PR `main` / scheduled builds remain separate. For PR builds without a `DEBUG_TEST_YAML` override, an unreadable label set fails the bootstrap instead of silently selecting the wrong tier. Debug overrides skip label lookup and are validated directly; invalid values, including whitespace-only values, fail the bootstrap. Until the paired-label condition is configured, use an authorized manual Buildkite build with `DEBUG_TEST_YAML=nightly` and verify that the bootstrap reports only the nightly suite.

**Rendering:** [`test-template-amd-omni.j2`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/amd/test-template-amd-omni.j2) wraps data steps with `amd-build` image build and `amd_<agent_pool>` queues. Do **not** hand-edit generated `pipeline.yaml`.

Expand All @@ -189,7 +190,7 @@ Canonical layout (prefer these paths for new changes):

**Adding a job**

1. Edit `test-amd-ready.yml` or `test-amd-merge.yml`.
1. Edit `test-amd-ready.yml`, `test-amd-merge.yml`, or `test-amd-nightly.yml`.
2. Copy a neighboring block: `label`, `agent_pool`, `mirror_hardwares`, `commands`, optional `grade`.
3. Regenerate via bootstrap / Jinja; update `skip_ci.py` if you add a new YAML path.

Expand Down Expand Up @@ -245,7 +246,7 @@ Label triggers (`ready`, `merge-test`) are unchanged—diff-aware logic only red
| Non-qualifying skip-mark and no CI YAML | normal CI | all on |
| Diff unavailable | normal CI | all on |

**`skip_all` exceptions (CUDA/NPU bootstrap only):** PR labels (`nightly-test`, `merge-test`, `weekly-test`, `ready`, …) do **not** revive jobs under `skip_all`. On `main`, scheduled `NIGHTLY=1` still builds the image and uploads L4; `WEEKLY=1` or `NON_CRITICAL=1` still builds the image and uploads L5 (CUDA). `WEEKLY=1` on `main` also uploads L2/L3 with `--e2e`.
**`skip_all` exceptions:** For CUDA/NPU, PR labels (`nightly-test`, `merge-test`, `weekly-test`, `ready`, …) do **not** revive jobs under `skip_all`; on `main`, scheduled `NIGHTLY=1` still builds the image and uploads L4, while `WEEKLY=1` or `NON_CRITICAL=1` still builds the image and uploads L5 (CUDA). `WEEKLY=1` on `main` also uploads L2/L3 with `--e2e`. AMD applies `skip_ci.py gate` only to selected ready/merge specs, so an explicitly selected AMD nightly spec (`nightly-test`, `NIGHTLY=1`, or a nightly debug override) remains runnable even for a docs-only diff.

**Yaml-gated nightly/weekly:** L4/L5 upload steps keep their normal label / `NIGHTLY` / `WEEKLY` / `NON_CRITICAL` conditions (for example PR `nightly-test` still uploads nightly). Only L2/L3 upload steps are matrix-gated.

Expand All @@ -262,6 +263,7 @@ Register new files in `L2_YAML_FILES`, `L3_YAML_FILES`, or `L45_YAML_FILES` in `
| L3 | `.buildkite/cuda/test-merge.yml` | cuda |
| L3 | `.buildkite/amd/test-amd-merge.yml` | amd |
| L4/L5 | `.buildkite/cuda/test-nightly.yml`, `test-weekly.yml` | cuda |
| L4/L5 | `.buildkite/amd/test-amd-nightly.yml` | amd |
| L4/L5 | `.buildkite/npu/test-npu-nightly.yml` | npu |

##### Yaml-gated category branches
Expand Down Expand Up @@ -294,7 +296,7 @@ Docs/skip-mark mixed with any row below follows the same matrix.
| CUDA ready + AMD merge | G4 | `cuda/l2` + `amd/l3` |
| CUDA ready + AMD ready | G2 | `cuda/l2` + `amd/l2` |
| CUDA merge + AMD merge | G3 | `cuda/l3` + `amd/l3` |
| Only nightly / weekly / npu-nightly YAML | G1 | (none) |
| Only nightly / weekly / amd-nightly / npu-nightly YAML | G1 | (none) |
| CUDA ready + nightly | G5 | `cuda/l2` |
| CUDA merge + nightly | G6 | `cuda/l3` |
| CUDA ready + merge + nightly | G7 | `cuda/l2` + `cuda/l3` |
Expand Down
2 changes: 2 additions & 0 deletions docs/contributing/ci/test_system_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ CUDA **L4** (`full_model`) jobs are defined once in [`.buildkite/cuda/test-night
- **Nightly:** scheduled `main` builds with `NIGHTLY=1` (and PR labels such as `nightly-test` / `omni-test`). Default fleet is typically H100.
- **Pre-release:** the same L4 suite runs again on extra GPU SKUs before a release (for example B200 via `MIRROR_HW=b200`), so `full_model` coverage is not limited to the nightly machine type.

AMD has an experimental, non-blocking `full_model` nightly vertical slice in [`.buildkite/amd/test-amd-nightly.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/amd/test-amd-nightly.yml). It selects ROCm Qwen3-Omni function-expansion, accuracy, documentation-example, and AITER-on smoke coverage on the MI300 pool. AMD-only PR validation uses `amd-test` to admit the external AMD pipeline and `nightly-test` to select this suite; scheduled `main` builds select it with `NIGHTLY=1`. Keep it non-blocking while the lane accumulates stability and completion-time evidence.

How count-form `mirror_hardwares` and `MIRROR_HW` pick a SKU: [CI Settings](./ci_settings.md).

### L2 / L3 diff-aware CI (CUDA)
Expand Down
2 changes: 1 addition & 1 deletion tests/buildkite/test_amd_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def test_label_lookup_selects_tiers(tmp_path, labels, expected_levels):
] == expected_levels


@pytest.mark.parametrize("debug_test_yaml", ["ready,ready", "nightly", ", ,", " \t"])
@pytest.mark.parametrize("debug_test_yaml", ["ready,ready", "nightly,nightly", ", ,", " \t"])
def test_invalid_debug_override_fails_without_label_lookup(tmp_path, debug_test_yaml):
result = _run_bootstrap(tmp_path, debug_test_yaml=debug_test_yaml, curl_status=22)

Expand Down
17 changes: 15 additions & 2 deletions tests/buildkite/test_amd_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]

AMD_MERGE_PIPELINE = Path(".buildkite/amd/test-amd-merge.yml")
AMD_NIGHTLY_PIPELINE = Path(".buildkite/amd/test-amd-nightly.yml")


def _find_step(label: str) -> dict:
pipeline = yaml.safe_load(AMD_MERGE_PIPELINE.read_text(encoding="utf-8"))
def _find_step(label: str, pipeline_path: Path = AMD_MERGE_PIPELINE) -> dict:
pipeline = yaml.safe_load(pipeline_path.read_text(encoding="utf-8"))

def walk(steps: list[dict]) -> dict | None:
for step in steps:
Expand All @@ -40,3 +41,15 @@ def test_qwen3_tts_base_preserves_advanced_model_arguments() -> None:
run_level_index = argv.index("--run-level")
assert argv[marker_index + 1] == "advanced_model and cuda"
assert argv[run_level_index + 1] == "advanced_model"


def test_qwen3_accuracy_defers_artifact_path_expansion() -> None:
step = _find_step("Qwen3-Omni Accuracy", AMD_NIGHTLY_PIPELINE)
staging_command = next(command for command in step["commands"] if "artifact_dir=" in command)

# Dynamic pipelines are interpolated once during upload. Double dollars
# preserve these variables for the GPU job's runtime shell.
assert '"$$PWD"' in staging_command
assert '"$${BUILDKITE_BUILD_CHECKOUT_PATH:?}"' in staging_command
assert '"$$artifact_dir"' in staging_command
assert step["artifact_paths"] == ["tests/e2e/accuracy/qwen3_omni/results/qwen_omni_acc/*.json"]
Loading
Loading