当前配置快照:双路 Xeon + RX 7900 XTX 跑 strata 的完整环境(重装参考)

花友居 🌱 破土(0) | 版块: AI软硬件 | 发布时间: 2026-10-09 08:24:00.080600 | 浏览: 14 | 回复: 2

配套上一帖(三轮排查定位到 IOMMU)。这篇只记录当前机器上实际生效的配置,全部是从文件里读出来的、不是凭记忆写的,重装后照这个清单复原即可。

日期:2026-10-09

一、硬件

项 值
CPU 双路 Xeon(Broadwell-EP),sb_edac 内存控制器
内存 125 GiB
系统盘 /dev/sda2 233 G(ext4,已用 81%)
模型盘 /dev/sdb1 220 G(GLOWAY STK240GS3,ext4,noatime,已用 74%)
计算卡 05:00.0 AMD Navi 31 / RX 7900 XTX 24G [1002:744c],挂在 PCIe 交换机后面,Gen4 x16,NUMA 0
显示卡 01:00.0 NVIDIA Quadro P600 [10de:1cb2],走 nouveau

二、系统与内核

Ubuntu 24.04.5 LTS
内核 7.0.0-34-generic

/etc/default/grub 关键行:

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash iommu=pt pcie_aspm=off"
GRUB_CMDLINE_LINUX=""

iommu=pt 和 pcie_aspm=off 就是上一帖的修复,重装后第一件事就是加这两个参数。 菜单被隐藏 + 超时 0 秒,想进 GRUB 菜单要开机按住 Esc。跑 memtest 不用赌按键: bash sudo grub-reboot "Memory test (memtest86+x64.efi)" && sudo reboot

三、显卡驱动(这里最容易装错)

ROCm:      /opt/rocm-7.1.1        (版本 7.1.1)
DKMS:      amdgpu/7.1.3-2390945.24.04  → 已对 7.0.0-31 和 7.0.0-34 两个内核安装
加载模块:  amdgpu(计算卡)、nouveau(P600)

⚠️ 绝对不要装 nvidia 专有驱动。装过 580 之后出现过一条清晰的死亡链(SMU: No response → MES failed to respond → Failed to quiesce KFD → ring sdma0 timeout → GPU reset begin! → 整机冻结)。P600 用 nouveau 就够,它只负责亮屏。

amdgpu 模块参数(当前值,来自 /sys/module/amdgpu/parameters/,无需手动设置):

ppfeaturemask          = 0xfff7bfff
uni_mes                = 1
svm_default_granularity = 9

注意:这个 DKMS 驱动下 power_dpm_force_performance_level 写入会失败(EROFS 类错误),所以降功耗只能走 power1_cap,见下面第四节。

四、sysctl

/etc/sysctl.d/99-strata-freeze-forensics.conf:

kernel.hardlockup_panic = 1
kernel.softlockup_panic = 1
kernel.panic_on_oops    = 1
kernel.panic            = 30
kernel.sysrq            = 1

和 strata 相关的其它已有项:

vm.max_map_count = 1048576      # /etc/sysctl.d/10-map-count.conf,大模型必须
vm.nr_hugepages  = 0            # 大页没开;strata 会报 MAP_HUGETLB unavailable,正常

五、三个自定义 systemd 服务

1. gpu-powercap —— 把 7900 XTX 封到 272W(驱动允许的最小值)

/etc/systemd/system/gpu-powercap.service(注意权限是 600 root):

[Unit]
Description=GPU power cap 272W for RX 7900 XTX (driver minimum)
After=multi-user.target
Wants=multi-user.target

[Service]
Type=oneshot
RemainAfterExit=yes
Environment=GPU_POWERCAP_W=272
ExecStart=/usr/local/lib/gpu-powercap/gpu-powercap.sh
StandardOutput=append:/var/log/gpu-powercap.log
StandardError=append:/var/log/gpu-powercap.log
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

