Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 294 additions & 16 deletions .claude/skills/review-pr/SKILL.md

Large diffs are not rendered by default.

335 changes: 335 additions & 0 deletions .claude/skills/validate-kernel-pr/SKILL.md

Large diffs are not rendered by default.

181 changes: 181 additions & 0 deletions .claude/skills/validate-kernel-pr/pick-idle-gpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
"""Select an AMD GPU that stays idle across a sampling window."""

from __future__ import annotations

import argparse
import sys
import time
from pathlib import Path


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--samples", type=int, default=10)
parser.add_argument("--interval", type=float, default=1.0)
parser.add_argument("--max-busy", type=int, default=2)
parser.add_argument("--max-used-gib", type=float, default=2.0)
parser.add_argument("--min-free-gib", type=float, default=16.0)
parser.add_argument("--quiet", action="store_true")
args = parser.parse_args()
if args.samples < 1 or args.interval < 0:
parser.error("samples must be positive and interval must be non-negative")
if args.max_busy < 0 or args.max_used_gib < 0 or args.min_free_gib < 0:
parser.error("thresholds must be non-negative")
return args


def import_amdsmi():
try:
import amdsmi

return amdsmi
except ImportError:
for candidate in (
Path("/usr/lib/python3/dist-packages"),
Path(
f"/usr/lib/python{sys.version_info.major}.{sys.version_info.minor}/dist-packages"
),
Path("/opt/rocm/libexec/amdsmi_cli"),
):
if candidate.is_dir() and str(candidate) not in sys.path:
sys.path.append(str(candidate))
import amdsmi

return amdsmi


def sample(amdsmi, count: int, interval: float) -> tuple[list[dict], int]:
gpus = []
for smi_index, handle in enumerate(amdsmi.amdsmi_get_processor_handles()):
enumeration = amdsmi.amdsmi_get_gpu_enumeration_info(handle)
gpus.append(
{
"smi_index": smi_index,
"hip_index": enumeration.get("hip_id"),
"bdf": amdsmi.amdsmi_get_gpu_device_bdf(handle),
"handle": handle,
"gfx": [],
"umc": [],
}
)
peak_concurrent = 0
for sample_index in range(count):
if sample_index:
time.sleep(interval)
busy = 0
for gpu in gpus:
activity = amdsmi.amdsmi_get_gpu_activity(gpu["handle"])
gfx = activity.get("gfx_activity")
umc = activity.get("umc_activity")
gfx = gfx if isinstance(gfx, int) else 0
umc = umc if isinstance(umc, int) else 0
gpu["gfx"].append(gfx)
gpu["umc"].append(umc)
busy += int(gfx > 5)
peak_concurrent = max(peak_concurrent, busy)
for gpu in gpus:
memory = amdsmi.amdsmi_get_gpu_vram_usage(gpu["handle"])
used = memory["vram_used"] / 1024
total = memory["vram_total"] / 1024
gpu.update(
{
"used_gib": used,
"free_gib": total - used,
"peak_gfx": max(gpu["gfx"]),
"mean_gfx": sum(gpu["gfx"]) / len(gpu["gfx"]),
"peak_umc": max(gpu["umc"]),
}
)
del gpu["handle"]
return gpus, peak_concurrent


def main() -> int:
args = parse_args()
try:
amdsmi = import_amdsmi()
except ImportError as error:
print(f"AMD SMI import failed: {error}", file=sys.stderr)
return 2
try:
amdsmi.amdsmi_init()
try:
gpus, peak_concurrent = sample(amdsmi, args.samples, args.interval)
finally:
amdsmi.amdsmi_shut_down()
except (OSError, amdsmi.AmdSmiException) as error:
print(f"AMD SMI probe failed: {error}", file=sys.stderr)
return 2

