
1. 为什么要在 MI250 上折腾 DeepSeek-V4-Flash1.1 一个不太常规的硬件选择先说清楚背景。DeepSeek-V4-Flash 是 DeepSeek 系列里主打高吞吐、低延迟的推理版本模型本身对显存带宽和矩阵计算单元的要求相当高。而 AMD MI250 是 CDNA2 架构的双芯封装加速卡单卡两个 GCDGraphics Compute Die每个 GCD 64GB HBM2e合计 128GBFP16/BF16 算力标称 362 TFLOPSFP8 大约 724 TFLOPS。纸面数据不差但生态上跟主流那套 CUDA 工具链完全是两条路。我拿到这张卡的时候第一反应是这玩意儿显存够大跑个 Flash 版本应该不至于 OOM。但真正上手才发现问题根本不在显存容量而在算子覆盖度、精度路径和通信拓扑这三件事上。CDNA2 的 Matrix Core 对 FP16/BF16 支持很好但 FP8 和 FP4 的支持是分阶段的——MI250 这一代对 FP8 有硬件支持通过 MFMA 指令但 FP4 基本要靠软件模拟或者查表反量化这就直接决定了 DeepSeek-V4-Flash 里那些低精度权重能不能高效落地。所以这篇东西不是一篇“跑通了”的炫耀帖而是一份从正确性验证到性能调优的完整踩坑记录。适合谁看手里有 MI250 或者类似 CDNA2 设备、想跑大模型推理但被 ROCm 生态折磨过的同学以及那些正在评估“非主流硬件跑主流模型”可行性的工程团队。如果你只是想找个开箱即用的方案那这篇可能不太适合你因为 MI250 上跑 DeepSeek-V4-Flash 目前还没有那种“一键脚本”。1.2 先修正确性再谈性能——这个顺序不能反我见过太多人一上来就调 batch size、调 tensor parallel、调 kernel fusion结果输出一堆乱码还以为是性能问题。在异构硬件上跑模型正确性验证必须是第一步而且要比在常规硬件上更严格。原因很简单CDNA2 的指令集和内存模型跟主流生态有差异很多算子在移植过程中会出现静默错误——不报错不崩溃但数值就是不对。比如 LayerNorm 的 epsilon 处理、RoPE 的旋转角度计算、MoE 路由的 top-k 选择这些在 CUDA 上跑了几百万次的逻辑换到 ROCm 上可能因为浮点累加顺序不同就产生偏差。偏差小的时候你看着输出“好像也对”但一旦进入长上下文或者高并发误差累积就会让结果完全不可用。我的做法是先在一个极小的输入上做逐层对比。用 PyTorch 在 CPU 上跑一个 reference然后在 MI250 上跑同样的输入逐层 dump 中间激活值算 cosine similarity。只有每一层的相似度都在 0.999 以上才认为这一层是“正确”的。这个过程很枯燥但能帮你把问题定位到具体的算子或者具体的精度转换点上。注意不要用“输出看起来合理”作为正确性标准。大模型的输出本身就有随机性肉眼根本判断不了数值偏差。必须用数值指标。2. 环境搭建与基础验证2.1 ROCm 版本选择与坑点MI250 对 ROCm 版本相当挑剔。我试过 ROCm 5.7、6.0、6.2 三个版本最后稳定在ROCm 6.2 PyTorch 2.4这个组合上。ROCm 5.7 的问题是对 FP8 的支持不完整很多算子会 fallback 到 FP32性能直接腰斩ROCm 6.0 的 hipBLASLt 在某些 shape 下会挂死6.2 相对最稳但也不是没问题。安装过程就不赘述了官方文档写得很清楚。这里只说几个文档里不会写的坑内核参数必须调vm.max_map_count默认是 65530跑大模型的时候 mmap 区域不够会直接报out of memory但实际显存还空着一大半。改成1048576以上。HSA_OVERRIDE_GFX_VERSION 不要乱设网上有些教程让你设成9.0.0来兼容但 MI250 是gfx90a设错了会导致 Matrix Core 完全不走性能掉到十分之一。双 GCD 的可见性MI250 是两个 GCDrocm-smi会显示成两张卡。如果你只想用一个 GCD要用HIP_VISIBLE_DEVICES0来限制如果想两个都用注意它们之间的互联带宽Infinity Fabric虽然不低但跟 NVLink 比还是有差距tensor parallel 的通信开销要重新评估。2.2 DeepSeek-V4-Flash 的权重加载与精度转换DeepSeek-V4-Flash 的权重格式是 safetensors里面混了 BF16 和 FP8 两种精度。在 MI250 上加载的时候不能直接from_pretrained就完事因为 ROCm 的 FP8 支持跟 CUDA 不一样。我的做法是分两步先把所有权重统一转成 BF16在 CPU 上做用safetensors库逐张量读取、转换、保存。这一步很慢但只做一次。在 MI250 上加载 BF16 权重然后针对特定的线性层主要是 attention 的 QKV 投影和 FFN 的 gate/up 投影做动态 FP8 量化。动态量化的好处是不需要校准集直接根据当前 batch 的激活值范围来定 scale。这里有个关键点MI250 的 FP8 是 E4M3 格式而 DeepSeek 原始权重里的 FP8 是 E4M3 和 E5M2 混用的。E5M2 的动态范围更大但精度更低直接转成 E4M3 会溢出。所以我在转换的时候加了一个per-channel 的 scale 因子把 E5M2 的值先缩放到 E4M3 的范围内再存成 E4M3 scale 的形式。推理的时候先反量化再计算。# 简化的 E5M2 - E4M3 转换逻辑 def convert_e5m2_to_e4m3(tensor, scaleNone): if scale is None: # 根据张量绝对值最大值计算 scale amax tensor.abs().max() scale amax / 448.0 # E4M3 的最大值 scaled tensor / scale # 裁剪到 E4M3 范围 scaled torch.clamp(scaled, -448.0, 448.0) # 模拟 E4M3 的舍入 e4m3 scaled.to(torch.float8_e4m3fn) return e4m3, scale这段代码看着简单但实际跑的时候要注意scale 的计算必须在 GPU 上做用 CPU 算再传回去会引入额外的同步开销在长序列推理时累积起来很可观。2.3 正确性验证的实操流程验证流程我设计了三层第一层单算子验证。把模型里的关键算子RMSNorm、RoPE、SwiGLU、Attention单独抽出来用固定随机种子生成输入在 CPU 和 MI250 上分别跑对比输出。这一层能发现大部分精度问题。第二层单层验证。把整个 Transformer block 拿出来输入一个[1, 128, 4096]的小张量逐层对比 hidden states。这一层能发现算子之间的交互问题比如残差连接的累加顺序。第三层端到端验证。用固定的 prompt比如一段 512 token 的文本设置do_sampleFalse对比 CPU 和 MI250 的完整输出 logits。这一层能发现位置编码、KV cache 管理、MoE 路由等全局逻辑的问题。实测下来第一层和第二层基本能覆盖 90% 的问题。我遇到的最诡异的一个 bug 是RoPE 的 sin/cos 表在 MI250 上因为精度问题导致长序列位置偏移——短序列看不出来超过 2048 token 之后输出开始重复。后来把 sin/cos 表从 FP16 改成 FP32 存储问题解决。3. 性能调优的核心手段3.1 算子融合与 kernel 选择正确性搞定之后性能调优才有意义。MI250 上的性能瓶颈通常不在算力而在内存带宽和 kernel launch 开销。CDNA2 的 HBM2e 带宽是 1.6TB/s 每 GCD看着很高但实际能达到 70% 就不错了。我做的第一件事是算子融合。PyTorch 的 eager 模式在 MI250 上跑小算子非常吃亏每个 kernel launch 都有固定开销。用torch.compile可以自动融合一部分但 ROCm 后端对torch.compile的支持还在完善中有些图会 fallback 回 eager。更可靠的做法是手动融合关键路径。比如RMSNorm QKV 投影把 norm 的 scale 吸收到 QKV 的权重里省掉一次读写。SwiGLU 的 gate 和 up 投影合并成一个大的 GEMM输出再 split。Attention 的 softmax dropout用 flash attention 的 ROCm 实现但要注意 MI250 的 flash attention 对 head dim 有限制超过 128 会 fallback。这里有个经验不要迷信 flash attention。在 MI250 上对于短序列512普通的 attention 实现可能更快因为 flash attention 的额外开销分块、重计算摊不薄。我实测下来序列长度 256 的时候普通 attention 比 flash attention 快 15% 左右。3.2 显存管理与 KV Cache 优化DeepSeek-V4-Flash 的 KV cache 是大头。在 MI250 上每个 GCD 64GB模型权重占掉大约 30GBBF16剩下的 34GB 要留给 KV cache 和激活值。我的配置是KV cache 用 FP8 存储MI250 支持 FP8 的 load/storeKV cache 从 BF16 降到 FP8 能省一半显存而且对精度的影响很小实测 perplexity 上升不到 0.1。PagedAttention 的 ROCm 适配vLLM 的 PagedAttention 在 ROCm 上有移植版本但 block size 要调。默认的 16 在 MI250 上会导致显存碎片改成 32 更合适。双 GCD 的显存池化如果两个 GCD 都用来跑同一个模型可以用 tensor parallel 把权重和 KV cache 分到两个 GCD 上。但要注意MI250 的两个 GCD 之间的通信要走 Infinity Fabric延迟比片内高一个数量级。所以 tensor parallel 的 degree 不要超过 2再高通信开销就吃掉收益了。3.3 批处理与并发策略批处理是提升吞吐最直接的手段但在 MI250 上要小心。CDNA2 的 Matrix Core 对 batch size 的敏感度跟 CUDA 不一样。我实测下来batch size 从 1 加到 8吞吐提升明显从 8 加到 16提升变缓超过 32 之后基本不涨因为显存带宽饱和了。所以我的建议是在线服务场景用 batch size 8-16离线批处理场景用 batch size 32-64。同时开多个进程跑不同的 batch比单进程大 batch 更划算因为 MI250 的双 GCD 可以并行处理不同的请求。还有一个细节continuous batching 的调度策略。vLLM 默认的调度是 FCFS但在 MI250 上因为 kernel launch 延迟较高FCFS 会导致长序列请求阻塞短序列请求。我改成了一种基于剩余 token 数的优先级调度短序列优先整体吞吐提升了大约 20%。4. 常见问题与排查实录4.1 典型问题速查表问题现象可能原因排查方法解决方案输出乱码或重复RoPE 精度问题对比 CPU 和 GPU 的 sin/cos 表sin/cos 表用 FP32 存储显存 OOM 但 rocm-smi 显示有空闲mmap 区域不足dmesg看是否有 mmap 错误调大vm.max_map_count性能只有预期的一半Matrix Core 未启用检查HSA_OVERRIDE_GFX_VERSION设为9.0.0或留空双 GCD 通信慢tensor parallel 度过高测 Infinity Fabric 带宽TP degree 不超过 2FP8 推理结果偏差大E5M2 转 E4M3 溢出检查权重转换时的 scale加 per-channel scalekernel launch 超时小算子太多用 profiler 看 kernel 数量手动融合或 torch.compile4.2 几个让我印象深刻的坑坑一hipBLASLt 的 workspace 限制。MI250 上 hipBLASLt 默认的 workspace 是 32MB跑大 GEMM 的时候不够会 fallback 到慢速路径。要手动设HIPBLASLT_WORKSPACE_SIZE环境变量我设的是 256MB。这个坑找了整整两天因为没有任何报错只是性能不对。坑二MoE 路由的 top-k 在双 GCD 上不一致。DeepSeek-V4-Flash 是 MoE 架构路由的 top-k 选择在 tensor parallel 下需要 all-reduce。但 MI250 的 RCCL 在某些 shape 下会有数值偏差导致两个 GCD 选出的 expert 不一样。解决方案是在路由之前先把 logits 同步到两个 GCD虽然多了一次通信但保证了正确性。坑三KV cache 的 FP8 量化在长序列下精度崩溃。短序列没问题超过 4096 token 之后FP8 的 KV cache 累积误差会导致 attention 分数完全失真。后来改成混合精度 KV cache最近的 1024 token 用 BF16更早的用 FP8。这样既省显存又保精度。提示在 MI250 上跑任何模型都建议先用一个小模型比如 1B 参数跑通全流程确认环境没问题再上大模型。直接上大模型排查成本太高。4.3 性能调优的检查清单每次调优之前我会按这个清单过一遍确认 Matrix Core 在工作用rocprof看 MFMA 指令的占比低于 60% 说明有优化空间。确认内存带宽利用率用rocm-smi看 HBM 带宽低于 70% 说明有内存瓶颈。确认 kernel launch 次数用torch.profiler看 kernel 数量超过 1000 个每 forward 说明需要融合。确认通信开销如果是多卡用rccl-tests测 all-reduce 带宽低于 50GB/s 说明通信是瓶颈。确认 batch size 是否合适画 throughput vs batch size 曲线找拐点。这套流程走下来基本能把 MI250 的性能压榨到理论峰值的 60%-70%。再往上就很难了因为 ROCm 的软件栈跟 CUDA 比还是有差距很多底层优化做不了。5. 一些个人体会MI250 跑 DeepSeek-V4-Flash目前的状态是能用但不够好用。正确性可以保证性能大概能达到同价位 CUDA 卡的 50%-60%。如果你手里已经有 MI250想跑个推理服务这套方案是可行的但如果你是要新采购硬件我建议还是优先考虑生态更成熟的方案。不过有一点值得说MI250 的显存是真的大。128GB 的显存让我可以跑更长的上下文、更大的 batch这在某些场景下比单纯的算力更重要。比如做长文档摘要或者代码补全MI250 的显存优势能抵消一部分性能劣势。最后分享一个小技巧在 MI250 上跑推理一定要把 CPU 的 governor 设成 performance。默认的 powersave 模式会导致 CPU 侧的调度延迟增加在 continuous batching 场景下影响很明显。改完之后P99 延迟能降 10% 左右。这个方向后续还可以继续挖比如试试 ROCm 6.3 的新特性、看看能不能把 FP4 的查表反量化做得更高效。但那是下一步的事了先把当前这套跑稳再说。