NUC9 + fnOS + RX 6600 部署 llama.cpp 本地大模型推理服务
前言
事情是这样的。
家里那台 NUC9 幽灵峡谷,装了飞牛 fnOS,插着一张 RX 6600 独显,日常跑 Docker 用 llama.cpp + ROCm 做本地推理。前不久刚经历了一次显卡凭空消失的诡异故障 —— 排查了半天发现是金手指接触不良,重新插拔解决了。那次折腾完之后我就想:既然硬件都搞定了,不如把整个部署过程完整记录下来。
坦率讲,在 NAS 上跑本地大模型不是刚需,但 GPU 闲着也是闲着。RX 6600 有 7.8GB 显存,跑个 9B 的量化模型绰绰有余。局域网内搭一个兼容 OpenAI API 的推理服务,macOS 上的 Hermes Agent 和浏览器的 Page Assist 都能直连,比每个月给云服务交 API 费用舒服多了。
这篇文章记录了从零开始在 NUC9 + fnOS + RX 6600 上,用 Docker 部署 llama.cpp ROCm 推理服务,最终接入日常使用的 AI 工具链的完整过程。包含硬件配置、Dockerfile 编写、docker-compose.yml 逐字段解释、模型选型、性能实测,以及几个容易踩的坑。
硬件配置
| 项目 | 参数 |
|---|---|
| 主机 | Intel NUC9 幽灵峡谷 (Ghost Canyon) |
| CPU | i9-9980HK,8 核 16 线程 |
| GPU | AMD Radeon RX 6600,7.8GB VRAM,gfx1032 |
| 内存 | 32GB DDR4 |
| 系统 | 飞牛 fnOS(基于 Debian) |
| 存储 | NVMe 固态,模型放 /vol4/1000/ai-models |
这套配置跑本地推理有几个好处:NUC9 体积小、功耗低(整机满载不到 150W),放角落 7x24 开着不心疼电费。RX 6600 虽然不算高端卡,但在消费级 AMD 显卡里 ROCm 支持算不错的,7.8GB 显存刚好能跑 9B 参数的 Q4 量化模型。i9-9980HK 虽然是移动端 CPU,但在这种场景下主要看 GPU,CPU 基本摸鱼。
前置准备
AMD 容器工具包
fnOS 的应用中心里搜「AMD」,装 AMD 容器工具包。这一步本质上是把 ROCm 运行时和 Docker 的 GPU 支持打通,让容器能访问宿主机的 /dev/kfd 和 /dev/dri。
装完之后验证一下设备节点:
ls -la /dev/kfdls -la /dev/dri/期望看到 /dev/kfd 存在,/dev/dri/ 下有 card0(核显)、card1(RX 6600)、renderD128、renderD129 等渲染节点。如果没有,说明系统没认出显卡 —— 先跑一遍 lspci | grep -i amd 排查。
模型选型
选了 Qwen3.5-9B 的 Q4_K_M 量化版。几个理由:
- 显存刚好:Q4_K_M 大约 5.5GB,加上推理时的 KV Cache 开销,7.8GB 显存不会爆
- 中英文都好:Qwen 系列对中文的支持在开源模型里是第一梯队,日常写作、翻译、问答都够用
- 9B 这个尺寸很甜:比 7B 聪明一截,比 14B 省显存,个人部署的甜蜜点
模型文件从 HuggingFace 下载:
huggingface-cli download Qwen/Qwen3.5-9B-GGUF Qwen3.5-9B-Q4_K_M.gguf \ --local-dir /vol4/1000/ai-models/如果没有装 huggingface-cli,直接 wget 也行:
wget -O /vol4/1000/ai-models/Qwen3.5-9B-Q4_K_M.gguf \ "https://huggingface.co/Qwen/Qwen3.5-9B-GGUF/resolve/main/Qwen3.5-9B-Q4_K_M.gguf"部署步骤
项目结构
/vol4/1000/ai-models/├── docker-compose.yml├── models/│ └── Qwen3.5-9B-Q4_K_M.gguf└── logs/Dockerfile
官方提供了预构建的 rocm/llama.cpp 镜像,省去自己编译 ROCm 版本的痛苦(说真的,从源码编译 ROCm 环境比部署本身还折腾)。直接用:
FROM rocm/llama.cpp:latest
EXPOSE 8000
ENTRYPOINT ["/llama-server"]CMD ["-m", "/models/Qwen3.5-9B-Q4_K_M.gguf", \ "--host", "0.0.0.0", \ "--port", "8000", \ "-ngl", "99"]-ngl 99 的意思是「把所有层都卸载到 GPU」。RX 6600 有 7.8GB 显存,Q4_K_M 模型大约 5.5GB,99 层全卸毫无压力。如果你的模型比显存大,需要调小这个数字,部分层留在 CPU 上跑。
docker-compose.yml
services: llama-server: build: . image: llama-rocm:latest container_name: llama-server restart: always ports: - "11890:8000" devices: - /dev/kfd:/dev/kfd - /dev/dri:/dev/dri volumes: - ./models:/models:ro - ./logs:/logs environment:8 collapsed lines
- HSA_OVERRIDE_GFX_VERSION=10.3.0 cap_add: - SYS_PTRACE security_opt: - seccomp=unconfined group_add: - video - render字段拆解:
- ports: 11890 — 宿主机 11890 映射容器 8000。选 11890 没别的原因,自己记着方便
- devices —
/dev/kfd是 ROCm 计算接口,/dev/dri是渲染节点,两个缺一个 GPU 就不干活 - volumes — 模型文件只读挂载(
:ro),防止容器里误删;日志目录可读写 - HSA_OVERRIDE_GFX_VERSION=10.3.0 — RX 6600 是 gfx1032,ROCm 官方支持列表里没有它,这行环境变量告诉 ROCm「把我当 gfx1030 处理」。没这行,llama.cpp 启动时会报找不到 GPU
- cap_add + security_opt — 给容器足够的权限访问 GPU 硬件。
SYS_PTRACE是 ROCm 调试用的,seccomp=unconfined放开系统调用限制 - group_add: video, render — 让容器内的进程有权限访问
/dev/dri设备
构建与启动
cd /vol4/1000/ai-modelsdocker compose builddocker compose up -d第一次 build 会拉 rocm / llama.cpp 镜像(大概 3-4GB),后面就快了。启动后看日志确认 GPU 被识别:
docker compose logs -f看到类似 ggml_cuda_init: found 1 ROCm devices 或者 llama_model_load: using ROCm 就说明 GPU 在工作。如果日志里出现 ggml_cuda_init: no ROCm devices found,回去检查 HSA_OVERRIDE_GFX_VERSION 和 devices 挂载。
验证
用 curl 测试一下 API 是否正常:
# 查模型列表curl http://localhost:11890/v1/models
# 发一条对话请求curl http://localhost:11890/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-9b", "messages": [{"role": "user", "content": "用一句话介绍你自己"}] }'模型名随便填,llama.cpp server 默认不校验。返回 JSON 带着 choices[0].message.content 就是通了。
性能实测
Qwen3.5-9B - Q4_K_M 在 RX 6600 上的表现:
| 指标 | 速度 |
|---|---|
| 提示词处理 (prompt eval) | ~136 tokens/s |
| 文本生成 (token generation) | ~32 tokens/s |
32 tok / s 是什么概念?差不多人眼阅读速度的两倍。日常问答、写作辅助、翻译这种场景完全够用 —— 你发一段话过去,一两秒就开始流式出字,体感几乎没有等待。
对比一下纯 CPU 推理:同一台机器用 i9-9980HK 跑,生成速度大概 5-8 tok / s,GPU 加速后有 4-6 倍的提升。7.8GB 显存是瓶颈,但在这个尺寸下已经够用了。
接入前端
Page Assist
Page Assist 是个浏览器扩展,可以在任何网页上划词调用本地模型。安装后在设置里添加:
- Provider: OpenAI API Compatible
- API Base URL:
http://192.168.3.166:11890/v1 - Model:
qwen3.5-9b - API Key: 随便填(llama.cpp 本地服务不校验)
配置完就能在浏览器里选中文字直接问本地大模型了,翻译、总结、改写都行。
Hermes Agent
在 ~/.hermes/config.yaml 的 custom_providers 里加一段:
custom_providers: - name: fnos-qwen3.5-9b base_url: http://192.168.3.166:11890/v1 model: qwen3.5-9b然后在 Hermes 里切换 Provider 到 fnos-qwen3.5-9b 就行了。这就是我现在在用的方案 ——Hermes 的对话模型跑在自己的 NUC 上,延迟低、不要钱、数据不离开局域网。
踩坑记录
坑一:容器里看不到 GPU
日志报 no ROCm devices found。
排查:进容器跑 rocminfo,如果也报错,99% 是 devices 没挂对或者 HSA_OVERRIDE_GFX_VERSION 没设。确认 devices: /dev/kfd:/dev/kfd 和 /dev/dri:/dev/dri 都在 compose 文件里,环境变量也别漏。
坑二:容器启动几秒后自动退出
docker compose ps 显示 Exited (1)。
通常是模型路径不对。确认 volumes 挂载路径和 CMD 里的 -m 参数一致。进容器手动跑一次 /llama-server -m /models/xxx.gguf 看报什么错。
坑三:GPU OOM
模型太大,显存放不下。
两个办法:一是换更小的量化版本(Q3 甚至 Q2),二是调低 -ngl 参数,把部分层留在 CPU 内存里跑。比如 -ngl 50 就只卸载一半层。性能会降,但至少能跑。
坑四:重启后 /dev/kfd 权限不对
有时候系统重启后 /dev/kfd 的权限会变成 root:root,容器里的非 root 进程访问不了。
检查:
ls -la /dev/kfd# 期望: crw-rw-rw- 1 root render如果 group 不是 render,执行:
sudo chmod 666 /dev/kfdsudo usermod -aG render,video $USER做了这些之后,下次重启应该就正常了。