eligible = [
gpu
for gpu in gpus
if gpu["hip_index"] is not None
and gpu["peak_gfx"] <= args.max_busy
and gpu["used_gib"] <= args.max_used_gib
and gpu["free_gib"] >= args.min_free_gib
]
eligible.sort(
key=lambda gpu: (
gpu["peak_gfx"],
gpu["mean_gfx"],
gpu["used_gib"],
-gpu["free_gib"],
)
)

if not args.quiet:
print(
f"Sampled {args.samples} times over {args.samples * args.interval:.0f}s",
file=sys.stderr,
)
print(
f"{'smi':>4} {'hip':>4} {'bdf':<14} {'peak%':>6} {'mean%':>6} "
f"{'umc%':>5} {'used':>9} {'free':>9} verdict",
file=sys.stderr,
)
for gpu in sorted(gpus, key=lambda item: item["smi_index"]):
if gpu["hip_index"] is None:
verdict = "SKIP no hip_id"
elif gpu["peak_gfx"] > args.max_busy:
verdict = f"BUSY peaked {gpu['peak_gfx']}%"
elif gpu["used_gib"] > args.max_used_gib:
verdict = f"HELD {gpu['used_gib']:.1f} GiB used"
elif gpu["free_gib"] < args.min_free_gib:
verdict = f"FULL {gpu['free_gib']:.1f} GiB free"
else:
verdict = "idle"
hip_index = "-" if gpu["hip_index"] is None else gpu["hip_index"]
print(
f"{gpu['smi_index']:>4} {hip_index:>4} {gpu['bdf']:<14} "
f"{gpu['peak_gfx']:>6} {gpu['mean_gfx']:>6.1f} "
f"{gpu['peak_umc']:>5} {gpu['used_gib']:>6.1f} GiB "
f"{gpu['free_gib']:>6.1f} GiB {verdict}",
file=sys.stderr,
)
if gpus and peak_concurrent >= len(gpus) - 1:
print(
f"WARNING: {peak_concurrent}/{len(gpus)} GPUs were busy together; "
"shared fabric/power may perturb the run.",
file=sys.stderr,
)

if not eligible:
print(
"No GPU stayed below the activity and resident-memory thresholds.",
file=sys.stderr,
)
return 1
selected = eligible[0]
if not args.quiet:
print(
f"Chose HIP index {selected['hip_index']} "
f"(amd-smi {selected['smi_index']}, {selected['bdf']}).",
file=sys.stderr,
)
print(selected["hip_index"])
return 0


if __name__ == "__main__":
raise SystemExit(main())
23 changes: 23 additions & 0 deletions .claude/skills/validate-kernel-pr/production_scale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Production scale — the facts a 32-bit overflow judgement needs

Whether an index x stride product can exceed 2^31 depends on deployment scale, which the
diff does not contain. Without these numbers a reviewer cannot name a triggering case and
correctly clears every candidate above. Snapshot -- keep sourced and current; a stale row
produces a confidently wrong verdict.

The first three rows are partly **in-sample**: they come from the problem statements of the
fix PRs that also supplied the known-defect labels used during development. They demonstrate
that scale facts can make the arithmetic decidable; they do not establish holdout recall or
generalize to deployments with different limits. Replace them with deployment-config sources
before treating this table as production policy.

| quantity | scale | source |
|---|---|---|
| DeepSeek-V4 unified KV pool | ~150M rows | aiter#4680 problem statement |
| Sparse-indexer decode batch | up to 512 concurrent sequences | aiter#4244 problem statement |
| KV stride passed by callers | may be a per-group page size, not a per-token stride, when the cache is a strided view into one shared allocation | aiter#4774 problem statement |
| Production token range | 1 -> 16384 per launch | P2, review-pr |
| MoE production configs | DSv4 E=385/topk=7; GPT-OSS 120B; Kimi-K2.5 | P2, review-pr |

Worked example: `stride = KVBlockSize * index_dim`; at `KVBlockSize=256, index_dim=132` a
block index of ~63.5K puts the product past 2^31 -- well inside a 150M-row pool.
Loading
Loading