端侧跑大模型最尴尬的从来不是算力不够而是生态不对。手里一台AMD机器显卡参数表看着挺漂亮翻开各种部署教程满屏都是另一套命令、另一套环境变量照抄过去第一步就报错。我前后在三台不同配置的AMD设备上折腾过Qwen3.8-Flash-Next的部署——一台是24GB显存的独显台式机一台是核显轻薄本还有一台带NPU的新平台迷你主机——踩的坑完全不同能跑通的参数也完全不同。这篇就把整个链路拆开讲清楚AMD端侧推理到底有哪几条路、显存该怎么提前算、llama.cpp的HIP后端怎么编译、启动参数每一项在干什么、哪些坑会让你卡一整天以及NPU那条路到底值不值得走。适合手里有AMD设备、想把模型真正落在本地跑起来的人从完全没碰过ROCm的新手到已经能跑但速度不理想的进阶用户都能拿到可以直接抄的东西。1. AMD上跑端侧大模型路线选择比参数调优更先决很多人一上来就问用什么参数最快其实在AMD平台上第一个要回答的问题根本不是参数而是走哪条后端。同样一张卡选错后端性能可能只有正确路线的一半甚至直接跑不起来。我把实际验证过的三条路线各自的适用边界列出来你可以先对号入座再往下看细节。1.1 ROCm HIP性能上限最高但对版本最挑剔这是AMD独显的主线方案。核心思路是把CUDA那套计算图通过HIP层翻译到AMD GPU上执行llama.cpp、vLLM、PyTorch都能走这条路。它的优势非常明确能吃到完整的矩阵运算加速库prefill阶段吞吐高显存管理也最贴近硬件。代价是版本耦合极紧。ROCm的版本、内核版本、GPU的gfx架构代号、编译器版本这四者任何一个不匹配你看到的报错就是device not found或者编译到一半挂掉而且报错信息往往和真正的原因毫无关系。我见过最离谱的一次编译失败的真实原因是系统里的python-dev包版本太老但报错指向的是hipcc找不到某个头文件。这条路线我建议这样判断如果你是RX 7000系列或更新的独立显卡且系统能装到官方支持的发行版版本就走ROCm别犹豫。如果你用的是核显或者比较老的卡先看看官方支持列表里有没有你的gfx代号没有的话直接跳到Vulkan路线能省掉大半天。1.2 Vulkan 后端驱动没配好时的保底方案Vulkan这条路经常被低估。它不依赖ROCm那一整套运行时只要有能正常工作的显卡驱动就能跑跨平台一致性极好Windows下尤其省心。实测下来Vulkan在decode阶段也就是逐token生成阶段的表现其实相当能打因为decode是访存密集型瓶颈在显存带宽而不是算力Vulkan的调度开销在这个场景里被摊薄了。差距主要体现在prefill阶段——处理长prompt的时候Vulkan比HIP慢三到五成是常见情况。所以判断标准很简单如果你的使用场景是短prompt、长输出的对话Vulkan完全够用如果是长文档摘要、代码库分析这种动辄上万token输入的活儿还是老老实实上HIP。1.3 Ryzen AI NPU省电优先的第三条路新一代带NPU的平台又多了一个选项。NPU的特点是能效比高跑推理的时候整机功耗明显低于独显方案风扇基本不转。但它的限制也很硬模型必须先转成特定格式的量化ONNX算子支持有白名单不在名单里的结构会被切回CPU执行一混合就慢得没法看。我目前的判断是NPU适合做常驻的后台小任务——比如本地文档的实时分类、输入法的语义联想、会议录音的转写后处理。想拿它当主力跑一个30B级别的模型现阶段还不现实。1.4 三条路线的横向对照维度ROCm HIPVulkanRyzen AI NPU硬件门槛独显且在支持列表内任意可正常驱动的GPU特定NPU平台部署难度高版本敏感低装好驱动即可很高需模型转换prefill 表现最好中等弱长序列受限decode 表现好接近中等功耗高高极低适合场景长上下文、批量任务对话、快速验证常驻轻量任务这张表建议存下来。后面所有内容除非特别说明都是围绕ROCm HIP这条主线展开的因为它是真正能发挥硬件实力的方案。2. 动手之前先把显存这笔账算清楚我见过太多人下载完模型直接启动然后眼睁睁看着加载到87%崩掉。问题几乎都出在同一个地方只算了权重没算KV Cache和运行时开销。这件事必须在下载之前做因为算完你可能发现需要换一个量化版本而重新下载几十GB是很痛苦的。2.1 权重占用量化格式直接决定下限权重占用可以用一个很朴素的方法估参数量乘以每个参数的字节数再留10%到15%的余量。FP16 / BF16每参数2字节INT8 / Q8_0每参数约1.06字节Q5_K_M每参数约0.72字节Q4_K_M每参数约0.60字节Q3_K_M每参数约0.48字节注意Q4_K_M这类混合量化不是严格的4bit它会把一部分敏感层比如embedding和输出层保留在更高精度所以实际字节数高于理论值。我一般按0.62估算比较稳。这里有个容易被忽略的点MoE结构的模型权重占用按总参数量算不是按激活参数量算。很多人看到激活参数只有3B就以为显存占用和3B模型差不多这是彻底的误解。激活参数只影响计算量权重还是得全部塞进显存或者内存。2.2 KV Cache真正吃掉余量的那头KV Cache的公式不难难的是很多人根本没意识到它存在。单token的KV占用 2 × 层数 × KV头数 × head维度 × 每元素字节数乘上你要跑的上下文长度就是总占用。注意公式里的KV头数不是注意力头数如果模型用了分组查询注意力KV头数会少很多这是好事。举个具体的一个48层、8个KV头、head维度128的模型用FP16存KV单token占用是 2 × 48 × 8 × 128 × 2 196608 字节约192KB。跑8192上下文就是1.5GB跑32768就是6GB。这个数字在权重已经吃掉20GB的情况下足够把一张24GB的卡逼到崩溃。把KV Cache量化成Q8占用直接减半而质量损失在绝大多数任务上感知不到。这是我优先推荐打开的一个开关。2.3 拿一个30B级MoE模型做一次完整测算以我手上这份Qwen3.8-Flash-Next权重为例总参数量在30B量级MoE结构激活参数约3B48层8个KV头在24GB显存卡上做测算项目精度/格式占用估算权重Q4_K_M约18.5 GBKV Cache8192上下文FP16约1.5 GBKV Cache8192上下文Q8约0.78 GB计算缓冲区—约1.0–1.5 GB运行时开销—约0.5 GBFP16 KV的情况下总计约21.5到22GB能塞进24GB但只剩不到2GB的喘息空间一旦上下文涨到16K立刻溢出。改成Q8 KV之后16K上下文的总占用还在22.5GB左右勉强守住。如果卡只有16GB结论就很清楚了要么把上下文压到4K以内要么把一部分层卸载到CPU。卸载的代价是速度断崖式下跌因为每生成一个token都要走一遍PCIe。我的经验是卸载比例超过20%之后速度会掉到原来的三分之一以下体验就废了。2.4 核显与UMA共享内存机型的特殊处理核显平台是另一套逻辑。它没有独立显存用的是系统内存的一部分通过GTT或者UMA的方式划给GPU。这时候显存不够这个判断本身就不成立你真正要关注的是两个东西BIOS里分配的显存上限以及内存带宽。内存带宽是核显的真正天花板。DDR5双通道理论带宽在80到100GB/s量级而独显是几百GB/s到上TB/s。decode阶段每个token都要把权重完整读一遍激活的那部分所以速度几乎和带宽成正比。这也解释了为什么核显跑MoE模型会比跑同尺寸的稠密模型快——MoE每个token只需要读取激活的那部分专家权重实际读的数据量小得多。核显机型上我通常会把BIOS里的显存分配调到最大同时在系统层面确认GTT上限够用Linux下可以通过内核参数调整。这一步不做模型加载会直接失败而且报错信息不会告诉你真实原因。3. llama.cpp HIP 从零编译到跑通选定了ROCm路线接下来就是把它跑起来。这一节我把整个链路按顺序拆开每一步都说明为什么这么做而不是只给命令。3.1 环境对齐先确认四件事再动手在敲任何编译命令之前先做四项检查这四项任何一项不对后面全是白费功夫。第一确认GPU的gfx架构代号。跑一条查询命令就能拿到记下这个代号后面编译和排错全靠它。第二确认ROCm版本支持你的gfx代号。官方支持列表里没有的代号要么走Vulkan要么用环境变量覆盖后面第4节细讲。第三确认内核版本和ROCm匹配。ROCm对内核版本有明确要求版本太高反而会出问题——新版内核经常会改一些驱动接口导致ROCm的预编译模块加载失败。第四确认当前用户在render和video用户组里。这一步纯粹是权限问题但症状表现为找不到设备非常迷惑人。加完组要重新登录才生效不是重启终端就行。提示这四项检查做完再动手能省掉后面至少一半的排错时间。尤其是用户组这一项我见过有人为此重装了三次系统。3.2 编译参数怎么选后端开关逐个说llama.cpp的编译选项看起来很多真正影响AMD平台性能的就那么几个。cmake -B build \ -DGGML_HIPON \ -DAMDGPU_TARGETSgfx1100 \ -DGGML_HIP_ROCWMMA_FATTNON \ -DCMAKE_BUILD_TYPERelease \ -DGGML_NATIVEON cmake --build build --config Release -j$(nproc)逐项解释一下。GGML_HIPON是打开HIP后端的主开关不开这个编译出来的还是CPU版本。AMDGPU_TARGETS必须精确填你的gfx代号。填错了编译能过运行时找不到kernel。填多个代号会让编译时间翻倍除非你要把二进制拷给不同机器用否则只填自己的。GGML_HIP_ROCWMMA_FATTNON打开的是基于ROCm矩阵指令的Flash Attention实现。这个开关对长上下文的prefill提升非常明显我的实测里长prompt场景能快出三到四成。但它对gfx架构有要求老卡开了会编译失败失败的话关掉即可不影响功能。GGML_NATIVEON让编译器针对当前CPU做优化主要影响那些跑在CPU上的辅助算子收益不大但没坏处。编译过程通常十到三十分钟取决于CPU。编译期间大量报warning是正常的只要不是error就不用管。3.3 模型文件的准备转换与再量化拿到原始权重之后一般要先转成GGUF格式再按需量化。转换脚本在仓库的转换目录里基本命令是传入原始权重路径和输出路径。这里有个坑转换脚本对原始权重的目录结构有要求如果你下载的是已经分片的文件需要保证所有分片都在同一目录下且命名符合规范否则脚本会读一半就报错。量化阶段Q4_K_M是我最常用的默认值。它在绝大多数任务上和FP16的差距感知不到体积却只有四分之一多一点。如果显存实在紧张可以往下降到Q3_K_M但要注意3bit量化对代码生成和数学推理的影响会明显一些日常对话还行。反过来如果你显存宽裕又想追求质量Q5_K_M或Q6_K是更好的选择代价是速度会略降——因为要读的字节多了而decode是带宽瓶颈。注意量化是一次性成本量化完的文件建议保留原始版本。我吃过一次亏为了省硬盘把原始权重删了后来想换个量化精度只能重新下载几十GB。3.4 启动参数逐项拆解这一步是最容易出问题的地方因为参数之间会相互影响。我给一份自己常用的配置然后逐项说清楚。./build/bin/llama-server \ -m ./models/qwen38-flash-next-Q4_K_M.gguf \ -ngl 99 \ -c 16384 \ --cache-type-k q8_0 \ --cache-type-v q8_0 \ -fa \ -b 2048 \ -ub 512 \ --threads 8 \ --host 127.0.0.1 \ --port 8080-ngl 99表示把所有层都放到GPU上。填99是个约定俗成的写法意思是能放多少放多少。如果显存不够llama.cpp会自动回退部分层到CPU但这个过程不透明你很难知道具体放了多少层。想精确控制就手动指定层数从总层数开始往下试直到能稳定加载。-c 16384是上下文长度。这个值直接决定KV Cache大小是显存占用的主要变量。不要一上来就填32768先按实际需求定跑通了再往上加。--cache-type-k和--cache-type-v是KV Cache的精度。K和V分开设置是有讲究的K对量化更敏感V相对宽容。实践里两个都设成q8_0问题不大但如果发现输出质量有异常可以只量化VK保持FP16。-fa打开Flash Attention。这个开关在长上下文下既省显存又快几乎没理由不开。-b和-ub是批处理大小和微批大小。这两个参数共同决定prefill阶段的并行度。-b 2048配合-ub 512是个比较稳的组合显存占用可控prefill速度也不错。显存宽裕的话可以把-ub往上加但别超过-b。--threads设成物理核心数。注意是物理核心不是超线程之后的逻辑核心数。设成逻辑核心数反而会因为调度竞争变慢这个坑我在好几台机器上都验证过。4. 卡住一整天的那几个坑以及它们真正的成因这一节是我认为最有价值的部分。下面每个问题的排查链路我都完整写出来因为很多时候报错信息和真实原因隔了十万八千里你需要的不是答案是排查思路。4.1 gfx架构不被识别报错指向的地方全是错的症状编译顺利通过运行时报no usable GPU found或者直接段错误。排查链路先确认ROCm本身能看见卡。跑一个设备信息查询命令如果这一步就没输出问题在驱动层不在llama.cpp。如果ROCm能看见卡但llama.cpp看不见那就是gfx代号不匹配。这时候去看编译时填的AMDGPU_TARGETS和实际代号是否一致。如果代号一致还是不行检查环境变量里有没有残留的旧配置尤其是那些指定可见设备的变量有时候会意外屏蔽掉你的卡。根因与修复最常见的情况是消费级显卡的gfx代号不在ROCm官方支持列表里。系统识别出的代号是一个值但ROCm的预编译kernel只认列表里的值。解决办法是用环境变量把代号覆盖成最接近的支持型号export HSA_OVERRIDE_GFX_VERSION11.0.0这个变量告诉运行时假装我是这个型号。绝大多数情况下功能正常少数涉及新指令集的特性和性能会受影响。这个方法在核显上尤其常用。提示这个环境变量要在启动进程的那个shell里生效写进配置文件容易被其他程序读到产生意想不到的副作用。4.2 加载到一半OOM不是显存不够是碎片症状进度条走到80%到95%之间崩掉报显存分配失败。但按你的计算明明够用。排查链路先看实际占用。加载过程中另开一个终端盯显存使用曲线如果是缓慢上涨然后突然失败说明确实是总量不够如果是锯齿状波动后失败那是碎片问题。把上下文长度砍半再试一次。如果能过说明KV Cache的预分配策略和你预期不符。关掉Flash Attention再试。某些架构上FA会额外申请一块较大的连续显存刚好卡在边界上。根因与修复有几种情况。一是显卡上还跑着桌面环境占了1到2GB你的计算没把这部分算进去。二是KV Cache在部分实现里是提前按最大上下文一次性分配的不是按需增长所以你以为的8192够用实际上启动时就按8192全额占了。三是驱动层的显存碎片重启能解决。我的处理顺序是先把上下文降到4096验证能不能跑通跑通之后按2的幂次往上加找到稳定上限然后在这个上限的80%位置定下来留出余量。4.3 输出重复、乱码、无限循环先怀疑采样而不是模型症状模型能跑速度也正常但输出会陷入重复或者冒出大量乱码字符。排查链路先把温度调到0贪心解码测试。如果乱码消失问题在采样参数不在模型文件。如果温度0下还乱换一个量化精度更低的版本试试。如果换了就好说明是这个量化文件本身有问题可能是转换时的精度损失叠加。如果所有量化版本都乱检查KV Cache量化。把--cache-type-k和--cache-type-v都去掉用FP16再测一次。最后才怀疑权重文件完整性做一次哈希校验。根因与修复我遇到过最常见的原因是重复惩罚参数设得太高。很多人为了让输出不啰嗦把repeat penalty调到1.3甚至更高结果模型被逼着换用生僻词越走越偏最后输出乱码。这个参数在1.05到1.15之间比较安全超过1.2就开始有副作用。第二个常见原因是KV Cache量化对K的影响。前面说过K更敏感如果你的任务对精度要求高比如代码补全建议K保持FP16只量化V。4.4 跑分很漂亮实际用起来卡问题不在GPU症状单独跑基准测试tok/s数字很好看但接到实际应用里感觉响应一顿一顿的。排查链路测首token延迟和后续token速度分开看。如果首token延迟很高但后续很快问题在prefill如果都慢问题在别处。看进程的CPU占用。如果某个核心跑满而GPU利用率不高是CPU侧的瓶颈。检查调用方是否每次请求都重新加载模型。有些客户端实现每次请求起一个新进程那就解释得通了。根因与修复最常见的是没有用服务端模式。如果你的应用每次调用都启动一次命令行程序模型要重新加载几十GB的权重到显存那个时间是以十秒计的。正确做法是用服务端模式常驻通过HTTP接口调用模型只加载一次。第二个原因是prompt构造得太啰嗦。系统提示词动辄两三千token每次请求都要重新prefill一遍首token延迟自然高。这个可以用前缀缓存缓解把固定的系统提示词部分缓存起来后续请求只处理变化的部分。5. 把速度从能用推到好用的几个抓手跑通只是及格线。真正决定这套东西能不能日常用的是吞吐和延迟。这一节讲三个我认为收益最高的优化方向。5.1 分清prefill和decode它们的瓶颈完全不同这是一个必须先建立的认识prefill是算力密集型decode是访存密集型。两者优化手段几乎相反。prefill阶段要处理整个输入序列矩阵乘法可以高度并行吃的是GPU的算力。优化方向是增大批处理大小、打开Flash Attention、用更高效的计算库。decode阶段每个token只处理一个位置但要把权重完整读一遍吃的是显存带宽。优化方向是降低权重的读取量——也就是用更激进的量化或者用MoE这种只读激活部分的结构。理解了这一点你就能判断自己的场景该往哪优化。长文档摘要类任务prefill占大头重点调-b和-ub对话类任务decode占大头重点在量化精度和显存带宽。搞反了方向调半天没效果。我的实测数据供参考24GB显存档Q4_K_M量化8K输入的prefill速度在600 tok/s上下decode速度在45 tok/s上下。这两个数字差了一个数量级所以短输入长输出的场景下prefill耗时几乎可以忽略你感受到的全是decode。5.2 KV Cache量化与上下文裁剪的取舍KV Cache量化是最划算的优化没有之一。把FP16降到Q8显存直接省一半长上下文的容量翻倍而质量损失在对话、摘要、翻译这些任务上基本测不出来。代价出现在两个场景一是数学和代码这类对数值精度敏感的任务二是超长上下文下的长程依赖。前者我建议K保持FP16只量化V后者建议把上下文控制在模型训练长度以内超出部分即使塞进去注意力也已经衰减得很厉害了。上下文裁剪是另一个思路。很多场景其实不需要那么长的上下文比如客服问答把历史对话按轮次截断保留最近若干轮加一个摘要效果和全量塞进去差不多但速度快得多、显存省得多。这个思路比调参更有效因为它从根上减少了工作量。5.3 服务化封装并发请求怎么处理单用户场景下一个常驻的服务端进程就够了。多用户或者批量任务场景就要考虑并发。最朴素的做法是串行排队简单但吞吐低。稍微好一点的是开多个进程各占一份显存问题是这样显存直接翻倍很快就爆了。正确做法是用支持连续批处理的推理框架。llama.cpp自带的服务端在较新版本里已经支持一定程度的并发原理是把多个请求的序列拼在一个batch里共享一次权重读取这样decode阶段的带宽利用率大幅提升。并发数不是越高越好。我的经验是在显存允许的前提下先测单个请求的decode速度然后逐步加并发观察总吞吐和单请求延迟的曲线。通常在并发数达到某个点之后总吞吐不再增长而延迟继续恶化那个拐点就是你的最优并发数。这个数在不同硬件上差别很大必须实测没法套公式。另外要记得设置请求队列上限。不加限制的话请求堆积会撑爆KV Cache表现为突然的OOM或者请求大面积超时。6. NPU 那条路现在该不该碰最后说说NPU。我花了差不多一周时间在这条路上结论是能做但有明确的前置条件不是所有人都值得投入。6.1 模型转换的实际工作量NPU路线的核心难点不在推理在转换。整个链路是原始权重 → 量化 → 导出ONNX → 用厂商工具链编译成能在NPU上执行的格式。第一道坎是算子支持。NPU的算子白名单是有限的模型里但凡有一个结构不在名单里编译阶段就会报错或者被切到CPU。而现在的模型结构迭代很快新结构往往还没有被工具链支持你就得自己拆算子、改图工作量不可控。第二道坎是量化精度。NPU通常对int8或者int4有硬件加速但量化过程需要校准数据。校准集选得不好精度掉得很厉害而且这个损失是模型级的你很难定位到具体哪一层出了问题。第三道坎是输入形状固定。很多NPU编译出来的模型要求固定的输入形状上下文长度、batch大小都得提前定死。这和对话场景的变长输入天然冲突做起来很别扭。我这一周的实际结果是小模型激活参数几个B的量级跑通了能效比确实漂亮功耗只有独显方案的零头但想跑大一点的模型卡在算子支持和精度上性价比不如直接上独显。6.2 什么场景该上NPU什么场景别碰该上的场景常驻后台、一直开着、负载轻、对功耗敏感。比如本地文档的实时索引和分类、语音输入的实时转写、邮件自动打标签这类任务。这些任务的特点是单次计算量小、频率高、可以接受一定延迟NPU的能效优势能完全发挥出来。不该碰的场景长上下文推理、批量数据处理、需要频繁切换模型的开发调试。这些场景要么对吞吐要求高要么对灵活性要求高NPU两条都不占优。还有一种折中思路值得提异构分工。把轻量的预处理和后处理放NPU把重负载的生成放独显两者并行不冲突。这个方案我在一台双硬件平台上试过实际收益主要体现在整机功耗和风扇噪音上推理速度提升有限但对需要长时间开机的场景来说体验改善明显。最后分享一个我踩坑得到的小经验端侧部署最容易被低估的成本不是硬件是版本管理。ROCm版本、驱动版本、编译参数、模型量化版本这四个变量组合起来的状态必须记下来。我后来养成了一个习惯每跑通一个配置就写一个脚本把这一整套固定下来包括环境变量和启动命令。因为在端侧环境里能跑通一次不代表下次还能跑通——系统更新、驱动升级、甚至换了张卡都可能让之前那套配置失效。有了这个脚本回滚和复现的成本就从半天降到十分钟。