fix(sbd): stop reporting GPU telemetry that was read but never parsed - #1445
fix(sbd): stop reporting GPU telemetry that was read but never parsed#1445luochen-amd wants to merge 3 commits into
Conversation
20a288e to
cf09ee9
Compare
CI E2E report — ❌ Timeout
|
|
Reviewed for correctness only. I read the diff plus The core fix looks right and I did not find a regression. The multi-node injector's flat Four things I'd call bugs. 1. The doc claims the key is absent, but it is always present as The new doc paragraph says "gpu_monitor_aggregate": _aggregate_gpu_monitor(all_reports, warnings),
2.
That is the same "well-formed section with heavy-looking sample counts behind nothing measured" shape this PR sets out to remove. 3. weights = [max(1.0, _to_float(b.get("sample_count")) or 1.0) for b in blocks]
4.
The result is a max below the avg. A related variant, Minor and ignorable: I'd fix #4 before merge since it is cheap and squarely on-topic. #1–#3 are fine to fold into a follow-up. |
3b38b51 to
f2acf52
Compare
|
One remaining telemetry gap: this fixes parsing for power, temperature, and GPU clock, but it still does not provide GPU utilization or VRAM usage. The multi-node injector already emits flat As a result, |
|
Added, with one finding that changes what "coverage for the Magpie nested shape" can mean. Multi-node: done. Single-node: the readings do not exist to collect. I checked 13 production {"sample_count": 59, "duration_sec": 124.02,
"temperature_c": {"min": 46.0, "max": 68.0, "avg": 61.3},
"gpu_clock_mhz": {"min": 131, "max": 2096, "avg": 1411.6},
"mem_clock_mhz": {"min": 900, "max": 1300, "avg": 1171.2},
"power_watts": {"min": 149.0, "max": 751.0, "avg": 524.3}}No utilization, no VRAM, in any of them. The regression fixture was thin because the producer is thin. And Magpie is external ( So single-node reports Coverage. Both shapes are tested: the flat multi-node samples, and the nested End to end over those 13 real reports: {"samples": 1984, "blocks": 13,
"avg_power_w": 347.8, "max_power_w": 753.0,
"avg_temp_c": 57.52, "max_temp_c": 78.0, "avg_clock_mhz": 1732.54,
"avg_gpu_util_pct": null, "max_gpu_util_pct": null,
"avg_vram_pct": null, "max_vram_pct": null} |
Every single-node session shipped a `gpu_monitor_aggregate` of all zeros.
Not "no data" -- the reports were found and opened. A production breakdown
showed `samples: 27` next to `avg_power_w: 0.0` while the same session's
benchmark_report.json carried `power_watts avg 774.8`. Twenty-seven reports
were read and every field came back empty.
Three independent defects stacked:
* **Key names.** Magpie writes `power_watts` / `gpu_clock_mhz`; the aggregator
looked for `power_w` / `power` and `clock_mhz` / `sclk_mhz`. Nothing matched.
* **Shape.** `temperature_c` did match by name, but Magpie's value is a nested
`{min, max, avg}` block, and `_to_float` returns None for a dict. The one key
that lined up was dropped silently anyway.
* **Fallback.** `_avg("power_w") or _avg("power")` used `or` while `_avg`
returned `0.0` for "absent". A metric nobody sampled and a card genuinely
drawing 0.0 W were the same value, so the alias fired on a real reading and
an all-absent metric shipped as a plausible-looking zero.
Each defect alone would have surfaced as obviously-missing data. Together they
produced a well-formed section of zeros that nobody could distinguish from a
quiet GPU, which is why this lasted.
The fix reads both producer shapes through one `_gpu_metric` helper, carries
the current Magpie key names first in each alias tuple, and makes every metric
tri-state -- `float | None`, never coerced to 0.0. Test with `is None`, not
truthiness.
Two changes go slightly past a minimal repair; trim them if you disagree:
* `samples` now counts underlying samples (the sum of each block's
`sample_count`) rather than blocks, which is what its docstring always
claimed. `blocks` is added alongside it because the two answer different
questions -- `blocks` says reports were found, `samples` says how much
measurement stands behind the numbers. That distinction is exactly what made
this bug findable.
* Averages are weighted by `sample_count`. Unweighted, a 10-sample block pulls
the session mean as hard as a 10,000-sample one.
Adds the function's first unit tests (it had none). Eight cases, seven of which
fail against the pre-fix code; the first is the verbatim production Magpie
block that used to aggregate to zeros. Schema change is additive, so
`schema_version` is unchanged, and the markdown reporter does not render
telemetry.
Refs: kvcache-metrics-plan.md 4.4, 8.2
Co-authored-by: Cursor <cursoragent@cursor.com>
…dy read
Review follow-ups. All four, not just the one flagged as pre-merge, since each
is a few lines and they are the same class of defect the PR is about.
**One alias per block, chosen once.** `_gpu_metric` fell through to the next
alias whenever the key was present but the requested statistic was not, so the
mean could come from `power_watts` while the peak came from a stale `power_w`
in the same block -- reporting a maximum below the average. The alias is now
resolved once, on "key present and at least one statistic parses", and both
statistics read from it. A statistic the winning alias omits stays `None`
rather than being borrowed from a sibling key.
**`samples` counted blocks that measured nothing.** It summed `sample_count`
across every block regardless of whether that block yielded a metric, so
`{"sample_count": 27000, "duration_sec": 10.0}` reported 27,000 samples beside
a row of `None`. Only contributing blocks count now. `blocks` still counts
every entry read, because the two answer different questions and the split is
what makes a collection gap legible.
**`sample_count: 0` was promoted to one.** `max(1.0, ... or 1.0)` turned a
monitor that started and sampled nothing into one sample -- the same conflation
of "no reading" with "a reading of zero" that this PR exists to remove. Absent
and zero are now distinguished explicitly.
**The section is omitted, as documented.** The docs say `gpu_monitor_aggregate`
is absent when no report carried a block; `collect_telemetry` wrote the key
unconditionally, so `{}` shipped instead and a consumer testing for absence
disagreed with one testing for content. Fixed on the code side rather than by
weakening the doc: absence is the more useful contract, and `Telemetry` is
`total=False`.
Also `int(sum(...))` -> `round(...)`, which only matters for a non-integral
`sample_count` but truncating was not deliberate.
Five new tests, four of which fail against the previous commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
…nd heat
Power, temperature and clock cannot say whether a slow round was compute-idle
or short of memory, and the multi-node harvester was already writing
gpu_util_pct / vram_pct that _aggregate_gpu_monitor then dropped on the floor.
Read them through the same alias-resolution and tri-state path as the existing
metrics, so both the flat multi-node samples and a nested {min,max,avg} block
parse.
The aliases are percent-named only. An absolute reading -- vram_used_mb,
memory_used_bytes -- is a different quantity, and letting one fall through into
a field called _pct would put 81920 where a percentage belongs, which is the
same class of bug as the one this branch started from.
Single-node stays honest rather than convenient: across 13 production
benchmark_report.json files, Magpie's GPUMonitor emits only sample_count,
duration_sec, temperature_c, gpu_clock_mhz, mem_clock_mhz and power_watts, and
samples no occupancy at all. Those sessions therefore report None for both new
metrics -- not 0.0, which would assert an idle GPU that was never measured.
Co-authored-by: Cursor <cursoragent@cursor.com>
eced521 to
5ef02d8
Compare
Problem
Every single-node session ships a
gpu_monitor_aggregateof all zeros.Not "no data" — the reports were found and opened. A production breakdown shows
samples: 27next toavg_power_w: 0.0, while the same session'sbenchmark_report.jsoncarriespower_watts avg 774.8. Twenty-seven reportswere read and every field came back empty.
The archive tells the same story:
gpu_metrics.csvhas 2 objects, newest2026-06-14, against 11,620
server.logs over the same window.Root cause
Three independent defects stacked in
_aggregate_gpu_monitor:power_watts/gpu_clock_mhz; the aggregator looked forpower_w/powerandclock_mhz/sclk_mhz. Nothing matched.temperature_cdid match by name, but Magpie's value is a nested{min, max, avg}block and_to_floatreturnsNonefor a dict. The one key that lined up was dropped silently anyway._avg("power_w") or _avg("power")usedorwhile_avgreturned0.0for "absent". A metric nobody sampled and a card genuinely drawing 0.0 W were the same value, so the alias fired on a real reading and an all-absent metric shipped as a plausible-looking zero.Any one of these alone would have surfaced as obviously-missing data. Together
they produce a well-formed section of zeros that nobody can distinguish from a
quiet GPU, which is why it lasted.
Fix
Both producer shapes are read through one
_gpu_metrichelper, the currentMagpie key names lead each alias tuple, and every metric is tri-state —
float | None, never coerced to0.0. Read them withis None, nottruthiness: a real
0.0is falsy too.Two changes go slightly past a minimal repair
Trim them if you disagree:
samplesnow counts underlying samples (the sum of each block'ssample_count) rather than blocks, which is what its docstring alwaysclaimed.
blocksis added alongside because the two answer differentquestions —
blockssays reports were found,samplessays how muchmeasurement stands behind the numbers. That distinction is exactly what made
this bug findable.
sample_count. Unweighted, a 10-sample block pullsthe session mean as hard as a 10,000-sample one.
Verification
Adds the function's first unit tests — it had none. Eight cases, seven of
which fail against the pre-fix code; the first is the verbatim production
Magpie block that used to aggregate to zeros.
Schema change is additive, so
schema_versionis unchanged, and the markdownreporter does not render telemetry.
Notes
Independent of the KV-cache work in #1446 — no shared files, can merge in
either order. Worth merging first: single-node GPU data is being lost right now,
and the measurement-validity checks planned on top of this plan read
clock_mhz, which is one of the fields currently stuck at zero.Refs:
kvcache-metrics-plan.md§4.4, §8.2