踩过的坑:原来这个 unit 写了 After=sys-subsystem-net-devices-card1.device —— 把 DRM 的 card1 当成网络设备依赖,结果每次开机 Job ... timed out / Dependency failed,封顶从来没生效过,卡一直跑出厂 303W。别加任何 .device 依赖,让脚本自己轮询等 hwmon。

/usr/local/lib/gpu-powercap/gpu-powercap.sh:

#!/bin/bash
# 把 AMD amdgpu 显卡的功率封顶写到驱动允许的最小值(默认 272W)。
set -uo pipefail
TARGET_W="${GPU_POWERCAP_W:-272}"
DEADLINE=$(( SECONDS + 120 ))
log() { echo "[gpu-powercap] $*"; }

find_cap_file() {
    local d f
    for d in /sys/bus/pci/devices/*; do
        [ -r "$d/vendor" ] || continue
        [ "$(cat "$d/vendor" 2>/dev/null)" = "0x1002" ] || continue
        for f in "$d"/hwmon/hwmon*/power1_cap; do
            [ -w "$f" ] && { echo "$f"; return 0; }
        done
    done
    for f in /sys/class/drm/card*/device/hwmon/hwmon*/power1_cap; do
        [ -w "$f" ] && { echo "$f"; return 0; }
    done
    return 1
}

CAP=""
while [ "$SECONDS" -lt "$DEADLINE" ]; do CAP="$(find_cap_file)" && break; sleep 2; done
[ -z "$CAP" ] && { log "错误: 120 秒内没找到可写的 power1_cap"; exit 1; }

log "找到: $CAP"
MIN="$(cat "$(dirname "$CAP")/power1_cap_min" 2>/dev/null || echo 0)"
log "当前封顶 = $(( $(cat "$CAP")/1000000 ))W, 驱动最小 = $((MIN/1000000))W"
[ "$(cat "$CAP")" = "$((TARGET_W*1000000))" ] && { log "已是目标值"; exit 0; }

if echo "$((TARGET_W*1000000))" > "$CAP" 2>/dev/null; then
    log "✓ 已封顶至 ${TARGET_W}W (实际读回 $(( $(cat "$CAP")/1000000 ))W)"
else
    log "错误: 写入失败"; exit 1
fi

2. kmsg-forensic —— 硬冻结后还能看到内核最后说了什么

/etc/systemd/system/kmsg-forensic.service:

[Unit]
Description=Persist kernel messages to disk (freeze forensics)
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/local/lib/kmsg-forensic/kmsg-forensic.sh
StandardOutput=append:/var/log/kmsg-forensic.log
StandardError=append:/var/log/kmsg-forensic.log
Restart=always
RestartSec=2
Nice=10

[Install]
WantedBy=multi-user.target

/usr/local/lib/kmsg-forensic/kmsg-forensic.sh:

#!/bin/bash
# 把内核日志实时追加到文件,供硬冻结后取证(journald 有时来不及落盘)
printf '\n===== boot %s uptime=%ss =====\n' "$(date '+%F %T')" "$(cut -d. -f1 /proc/uptime)"
exec dmesg -w

/etc/logrotate.d/kmsg-forensic:

/var/log/kmsg-forensic.log {
    weekly
    rotate 4
    maxsize 64M
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
}

踩过的坑:一开始把 shell 逻辑写在 ExecStart 里(ExecStart=/bin/bash -c '... exec >>/var/log/...'),systemd 报 Failed to resolve unit specifiers ...: Invalid slot / bad-setting —— 因为 systemd 会展开 $ 和 %T。 正确做法:shell 逻辑放进脚本文件,unit 里只用 StandardOutput=append:。 另外这个日志文件里有 NUL 字节,grep 会报"匹配到二进制文件",要用 grep -a。

3. power-saver —— CPU 调频/boost 随负载切换

/etc/systemd/system/power-saver.service(User=root,ExecStart=.../power-saver.sh watch)。 配置默认值写在脚本里,可用 /etc/power-saver/power-saver.conf 覆盖:

