[Klaud] [AMD][Power] fix: wait for AMD telemetry to cover the benchmark window end before stopping the monitor / 修复 AMD 遥测在基准窗口结束前被截断导致功耗校验失败的问题 - #2761
Conversation
…e killing the monitor / 修复:AMD 遥测覆盖停止请求后再终止功耗监控进程 The AMD stop path used an open-loop fixed sleep (interval+2) before killing the awk pipeline consumer, so rows still in the OS pipe or not yet emitted by a slow amd-smi iteration were lost and the last on-file tick could trail the aiperf window end by several seconds (run 32433563482 conc1: last tick 1787277605 vs end ...609.157), failing power validation with benchmark_window_not_bracketed. Replace the fixed tail with a bounded poll of the output CSV: wait until every observed GPU has a usable tick (numeric epoch timestamp, power > 0) stamped at the first whole second past stop entry — amd-smi stamps integer seconds, and the window end never exceeds the stop-entry wall clock, so that tick strictly covers any fractional window end. Bounded by AMD_MONITOR_STOP_TIMEOUT_S (default 30 s, 0 skips); non-epoch timestamps keep the legacy fixed tail; on timeout or early monitor death it warns and proceeds so aggregation attributes the failure (fail-safe, never fail-silent). AgentX INT/TERM/EXIT traps stop in abort mode (timeout 0) to keep signal teardown fast; the explicit post-replay stop performs the full wait. The NVIDIA branch is unchanged. AMD 停止路径原先在杀掉 awk 管道消费者前只做固定时长的 sleep,导致管道缓冲中的 采样行丢失、文件中最后一个时间戳可能落后于基准窗口结束数秒,功耗校验因 benchmark_window_not_bracketed 失败。本补丁改为有界轮询输出 CSV:等到每个 GPU 都有一条时间戳到达停止时刻下一整秒的可用采样(数值时间戳、功率 > 0)再终止监控, 由 AMD_MONITOR_STOP_TIMEOUT_S 限定(默认 30 秒,0 表示跳过);非 epoch 时间戳 回退到旧的固定等待;超时或监控提前退出时告警并继续,由聚合端归因。AgentX 的 INT/TERM/EXIT trap 以 abort 模式停止(跳过等待),正常收尾仍执行完整覆盖等待。 NVIDIA 分支不变。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oisoning with them / 修复:统计窗口外的异常功耗行,不再用其伪造窗口覆盖或污染校验 SMI teardown rows can carry N/A or 0 W power cells. Inside the +/-3 s ingest band but outside the formal window, an N/A row previously flipped validity via invalid_power_sample, and a 0 W row could silently satisfy end bracketing while corrupting the boundary interpolation. integrate_power now skips such rows (power missing, non-finite, or <= 0 outside [start, end]) and counts them per GPU in a new additive sidecar field boundary_degenerate_rows. In-window semantics are unchanged: in-window N/A still yields invalid_power_sample and in-window 0 W still integrates. No new reason codes; sidecar schema_version stays 1; aggregate rows are untouched. SMI 收尾阶段可能输出功率为 N/A 或 0 W 的行:在 ±3 秒摄取带内但位于正式窗口外时, N/A 行会误置 invalid_power_sample,0 W 行则可能伪造窗口末端覆盖并污染边界插值。 integrate_power 现在跳过此类行(窗口外且功率缺失、非有限或 <= 0),并按 GPU 计入 新增的 sidecar 字段 boundary_degenerate_rows。窗口内语义不变:窗口内 N/A 仍记 invalid_power_sample,窗口内 0 W 仍参与积分。不新增 reason 代码,sidecar schema_version 保持 1,聚合结果行结构不变。 Includes a regression fixture for run 32433563482 conc1 (MI355X integer-second ticks ending 4 s before the fractional aiperf window end) asserting the producer failure stays attributed as benchmark_window_not_bracketed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…epochs / 加固 AMD 停止覆盖轮询:容错非法超时值并归一化毫秒时间戳 - Sanitize AMD_MONITOR_STOP_TIMEOUT_S: a non-integer value (e.g. "30s") previously aborted stop_gpu_monitor via a bash arithmetic error under set -e, leaking the monitor process and skipping tail repair and the energy sidecar; it now warns and falls back to the default 30. - Normalize millisecond epoch timestamps (>1e12) in the awk coverage helper, mirroring _parse_timestamp in utils/aggregate_power.py, so a ms-stamping amd-smi build cannot trivially satisfy the stop target. - Shell-contract tests pin both behaviors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for the contribution! Please reach out to respective companies' CODEOWNER to fill in the latest PR_REVIEW_CHECKLIST.md before pinging core maintainer on Slack for review. In order for the signoff PR check bot to trigger, you must follow the PR_REVIEW_CHECKLIST.md template correctly, including the phrase For PR verification, add the PR authors are responsible for ensuring that after merging, all GitHub Action jobs fully pass. A lot of the time, failures are just flakes and simply re-running the failed jobs will fix it. See GitHub's docs on re-running failed jobs 感谢你的贡献!请联系相应公司的 CODEOWNER 填写最新的 PR_REVIEW_CHECKLIST.md,然后再在 Slack 上联系核心维护者进行审阅。为了触发 signoff PR 检查机器人,你必须正确遵循 PR_REVIEW_CHECKLIST.md 模板,包括保留英文语句 如需进行 PR 验证,请为此 PR 添加 PR 作者有责任确保合并后所有 GitHub Action 任务完全通过。 很多时候失败只是偶发抖动(flake),重新运行失败的任务即可解决。参见 GitHub 关于重新运行失败任务的文档 |
| timeout_s="${AMD_MONITOR_STOP_TIMEOUT_S:-30}" | ||
| if [[ ! "$timeout_s" =~ ^-?[0-9]+$ ]]; then | ||
| echo "[GPU Monitor] Warning: ignoring non-integer AMD_MONITOR_STOP_TIMEOUT_S='$timeout_s', using 30" >&2 | ||
| timeout_s=30 | ||
| fi | ||
| if [[ "$timeout_s" -le 0 ]]; then | ||
| return 0 | ||
| fi | ||
| target=$(( $(date +%s) + 1 )) | ||
| deadline=$(( target + timeout_s )) |
There was a problem hiding this comment.
🟡 _wait_for_amd_stop_coverage's timeout sanitization regex ^-?[0-9]+$ (line 299) accepts leading-zero digit strings like "08"/"09", but bash arithmetic treats a leading-0 numeral as octal; digits 8/9 make it an invalid octal literal. The very next arithmetic uses of $timeout_s -- [[ "$timeout_s" -le 0 ]] (line 303) and deadline=$(( target + timeout_s )) (line 307) -- both perform bash arithmetic evaluation and hit "value too great for base", which is a fatal bash expansion error that aborts the shell outright (not just a non-zero return), independent of set -e.
Extended reasoning...
Set AMD_MONITOR_STOP_TIMEOUT_S=08 (or any leading-zero value containing an 8/9 digit, e.g. "09", "018"). The regex guard added by this PR to catch non-integer timeouts (and specifically tested by test_amd_stop_survives_non_integer_timeout for "30s") lets "08" through as "valid", so no fallback-to-30 warning fires. The subsequent -le 0 test / deadline=$((...)) arithmetic then throws bash's octal "value too great for base" error, which is a fatal, non-interactive-shell-terminating error class -- reproducing (and likely worsening, since it can kill the whole caller script rather than just stop_gpu_monitor) the exact leaked-monitor/skipped-tail-repair/skipped-energy-sidecar failure this PR's sanitization was specifically added to prevent. Fix: strip/reject leading zeros (or force base-10 with 10#$timeout_s) before using $timeout_s in any arithmetic context.
Verification: Severity: nit. The regex at benchmark_lib.sh:299 (^-?[0-9]+$) accepts leading-zero strings like "08"/"09"/"018", so the non-integer fallback (line 300-301) does not fire. Bash arithmetic then treats a leading-0 numeral as octal, and digits 8/9 make it an invalid octal literal. Line 303 [[ "$timeout_s" -le 0 ]] is an if condition (set -e exempt: it prints the error but continues),…
| trap '_stop_agentx_power_monitor abort' EXIT | ||
| trap '_stop_agentx_power_monitor abort; exit 130' INT | ||
| trap '_stop_agentx_power_monitor abort; exit 143' TERM | ||
| fi | ||
|
|
||
| echo "$REPLAY_CMD" > "$result_dir/benchmark_command.txt" |
There was a problem hiding this comment.
🟣 The explicit post-replay stop (_stop_agentx_power_monitor at line 2385) runs stop_gpu_monitor's now-up-to-30s AMD coverage wait (_wait_for_amd_stop_coverage, AMD_MONITOR_STOP_TIMEOUT_S default 30) while the INT/TERM traps installed at lines 2370-2372 are still active (they're only removed at line 2386, after the call returns). If SIGINT/SIGTERM arrives during that wait, the trap fires _stop_agentx_power_monitor abort; exit N; since agentx_monitor_stopped was already set to 1 at function entry (line 2317, before calling stop_gpu_monitor), the trap's guarded body is skipped but the trailing exit 130/exit 143 still runs unconditionally, killing the subshell immediately.
Extended reasoning...
A user Ctrl-C's (or a scheduler SIGTERMs) the benchmark while the explicit post-replay stop is mid-poll waiting for AMD telemetry coverage. The process exits via the trap's exit 130/143 before stop_gpu_monitor reaches kill/wait on GPU_MONITOR_PID, tail repair, or the end-of-run amd-smi energy sidecar — leaking the orphaned awk/amd-smi consumer process and dropping the energy_end.csv artifact. This same trap-during-explicit-stop race existed pre-diff too, but was bounded to the old fixed ~3s sleep window; this diff widens the exposed window roughly 10x (up to 30s by default) by design, making the previously-negligible race a realistically triggerable teardown gap. A correct fix must make the explicit stop itself not re-enter abort-truncation (e.g. temporarily disable/replace the INT/TERM traps for the duration of the explicit stop, or have stop_gpu_monitor ignore/defer signals during its wait) rather than leaving the pre-existing unconditional exit reachable mid-wait.
Verification: pre-existing. The race is real and reachable: line 2385 calls _stop_agentx_power_monitor (full-wait mode) while the INT/TERM traps at lines 2371-2372 are still armed (removed only at 2386). _stop_agentx_power_monitor sets agentx_monitor_stopped=1 before calling stop_gpu_monitor, which on the AMD path enters _wait_for_amd_stop_coverage (a while :; ... sleep 1 loop bounded by…
|
Superseded by #2767 (branch renamed to fix/amd-agentx-window-bracketing); identical diff. |
Closes gap G10 (PLAN-05). Fixes the
benchmark_window_not_bracketedpower-validation failures on AMD single-node AgentX runs (e.g. run 32433563482,kimik3-fp4-mi355x-atom-agentic-mtp, 3 of 4 points failed).Problem
On run 32433563482 conc1 the amd-smi stream's last CSV tick is
1787277605(integer epoch second) while the aiperf window end is1787277609.157— telemetry stops ~4 s before the formal window end. Three interacting mechanisms, none bounded by the old fixedsleep interval+2before killing the pipeline:GPU_MONITOR_PIDis theawkconsumer, notamd-smi; rows sitting in the OS pipe (awk's per-rowfflush()can block on NFS-backed$result_dir) are discarded at kill.amd-smi metric -p -c -t -uiteration over 8 GPUs takes non-trivial time, so the newest emitted tick can trail wall clock by seconds.invalid_power_sample, and a 0 W row past the window end could silently fake end bracketing while corrupting the boundary interpolation.Solution
benchmarks/benchmark_lib.sh— deterministic poll-until-covered stop.stop_gpu_monitor's AMD branch now polls$GPU_METRICS_CSVuntil every observed GPU has a usable tick (numeric epoch timestamp, numeric power > 0) stamped ≥ floor(stop-entry epoch)+1, then kills. The window end is always ≤ the stop-entry wall clock and amd-smi stamps integer seconds, so that tick strictly covers any fractional window end — no aiperf knowledge needed in the shell. Details:AMD_MONITOR_STOP_TIMEOUT_S(default 30 s, env-overridable,0skips the wait; a non-integer value warns and falls back to 30 instead of aborting the stop). On timeout or early monitor death: warn and proceed — aggregation then flags the point exactly as today (fail-safe, never fail-silent).interval+2fixed tail once; millisecond epochs (>1e12) are normalized to seconds, mirroring_parse_timestampinutils/aggregate_power.py.INT/TERM/EXITtraps stop in abort mode (AMD_MONITOR_STOP_TIMEOUT_S=0): a cancelled run's power validity is moot and teardown stays fast. The explicit post-replay stop performs the full wait. Idempotency viaagentx_monitor_stoppedpreserved.stop_gpu_monitor(allbenchmarks/single_node/fixed_seq_len/*recipes), not just AgentX.utils/aggregate_power.py— explicit boundary-degenerate handling.integrate_powernow skips-and-counts rows inside the ±max_sample_gap_singest band but outside the formal window whose power is unparseable, non-finite, or ≤ 0, into a new per-GPU sidecar fieldboundary_degenerate_rows("unknown"buckets rows without a GPU identity). They are never used for bracketing and never poison validity. In-window semantics are byte-identical: in-window N/A still ⇒invalid_power_sample; in-window 0 W still integrates.Contract compatibility (PLAN-06 / PLAN-07)
benchmark_window_not_bracketedkeeps meaning "a real usable sample does not bracket the boundary".power_validstill numeric; nopower_invalid_reasonson aggregates).schema_versionstays 1;boundary_degenerate_rowsis additive and readers tolerate its absence.Validity flips on re-aggregated legacy artifacts (intentional)
Only when explicitly re-running aggregation on old raw artifacts (e.g. the recover-failed-ingest path):
benchmark_window_not_bracketed(they were silently corrupt before).Already-ingested aggregate rows are untouched.
Cost
Normal case ~1–2 s at stop (first whole second after stop entry). Pathological hang: up to 30 s (was 3 s), bounded, logged, env-tunable.
Tests
bash -n benchmarks/benchmark_lib.shclean.utils/test_aggregate_power.py+utils/agentic/aggregation/test_power_lifecycle.py: 74 passed (57 + 17)....609.157497, real power values, quoted list cells, CR endings, blank inter-tick lines, plus the documented synthetic N/A / 0 W teardown shapes) assertingbenchmark_window_not_bracketedattribution.stop_gpu_monitoragainst a scripted producer (coverage wait for all GPUs; degenerate rows never satisfy coverage → timeout warning; per-GPU min semantics; ISO-timestamp legacy fallback; non-integer timeout survival; millisecond-epoch normalization), plus the signal test now pins abort mode (AMD_MONITOR_STOP_TIMEOUT_S=0at trap-driven stop) and the new trap strings.utils/test_process_result.py: the AMD stop test now pins the new contract (covered tick ⇒ no legacy sleep, energy snapshot still written); truncated-row repair test runs with the wait skipped.test_aggregate_power*,test_process_result,utils/agentic/aggregation/): 224 passed.utils/suite: 804 passed; the only 2 failures (utils/evals/test_run_eval_dispatch.py) reproduce identically on cleanorigin/mainin this local environment (macOS temp-path issue), unrelated to power.Review
Two independent review passes; every finding verified and resolved:
AMD_MONITOR_STOP_TIMEOUT_S(e.g."30s") aborted the wholestop_gpu_monitorcall via a bash arithmetic error underset -e— leaking the monitor process and skipping tail repair and the energy sidecar. Fixed: the timeout is sanitized once before any arithmetic (warn + fall back to 30); the timeout warning now always prints a real number. Pinned bytest_amd_stop_survives_non_integer_timeout._parse_timestamp. Pinned bytest_amd_stop_normalizes_millisecond_epoch_timestamps.Evidence note
The retrieved run-32433563482 conc1 artifact shows the trailing rows all carry valid socket_power (254–264 W) with N/A activity/voltage cells — the observed failure is purely mechanisms (1)+(2) (the stream stops 4 s early). The degenerate-row handling hardens against mechanism (3) using the plan's documented synthetic shapes.
Hardware smoke (for PLAN-14; not a merge gate for this PR, but the gap-closure gate)
Re-sweep
kimik3-fp4-mi355x-atom-agentic-mtp(configs/amd-master.yaml:656) from this PR branch on the MI355X ATOM cluster. For each point (conc 1, 4, 8, 10) in the raw-results artifact:agg_*.jsonhaspower_valid == 1and finiteavg_power_w/total_gpu_energy_j;results/**/power_validation.jsonhasreasons == [], and for every GPU the newest usablegpu_metrics.csvtick ≥benchmark_window.end_time_unix;boundary_degenerate_rows(if non-empty) shows teardown rows were counted, not integrated;accumulator_check.within_tolerance == truewhere the sidecar snapshots exist;never covered the stop requestwarning, and per-point wall time increased by ≲ a few seconds versus the failing run.If any point still fails with
benchmark_window_not_bracketed, attach its CSV tail +power_validation.jsonto this PR before iterating (per-GPUsampling_gap_exceededwould instead indicate the separate cadence issue listed under PLAN-05 follow-ups).🤖 Generated with Claude Code
Note
Medium Risk
Changes benchmark teardown timing and power-validation semantics for AMD telemetry and boundary rows; affects AgentX power metrics but is bounded, logged, and covered by contract tests.
Overview
Fixes AMD AgentX
benchmark_window_not_bracketedfailures when amd-smi integer-second ticks trail the fractional benchmark window end or pipe-buffered rows are lost at kill.stop_gpu_monitor(AMD) no longer uses a fixedsleep interval+2before killing the watch pipeline. It pollsgpu_metrics.csvuntil every GPU has a usable sample (numeric epoch timestamp, power > 0) at or after the first whole second past stop entry, bounded byAMD_MONITOR_STOP_TIMEOUT_S(default 30s;0skips the wait). ISO timestamps fall back to the legacy tail sleep; ms epochs are normalized likeaggregate_power. AgentX INT/TERM/EXIT traps call stop in abort mode (AMD_MONITOR_STOP_TIMEOUT_S=0); the normal post-replay stop keeps the full wait.integrate_powerskips N/A, non-finite, or ≤0 W rows that sit in the ingest band but outside the formal window, counting them in sidecarboundary_degenerate_rowsinstead of invalidating power or faking end bracketing (0 W tails now fail bracketing explicitly). Tests cover the shell poll, signal abort path, and aggregation regressions including the MI355X run shape.Reviewed by Cursor Bugbot for commit 3c08814. Bugbot is set up for automated code reviews on this repo. Configure here.