The RDNA4 offload campaign left three HIP probes and a driver sitting loose in $HOME, where the next agent cannot find them and a stray `rm -rf` ends them. They answer a recurring question on the R9700 box — is decode limited by the link or by the engine's read path — so they move into scripts/probes/ with a driver, tests, and a page. Two things changed in the code, not just the address: - The driver measures only after a guard that runs BEFORE anything is built: gpu_busy_percent over PCIE_PROBE_MAX_BUSY, or any process holding /dev/kfd, is a refusal (exit 3) rather than a measurement. A benchmark taken beside a live endpoint is both disruptive and worthless, and silently unloading a production service is not a tool's decision — --force is the human saying otherwise. The refusal-before-build ordering is pinned by a test, because the sysfs and kfd paths are overridable and a refusal is otherwise untestable off the card box. - hip_smoke.hip and pcie_probe.hip called hipGetDeviceProperties, which SEGFAULTS on this ROCm build even though hipGetDeviceCount works; only pcie_bw.hip avoided it, and that is the one that produced the campaign's numbers. Device identity now comes from the sysfs topology block the driver prints, so all three probes are runnable on the build we ship. Verified here: refusal on a real busy APU (exit 3), missing toolchain (exit 4), missing sources (exit 2). Compile-and-run still needs the box with hipcc. Refs #53
127 lines
5.4 KiB
Bash
Executable File
127 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# scripts/pcie-probe.sh — is the decode bottleneck the LINK, or the engine's read path?
|
|
#
|
|
# Instruments live in scripts/probes/: hip_smoke.hip answers "is the host ROCm usable from a plain
|
|
# process", pcie_bw.hip measures host→device bandwidth at the engine's real expert-slab size, and
|
|
# pcie_probe.hip sweeps sizes so a stall can be attributed to the link rather than to the engine.
|
|
# Written during the RDNA4 expert-offload campaign, kept because the same cards are in Dankserver.
|
|
#
|
|
# LAW — never benchmark beside a live service. The guard below runs BEFORE anything is built or run,
|
|
# and it stops nothing: putting a production endpoint down is not this tool's decision to make. When
|
|
# you have deliberately decided that contention is acceptable, say so with --force.
|
|
#
|
|
# Trap carried in the sources: hipGetDeviceProperties SEGFAULTS on this ROCm build even though
|
|
# hipGetDeviceCount works fine and reports every device. Nothing here calls it. Keep it that way.
|
|
#
|
|
# usage: pcie-probe.sh [--device N]... [--build-only] [--force]
|
|
#
|
|
# --device N measure this device; repeat for each card. Without it the probes run once with the
|
|
# environment's own device selection — one shared run cannot attribute bandwidth.
|
|
# --build-only compile and stop; never touches the cards.
|
|
# --force measure even though the guard says the GPU is busy.
|
|
#
|
|
# Environment (defaults in parentheses) — the sysfs and kfd paths are overridable so the guard can
|
|
# be tested on a machine with no cards, which is the only honest way to test a refusal:
|
|
# PCIE_PROBE_SRC probe sources (<repo>/scripts/probes)
|
|
# PCIE_PROBE_BUILD build output ($HOME/.cache/pcie-probe)
|
|
# PCIE_PROBE_SYSFS drm sysfs root (/sys/class/drm)
|
|
# PCIE_PROBE_KFD KFD device node (/dev/kfd)
|
|
# PCIE_PROBE_HIPCC compiler (hipcc)
|
|
# PCIE_PROBE_MAX_BUSY refusal threshold, percent (5)
|
|
#
|
|
# Exit status: 0 measured · 2 usage or missing sources · 3 REFUSED, GPU busy · 4 no toolchain or
|
|
# build failed.
|
|
|
|
set -uo pipefail
|
|
|
|
SRC="${PCIE_PROBE_SRC:-$(dirname "$(realpath "$0")")/probes}"
|
|
BUILD="${PCIE_PROBE_BUILD:-$HOME/.cache/pcie-probe}"
|
|
SYSFS="${PCIE_PROBE_SYSFS:-/sys/class/drm}"
|
|
KFD="${PCIE_PROBE_KFD:-/dev/kfd}"
|
|
HIPCC="${PCIE_PROBE_HIPCC:-hipcc}"
|
|
MAX_BUSY="${PCIE_PROBE_MAX_BUSY:-5}"
|
|
|
|
force=0
|
|
build_only=0
|
|
devices=()
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--device) [[ $# -ge 2 ]] || { echo "pcie-probe: --device needs an argument" >&2; exit 2; }
|
|
devices+=("$2"); shift 2 ;;
|
|
--build-only) build_only=1; shift ;;
|
|
--force) force=1; shift ;;
|
|
*) echo "usage: pcie-probe.sh [--device N]... [--build-only] [--force]" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
mapfile -t sources < <(find "$SRC" -maxdepth 1 -name '*.hip' 2>/dev/null | sort)
|
|
if (( ${#sources[@]} == 0 )); then
|
|
echo "pcie-probe: no .hip probe sources in $SRC" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# ---- the guard, before anything touches the card --------------------------------
|
|
if (( ! force )); then
|
|
busy=""
|
|
if [[ -e "$KFD" ]] && command -v fuser > /dev/null 2>&1; then
|
|
holders="$(fuser "$KFD" 2>/dev/null | tr -s '[:space:]' ' ')"
|
|
[[ -n "${holders// /}" ]] && busy="KFD in use by:${holders}"
|
|
fi
|
|
for card in "$SYSFS"/card*; do
|
|
[[ -f "$card/device/gpu_busy_percent" ]] || continue
|
|
pct="$(cat "$card/device/gpu_busy_percent" 2>/dev/null)"
|
|
[[ "$pct" =~ ^[0-9]+$ ]] || continue
|
|
(( pct > MAX_BUSY )) && busy="${busy:+$busy; }$(basename "$card") at ${pct}%"
|
|
done
|
|
if [[ -n "$busy" ]]; then
|
|
printf 'pcie-probe: REFUSED — GPU busy (%s)\n' "$busy" >&2
|
|
printf ' never benchmark beside a live service. Take the service down deliberately,\n' >&2
|
|
printf ' or pass --force if you have decided the contention is acceptable.\n' >&2
|
|
exit 3
|
|
fi
|
|
fi
|
|
|
|
# ---- build ----------------------------------------------------------------------
|
|
if ! command -v "$HIPCC" > /dev/null 2>&1; then
|
|
printf 'pcie-probe: no HIP toolchain (%s) — run this on the box with the cards, not here\n' "$HIPCC" >&2
|
|
exit 4
|
|
fi
|
|
mkdir -p "$BUILD"
|
|
for src in "${sources[@]}"; do
|
|
bin="$BUILD/$(basename "${src%.hip}")"
|
|
if ! "$HIPCC" -O2 -o "$bin" "$src" 2>&1 | tail -3; then
|
|
printf 'pcie-probe: build failed for %s\n' "$(basename "$src")" >&2
|
|
exit 4
|
|
fi
|
|
done
|
|
if (( build_only )); then
|
|
printf 'pcie-probe: built %s probe(s) into %s\n' "${#sources[@]}" "$BUILD"
|
|
exit 0
|
|
fi
|
|
|
|
# ---- topology: the link the numbers must be read against ------------------------
|
|
printf 'pcie-probe host=%s hipcc=%s probes=%s\n' "$(hostname)" "$HIPCC" "${#sources[@]}"
|
|
for card in "$SYSFS"/card*; do
|
|
[[ -f "$card/device/current_link_width" ]] || continue
|
|
printf ' link %-9s current x%s @ %s max x%s @ %s\n' "$(basename "$card")" \
|
|
"$(cat "$card/device/current_link_width")" \
|
|
"$(cat "$card/device/current_link_speed" 2>/dev/null || echo '?')" \
|
|
"$(cat "$card/device/max_link_width" 2>/dev/null || echo '?')" \
|
|
"$(cat "$card/device/max_link_speed" 2>/dev/null || echo '?')"
|
|
done
|
|
|
|
# ---- measure, per device --------------------------------------------------------
|
|
(( ${#devices[@]} > 0 )) || devices=(default)
|
|
for d in "${devices[@]}"; do
|
|
if [[ $d == default ]]; then
|
|
printf '\n########## engine device selection ##########\n'
|
|
else
|
|
printf '\n########## device %s ##########\n' "$d"
|
|
fi
|
|
for name in hip_smoke pcie_bw pcie_probe; do
|
|
bin="$BUILD/$name"
|
|
[[ -x "$bin" ]] || continue
|
|
if [[ $d == default ]]; then "$bin"; else HIP_VISIBLE_DEVICES="$d" "$bin" "$d"; fi
|
|
done
|
|
done
|