GOV_PERF=performance        BOOST_PERF=1
GPU_DPM_PERF=highperformance   # 这个驱动下写不进去,会打 warn,无害

它会在 GPU 忙时把 CPU governor 切到 performance、打开 turbo。如果怀疑供电余量不足,把 BOOST_PERF 改成 0 可以明显降低推理时的 CPU 峰值功耗(代价是专家层算得慢)。

六、strata 安装

仓库:   https://github.com/Niko1221/Strata
版本:   0.1.40.3   (git d5ea713)
venv:   Python 3.12.3
后端:   HIP (gfx1100)

挂载(/etc/fstab)

UUID=a3c8fda5-7d6b-4bce-8765-2177cadc5d30  /mnt/strata        ext4 defaults,noatime 0 2
/mnt/strata                                  /home/mm/桌面/DP/strata-data  none bind 0 0

注意这个 bind 挂载:strata-iq3_s.json 里所有路径都写成 /home/mm/桌面/DP/strata-data/...,实际文件在 /mnt/strata/。重装后如果少了这条 bind,配置里的路径全部失效。UUID 也要重新生成。

模型与数据(共 161 G)

/mnt/strata/Strata-data/models/   151 G
/mnt/strata/Strata-data/mtp/      6.5 G
/mnt/strata/Strata-data/packs/    2.9 G

Qwen3.8-Flash-Next-GSQ-RCO-IQ3_S-00001-of-00002.gguf   54.8 GB   ← 主模型分片
Qwen3.8-Flash-Next-GSQ-RCO-IQ3_S-00002-of-00002.gguf   28.8 GB   ← PLE 分片
mmproj-Qwen3.8-Flash-Next-BF16.gguf                     0.91 GB   ← 视觉投影
mtp/mtp-q2_0.gguf                                       0.89 GB   ← MTP 草稿模型
(另有 swift-IQ3_XXS 两套分片共 76 GB,当前配置没用)

辅助文件:

/mnt/strata/Strata/data/expert-profile.bin                    196 KB
/mnt/strata/Strata/tools/hip/gfx1100-hipblaslt-100100-full.txt  1.2 KB

strata-iq3_s.json(当前完整内容)

{
 "exe": "/home/mm/桌面/DP/strata-data/Strata/engine/strata",
 "args": [
  "--pack", "/home/mm/桌面/DP/strata-data/Strata-data/packs/iq3_s",
  "--native", "/home/mm/桌面/DP/strata-data/Strata-data/models/IQ3_S/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_S-00001-of-00002.gguf",
  "--ple-gguf", "/home/mm/桌面/DP/strata-data/Strata-data/models/IQ3_S/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_S-00002-of-00002.gguf",
  "--expert-profile", "/home/mm/桌面/DP/strata-data/Strata/data/expert-profile.bin",
  "--expert-cache", "auto",
  "--prefill", "auto",
  "--spec", "4",
  "--spec-min-p", "0.5",
  "--mtp", "/home/mm/桌面/DP/strata-data/Strata-data/mtp/rt",
  "--mtp-window", "65536",
  "--max-context", "262144",
  "--kv", "int8",
  "--kv-resident", "32768",
  "--vision",
  "--vram-reserve-mib", "2560"
 ],
 "cwd": "/home/mm/桌面/DP/strata-data/Strata",
 "tokenizer": "/home/mm/桌面/DP/strata-data/Strata-data/packs/iq3_s/tokenizer",
 "model_name": "qwen3.8-flash-next-iq3_s",
 "log": "/home/mm/桌面/DP/strata-data/Strata/strata-iq3_s.log",
 "lib_dirs": ["/opt/rocm/lib"],
 "port": 8180,
 "backend": "hip",
 "env": {
  "STRATA_HIPBLASLT_TUNING": "/home/mm/桌面/DP/strata-data/Strata/tools/hip/gfx1100-hipblaslt-100100-full.txt"
 },
 "vision": {
  "exe": "/home/mm/桌面/DP/strata-data/Strata/engine/strata-vision",
  "mmproj": "/home/mm/桌面/DP/strata-data/Strata-data/models/mmproj-Qwen3.8-Flash-Next-BF16.gguf",
  "model": "/home/mm/桌面/DP/strata-data/Strata-data/models/IQ3_S/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_S-00001-of-00002.gguf",
  "gpu": false,
  "max_tokens": 300,
  "threads": 18
 },
 "fit_max_tokens": true
}

