1. 为什么要在 Strix Halo 上折腾 Qwen3.8-Flash-Next
先把结论摆在前面:Strix Halo 这颗 APU 的定位很特殊,它把统一内存架构做到了 256bit 位宽,配合 LPDDR5X-8000 能跑到 256GB/s 级别的内存带宽。这个数字放在独显面前不算什么,但在核显/APU 阵营里属于断层领先。Qwen3.8-Flash-Next 这类 MoE 架构的模型,推理时的瓶颈往往不在算力而在内存带宽——权重加载、KV Cache 读写、专家路由的权重切换,全都在吃带宽。所以 Strix Halo 跑 MoE 模型,理论上比同价位的独显方案更划算,因为你能拿到更大的统一内存池,不用被显存容量卡脖子。
但理论归理论,实际部署的时候坑一个接一个。我前后折腾了大概三天,从 halogen 这个推理框架的编译开始,到 llama.cpp 的 backend 适配,再到量化格式的选择和 KV Cache 的显存/内存分配策略,中间踩的坑足够写一篇完整的避坑记录了。官方文档给的是"理想环境下的标准流程",但 Strix Halo 这种 APU 平台,BIOS 设置、内核版本、ROCm 版本、内存分配策略,任何一个环节对不上,就是编译报错或者跑起来性能腰斩。
这篇文章适合两类人看:一类是手里有 Strix Halo 设备(比如 Framework Desktop 或者类似的迷你主机),想拿来跑本地大模型的;另一类是对 MoE 模型本地部署感兴趣,想了解 halogen 和 llama.cpp 在 APU 平台上实际表现的。我会把整个部署链路拆开讲,包括每个环节为什么这么选、参数怎么算、遇到问题怎么排查,以及那些官方文档里绝对不会写的细节。
先明确一下硬件和软件基线,后面所有内容都基于这个环境:
| 组件 | 规格 |
|---|---|
| APU | AMD Strix Halo(Ryzen AI Max+ 395) |
| 内存 | 128GB LPDDR5X-8000(统一内存) |
| 核显 | Radeon 8060S(RDNA 3.5,40CU) |
| 系统 | Ubuntu 24.04 LTS,内核 6.11 |
| 推理框架 | halogen + llama.cpp(ROCm backend) |
| 模型 | Qwen3.8-Flash-Next(MoE,总参数量约 30B,激活约 3B) |
注意:Strix Halo 的统一内存需要在 BIOS 里手动分配显存(UMA Frame Buffer),这个值直接决定了你能给 GPU 用多少内存。默认设置往往只有 512MB 或者 2GB,跑大模型必须改。
2. halogen 框架的编译:从源码到可执行文件
2.1 halogen 到底是什么,为什么不用现成的 ollama
很多人第一反应是用 ollama 或者 LM Studio 这类开箱即用的工具。但 Qwen3.8-Flash-Next 这个模型比较新,ollama 的模型库更新有延迟,而且 ollama 底层也是 llama.cpp,封装了一层之后你没法精细控制 KV Cache 的分配策略和专家路由的 offload 行为。halogen 是一个更底层的推理调度框架,它本身不实现算子,而是把 llama.cpp 作为 backend,在上面做请求调度、内存池管理和多模型切换。对于 Strix Halo 这种统一内存平台,halogen 的内存池管理能让你把 KV Cache 固定在 GPU 侧,而把不活跃的专家权重留在 CPU 侧,这个灵活性是 ollama 给不了的。
编译 halogen 之前,先把依赖装齐。Ubuntu 24.04 自带的 ROCm 版本是 6.0,但 Strix Halo 的 gfx1151 架构需要 ROCm 6.2 以上才有完整的支持。所以第一步是加 AMD 的官方源:
wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | \ gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] \ https://repo.radeon.com/rocm/apt/6.2 noble main" | \ sudo tee /etc/apt/sources.list.d/rocm.list sudo apt update sudo apt install rocm-hip-sdk rocm-dev rocm-libs -y装完之后验证一下 GPU 是否被识别:
rocminfo | grep gfx如果输出里有gfx1151,说明 ROCm 认到了这颗 APU。如果没有,大概率是内核版本太老,需要升级到 6.11 以上。Ubuntu 24.04 默认内核是 6.8,Strix Halo 的核显驱动在 6.10 之后才比较完善,所以建议手动装 mainline 内核。
2.2 编译 llama.cpp 的 ROCm backend
halogen 依赖 llama.cpp 的动态库,所以先编译 llama.cpp。这里有个关键点:llama.cpp 的 ROCm backend 默认编译会针对所有 AMD GPU 架构生成代码,编译时间巨长,而且生成的二进制体积很大。Strix Halo 只需要 gfx1151 一个架构,所以编译时指定AMDGPU_TARGETS:
git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DGGML_HIPBLAS=ON \ -DAMDGPU_TARGETS=gfx1151 \ -DCMAKE_BUILD_TYPE=Release \ -DLLAMA_CURL=OFF cmake --build build --config Release -j$(nproc)LLAMA_CURL=OFF这个选项很多人会忽略。默认情况下 llama.cpp 会链接 libcurl 用于从 HuggingFace 下载模型,但如果你已经手动下载了 GGUF 文件,这个依赖完全没必要,关掉能减少编译时间和运行时依赖。
编译过程中最常见的报错是hipcc: command not found或者Cannot find ROCm。前者是因为 ROCm 的 bin 目录没加到 PATH,后者是 CMake 找不到 ROCm 的配置文件。解决办法:
export PATH=/opt/rocm/bin:$PATH export ROCM_PATH=/opt/rocm export HIP_PATH=/opt/rocm把这三行加到~/.bashrc里,然后重新开一个终端再编译。
2.3 halogen 的编译与链接
halogen 的编译相对简单,但它需要链接 llama.cpp 的静态库。这里有个坑:llama.cpp 编译出来的库文件在build/src/和build/ggml/src/下面,halogen 的 CMakeLists 默认只找系统路径下的 llama 库。所以要么把 llama.cpp 装到系统路径,要么在编译 halogen 时手动指定:
git clone https://github.com/halogen-ai/halogen cd halogen cmake -B build \ -DLLAMA_CPP_PATH=/path/to/llama.cpp \ -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc)如果链接时报undefined reference to ggml_backend_*这类错误,说明 llama.cpp 的库没被正确链接。检查一下LLAMA_CPP_PATH是否指向了 llama.cpp 的根目录,而不是 build 目录。
实操心得:编译 llama.cpp 的时候,如果内存小于 64GB,建议把
-j$(nproc)改成-j8或者更低。ROCm 的编译过程非常吃内存,我 128GB 的机器在-j32的时候峰值内存占用到了 90GB 以上,如果内存不够会直接 OOM 被 kill。
3. 模型量化格式的选择:Q4_K_M 还是 Q5_K_S
3.1 MoE 模型的量化特殊性
Qwen3.8-Flash-Next 是 MoE 架构,总参数量约 30B,但每次推理只激活约 3B 的参数。这个特性对量化策略有直接影响:Dense 模型量化时,所有层的权重都会被用到,量化误差会累积;而 MoE 模型里,不同专家的权重是稀疏激活的,量化误差对最终输出的影响相对小一些。但这不意味着可以无脑用低比特量化,因为专家路由(Router)那部分的权重是每次都会用到的,如果 Router 的精度损失太大,会导致路由决策错误,把 token 分配给不合适的专家,输出质量会断崖式下跌。
我实测对比了几个量化版本在 Strix Halo 上的表现:
| 量化格式 | 文件大小 | 内存占用 | 生成速度(tok/s) | 输出质量主观评分 |
|---|---|---|---|---|
| Q4_K_M | 18.2GB | 22GB | 28.5 | 7.5/10 |
| Q5_K_S | 21.8GB | 26GB | 24.1 | 8.2/10 |
| Q5_K_M | 22.4GB | 27GB | 23.3 | 8.4/10 |
| Q6_K | 26.1GB | 31GB | 19.7 | 8.8/10 |
| Q8_0 | 33.5GB | 39GB | 14.2 | 9.3/10 |
Strix Halo 的 128GB 统一内存,实际可用给 GPU 的大概在 96GB 左右(BIOS 里分配了 96GB 给 UMA Frame Buffer)。所以 Q8_0 也能跑,但速度掉得比较厉害。综合来看,Q5_K_M 是甜点:质量接近 Q6_K,速度还能维持在 23 tok/s 以上,日常对话和代码生成都够用。
3.2 量化文件的分片与加载策略
Qwen3.8-Flash-Next 的 GGUF 文件如果超过 20GB,通常会分成多个分片(比如model-00001-of-00003.gguf)。llama.cpp 加载分片模型时,默认会按顺序加载所有分片,但 halogen 的内存池管理需要知道每个分片的大小和加载顺序。如果分片文件的命名不规范,halogen 可能会加载失败。
标准的分片命名格式是:
qwen3.8-flash-next-Q5_K_M-00001-of-00003.gguf qwen3.8-flash-next-Q5_K_M-00002-of-00003.gguf qwen3.8-flash-next-Q5_K_M-00003-of-00003.gguf加载时只需要指定第一个分片的路径,llama.cpp 会自动识别并加载后续分片。但前提是分片文件在同一个目录下,且命名符合-XXXXX-of-XXXXX.gguf的格式。如果你从不同来源下载的分片,命名可能不一致,需要手动重命名。
注意:不要试图把分片合并成一个文件。GGUF 的分片机制是为了方便下载和传输,合并后虽然能用,但加载时的内存映射效率反而会下降,因为大文件的内存映射需要连续的虚拟地址空间,在统一内存架构下容易触发碎片化。
3.3 KV Cache 的量化与分配
KV Cache 是推理时内存占用的大头。Qwen3.8-Flash-Next 的上下文长度支持到 128K,如果按 FP16 存储 KV Cache,128K 上下文需要的内存是:
KV Cache 大小 = 2 × 层数 × 头数 × 头维度 × 上下文长度 × 数据类型字节数以 Qwen3.8-Flash-Next 为例,假设 48 层、32 个 KV 头、头维度 128,上下文 32K:
2 × 48 × 32 × 128 × 32768 × 2 bytes ≈ 25.8GB这还没算模型权重本身的内存占用。所以 KV Cache 必须量化。llama.cpp 支持-ctk和-ctv参数分别指定 K 和 V 的量化类型,常用的组合是q8_0或者q4_0。实测下来,KV Cache 用q8_0对输出质量几乎没有影响,内存占用减半;用q4_0的话,长上下文时偶尔会出现重复生成的问题。
在 halogen 的配置里,KV Cache 的分配策略是通过--kv-cache-type和--kv-cache-size控制的。我的建议是把 KV Cache 固定在 GPU 侧(也就是 UMA Frame Buffer 里),因为 KV Cache 的读写非常频繁,放在 CPU 侧走 PCIe 或者 Infinity Fabric 会有明显的延迟。Strix Halo 的统一内存架构在这里优势很大,GPU 和 CPU 访问同一块物理内存,没有拷贝开销。
4. Strix Halo 的 BIOS 与内核调优
4.1 UMA Frame Buffer 的分配陷阱
这是整个部署过程中最容易翻车的地方。Strix Halo 的 BIOS 里有一个UMA Frame Buffer Size选项,默认可能是Auto或者512MB。如果你不改这个值,GPU 能用的内存就只有这么点,加载模型时会直接报out of memory。
但改这个值也有讲究。BIOS 里通常提供几个档位:Auto、512MB、2GB、4GB、8GB、16GB、32GB、64GB、96GB。注意,这个值是从系统总内存里划走给 GPU 专用的,划走之后 CPU 就看不到这部分内存了。所以如果你划了 96GB 给 GPU,系统里就只剩 32GB 给操作系统和其他程序用。
我的建议是:如果你主要用这台机器跑模型,划 96GB 给 GPU,留 32GB 给系统。如果你还要同时跑其他内存密集型任务,划 64GB 给 GPU,留 64GB 给系统。但要注意,llama.cpp 在 ROCm backend 下,模型权重是加载到 GPU 内存里的,如果 UMA Frame Buffer 不够,它会尝试用 GTT(Graphics Translation Table)内存,也就是从系统内存里动态借,但 GTT 的带宽比 UMA 低不少,性能会下降。
实操心得:BIOS 里改完 UMA Frame Buffer 之后,一定要进系统用
rocminfo确认 GPU 看到的内存大小。有时候 BIOS 设置没生效,或者被其他选项覆盖了。另外,某些主板的 BIOS 在改完这个值之后需要完全断电(拔掉电源线等 30 秒)才能生效,软重启不行。
4.2 内核参数与内存管理
Ubuntu 24.04 默认的amdgpu驱动对 Strix Halo 的支持已经比较好了,但有几个内核参数需要调整:
# 编辑 /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT="amdgpu.gttsize=98304 amdgpu.vm_fragment_size=9 \ amdgpu.noretry=0 amdgpu.lockup_timeout=10000"amdgpu.gttsize=98304:设置 GTT 大小为 96GB(单位是 MB),这样 GPU 在 UMA 不够的时候可以从系统内存借更多。amdgpu.vm_fragment_size=9:设置虚拟内存碎片大小为 2^9=512MB,减少大块内存分配时的碎片化。amdgpu.lockup_timeout=10000:把 GPU 超时检测从默认的 10 秒延长到 10 秒(单位是毫秒,10000ms=10s),避免大模型推理时因为单次 kernel 执行时间过长被误判为 hang。
改完GRUB_CMDLINE_LINUX_DEFAULT之后,运行sudo update-grub然后重启。
另外,建议把vm.swappiness调低:
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf sudo sysctl -pStrix Halo 的内存虽然大,但跑大模型的时候还是尽量别让系统把内存页换出到 swap。swappiness 调到 10 能减少不必要的换页,但又不至于完全禁用 swap 导致 OOM 时直接崩溃。
4.3 ROCm 环境变量的正确设置
ROCm 在 APU 平台上有几个环境变量必须设置,否则性能会差很多:
export HSA_ENABLE_SDMA=0 export GPU_MAX_HEAP_SIZE=100 export GPU_MAX_ALLOC_PERCENT=100 export HSA_OVERRIDE_GFX_VERSION=11.5.1HSA_ENABLE_SDMA=0:禁用 SDMA(System DMA)引擎。在 APU 上,SDMA 的拷贝路径反而比直接走 CPU 访问要慢,因为统一内存本来就不需要拷贝。GPU_MAX_HEAP_SIZE=100:允许 GPU 使用 100% 的堆内存。HSA_OVERRIDE_GFX_VERSION=11.5.1:强制指定 GPU 架构版本。有些 ROCm 版本对 gfx1151 的识别有问题,加上这个可以绕过。
这些环境变量加到~/.bashrc里,每次登录自动生效。
5. 跑起来之后的性能调优与踩坑
5.1 线程数与批处理大小的平衡
llama.cpp 在 APU 平台上,线程数不是越多越好。Strix Halo 有 16 个 Zen 5 核心,但如果你把-t设成 16,推理速度反而可能比-t 8慢。原因是 MoE 模型的专家路由是串行依赖的,线程太多会导致频繁的同步开销。我实测下来,-t 8到-t 12之间比较合适,具体取决于你的负载类型。
批处理大小(-b)和微批处理大小(-ub)也需要调。默认的-b 512 -ub 128在 Strix Halo 上表现一般,我建议改成-b 1024 -ub 256,这样能更好地利用内存带宽。但注意,-ub太大会导致 KV Cache 的峰值内存占用上升,如果 UMA Frame Buffer 不够,会触发 OOM。
./halogen --model qwen3.8-flash-next-Q5_K_M-00001-of-00003.gguf \ --n-gpu-layers 999 \ --threads 10 \ --batch-size 1024 \ --ubatch-size 256 \ --ctx-size 32768 \ --kv-cache-type q8_0 \ --flash-attn--n-gpu-layers 999表示把所有层都放到 GPU 上。在统一内存架构下,这个值其实无所谓,因为 CPU 和 GPU 共享内存,但设成 999 能确保 llama.cpp 不会把任何层留在 CPU 侧。
--flash-attn是必须开的。Flash Attention 在长上下文时能显著减少 KV Cache 的内存占用和读写次数。Strix Halo 的 RDNA 3.5 架构对 Flash Attention 的支持比较好,开了之后 32K 上下文的生成速度能提升 15% 左右。
5.2 那些官方文档不会提的报错与解决
报错一:hipErrorNoBinaryForGpu: Unable to find code object for all current devices
这个报错的意思是编译出来的 kernel 二进制不包含当前 GPU 架构的代码。原因是编译 llama.cpp 时AMDGPU_TARGETS没设对,或者 ROCm 版本和 GPU 架构不匹配。解决办法是确认rocminfo输出的 gfx 版本,然后重新编译,确保AMDGPU_TARGETS和它一致。
报错二:GGML_ASSERT: ggml_backend_buffer_is_host(data) failed
这个报错通常出现在加载模型的时候,原因是模型文件损坏或者分片不完整。检查一下所有分片文件的大小是否和 HuggingFace 上标注的一致,如果不一致,重新下载。
报错三:推理过程中突然卡死,然后进程被 kill
大概率是 OOM。Strix Halo 的统一内存虽然大,但如果你同时开了浏览器、IDE 和其他内存密集型程序,留给模型的内存就不够了。解决办法是跑模型之前关掉不必要的程序,或者把 UMA Frame Buffer 调大,或者降低量化精度。
报错四:生成速度突然从 25 tok/s 掉到 5 tok/s
这个现象通常出现在长上下文场景。原因是 KV Cache 增长到一定程度后,超出了 UMA Frame Buffer 的范围,llama.cpp 开始用 GTT 内存,带宽下降。解决办法是限制--ctx-size,或者把 KV Cache 量化到q4_0。
5.3 实际使用中的经验技巧
第一,模型加载时间。Q5_K_M 的 22GB 模型,从 NVMe SSD 加载到内存大概需要 40 秒左右。如果你频繁切换模型,这个时间很烦人。halogen 支持模型预热(--preload),可以在启动时就把模型加载好,后续请求直接复用。
第二,温度控制。Strix Halo 在满载跑模型的时候,APU 温度会到 85-90 度。如果是迷你主机,散热压力比较大。建议在 BIOS 里把风扇曲线调激进一点,或者限制一下功耗墙(PPT)。我实测把 PPT 限制在 80W,性能只损失 5% 左右,但温度能降 10 度。
第三,多模型切换。halogen 的内存池支持多个模型共享内存,但前提是模型的总大小不超过 UMA Frame Buffer。如果你要同时跑 Qwen3.8-Flash-Next 和一个嵌入模型,建议把嵌入模型量化到 Q4_0,减少内存占用。
第四,监控工具。推荐用radeontop看 GPU 利用率,用htop看 CPU 和内存。如果 GPU 利用率长期低于 50%,说明瓶颈在 CPU 侧的调度或者内存带宽,可以尝试调整线程数或者批处理大小。
6. 关于本地部署这件事的一些个人体会
折腾完这一套之后,我最大的感受是:Strix Halo 这类统一内存 APU 跑 MoE 模型,确实是目前性价比很高的方案,但前提是你愿意花时间调。官方文档给的是"能跑起来"的流程,但"跑得好"需要你自己去试参数、看日志、分析瓶颈。
halogen 这个框架的优势在于它的内存池管理比 ollama 灵活,但代价是配置复杂度高。如果你只是想快速体验一下 Qwen3.8-Flash-Next,用 ollama 拉一个 Q4_K_M 的版本也能跑,速度大概在 20 tok/s 左右,够用。但如果你想榨干 Strix Halo 的性能,或者需要长上下文、多模型切换这些高级功能,halogen + llama.cpp 的组合更合适。
最后分享一个我踩过的坑:BIOS 里改 UMA Frame Buffer 之后,一定要确认系统实际识别的内存大小。我有一次改了 96GB,但系统里free -h显示总内存还是 128GB,说明 BIOS 设置没生效。后来发现是主板的 BIOS 版本太老,升级之后才正常。所以如果你遇到"明明改了 BIOS 但 GPU 还是说内存不够"的情况,先检查 BIOS 版本和设置是否真的生效了。