xe GPU alerts¶
Source rule: cluster/monitoring/xe-gpu-alerts.yaml (PrometheusRule xe-gpu-alerts, namespace monitoring, group xe-gpu-eviction).
Metrics are produced by the xe-textfile-exporter systemd timer (ansible/roles/xe-textfile-exporter, 30s interval) and surfaced through the node_exporter textfile collector on the Intel xe (Battlemage) nodes. All instance labels are therefore node_exporter targets (<host>:9100), not Kubernetes node names — map one to the other with kubectl get nodes -o wide before filtering pods.
Available series and their labels:
| Metric | Labels | Meaning |
|---|---|---|
xe_client_spilled_kib |
(none) | Sum of drm-resident-gtt + drm-resident-system KiB across all renderD clients |
xe_dmem_used_bytes |
region |
VRAM charge per region, from cgroup dmem.current |
xe_dmem_capacity_bytes |
region |
VRAM capacity per region, from cgroup dmem.capacity |
xe_dmem_pod_used_bytes |
pod_uid, region |
Per-pod VRAM charge (non-zero only) |
xe_gt_throttle_status |
card, tile, gt |
1 = GT frequency-throttled |
xe_gt_throttle_reason |
card, tile, gt, reason |
Per-reason throttle flag |
xe_gt_act_freq_mhz |
card, tile, gt |
Actual GT frequency in MHz |
Staleness caveat. These are textfile-collector gauges. If the exporter timer stops, the series go stale and every alert below silently stops firing — absence of alerts is not evidence of health. Confirm freshness with
node_textfile_mtime_secondsandnode_textfile_scrape_errorfor the affected instance.GitOps. This cluster is mutation-free outside Git. Every command in this runbook is read-only. All fixes land as a PR that ArgoCD reconciles — never
kubectl apply/patch/scale/delete/cordon/draina live object to resolve one of these alerts.
XeVramEvictCliff¶
Severity: critical
Fires when: xe_client_spilled_kib > 262144 sustained for: 3m — i.e. total GTT + system-RAM residency across all renderD clients exceeds 256 MiB for three minutes. Healthy baseline is roughly 40 MiB.
Why this metric exists: when VRAM fills, the xe driver evicts buffers to GTT/system RAM and the dmem cgroup uncharges on evict. No VRAM-usage gauge ever shows the cliff, so this fdinfo residency sum is the only direct signal. The throughput penalty is roughly 16x.
Likely causes¶
- A model, context length, or batch size on this node exceeds the VRAM that is actually free.
- Co-resident workloads that individually fit but collectively do not (a second model tier got scheduled or scaled up from zero).
- A GPU-bound process leaking resident buffers over time.
Diagnose (read-only)¶
- Confirm the spill is persistent, not a scrape artifact:
xe_client_spilled_kib{instance="<instance>"}[30m] - Attribute it to a pod — this is the metric that actually answers "who":
topk(5, xe_dmem_pod_used_bytes{instance="<instance>"}) - Resolve the
pod_uidfrom that result to a real pod:kubectl get pods -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,UID:.metadata.uid | grep <pod_uid> - Check headroom at the same timestamp:
xe_dmem_used_bytes / xe_dmem_capacity_bytes - Inspect the identified workload for load-time OOM or fallback-to-host messages:
kubectl logs -n <namespace> <pod> --tail=200
Resolution — requires a Git change. Reduce the GPU-side footprint of the identified workload in
the repo: smaller quantization, lower context length, smaller batch, or fewer concurrently warm
model tiers. Commit the change and let ArgoCD reconcile. Deleting or restarting the pod live clears
the symptom without changing the sizing, and the alert returns on the next load. Consult
.workingdir2/gpu-unified-backend-design.md before changing which tiers are co-resident — the B60
cannot hold more than two warm tiers.
XeVramNearFull¶
Severity: warning
Fires when: xe_dmem_used_bytes / xe_dmem_capacity_bytes > 0.93 sustained for: 5m. The division matches on the region label, so this evaluates per VRAM region.
Interpretation: dmem never exceeds capacity because it uncharges on evict, which makes >93% the last warning before XeVramEvictCliff. Treat this as the actionable one — by the time the cliff fires, throughput is already gone.
Likely causes¶
- Gradual growth in resident buffers from a long-running inference workload.
- A newly scheduled or scaled-from-zero GPU workload consuming the remaining headroom.
- Allocator fragmentation leaving usable-but-unallocatable VRAM.
Diagnose (read-only)¶
- Current utilisation per region:
xe_dmem_used_bytes / xe_dmem_capacity_bytes - Per-pod breakdown of the charge:
sort_desc(xe_dmem_pod_used_bytes{instance="<instance>"}) - Map
pod_uidvalues to pods:kubectl get pods -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,UID:.metadata.uid | grep <pod_uid> - Check whether spill has already begun:
xe_client_spilled_kib{instance="<instance>"} - List GPU-requesting pods on the node (translate
<instance>to a node name first):kubectl get pods -A -o wide --field-selector spec.nodeName=<node>
Resolution — requires a Git change. Adjust the workload footprint or the set of co-resident tiers in the repo, exactly as for XeVramEvictCliff. If the fix is to stop scheduling further GPU work here, express that through the workload's own manifests in Git; do not cordon or drain the node by hand.
XeGtSustainedThrottle¶
Severity: warning
Fires when: xe_gt_throttle_status == 1 sustained for: 10m — a GT has been frequency-throttled continuously for ten minutes. Labels identify the specific card/tile/gt.
Likely causes¶
- Thermal ceiling — inadequate airflow or high ambient temperature at the site.
- Power/current limit under sustained load.
- PROCHOT asserted by another component in the chassis.
Diagnose (read-only)¶
- Identify why it is throttling — this distinguishes all three causes above:
xe_gt_throttle_reason{instance="<instance>"} == 1 - Measure the actual cost in clock terms:
xe_gt_act_freq_mhz{instance="<instance>"} - Correlate with load:
xe_dmem_used_bytes{instance="<instance>"}over the same window. - Confirm the exporter is still fresh before trusting a flat line:
node_textfile_mtime_seconds{instance="<instance>"}
Resolution — depends on the reason label¶
- Thermal or PROCHOT: physical. Check chassis airflow, fan health, and ambient temperature at the affected site. Host-level inspection goes through Proxmox (
qm guest execon the PVE host owning the VM), not a separate management interface. - Power limit: if the card is being held at a configured power ceiling, that ceiling lives in the node's Ansible/host configuration — changing it is a Git change, applied via the repo, not an ad-hoc
sysfswrite that a reboot discards. - A short throttle under genuine peak load is the hardware protecting itself and is not a defect. Escalate only when it is sustained across an otherwise normal workload, or when
xe_gt_act_freq_mhzshows a materially degraded clock.