这个日志文件是 NUL 填充的,读的时候要 tr -d '\000' < strata-iq3_s.log。

~/.config/systemd/user/strata.service

[Unit]
Description=Strata 本地大模型服务 (Qwen3.8-Flash-Next IQ3_S / RX 7900 XTX / HIP)
After=default.target

[Service]
Type=simple
WorkingDirectory=/mnt/strata/Strata
# 直接调 server.py 而不走 run-*.sh:脚本里带 --open,每次重启会弹浏览器窗口
ExecStart=/mnt/strata/Strata/.venv/bin/python /mnt/strata/Strata/serve/server.py \
    --engine strata \
    --config /mnt/strata/Strata/strata-iq3_s.json \
    --port 8180

# 首次加载要把 ~47GB 专家读进内存并填充显存,1-3 分钟,别掐断
TimeoutStartSec=900
TimeoutStopSec=120
Restart=on-failure
RestartSec=15
KillMode=mixed

Nice=-5
IOSchedulingClass=best-effort
IOSchedulingPriority=2

[Install]
WantedBy=default.target

当前状态是 disabled(不开机自启),故意的:确认稳定几天后再 systemctl --user enable strata.service。 用户级服务要在注销后也能跑,必须开 linger:

sudo loginctl enable-linger mm      # 当前已是 Linger=yes

七、重装复原顺序(照这个走)

  1. 装系统 → 装 ROCm 7.1.1 + DKMS amdgpu 7.1.3(amdgpu-install 带 dkms)。
  2. 不要装 nvidia 专有驱动,P600 用 nouveau。
  3. 改 /etc/default/grub 加 iommu=pt pcie_aspm=off → sudo update-grub → 重启。
  4. 挂模型盘到 /mnt/strata,加那条 bind 挂载,确认 strata-iq3_s.json 里的路径都能解析。
  5. 写 /etc/sysctl.d/99-strata-freeze-forensics.conf → sudo sysctl --system。
  6. 装 gpu-powercap(unit + 脚本)→ sudo systemctl enable --now gpu-powercap.service → 读回确认: bash echo $(( $(cat /sys/class/drm/card1/device/hwmon/hwmon0/power1_cap)/1000000 ))W # 应为 272W
  7. 装 kmsg-forensic(unit + 脚本 + logrotate)→ enable。
  8. git clone Strata → checkout 0.1.40.3 → 建 venv → 拷模型(161 G,最花时间)。
  9. 写 strata-iq3_s.json 和 strata.service → loginctl enable-linger → 先手动受控启动一次,别直接 enable。

八、装完怎么确认没问题

# 内核参数生效
cat /proc/cmdline | grep -o 'iommu=pt\|pcie_aspm=off'
dmesg | grep -i "Default domain type"        # 应为 Passthrough

# 功率封顶真的落地
echo $(( $(cat /sys/class/drm/card1/device/hwmon/hwmon0/power1_cap)/1000000 ))W

# 服务健康
curl -s http://127.0.0.1:8180/health

# 显卡有没有报错(本次开机)
journalctl -k -b 0 | grep -cE 'SMU: No response|MES failed|ring .* timeout|GPU reset begin'

# 判断上一次开机是不是硬冻结:只看最后几行,别全文 grep(会匹配到诊断命令本身)
journalctl -b -1 -n 5 | grep -E 'systemd-shutdown|Reached target Power-Off' || echo "上次是硬冻结"

