现象:AMD Radeon AI PRO R9700(gfx1201 / RDNA4)上跑常驻推理服务(llama.cpp / vLLM 等),GPU 空闲时功耗降不下来,长期停在 41 MHz / 610 mV,进不了深度省电状态。
一句话结论
只要有进程持有 /dev/kfd,GPU 就进不了 GFXOFF,卡在最低 DPM 档 41 MHz / 610 mV。
真因是驱动里 KFD 常驻持有 gfx_off_req_count 引用(不是 MES 固件、不是推理引擎、不是 runpm、不是 ppfeaturemask)。
解法:写 /sys/kernel/debug/dri/0/amdgpu_gfxoff = 1 抵消该引用 → 0 MHz / 30 mV。
解法
# 手动进 GFXOFF
sudo bash -c "printf '\001\000\000\000' > /sys/kernel/debug/dri/0/amdgpu_gfxoff"
# 验证
cat /sys/class/drm/card0/device/pp_dpm_sclk # 应为 S: 0Mhz *
# 撤销
sudo bash -c "printf '\000\000\000\000' > /sys/kernel/debug/dri/0/amdgpu_gfxoff"
固化:gfxoff-ensure.sh + .service + .timer(OnBootSec=30 / OnUnitActiveSec=60,已 enable)。
必须用 timer:进程重启后手动写的 1 会掉回 0。
gfxoff-ensure.sh(放 /usr/local/bin/)
#!/usr/bin/env bash
# 确保 AMD GFXOFF 处于允许状态:gfx_off_state 为 0 时写 1。
#
# 背景:KFD 通过 gfx_off_req_count 常驻引用压住 GFXOFF,导致只要有进程持有
# /dev/kfd,GPU 就停在最低 DPM 档(gfx1201 / R9700 上是 41 MHz / 610 mV)。
# 写 debugfs 抵消那个常驻引用,让 GPU 进入 GFXOFF。
# 参考:https://github.com/ROCm/ROCm/issues/5706
set -u
DEV=/sys/class/drm/card0/device
D=$DEV/debug
[ -d "$D" ] || D=/sys/kernel/debug/dri/0
F=$D/amdgpu_gfxoff
log() { logger -t gfxoff-ensure -- "$*" 2>/dev/null || echo "gfxoff-ensure: $*"; }
[ -e "$F" ] || { log "debugfs 文件不存在: $F(debugfs 未挂载或 amdgpu 未加载)"; exit 0; }
# 该 debugfs 的 read/write 要求 4 字节对齐:cat/od 读整页会被 EINVAL 拒掉
read_state() { dd if="$F" bs=4 count=1 2>/dev/null | od -An -tu4 | tr -d ' \n'; }
state=$(read_state)
[ -z "$state" ] && { log "读取 gfx_off_state 失败,跳过"; exit 0; }
[ "$state" = "1" ] && exit 0 # 已允许 GFXOFF,不动
# 只在 GPU 空闲时动手,避免干扰正在进行的推理
busy=$(cat $DEV/gpu_busy_percent 2>/dev/null || echo 0)
[ "${busy:-0}" -ge 5 ] && exit 0
# 没有进程持有 /dev/kfd 时 gfx_off_req_count 本来就是 0,
# 再写 enable 会触发内核 WARN_ON_ONCE,所以不写(此时 GPU 本来就能 idle)。
# 找持有者:fuser 对 /dev/kfd 不工作(字符设备匹配 bug);
# 而 `ls -l /proc/*/fd | awk '{print $1}'` 取到的是权限串(lrwx------)不是 PID,
# 必须用 find -lname 再从路径里取 PID。
holders=$(find /proc/[0-9]*/fd -lname /dev/kfd -printf '%h\n' 2>/dev/null |
cut -d/ -f3 | sort -u | tr '\n' ' ')
[ -z "$holders" ] && exit 0
printf '\001\000\000\000' > "$F" 2>/dev/null || { log "写入 $F 失败"; exit 1; }
# 内核要等 GFX_OFF_DELAY_ENABLE 的延迟工作才真正置位,等 2s 再回读
sleep 2
new=$(read_state)
hm=$(ls -d $DEV/hwmon/hwmon* 2>/dev/null | head -1)
freq=$(( $(cat $hm/freq1_input 2>/dev/null || echo 0) / 1000000 ))
volt=$(cat $hm/in0_input 2>/dev/null || echo '?')
pw=$(( $(cat $hm/power1_average 2>/dev/null || echo 0) / 1000000 ))
log "gfx_off_state $state -> ${new:-?} busy=${busy}% gfx=${freq}MHz ${volt}mV ${pw}W holders: ${holders}"
已知代价与风险
开机后最多一条内核 WARNING(amdgpu_gfx.c:941)+ tainted 标记,功能无害。
debugfs 是内核调试接口,驱动升级后行为可能变;未做长期稳定性压测。