You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GPU CI reliability is constrained by three categories of issues:
Build/deploy performance: H100 x2 workflows are severely bottlenecked by slow Docker/registry I/O on the linux-amd64-gpu-h100-latest-2 runner pool. Build aicr takes up to 40 min, and the kai-scheduler crd-upgrader hook frequently exceeds Helm timeout due to slow in-cluster image pulls. PR fix(ci): improve GPU test reliability and deploy timeout handling #539 mitigates with selective validator builds, per-component Helm timeouts, and retry logic. The structural fix is migrating from kind load to a local registry.
Workflow duplication: Training and Conformance workflows are mostly redundant, doubling H100 x2 runner consumption. Training and Inference trigger on every PR regardless of intent.
Behavioral test fragility: Dynamo inference tests are slow (multi-GB image pulls, model loading) and fragile (hardcoded sleep-poll loops, no backoff). Chainsaw health checks can fail on timing-dependent assertions (e.g., grafana availability window).
Problems
1. H100 x2 Build and Deploy Time Budget
H100 x2 workflows consistently exhaust their time budget due to slow runner I/O. Findings from April 11, 2026 debugging:
kai-scheduler crd-upgrader hook is the single biggest deploy bottleneck:
The pre-install hook Job runs inside the kind cluster and must pull its container image from ghcr.io
On H100 x2 runners, this frequently exceeds 10 min, likely due to slow cold image pull and pod startup on these runners
At the original 10m Helm timeout, this consistently timed out on the first attempt
Retries then succeed — likely because the image or its layers are partially or fully cached on the node from the first attempt
Conformance: gang-scheduling validator failed, then job cancelled at 90 min during ai-service-metrics
2. Conformance workflow is a duplicate of Training
Training and Conformance are mostly redundant — same runner (H100x2), same recipe (--intent training), same aicr validate --phase conformance, same chainsaw test dir (kind-training), same 5 evidence assertion files. The intentional difference is step ordering (training runs chainsaw before validate; conformance runs validate before chainsaw), which can expose different timing-dependent failures. However, running both on the same PR doubles H100x2 runner consumption for marginal additional signal.
3. Training and Inference are mutually exclusive but both always run
A Kind cluster is configured with either --intent training or --platform dynamo (inference), never both. Most PRs only affect one intent, yet both workflows trigger, doubling H100 runner consumption for no additional signal.
4. Runner contention from parallel H100x2 workflows
Training and Conformance both require linux-amd64-gpu-h100-latest-2 (2× H100 GPUs). A single PR triggers 2 H100x2 jobs; two concurrent PRs trigger 4, all competing for what appears to be a single-runner pool. Jobs fail at "Set up runner" or get cancelled waiting.
Runner Pool
GPU
Workflows
linux-amd64-gpu-t4-latest-1
T4 x1
Smoke only
linux-amd64-gpu-h100-latest-1
H100 x1
Inference only
linux-amd64-gpu-h100-latest-2
H100 x2
Training + Conformance (contention here)
5. Dynamo inference test is slow and fragile
The inference test deploys a full Dynamo stack and runs live inference:
Pulls multi-GB nvcr.io/nvidia/ai-dynamo/vllm-runtime:0.9.0 into Kind
Polls model registration: 30 × 10s = 5min max, no backoff
Port-forward with fixed sleep 3 before first curl
Any of these can fail due to slow image pull, OOM, operator race, or port-forward delay.
6. Hardcoded sleep-poll loops
All retry loops use fixed intervals with no exponential backoff. Device plugin DaemonSet wait: 30 × 10s = 5min max.
7. Existing path filters miss actual workflow inputs
All GPU workflows (training, inference, smoke) use composite actions (setup-build-tools, install-karpenter-kwok, load-versions) and .settings.yaml that are not consistently represented in their path filters. This means changes to tool versions or shared actions can silently skip the wrong jobs. Any workflow consolidation or trigger change must include a path filter audit for the affected workflows.
Known gaps:
.github/actions/setup-build-tools/** — used by inference but not in its path filter
.github/actions/install-karpenter-kwok/** — used by both but missing from some filters
.github/actions/load-versions/** — used by both, inconsistently filtered
.settings.yaml — drives tool versions, not in all filters
8. Debug diagnostics duplicated as inline scripts
All 3 H100 workflows have largely identical inline debug diagnostics (GPU operator pods, non-running pods, node status, custom metrics API). Not reusable.
9. kind load bottleneck for image distribution
The current GPU workflow design uses kind load docker-image to push images into kind nodes via tar pipe through Docker. This is inherently slow on runners with limited disk I/O and scales poorly with node count. The e2e action already uses a local registry pattern that avoids this bottleneck entirely.
The following mitigations are implemented in PR #539:
Fix
Detail
Selective validator phases
New validator_phases input on aicr-build action. Training/conformance build only conformance (prev: all 3). Cut conformance Build aicr from 36→17 min.
H100 x2 timeout bump
Training + Conformance: 60→90 min. Inference: 45→60 min.
Per-component Helm timeout
Three independent concerns: HELM_TIMEOUT (global 10m default), COMPONENT_HELM_TIMEOUT (per-component override), NO_WAIT/ASYNC_COMPONENTS (wait behavior). kai-scheduler gets 20m; all others keep 10m. Validated: both H100 x2 runs passed kai-scheduler on first attempt with 20m.
--no-wait preserves hook timeout
--no-wait now keeps --timeout for Helm hooks instead of clearing all wait args. Previously, --no-wait set WAIT_ARGS="", causing hooks to fall back to Helm's 5-minute default.
Async component hook timeout
ASYNC_COMPONENTS (kai-scheduler) now keeps --timeout instead of clearing all wait args. Same root cause as --no-wait.
Helm hook cleanup on retry
cleanup_helm_hooks() deletes any non-succeeded hook Job before retry. Uses per-Job JSON lookup (not fragile jsonpath). Handles failed, pending, and stuck hooks.
helm_retry function
Replaces retry for Helm installs. Calls cleanup_helm_hooks before each retry attempt.
HELM_TIMEOUT variable
Single source of truth for default timeout (was hardcoded in 3 places).
CUDA kind load timeout
Increased from 300s to 600s per attempt. On slow H100 x2 runners, the ~280MB CUDA image load was timing out both attempts at 300s.
DRA rollout always-wait
DRA kubelet plugin rollout status is a correctness gate, not skipped under --no-wait. CI uses --no-wait via gpu-operator-install, so the rollout wait was being skipped on the exact path that H100 validation depends on.
Evidence gating
steps.bundle-install.outcome == 'success' prevents evidence collection after failed installs.
Smoke workflow uses validator_phases: 'none' to skip all validator builds (previously used deprecated build_validators: 'false').
Proposal
1. Image Build and Deployment Optimization
Migrate from kind load to local registry (structural fix, highest impact)
Replace kind load docker-image with the local registry pattern already used in .github/actions/e2e/action.yml
Set up localhost:5001 registry in .github/actions/gpu-cluster-setup/action.yml
aicr-build pushes images to localhost:5001 instead of kind load
Snapshot agent uses --image=localhost:5001/aicr:local, validators use AICR_VALIDATOR_IMAGE_REGISTRY=localhost:5001
Nodes pull on demand — no tar-pipe bottleneck, no topology assumptions, no special cases for 1-node vs 2-node clusters
The validator path already supports localhost registries (pkg/validator/job/deployer.go:255)
Use a run-specific tag (e.g., :local) instead of :latest — in pkg/validator/job/deployer.go:255, :latest maps to PullAlways which defeats caching; :local gives IfNotPresent
Reduce build-side waste
Stop building the aicr binary twice in aicr-build (once for smoke-test image, once standalone)
Reduce Docker build context for smoke-test and validator images (currently sends full repo)
Delete the standalone conformance workflow — Training and Inference already run aicr validate --phase conformance
Consolidating them loses no meaningful validation coverage. The only thing removed is incidental timing variation from running the same checks in a different order (the current conformance workflow runs validate→chainsaw while training runs chainsaw→validate — this ordering difference is accidental, not an intentional testing strategy).
Fold conformance's broader path filter into training to avoid coverage gaps (includes tests/chainsaw/ai-conformance/cluster/**, docs/conformance/cncf/**)
This change must include a path filter/dependency audit for the conformance workflow being folded — verify all composite actions and file dependencies are represented in the training workflow's paths: filter before deleting the standalone workflow
Bundle deploy → Conformance → Chainsaw → Dynamo → Inference test → Evidence
Proposed (2 clusters)
Runner
What it does
Training + Conformance
H100x2
Bundle deploy → Chainsaw → Conformance → Evidence
Inference + Conformance
H100x1
Bundle deploy → Chainsaw → Conformance → Dynamo → Inference test → Evidence
Canonical step order: Chainsaw health checks → conformance validation → intent-specific checks (Dynamo, inference) → evidence collection. This order ensures: (1) failures are easier to interpret — deployment health is verified before deeper checks; (2) no time is wasted on conformance or intent-specific tests when the deployment is obviously unhealthy; (3) metrics- and operator-dependent components get warm-up time before conformance checks.
Audit and fix path filters (prerequisite for intent-aware triggers)
For each workflow, verify all composite actions it uses: have corresponding paths: filter entries
Add .settings.yaml to all workflow filters (drives tool versions)
PR touches training-specific paths → run Training only
PR touches inference-specific paths (Dynamo, kgateway, inference overlays) → run Inference only
PR touches shared infra (validators, CI actions, recipes/registry.yaml, collectors) → run both
Split run-gpu-tests label into run-gpu-training-test and run-gpu-inference-test
Additional Improvements
Reduce retry timeouts — Tune based on measured startup times (p95 + margin), not fixed targets. Validate against real data before cutting.
Replace sleep-poll with kubectl wait — Replace hardcoded sleep-poll loops in inference test with kubectl wait --for=condition=.... Add exponential backoff to curl retries.
GPU-specific chainsaw config — Create tests/chainsaw/chainsaw-gpu-config.yaml with failFast: false for GPU CI health-check steps, instead of modifying the shared config.
These are valuable but deferred until the workflow shape stabilizes:
Move inference to nightly + on-demand: For PRs that don't touch inference paths, run inference on nightly schedule + run-gpu-inference-test label only. Part of a broader tiering strategy.
Tiered testing: Structural-only PR gate (~15-20min) with change-aware behavioral coverage
--conformance-mode=fast|thorough: Requires plumbing through job deployer, runner, context, catalog
Parallel + serial check execution: Requires isolation analysis of shared GPU state across checks
Increase H100x2 runner pool: Request from infra team (nv-gpu-amd64-h100-2gpu runner group)
Investigate H100 x2 runner pool I/O degradation: The 10-24x slowdown vs H100 x1 suggests infrastructure issues beyond node count
Summary
GPU CI reliability is constrained by three categories of issues:
Build/deploy performance: H100 x2 workflows are severely bottlenecked by slow Docker/registry I/O on the
linux-amd64-gpu-h100-latest-2runner pool. Build aicr takes up to 40 min, and the kai-scheduler crd-upgrader hook frequently exceeds Helm timeout due to slow in-cluster image pulls. PR fix(ci): improve GPU test reliability and deploy timeout handling #539 mitigates with selective validator builds, per-component Helm timeouts, and retry logic. The structural fix is migrating fromkind loadto a local registry.Workflow duplication: Training and Conformance workflows are mostly redundant, doubling H100 x2 runner consumption. Training and Inference trigger on every PR regardless of intent.
Behavioral test fragility: Dynamo inference tests are slow (multi-GB image pulls, model loading) and fragile (hardcoded sleep-poll loops, no backoff). Chainsaw health checks can fail on timing-dependent assertions (e.g., grafana availability window).
Problems
1. H100 x2 Build and Deploy Time Budget
H100 x2 workflows consistently exhaust their time budget due to slow runner I/O. Findings from April 11, 2026 debugging:
kai-scheduler crd-upgrader hook is the single biggest deploy bottleneck:
ghcr.ioDownstream failures observed (appear infrastructure/runner-dependent, not PR regressions):
assert-monitoringERROR onapps/v1/Deployment @ monitoring/grafana)gang-schedulingvalidator failed, then job cancelled at 90 min duringai-service-metrics2. Conformance workflow is a duplicate of Training
Training and Conformance are mostly redundant — same runner (H100x2), same recipe (
--intent training), sameaicr validate --phase conformance, same chainsaw test dir (kind-training), same 5 evidence assertion files. The intentional difference is step ordering (training runs chainsaw before validate; conformance runs validate before chainsaw), which can expose different timing-dependent failures. However, running both on the same PR doubles H100x2 runner consumption for marginal additional signal.3. Training and Inference are mutually exclusive but both always run
A Kind cluster is configured with either
--intent trainingor--platform dynamo(inference), never both. Most PRs only affect one intent, yet both workflows trigger, doubling H100 runner consumption for no additional signal.4. Runner contention from parallel H100x2 workflows
Training and Conformance both require
linux-amd64-gpu-h100-latest-2(2× H100 GPUs). A single PR triggers 2 H100x2 jobs; two concurrent PRs trigger 4, all competing for what appears to be a single-runner pool. Jobs fail at "Set up runner" or get cancelled waiting.linux-amd64-gpu-t4-latest-1linux-amd64-gpu-h100-latest-1linux-amd64-gpu-h100-latest-25. Dynamo inference test is slow and fragile
The inference test deploys a full Dynamo stack and runs live inference:
nvcr.io/nvidia/ai-dynamo/vllm-runtime:0.9.0into KindQwen/Qwen3-0.6Bmodel into GPU memory60 × 10s= 10min max, no backoff30 × 10s= 5min max, no backoffsleep 3before first curlAny of these can fail due to slow image pull, OOM, operator race, or port-forward delay.
6. Hardcoded sleep-poll loops
All retry loops use fixed intervals with no exponential backoff. Device plugin DaemonSet wait:
30 × 10s= 5min max.7. Existing path filters miss actual workflow inputs
All GPU workflows (training, inference, smoke) use composite actions (
setup-build-tools,install-karpenter-kwok,load-versions) and.settings.yamlthat are not consistently represented in their path filters. This means changes to tool versions or shared actions can silently skip the wrong jobs. Any workflow consolidation or trigger change must include a path filter audit for the affected workflows.Known gaps:
.github/actions/setup-build-tools/**— used by inference but not in its path filter.github/actions/install-karpenter-kwok/**— used by both but missing from some filters.github/actions/load-versions/**— used by both, inconsistently filtered.settings.yaml— drives tool versions, not in all filters8. Debug diagnostics duplicated as inline scripts
All 3 H100 workflows have largely identical inline debug diagnostics (GPU operator pods, non-running pods, node status, custom metrics API). Not reusable.
9.
kind loadbottleneck for image distributionThe current GPU workflow design uses
kind load docker-imageto push images into kind nodes via tar pipe through Docker. This is inherently slow on runners with limited disk I/O and scales poorly with node count. The e2e action already uses a local registry pattern that avoids this bottleneck entirely.Fixes Implemented (PR #539)
The following mitigations are implemented in PR #539:
validator_phasesinput onaicr-buildaction. Training/conformance build onlyconformance(prev: all 3). Cut conformance Build aicr from 36→17 min.HELM_TIMEOUT(global 10m default),COMPONENT_HELM_TIMEOUT(per-component override),NO_WAIT/ASYNC_COMPONENTS(wait behavior). kai-scheduler gets 20m; all others keep 10m. Validated: both H100 x2 runs passed kai-scheduler on first attempt with 20m.--no-waitpreserves hook timeout--no-waitnow keeps--timeoutfor Helm hooks instead of clearing all wait args. Previously,--no-waitsetWAIT_ARGS="", causing hooks to fall back to Helm's 5-minute default.ASYNC_COMPONENTS(kai-scheduler) now keeps--timeoutinstead of clearing all wait args. Same root cause as--no-wait.cleanup_helm_hooks()deletes any non-succeeded hook Job before retry. Uses per-Job JSON lookup (not fragile jsonpath). Handles failed, pending, and stuck hooks.helm_retryfunctionretryfor Helm installs. Callscleanup_helm_hooksbefore each retry attempt.HELM_TIMEOUTvariablekind loadtimeout--no-wait. CI uses--no-waitviagpu-operator-install, so the rollout wait was being skipped on the exact path that H100 validation depends on.steps.bundle-install.outcome == 'success'prevents evidence collection after failed installs.Install GPU operator (bundle)→Install runtime bundlewithid: bundle-install.validator_phases: 'none'to skip all validator builds (previously used deprecatedbuild_validators: 'false').Proposal
1. Image Build and Deployment Optimization
kind loadto local registry (structural fix, highest impact)kind load docker-imagewith the local registry pattern already used in.github/actions/e2e/action.ymllocalhost:5001registry in.github/actions/gpu-cluster-setup/action.ymlaicr-buildpushes images tolocalhost:5001instead ofkind load--image=localhost:5001/aicr:local, validators useAICR_VALIDATOR_IMAGE_REGISTRY=localhost:5001pkg/validator/job/deployer.go:255):local) instead of:latest— inpkg/validator/job/deployer.go:255,:latestmaps toPullAlwayswhich defeats caching;:localgivesIfNotPresentaicrbinary twice inaicr-build(once for smoke-test image, once standalone)values.yamlso consumers can pre-pull or mirror it2. Combine Conformance into Training and Inference Workflows, Make Triggers Intent-Aware
Deduplicate conformance (immediate runner savings)
aicr validate --phase conformancetests/chainsaw/ai-conformance/cluster/**,docs/conformance/cncf/**)paths:filter before deleting the standalone workflowCanonical step order: Chainsaw health checks → conformance validation → intent-specific checks (Dynamo, inference) → evidence collection. This order ensures: (1) failures are easier to interpret — deployment health is verified before deeper checks; (2) no time is wasted on conformance or intent-specific tests when the deployment is obviously unhealthy; (3) metrics- and operator-dependent components get warm-up time before conformance checks.
Audit and fix path filters (prerequisite for intent-aware triggers)
uses:have correspondingpaths:filter entries.settings.yamlto all workflow filters (drives tool versions)setup-build-tools,install-karpenter-kwok,load-versions)Make triggers intent-aware
run-gpu-testslabel intorun-gpu-training-testandrun-gpu-inference-testAdditional Improvements
kubectl wait --for=condition=.... Add exponential backoff to curl retries.tests/chainsaw/chainsaw-gpu-config.yamlwithfailFast: falsefor GPU CI health-check steps, instead of modifying the shared config..github/actions/gpu-debug-diagnostics/action.yml.Sequencing
Related
Future Work (out of scope)
These are valuable but deferred until the workflow shape stabilizes:
run-gpu-inference-testlabel only. Part of a broader tiering strategy.--conformance-mode=fast|thorough: Requires plumbing through job deployer, runner, context, catalognv-gpu-amd64-h100-2gpurunner group)