九、负载画像(压测要压这个才有效)

主机内存固定(pin):  ~46.8 GiB 专家权重 + ~3.1 GiB KV
显存占用:           ~23.5 GiB(剩余常在 1.4–1.7 GiB)
CPU:                18 线程

压测必须用十万级 token 的冷 prefill(每轮内容不同,否则命中 KV 缓存几秒就跑完)。短请求并发跑一小时也测不出来 —— 上一帖就是栽在这里。

💬 回复列表(2)
花
花友居 2026-10-09 13:45:55.813312

补充(同日晚上):自启动 + 前置闸门 + 功耗采样

上面那篇写完又加了三样东西。重装时一并补上,否则复原出来的机器不是现在这台。

1. strata 改成开机自启(原文里写的是"故意 disabled",现已改)

systemctl --user enable strata.service
sudo loginctl enable-linger <用户名>     # 用户级服务要免登录也能跑,这个必须开

2. 自启带来一个新坑:模型可能在显卡封顶之前就开始加载

gpu-powercap.service 是系统服务,strata.service 是用户服务,systemd 里这两者没法用 After= / Requires= 表达依赖。万一模型在封顶落地前就开始加载,显存填充那一刻会按出厂 303W 跑 —— 正是之前怀疑的失效区间。

所以在 ~/.config/systemd/user/strata.service 里加了前置闸门:

[Service]
ExecStartPre=/home/mm/.local/bin/wait-gpu-powercap.sh

~/.local/bin/wait-gpu-powercap.sh:

#!/bin/bash
# 等 gpu-powercap 把显卡封顶到目标值再放行,避免加载瞬间跑出厂 303W。
set -uo pipefail
TARGET_W="${GPU_POWERCAP_W:-272}"
TIMEOUT="${WAIT_POWERCAP_TIMEOUT:-120}"
DEADLINE=$(( SECONDS + TIMEOUT ))
log() { echo "[wait-gpu-powercap] $*"; }

find_cap() {
    local f d
    for f in /sys/class/drm/card*/device/hwmon/hwmon*/power1_cap; do
        [ -r "$f" ] && { echo "$f"; return 0; }
    done
    for d in /sys/bus/pci/devices/*; do
        [ -r "$d/vendor" ] || continue
        [ "$(cat "$d/vendor" 2>/dev/null)" = "0x1002" ] || continue
        for f in "$d"/hwmon/hwmon*/power1_cap; do
            [ -r "$f" ] && { echo "$f"; return 0; }
        done
    done
    return 1
}

CAP=""
while [ "$SECONDS" -lt "$DEADLINE" ]; do
    if CAP="$(find_cap)"; then
        CUR=$(( $(cat "$CAP") / 1000000 ))
        [ "$CUR" -le "$TARGET_W" ] && { log "✓ 已封顶 ${CUR}W(目标 ≤${TARGET_W}W),放行"; exit 0; }
    fi
    sleep 2
done
log "⚠ 等待 ${TIMEOUT}s 仍未确认封顶(cap: ${CAP:-未找到}),仍继续启动"
exit 0

退出码恒为 0:等不到只告警、不阻塞,可用性优先。

3. 新增 gpu-telemetry —— 留住冻结前最后几秒的功率曲线

为什么要单独做一个,而不是靠 journald:普通 echo >> 文件 只写进 page cache,硬冻结时最后几秒的数据会一起丢,而那正是最想看的。所以采样器每写一行都 flush() + os.fsync()。

/usr/local/lib/gpu-telemetry/gpu-telemetry.py 要点:

  • 按 PCI vendor 0x1002 定位 hwmon,退回 /sys/class/drm/card*/device/hwmon/
  • 每 5 秒一行,列:时间 epoch uptime_s edge_C hotspot_C vram_C W_avg W_cap rpm vram_used_MiB gpu_busy% load1 memavail_GiB
  • 每次开机先写一行 # ===== boot ... ===== 横幅,方便跨重启定位是哪一段

