fix(chart): make GPU alerts exporter-agnostic instead of DCGM-only - #1372
fix(chart): make GPU alerts exporter-agnostic instead of DCGM-only#1372Tanguille wants to merge 6 commits into
Conversation
DCGM_FI_DEV_* series only exist on NVIDIA clusters, so the GPU alert group installed clean on AMD (drm-exporter) clusters and never fired or errored — indistinguishable from healthy GPUs (defilantech#1356). rules.gpu.vendor (nvidia|amd, default nvidia) selects between the DCGM expressions and an AMD set over drm-exporter's drm_engine_utilization_ratio/drm_temperature_celsius/drm_power_watts/ drm_memory_*_bytes. An unrecognized vendor fails the schema and the template render instead of installing rules that match nothing. Both vendors also get a new GPUMetricsMissing alert so a dead exporter (or a vendor/cluster mismatch) is visible rather than silent, same as ControllerMetricsMissing already does for the controller scrape target. Signed-off-by: Tanguille <91473554+Tanguille@users.noreply.github.com>
Move per-vendor metric names into llmkube.gpuMetricNames in _helpers.tpl instead of duplicating all five rule bodies per vendor; a third vendor is now one map entry, not five more rules. Drop the unreachable fail branch (values.schema.json enum is the only guard Helm ever exercises) and rename its test to state it asserts schema rejection, not template code. Note in values.yaml that the alert vendor is narrower than the Model CRD's vendor field on purpose. Signed-off-by: Tanguille <91473554+Tanguille@users.noreply.github.com>
…-fallback Live AMD testing found drm_temperature_celsius carries per-sensor series (edge/junction/mem) so one threshold meant different things per exporter, and GPUMemoryPressure fired permanently on healthy inference hosts since SGLang/vLLM preallocate the KV pool to ~99% VRAM by design. Review also found the drm_* metric family disagreed with the chart's own AMD dashboard and dashboard_sync_test.go allowlist, which both expect amdgpu_*. Rules are now keyed by role (util/temp/mem/power) and OR every known exporter (DCGM, amdgpu-sysfs, drm) in one normalized expression, pinning sensor="edge" and pool="vram" where needed. rules.gpu.vendor is gone. GPUMemoryPressure is opt-in (rules.gpu.memoryPressure.enabled, default false). Signed-off-by: Tanguille <91473554+Tanguille@users.noreply.github.com>
A bare node_hwmon_temp_celsius is every hwmon sensor on the node, not the GPU. Measured on a live cluster it fired GPUHighTemperature at 89C off a chipset sensor with no GPU involved, while the GPU itself sat at 69C. Scoped through node_hwmon_chip_names, the same join amd-gpu-observability.json already uses, and pinned to temp1 so the amdgpu hwmon split (temp1/temp2/temp3 = edge/junction/mem) matches the edge sensor the other branches report. Signed-off-by: Tanguille <91473554+Tanguille@users.noreply.github.com>
Drops the stale block describing the removed vendor parameter. Signed-off-by: Tanguille <91473554+Tanguille@users.noreply.github.com>
VRAM utilization on an inference host reports the fraction the runtime was configured to reserve, not memory pressure. The old wording implied 99% is inherent; it is whatever mem-fraction-static or gpu-memory-utilization was set to, and a lower setting is equally healthy. Signed-off-by: Tanguille <91473554+Tanguille@users.noreply.github.com>
Defilan
left a comment
There was a problem hiding this comment.
Tanguille, excellent, well-researched change, thank you. I verified every AMD/DCGM/drm metric name against amd-gpu-observability.json and the dashboard_sync_test.go allowlist and they all check out, including the correct call to source temp/power from node_hwmon_* rather than a nonexistent amdgpu_temperature_celsius.
One blocker: GPUMetricsMissing (prometheusrule.yaml:75) never fires. PromQL and matches only on exactly-equal label sets, and absent(drm_engine_utilization_ratio{engine="gpu"}) produces {engine="gpu"} while the other two absent() terms produce {}, so the intersection is empty even when all three exporter families are absent, the exact silent no-op #1356 exists to kill. promtool test rules against an empty series set confirms it. Drop the {engine="gpu"} matcher inside that absent() call (keep it in the util alert, where it's a value comparison and unaffected), and update the expected string in prometheusrule_test.yaml:196 to match.
Two smaller things: please add the Assisted-by: disclosure line to the PR body per CONTRIBUTING, and consider keeping {{ $labels.instance }} in the descriptions so alerts still name a GPU (instance is present on all three families). I'll authorize the CI runs so we get a green signal.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
CI heads-up, and it's a real consequence of the change rather than a flake. The That assertion pins the old DCGM-only alert shape. Your generalized so the custom threshold is applied correctly, but the contiguous string Since this PR intentionally changes the alert shape, please update that assertion in the same pass, e.g.: if ! grep -qE 'DCGM_FI_DEV_GPU_UTIL.*> 95' /tmp/custom-thresholds.yaml; thenwhich confirms the DCGM term is still present and the custom threshold applied, without requiring the old contiguous form. That, plus the |
Fixes #1356. Off
main, independent of #1368, touches no dashboard.All four GPU rules key on
DCGM_FI_DEV_*whilerules.gpu.enableddefaults to true, so a cluster without DCGM gets four rules that never fire and never error.Change
Each rule ORs the normalized expression for every exporter the chart knows, so the one a cluster runs is the one that evaluates.
rules.gpu.vendoris gone: nothing needs configuring.DCGM_FI_DEV_GPU_UTIL or amdgpu_gpu_busy_percent or drm_engine_utilization_ratio{engine="gpu"} * 100DCGM_FI_DEV_GPU_TEMP or node_hwmon_temp_celsius{sensor="temp1"} * on(chip, instance) group_left() node_hwmon_chip_names{chip_name="amdgpu"} or drm_temperature_celsius{sensor="edge"}DCGM_FI_DEV_POWER_USAGE or node_hwmon_power_watt * on(chip, instance) group_left() node_hwmon_chip_names{chip_name="amdgpu"} or drm_power_watts(DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL) * 100 or (amdgpu_vram_used_bytes / amdgpu_vram_total_bytes) * 100 or (drm_memory_used_bytes{pool="vram"} / drm_memory_total_bytes{pool="vram"}) * 100Each union is parenthesised before the comparison:
(a or b or c) > 90, nota or b or (c > 90). Rule bodies are written once from a role-keyed helper.GPUMetricsMissingis now absent() over all three families, ANDed.GPUMemoryPressureships off by default (rules.gpu.memoryPressure.enabled): on an inference host VRAM utilization reports the fraction the runtime was configured to reserve (mem-fraction-static,gpu-memory-utilization), not pressure. It is held for the process lifetime and does not fall under load, so no static threshold on it distinguishes healthy from saturated.Normalization, measured on a live AMD cluster
sensor="edge"/temp1node_hwmon_chip_names{chip_name="amdgpu"}, as amd-gpu-observability.json doespool={vram,smem}; unfiltered denominator worked only by implicit label matchingpool="vram"both sidesEvidence
Rendered from this branch, evaluated against live VictoriaMetrics:
Both exporters run on that cluster, so the temp union returns the node_hwmon and drm branches for the same GPU: they agree at 69 C.
helm unittest67/67,go test ./internal/metrics/...ok,helm lintclean. The nvidia path renders byte-identical to the current release apart fromGPUMetricsMissing.If you would rather keep an explicit vendor knob, or disagree with any of the normalization choices, say so and I will revert it.