Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/federation.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@
"path": "skills/lemonade-router-builder"
}
]
},
{
"repo": "amd/ZenDNN-pytorch-plugin",
"license": "Apache-2.0",
"skills": [
{
"path": "skills/serving-llms-on-epyc",
"as": "serving-llms-on-epyc"
}
]
}
]
}
10 changes: 10 additions & 0 deletions skills/serving-llms-on-epyc/.federated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"source": "amd-zendnn-pytorch-plugin",
"repo": "amd/ZenDNN-pytorch-plugin",
"ref": "main",
"commit": "6b2db6f2a47cc9b603570f6b4a93c37e5d04dba7",
"path": "skills/serving-llms-on-epyc",
"license": "Apache-2.0",
"content_hash": "eaa5899ac5e840f765fb855097d86ba9748f6dc6d0892a55e2394aaf37599733",
"imported_at": "2026-10-09T10:09:25Z"
}
2 changes: 1 addition & 1 deletion skills/serving-llms-on-epyc/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
## Hardware support

This recipe supports the **AMD EPYC 9000 server series** for now: Genoa (9004),
Turin (9005), and 6th Gen [Venice (9006)](https://ir.amd.com/news-events/press-releases/detail/1294/aai-2026-amd-delivers-full-stack-compute-for-the-agentic-ai-era)
Turin (9005), and 6th Gen Venice (9006)
(launched at Advancing AI 2026). `scripts/detect.py` reports only these three
generations as `is_supported_epyc: true`.

Expand Down
10 changes: 5 additions & 5 deletions skills/serving-llms-on-epyc/scripts/check_model.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -203,18 +203,18 @@ def main():
# Cannot read the config (gated/offline) -- do not positively block; the
# gating check and launch will catch real problems.
print(json.dumps({"model_id": a.model_id, "supported": None, "kind": "undetermined",
"message": f"Could not read architectures ({aerr or 'none declared'}); support unverified. "
"If gated, set HF_TOKEN. This does not bypass the gating/launch checks."}, indent=2))
"message": f"Could not read architectures ({aerr or 'none declared'}); support unverified. "
"If gated, set HF_TOKEN. This does not bypass the gating/launch checks."}, indent=2))
sys.exit(0)

reg, source = registry_from_github(a.vllm_version)
if reg is None:
reg, source = registry_from_local()
if reg is None:
print(json.dumps({"model_id": a.model_id, "architectures": archs, "supported": None,
"kind": "undetermined",
"message": "Could not load vLLM's model registry (no network and no importable vllm); "
"support unverified. vLLM confirms support at load (no-retry rule applies)."}, indent=2))
"kind": "undetermined",
"message": "Could not load vLLM's model registry (no network and no importable vllm); "
"support unverified. vLLM confirms support at load (no-retry rule applies)."}, indent=2))
sys.exit(0)

kinds = [reg.get(arch) for arch in archs]
Expand Down
25 changes: 15 additions & 10 deletions skills/serving-llms-on-epyc/scripts/cpu_tune.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
import argparse
import json
import re
import shlex
import shutil
import subprocess
import sys
import time

OS_HEADROOM_GB = 16
Expand Down Expand Up @@ -111,14 +111,17 @@ def socket_busy_pct(cpus, interval=0.5):
"""Mean CPU-busy% across `cpus` over `interval` seconds, from /proc/stat."""
def snap():
d = {}
for ln in open("/proc/stat"):
if ln.startswith("cpu") and len(ln) > 3 and ln[3].isdigit():
p = ln.split()
vals = list(map(int, p[1:]))
idle = vals[3] + (vals[4] if len(vals) > 4 else 0)
d[int(p[0][3:])] = (idle, sum(vals))
with open("/proc/stat") as f:
for ln in f:
if ln.startswith("cpu") and len(ln) > 3 and ln[3].isdigit():
p = ln.split()
vals = list(map(int, p[1:]))
idle = vals[3] + (vals[4] if len(vals) > 4 else 0)
d[int(p[0][3:])] = (idle, sum(vals))
return d
a = snap(); time.sleep(interval); b = snap()
a = snap()
time.sleep(interval)
b = snap()
di = sum(b[c][0] - a[c][0] for c in cpus if c in a and c in b)
dt = sum(b[c][1] - a[c][1] for c in cpus if c in a and c in b)
return round(100 * (1 - di / dt), 1) if dt else 0.0
Expand Down Expand Up @@ -157,8 +160,10 @@ def main():
else:
chosen = min(sids, key=lambda s: busy[s])
reason = f"all sockets busy (>={args.busy_threshold}%) -> least-busy"
# Dict is built outside the f-string: "{ {...} }" needs spaces that flake8 rejects (E201/E202).
busy_map = {s: busy[s] for s in sids}
warn = (f"all {len(sids)} sockets are busy (>= {args.busy_threshold}%): "
f"{ {s: busy[s] for s in sids} }. Proceeding on the least-busy socket "
f"{busy_map}. Proceeding on the least-busy socket "
f"{chosen}; performance may suffer. Pass --socket N to override.")

sock = socks[chosen]
Expand Down Expand Up @@ -213,7 +218,7 @@ def main():
print(json.dumps(result, indent=2))
return

print(f'export VLLM_CPU_OMP_THREADS_BIND="{bind}"')
print(f"export VLLM_CPU_OMP_THREADS_BIND={shlex.quote(bind)}")
print(f"export VLLM_CPU_KVCACHE_SPACE={kv}")
print(f"# socket {chosen} ({reason}); per-socket busy%: {busy}")
print(f"# container: {container_cpuset}")
Expand Down
Empty file modified skills/serving-llms-on-epyc/scripts/detect.py
100644 → 100755
Empty file.
Empty file modified skills/serving-llms-on-epyc/scripts/estimate_memory.py
100644 → 100755
Empty file.
8 changes: 5 additions & 3 deletions skills/serving-llms-on-epyc/scripts/validate.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def _probe_stack(run_prefix, source):
labels which path was probed. Returns the parsed stack dict (with `source`)
or (None, message) if the probe could not run or produced no JSON.
"""
cmd = f'{run_prefix}python -c "{PROBE}"'
cmd = f"{run_prefix}python -c {shlex.quote(PROBE)}"
rc, out, err = _sh(cmd, timeout=120)
if rc != 0 or not out:
return None, (err or "probe failed")[:200]
Expand Down Expand Up @@ -288,8 +288,10 @@ def main():
missing = [lib for lib in ("libtcmalloc", "libiomp") if lib not in ld]
if missing:
issues.append({"check": "perf_libs", "severity": "advisory",
"message": f"LD_PRELOAD is missing {', '.join(missing)}; vLLM CPU warns about this and throughput suffers without them (host/conda path).",
"fix": "export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4:$CONDA_PREFIX/lib/libiomp5.so:$LD_PRELOAD"})
"message": f"LD_PRELOAD is missing {', '.join(missing)}; vLLM CPU warns about this and "
"throughput suffers without them (host/conda path).",
"fix": "export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4:"
"$CONDA_PREFIX/lib/libiomp5.so:$LD_PRELOAD"})

# 8. HF cache mount (container path). The default bind-mounts the HF cache into
# the container; on NFS homes / symlinked caches / root-squash that fails at
Expand Down
2 changes: 1 addition & 1 deletion skills/serving-llms-on-epyc/skill-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ AMD

## License

MIT
Apache-2.0
Loading