docs(examples): add end-to-end GPU inference codelab - #5965
Li Chen (AaronLiChen) wants to merge 9 commits into
Conversation
|
|
Successful end-to-end validation:
Validation produced two fixes:
The validation resource group was deleted after testing. |
|
The checked-in codelab now matches the validated run ( |
There was a problem hiding this comment.
🟡 Changes recommended
Cleanup can delete shared resources, while configuration and preflight gaps can leave workloads stuck or fail unexpectedly.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds an end-to-end AKS GPU inference codelab covering Kueue admission, autoscaling, vLLM inference, monitoring, and cleanup.
Changes:
- Adds eight guided codelab modules and documentation.
- Adds infrastructure, validation, monitoring, and cleanup scripts.
- Adds Kueue configuration and a three-pod vLLM inference workload.
File summaries
| File | Description |
|---|---|
examples/end-to-end-AI-codelabs/README.md |
Adds the codelab index. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/README.md |
Documents the complete lab. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/01-check-prerequisites.md |
Documents prerequisites. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/02-create-cluster.md |
Guides cluster creation. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/03-create-gpu-pool.md |
Guides GPU pool setup. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/04-install-controllers.md |
Documents controller installation. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/05-configure-provisioning.md |
Configures Kueue provisioning. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/06-run-inference.md |
Runs and validates inference. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/07-observe-and-troubleshoot.md |
Provides diagnostics. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/08-cleanup.md |
Documents cleanup. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/lib.sh |
Defines shared configuration. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/00-preflight.sh |
Validates prerequisites and quota. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/10-create-cluster.sh |
Creates the AKS cluster. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/20-create-gpu-pool.sh |
Creates the autoscaling GPU pool. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/30-install-controllers.sh |
Installs Kueue and the NVIDIA plugin. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/40-configure-queue.sh |
Applies the Kueue queue. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/50-submit-inference.sh |
Submits the inference job. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/60-watch-flow.sh |
Watches execution progress. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/90-cleanup.sh |
Removes lab resources. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/manifests/kueue-gpu-queue.yaml |
Defines Kueue resources and provisioning. |
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/manifests/inference-job.yaml |
Defines the vLLM workload. |
Review details
Suppressed comments (2)
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/manifests/kueue-gpu-queue.yaml:14
LAB_GPU_POOLis treated as configurable by the scripts, but this flavor hard-codesagentpool: gpupool. If a user changes the variable, the script creates and watches one pool while Kueue provisions against another name, so the workload remains pending even when the custom pool is Ready. Template the manifest from the shared configuration or make the pool name non-configurable.
nodeLabels:
agentpool: gpupool
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/00-preflight.sh:32
- This preflight only checks that
$LAB_GPU_SKUis listed and unrestricted. It does not verify that the SKU advertises an NVIDIA GPU or that the two$LAB_SYSTEM_SKUnodes and their quota are available, so a run can pass preflight and then either fail during cluster creation or remain pending withoutnvidia.com/gpucapacity. Validate both required SKU families and the GPU capability before reporting success.
step "Checking $LAB_GPU_SKU in $LAB_LOCATION"
SKU_JSON=$(az vm list-skus --location "$LAB_LOCATION" --size "$LAB_GPU_SKU" --all -o json)
SKU_COUNT=$(python3 -c 'import json,sys; print(len(json.load(sys.stdin)))' <<<"$SKU_JSON")
[[ "$SKU_COUNT" -gt 0 ]] || fail "$LAB_GPU_SKU isn't listed in $LAB_LOCATION. Set LAB_LOCATION or LAB_GPU_SKU."
- Files reviewed: 21/21 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical scheduling and cluster-scoped resource ownership and cleanup issues remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/00-preflight.sh:25
- This preflight claims to check the required resource providers, but it checks only Compute and ContainerService while the next script also creates AKS networking and enables a managed identity. A subscription with Microsoft.Network or Microsoft.ManagedIdentity unregistered can pass this check and then fail during
az aks create; check every provider required by that command so the failure is reported before resource creation.
for provider in Microsoft.Compute Microsoft.ContainerService; do
state=$(az provider show --namespace "$provider" --query registrationState -o tsv 2>/dev/null || true)
if [[ "$state" == "Registered" ]]; then
pass "$provider is registered"
else
fail "$provider is $state. Run: az provider register --namespace $provider"
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/00-preflight.sh:77
- These checks compare each SKU only with its own family quota. Azure also enforces the subscription's
Total Regional vCPUslimit across both the two system nodes and three GPU nodes; a subscription can have family headroom but fewer than the 26 total vCPUs required, so preflight passes and cluster or pool creation then fails. Check the aggregate requirement against the total-regional usage entry as well.
quota = next((q for q in json.load(open(usage_file))
if norm(q.get("name", {}).get("value", "")) == norm(family)), None)
if quota is None:
raise SystemExit(f"FAIL|quota family {family!r} isn't present in the regional usage list")
used, limit = int(quota["currentValue"]), int(quota["limit"])
required = vcpus * count
if limit - used < required:
raise SystemExit(
f"FAIL|{family} has {limit-used} free vCPUs; {count} {expected_sku} nodes need {required}")
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/20-create-gpu-pool.sh:24
- The existing-pool validation does not check the node operating system. A same-SKU Windows
gpupoolcan therefore be accepted, but the workload uses a Linux vLLM image and cannot run on Windows nodes. Validate that an existing pool is Linux before treating it as compatible.
if pool.get("mode", "").lower() != "user":
errors.append(f"mode is {pool.get('mode')}, expected User")
if not pool.get("enableAutoScaling"):
errors.append("cluster autoscaler is disabled")
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/90-cleanup.sh:21
- Deleting the Job is not enough synchronization for Kueue: the Job can disappear while its Workload and ProvisioningRequest are still being reconciled. This immediately removes the queue, admission, and provisioning resources, racing the controller and potentially leaving provisioning state or GPU capacity behind. Wait until the namespace has no Workloads or ProvisioningRequests, with a timeout/error path, before deleting the queue manifest.
kubectl delete -f "$ROOT/manifests/inference-job.yaml" --ignore-not-found
kubectl delete -f "$ROOT/manifests/kueue-gpu-queue.yaml" --ignore-not-found
- Files reviewed: 21/21 changed files
- Comments generated: 4
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Four unresolved findings remain, including a critical cleanup context-safety issue and three moderate configuration or ownership issues.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/00-preflight.sh:65
- Module 1 requires an override SKU to expose one GPU per node because the workload and documented zero-to-three-node flow assume three GPUs map to three nodes, but this check accepts any
gpus >= 1. A multi-GPU SKU can therefore pass preflight and satisfy all three pods on fewer nodes, invalidating the codelab's advertised topology. Reject non-1 GPU SKUs here, or update the workload and expectations to support them.
if require_gpu and gpus != 1:
raise SystemExit(
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/00-preflight.sh:20
- The preflight labels these as the required resource providers but checks only Compute and ContainerService. A subscription with the Network provider (or another provider required by the managed-identity AKS create path) unregistered passes preflight and then fails in Module 2. Include every provider used by the exact
az aks createinvocation, at minimumMicrosoft.Network, in this check.
for provider in Microsoft.Compute Microsoft.ContainerService; do
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/90-cleanup.sh:21
- This deletes the cluster-scoped ResourceFlavor, ProvisioningRequestConfig, AdmissionCheck, and ClusterQueue from the queue manifest, but the guard only verifies a label on the namespace. Because
40-configure-queue.shapplies these fixed names without checking ownership, running the codelab against an existing cluster can mutate or delete another Kueue setup. Add ownership markers and refuse unowned cluster-scoped objects before applying/deleting them, or use unique names.
"admissioncheck/gpu-inference-provisioning"
- Files reviewed: 21/21 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate watcher and cleanup issues can cause stale monitoring, ongoing GPU billing, or incomplete cleanup.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (5)
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/modules/07-observe-and-troubleshoot.md:47
- The backslash before the dot is consumed by the shell because this
-oargument isn't quoted, so kubectl receives...nvidia.com/gpuinstead of the escaped extended-resource key. The custom-columns path then splits the resource name at the dot and won't show the GPU capacity; quote the argument or pass a doubled backslash so the escape reaches kubectl.
-o custom-columns=NODE:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/60-watch-flow.sh:80
- When this 45-minute timeout fires, only the watcher exits; the Job and any ProvisioningRequest remain in the cluster. A suspended or running Job can therefore keep the GPU pool at three nodes and continue billing after the script reports failure. Bound the Job with an active deadline or clean up the workload on timeout and interruption.
if (( elapsed >= 2700 )); then
print_diagnostics
fail "Timed out after 45 minutes."
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/60-watch-flow.sh:36
- These queries always select
.items[0]and aren't scoped to the Workload or ProvisioningRequest created for the current Job. After50-submit-inference.shdeletes and recreates the fixed-name Job, the previous Kueue objects can still exist briefly, so the watcher may display an old admission or provisioning state while monitoring the new Job. Wait for old objects to disappear before reapplying, or select resources through the current Job's owner reference.
workload=$(kubectl -n "$LAB_NAMESPACE" get workload \
-o jsonpath='{.items[0].status.conditions[?(@.type=="Admitted")].status}' \
2>/dev/null || true)
[[ -n "$workload" ]] || workload="Pending"
provisioning=$(kubectl -n "$LAB_NAMESPACE" get provisioningrequest \
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/60-watch-flow.sh:31
- Because
lib.shenablesset -euo pipefail, a transient nonzero fromkubectl get nodesmakes this assignment fail and exits the watcher instead of retrying during its 45-minute loop. Handle this pipeline failure (for example, defaultready_nodesto 0) so temporary API errors don't abort the observation flow.
ready_nodes=$(kubectl get nodes -l "agentpool=$LAB_GPU_POOL" --no-headers 2>/dev/null \
| awk '$2 == "Ready" {count++} END {print count+0}')
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/90-cleanup.sh:40
- Deleting the Job does not synchronously remove Kueue's generated Workload and ProvisioningRequest. Immediately deleting the queue and namespace can race those controllers, leaving namespace termination stuck or cleanup incomplete; the repository's CAS example waits until these objects disappear before removing queue resources. Poll for an empty
workloads,provisioningrequestsset, with a bounded timeout, before deleting this manifest.
kubectl delete -f "$ROOT/manifests/kueue-gpu-queue.yaml" --ignore-not-found
- Files reviewed: 21/21 changed files
- Comments generated: 1
- Review effort level: Lite
|
Also addressed the latest review's suppressed findings in
|
There was a problem hiding this comment.
🔵 Needs a closer look
Two moderate setup issues and one diagnostic nit remain unresolved.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/30-install-controllers.sh:6
- Unlike the other scripts that mutate the cluster, this script never calls
require_lab_contextbefore the Helm installs. If the user switches the current kubeconfig context between modules, both releases are installed or upgraded in whichever cluster is active while the Azure variables still refer to the lab cluster, so this can modify an unrelated cluster. Callrequire_lab_contextbefore the first Helm command (or pass an explicit context).
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/30-install-controllers.sh:7
helm repo addfails when thenvdprepository already exists, andset -eexits before either chart is installed. That makes Module 4 fail on reruns or on machines that already have this repo configured; use--force-update(or explicitly handle an existing repo) so the setup remains repeatable.
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin >/dev/null
examples/end-to-end-AI-codelabs/kueue-gpu-inference-autoscaling/scripts/60-watch-flow.sh:24
- On failure or timeout, the watcher calls
print_diagnosticsand then deletes the Job (lines 88/93), but this function only describes pods and never prints their container logs. That loses the vLLM/model-load error while Modules 6 and 7 tell users to inspectkubectl logs job/vllm-inference-check; emit the pod logs beforestop_workload(or retain the Job) so failures remain diagnosable.
echo "--- Pending pod ---"
kubectl -n "$LAB_NAMESPACE" describe pod -l job-name="$LAB_JOB" 2>/dev/null || true
- Files reviewed: 21/21 changed files
- Comments generated: 0 new
- Review effort level: Lite
What this adds
Adds the first example under
examples/end-to-end-AI-codelabs: a guided AKS lab that starts with zero GPU nodes and follows a three-pod inference workload through:count: 3The lab includes eight guided modules, Kubernetes manifests, preflight/deployment/watch/cleanup scripts, checkpoints, and focused troubleshooting.
Scope note
The autoscaling path uses a conventional GPU pool with the AKS-installed driver and the upstream NVIDIA device plugin. Managed GPU node pools don't support cluster autoscaler during preview, so the lab documents that limitation rather than presenting the two features as compatible.
The node pool carries
nvidia.com/gpu.present=true. This satisfies the upstream device-plugin chart's discovery affinity without requiring Node Feature Discovery.Live validation
Validated end to end in
eastasia:/Standard_NV6ads_A10_v5(A10-4Q, 4 GB each), autoscaling 0–3POOL=0,READY=0, Job suspendedcount: 3Provisioned=True, reasonCapacityIsProvisioned3/3; every pod printedINFERENCE_VALIDATEDvLLM was constrained for the 4-GB profile with 70% GPU memory utilization, a 2,048-token context, four sequences, and eager execution.
Additional validation
All validation resource groups were deleted after testing.