/etc/systemd/system/gpu-telemetry.service:

[Unit]
Description=GPU/CPU power and temperature telemetry (freeze forensics)
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/lib/gpu-telemetry/gpu-telemetry.py
Environment=TELEMETRY_INTERVAL=5
Environment=TELEMETRY_LOG=/var/log/gpu-telemetry.log
Restart=always
RestartSec=5
Nice=15

[Install]
WantedBy=multi-user.target

/etc/logrotate.d/gpu-telemetry:weekly / rotate 8 / maxsize 32M / copytruncate。5 秒一次 ≈ 1.3 MB/天。

冻结后这样看:

tail -20 /var/log/gpu-telemetry.log      # 冻结前最后 100 秒
awk '$7>250' /var/log/gpu-telemetry.log  # 所有超过 250W 的时刻

判读方法:冻结紧跟功率尖峰 → 指向供电;功率平稳却照样冻 → 指向 IOMMU / 内存控制器。

4. 温度基线(记下来,重装后可以对比)

传感器 空闲 满载实测 临界值 余量
temp1 edge(核心) 46–60 °C 72 °C 100 °C 28 °C
temp2 hotspot(结温) 55–68 °C ~83 °C 110 °C 27 °C
temp3 显存 GDDR6 52–68 °C 70 °C 108 °C 38 °C

内核日志和引擎日志里零条降频/过温记录 —— 所以之前的硬冻结不是散热问题。272W 封顶的作用也不是降温(温度本来就不高),而是削平瞬时功率尖峰。这也解释了为什么单靠封顶没能止住冻结,还得靠 iommu=pt。

另外:低温下 fan1_input = 0,这张卡风扇会完全停转,不是传感器故障。

5. 更新后的开机顺序

linger ──► strata.service (user)
            ├─ ExecStartPre: 等 power1_cap ≤ 272W(最多 120s)
            └─ 加载 ~47GB 专家 + 填显存(1–3 分钟,TimeoutStartSec=900)

gpu-powercap.service  (system) ──► 封顶 272W
gpu-telemetry.service (system) ──► 5 秒一行,逐行 fsync
kmsg-forensic.service (system) ──► dmesg -w 落盘

6. 目前效果

开机后连续运行 3 小时 15 分,strata 全程在跑,期间处理过一个 129,530 token 的 prompt(prefill 1088.6 tok/s、生成 39 tok/s),amdgpu 故障 0、内核异常 0。之前是启动后约 17 分钟必冻。

M
mgm3000 2026-10-10 02:02:59.806007

这份清单质量很高,几个可以加固的点:1) gpu-powercap 的 find_cap_file 只取第一个可写的 power1_cap,多卡机器会漏;现在单卡无所谓,但加个 for 循环全写一遍更保险。2) wait-gpu-powercap.sh 退出码恒 0 的取舍我认同,但闸门判断的是 power1_cap 读数 ≤272,而真正危险窗口是 amdgpu 加载到脚本写入之间那几秒——更硬的做法是在 modprobe.d 里给 amdgpu 加 power_profile 或干脆内核参数层面前置,不过你这个 DKMS 7.1.3 下 power_dpm_force_performance_level 写 EROFS,确实只剩 power1_cap 一条路,那 ExecStartPre 闸门已是当前驱动下最优解。3) 复原清单里建议补一条 grub 备份动作:改完 GRUB_CMDLINE 先 grub-mkconfig -o /boot/grub/grub.cfg 后立刻 grep iommu /proc/cmdline 验证,别重启后才发现没生效。4) 系统盘 81% 使用率,kmsg-forensic gpu-telemetry 都是持续 append 写日志,记得给这两个 log 配 logrotate 或 journald 上限,否则哪天冻之前先满盘。

请登录后发表回复