摘要
本文是一篇基于 ModelScope DSW AMD GPU 实例的原创工程实践记录,内容来自我在 ROCm 环境中反复部署、重启恢复、补丁验证、长上下文测试和 8K 性能扫参后的整理。文章重点不是泛泛介绍 DeepSeek-V4-Flash,而是记录一次“如何把原本在 AMD ROCm 上不容易直接跑稳的模型,整理成可运行、可验证、可复现的研究 baseline”的过程。
DeepSeek-V4-Flash 对底层执行路径要求较高,在 ROCm 环境中直接用 vLLM 启动时,会遇到 kernel 导入、dtype、mHC、MXFP4、sparse top-k 等多处兼容问题。我的做法是先用 vLLM 搭建 OpenAI API 服务层,再结合 ROCm/AITER/PyTorch fallback 和少量兼容补丁建立稳定 baseline,而不是一开始就追求极限性能。
围绕这条 baseline,我完成了短问答、2K/8K/32K needle retrieval 和 8K topk sweep。实测结果显示:/v1/models 可正常返回 HTTP 200;短问答和 2K/8K/32K 隐针检索均形成可复现实验记录;32K correctness 在 index_topk=4096 下通过;8K 场景中 index_topk=2048 是这一轮较优点,单请求首 token 延迟从 96.589s 降到 80.313s,等效 prefill 从 84.740 tok/s 提升到 101.914 tok/s。
这套方案目前仍然是 fallback-heavy 的研究型部署,更适合作为 AMD ROCm 上的可复现研究 baseline。它的价值在于:把“DeepSeek-V4-Flash 能否在 AMD ROCm 上跑起来”推进为“如何验证长上下文 correctness,以及哪些 ROCm kernel 决定后续性能上限”。后续优化重点会继续落在 sparse MLA、top-k、mHC、MoE、FlashMLA 等关键路径上。

选题背景
我选择 DeepSeek-V4-Flash,是因为它不是一个“下载权重后直接起服务”就能讲完的模型。它很适合作为 AMD GPU / ROCm 大模型部署研究对象:一方面模型本身足够新,涉及的执行路径比较复杂;另一方面它在 ROCm 上会暴露出很多真实工程问题,而不是停留在安装教程层面。
这次研究过程中,我重点关注几个问题:
- vLLM 服务层能启动,不代表模型执行路径稳定;
- ROCm kernel 支持、AITER 路径、PyTorch fallback 之间需要取舍;
- 长上下文不是只看
max_model_len,还要做语义级 correctness 验证; - 性能优化不是只调 batch 参数,还要看 sparse attention、top-k、KV cache 和 MoE 路径。
我给这轮研究设定的问题是:
在 ModelScope DSW AMD GPU 环境中,我能否通过 vLLM + ROCm/AITER/PyTorch fallback,把 DeepSeek-V4-Flash 跑成一套可复现的长上下文研究 baseline,并进一步定位 ROCm kernel 优化方向?
我对“跑通”的标准不是只看服务进程是否存在,而是分成三层:
- 服务可用:OpenAI API server 能启动,
/v1/models正常; - 输出可信:短问答能稳定返回正确答案,不出现乱码或明显错误;
- 长上下文可验证:2K/8K/32K needle retrieval 能命中隐藏 token。
环境与复现口径
本次实验在 ModelScope DSW 的 AMD GPU 实例上完成,环境中已包含 ROCm、PyTorch 和 ModelScope 等基础组件。为了方便后续复现,我没有把命令绑定到某个固定目录,而是统一使用 MODEL_DIR、RUN_MODEL_DIR、REPRO_DIR 等变量表示路径。这样无论模型权重放在临时盘、数据盘还是持久化盘,都可以按同一套流程迁移。
我把复现环境拆成三类目录:
# 模型权重目录:可以放在临时盘、数据盘或持久化盘,取决于实例空间策略
export MODEL_DIR=/path/to/DeepSeek-V4-Flash
# 运行模型目录:可以是模型权重目录本身,也可以是软链接/补丁后的运行树
export RUN_MODEL_DIR=/path/to/runtime/DeepSeek-V4-Flash
# 复现资料目录:建议持久化保存,放脚本、补丁、日志摘要和测试报告
export REPRO_DIR=/path/to/persistent/deepseek_v4_flash_rocm_repro
路径选择没有唯一答案。我更关注的是复现资料是否完整:模型权重体积大,必要时可以重新下载;脚本、补丁、日志摘要和实验报告体积小,但决定了实验能否复现,因此需要优先保存。实例空间充足时,当然也可以把权重长期保留。只要后续还能根据同一套脚本、参数和补丁记录恢复实验过程,这个路径设计就是合理的。
环境检查建议至少记录以下信息:
python3 - <<'PY'
import torch
print("torch:", torch.__version__)
print("hip:", getattr(torch.version, "hip", None))
print("cuda_is_available_name:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "N/A")
print("device_count:", torch.cuda.device_count())
PY
python3 -m pip show vllm | sed -n '1,20p'
python3 -m pip show aiter | sed -n '1,20p'
rocminfo | head -n 40
我把这些信息写进 env_report.txt。原因很简单:ROCm、PyTorch、vLLM、AITER 的版本变化很快,很多问题并不是模型代码逻辑错了,而是底层 wheel、ABI 或 kernel 支持发生了变化。没有环境记录,后面很难判断问题到底来自参数、补丁还是版本漂移。
直接启动时的主要问题
我一开始也尝试过按常规思路直接启动。很快发现,DeepSeek-V4-Flash 在 ROCm 上真正难的不是 HTTP API,而是模型内部执行路径。直接启动时,我主要遇到几类问题:
| 问题类别 | 表现 | 处理思路 |
|---|---|---|
| kernel 导入失败 | 部分 CUDA-like 或 tilelang 路径无法在 ROCm 上导入 | 对不稳定 import 做 fallback |
| MXFP4 / FP8 dtype | scale、W8A8、unfused/fused 路径类型不匹配 | 对 scale 做 upcast,必要时切到 unfused |
| sparse top-k | 长上下文下 top-k 过小会影响检索正确性 | 用 needle retrieval 反推稳定 topk |
| FlashMLA / mHC | 部分高性能路径未完全适配 ROCm | 先用 AITER/PyTorch fallback 兜底 |
| graph capture | 高性能和稳定性之间存在取舍 | 初期使用 eager 建立 correctness baseline |
所以我这次没有一开始就追求极限性能,而是先按下面的顺序推进:
- 先让服务稳定启动;
- 再让短输出不乱码;
- 然后做 2K/8K/32K 长上下文 correctness;
- 最后对 8K 场景做有限性能扫参。
最小可复现流程
1. 下载或准备模型权重
模型可以通过 ModelScope 或其他权重来源准备。下面以 ModelScope 为例:
export MODEL_ID=deepseek-ai/DeepSeek-V4-Flash
export MODEL_DIR=/path/to/model-cache/DeepSeek-V4-Flash
python3 - <<'PY'
import os
from modelscope import snapshot_download
model_id = os.environ.get("MODEL_ID", "deepseek-ai/DeepSeek-V4-Flash")
model_dir = os.environ["MODEL_DIR"]
snapshot_download(
model_id,
cache_dir=os.path.dirname(model_dir),
)
print("downloaded:", model_id)
print("target:", model_dir)
PY
如果实例重启后本地盘会清空,权重可以重新下载;如果持久化盘足够大,也可以把权重长期保存。我不把路径策略写成固定要求,只强调一点:复现脚本、补丁资料、参数记录和日志摘要一定要保留。
2. 准备运行模型目录
如果不需要修改模型目录,可以直接:
export RUN_MODEL_DIR="$MODEL_DIR"
如果需要把非权重文件和权重 shard 分开管理,可以建立运行模型树:
mkdir -p "$RUN_MODEL_DIR"
# 示例:复制配置、tokenizer、代码文件,权重文件可以按需复制或软链接
rsync -av --exclude='*.safetensors' "$MODEL_DIR"/ "$RUN_MODEL_DIR"/
find "$MODEL_DIR" -name '*.safetensors' -maxdepth 1 -print0 \
| xargs -0 -I{
} ln -sf "{}" "$RUN_MODEL_DIR"/
这种做法只是为了便于补丁和恢复,不是必须步骤。读者可以根据自己的存储策略选择。
3. 恢复兼容补丁
这轮实验里的补丁不是为了“魔改模型能力”,而是为了绕开 ROCm 环境中暂不稳定的执行路径,让 vLLM 服务先建立可验证 baseline。我的补丁思路如下:
| 补丁类别 | 目的 | 风险 |
|---|---|---|
| mHC / tilelang import fallback | 避免启动阶段因不支持的 kernel 导入失败 | 性能可能下降 |
| MXFP4 Triton unfused route | 绕开 fused path 在 ROCm 上的兼容问题 | 失去部分融合收益 |
| FP8 scale upcast | 修复 scale dtype 兼容问题 | 需要验证数值正确性 |
| sparse top-k fallback | top-k ROCm/AITER 路径不稳时回退 PyTorch | prefill 延迟上升 |
| AITER MQA 参数修正 | 修正 block/shared memory 相关参数 | 依赖具体版本 |
补丁应用后,建议立刻做一次只读检查:
python3 - <<'PY'
import importlib
mods = [
"vllm",
"torch",
]
for m in mods:
mod = importlib.import_module(m)
print(m, getattr(mod, "__version__", "ok"))
PY
如果这一步都不能通过,就不要继续启动大模型服务,应先回到 import 和 wheel 版本问题。
4. 启动 vLLM OpenAI API 服务
下面是我这轮稳定 baseline 使用的核心启动参数。实际复现时,只需要把路径替换成自己的 RUN_MODEL_DIR。
export RUN_MODEL_DIR=/path/to/runtime/DeepSeek-V4-Flash
export SERVED_MODEL_NAME=deepseek-v4-flash-amd-32k
export MAX_MODEL_LEN=32768
python3 -m vllm.entrypoints.openai.api_server \
--model "$RUN_MODEL_DIR" \
--served-model-name "$SERVED_MODEL_NAME" \
--host 0.0.0.0 \
--port 8000 \
--dtype auto \
--trust-remote-code \
--kv-cache-dtype fp8 \
--block-size 256 \
--tokenizer-mode deepseek_v4 \
--tool-call-parser deepseek_v4 \
--enable-auto-tool-choice \
--reasoning-parser deepseek_v4 \
--max-model-len "$MAX_MODEL_LEN" \
--max-num-seqs 8 \
--max-num-batched-tokens 16384 \
--gpu-memory-utilization 0.90 \
--enforce-eager \
--async-scheduling \
--no-disable-hybrid-kv-cache-manager \
--moe-backend triton \
--disable-uvicorn-access-log
几个关键参数的含义:
| 参数 | 作用 | 我这样设置的原因 |
|---|---|---|
--max-model-len 32768 |
目标上下文长度 | 我把阶段目标从 128K 收敛到 32K,先建立稳定 baseline |
--kv-cache-dtype fp8 |
降低 KV cache 显存压力 | 在长上下文下更容易启动 |
--enforce-eager |
禁用 graph capture | 牺牲部分性能,换取 fallback-heavy 路径稳定性 |
--max-num-batched-tokens 16384 |
控制 prefill 批 token | 比 32768 更容易满足 KV cache 规划 |
--moe-backend triton |
指定 MoE backend | 便于稳定复现当前路径 |
这里的参数不是最终性能最优配置,而是“能稳定进入 correctness 验证”的配置。后续性能优化应在这个 baseline 上逐步修改。
服务可用性验证
服务启动后,先做 /v1/models:
curl -s http://127.0.0.1:8000/v1/models | python3 -m json.tool | head -n 40
然后做最短生成:
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v4-flash-amd-32k",
"messages": [{"role": "user", "content": "What is the capital of France? Answer with one word."}],
"max_tokens": 8,
"temperature": 0
}'
只有当短问答不乱码、结果稳定时,才进入长上下文测试。

长上下文正确性验证:needle retrieval
长上下文测试不能只看模型是否能返回 token。我采用 needle retrieval:在长文本里插入一个隐藏 token,然后要求模型只返回这个 token。只有模型能从长上下文中准确找回这个 token,我才把它视为通过。测试脚本可以写成下面这种形式:
import json
import requests
def build_prompt(target_tokens: int, needle: str = "cobalt-7391"):
prefix = "This is a long context test. " * 200
filler = "The deployment note discusses ROCm, vLLM, sparse attention, and fallback paths. " * 2000
hidden = f"\nThe secret retrieval key is {needle}.\n"
prompt = (prefix + hidden + filler)[: target_tokens * 4]
return prompt, needle
def run_needle(target_tokens: int):
prompt, needle = build_prompt(target_tokens)
payload = {
"model": "deepseek-v4-flash-amd-32k",
"messages": [
{
"role": "system", "content": "Return only the secret retrieval key."},
{
"role": "user", "content": prompt},
],
"max_tokens": 16,
"temperature": 0,
}
r = requests.post(
"http://127.0.0.1:8000/v1/chat/completions",
headers={
"Content-Type": "application/json"},
data=json.dumps(payload),
timeout=900,
)
r.raise_for_status()
text = r.json()["choices"][0]["message"]["content"]
return needle in text, text
for n in [2048, 8192, 32768]:
ok, text = run_needle(n)
print(n, ok, text)
这一轮关键结果如下:
| 测试 | 结果 | 耗时 |
|---|---|---|
| 短问答:法国首都 | HTTP 200,输出 Paris |
13.540s |
| 2K 隐针检索 | HTTP 200,命中 cobalt-7391 |
53.376s |
| 8K 隐针检索 | HTTP 200,命中 cobalt-7391 |
151.976s |
| 32K 隐针检索 | HTTP 200,prompt_tokens=32768,命中 cobalt-7391 |
497.470s |
| 32K 后健康检查 | HTTP 200,输出 5 |
1.652s |
这些结果说明,当前部署不是只完成了“模型加载”,而是已经形成了长上下文语义验证证据。
关键参数:index_topk
在这轮实验中,index_topk 是一个重点观察参数。它会影响 sparse attention / sparse MLA 相关路径中的候选保留规模:
- topk 越小,潜在 prefill 成本越低,但长距离信息更容易丢;
- topk 越大,长上下文 correctness 更稳,但延迟和显存压力会上升。
32K correctness 探索结果如下:
| 配置 | 测试 | 结果 | 耗时 | 备注 |
|---|---|---|---|---|
| topk2048 | 16K 开头隐针 | 通过 | 176.77s | 命中 token |
| topk2048 | 32K 开头隐针 | 失败 | 303.88s | 输出缺失关键片段 |
| topk3072 | 32K 开头隐针 | 失败 | 519.05s | token 不完整 |
| topk3584 | 32K 开头隐针 | 失败 | 549.69s | token 不完整 |
| topk4096 | 32K 开头隐针 | 通过 | 572.91s | 命中完整 token |
所以如果目标是 32K correctness,我这一轮最稳的配置是:
index_topk=4096
如果目标是 8K 性能探索,则可以尝试更低 topk。

这张矩阵也说明了一个容易被忽视的问题:8K 的最优性能点不一定是 32K 的稳定配置。长上下文模型部署不能只给一个参数结论,必须说明适用上下文长度。
8K 性能扫参
为了观察 8K 场景下的性能边界,我对 index_topk=1024/1536/2048 做了扫参,并把结果和 4096 correctness baseline 做对比。
示例命令如下:
export REPRO_DIR=/path/to/persistent/deepseek_v4_flash_rocm_repro
TOPKS="1024 1536 2048" \
bash "$REPRO_DIR/scripts/run_8k_topk_sweep.sh"
实验结果如下:
| index_topk | 8K 隐针 | Gate 耗时 | 首 token 延迟 | 等效 prefill |
|---|---|---|---|---|
| 4096 | 通过 | 151.976s | 96.589s | 84.740 tok/s |
| 1024 | 通过 | 138.563s | 95.217s | 85.962 tok/s |
| 1536 | 通过 | 154.255s | 94.250s | 86.844 tok/s |
| 2048 | 通过 | 156.403s | 80.313s | 101.914 tok/s |
这一轮 8K 场景下的最优点是 index_topk=2048。相对 4096 baseline:
- 首 token 延迟从
96.589s降到80.313s,约改善16.9%; - 等效 prefill 从
84.740 tok/s提升到101.914 tok/s,约改善20.3%; - 8K 隐针检索仍然命中目标 token。

但这里必须谨慎:topk2048 只能作为 8K 性能探索点,不能直接替代 32K 稳定配置。它在 32K 开头隐针检索中已经出现语义失败。

负例分析:完整预填充不一定更好
我也试过把 max_num_batched_tokens 从 16384 提升到 32768,希望让 32K prompt 尽量一次 prefill 完成。但服务在 KV cache 规划阶段直接失败:
To serve max seq len 32768, 13.17 GiB KV cache is needed.
Available KV cache memory: 1.39 GiB.
Estimated maximum model length: 3448.
这说明 scheduler 参数不能孤立调整。显存规划、KV cache、batch token、上下文长度和模型结构必须一起看。这个负例对后续优化很有价值,因为它排除了一个看似直观但实际不可行的方向。
与公开高性能 serving 结果的对照口径
公开资料中,DeepSeek-V4-Flash 的高性能 serving 结果更多围绕 Nvidia H/B 系列 GPU 展开,这类结果通常同时依赖成熟的 CUDA kernel、graph capture、并发调度和专门的 benchmark 脚本。它们对我这次实验的作用,主要是提供一个性能参照系,而不是形成直接横向评判。
本次测试口径是:
8192 input / 1 output
单请求
ModelScope DSW AMD ROCm 环境
fallback-heavy baseline
本轮最佳首 token 延迟:80.313s
在这个口径下,我更关注两件事:第一,DeepSeek-V4-Flash 能否在 AMD ROCm 环境中稳定完成 OpenAI API 服务、短问答和长上下文检索;第二,哪些路径会成为后续优化的主要瓶颈。也就是说,这轮实验的结论不是给不同硬件下性能排座次,而是先把 AMD ROCm 上的可运行、可验证、可复现 baseline 建起来。
有了这个 baseline 之后,后续性能优化就不再是盲目调启动参数,而可以围绕 sparse MLA、top-k、mHC、MoE、FlashMLA 等具体路径逐步推进。对我来说,这正是这轮实验最有价值的地方:问题从“能不能启动”变成了“哪些 kernel 和调度策略决定上限”。
当前瓶颈判断
从日志和实测结果看,我判断当前瓶颈主要不在 HTTP 服务层,也不在模型是否能加载,而在 DeepSeek-V4-Flash 的底层执行路径:
- sparse MLA / sparse attention indexer 仍然依赖 fallback-heavy 路径;
- FlashMLA prefill/decode fallback 仍然启用;
- qnorm、RoPE、KV、mHC 缺少足够融合;
- MXFP4/FP8 MoE 没有获得成熟 ROCm 高性能路线;
enforce-eager提高了稳定性,但放弃了 graph capture 的性能空间;- 投机解码还没有进入当前 ROCm baseline。
因此,后续我不会继续只盯着启动参数做微调,而是会把重点放到 sparse MLA、top-k、mHC、MoE、FlashMLA 等关键 kernel 上。
复现资料整理
为了让这轮实验以后还能被复查、复跑,我至少沉淀了这些资料:
repro/
scripts/
start_server.sh
health_check.sh
run_needle_tests.py
run_8k_topk_sweep.sh
patches/
rocm_compat_notes.md
patch_summary.md
reports/
env_report.txt
32k_correctness_report.md
8k_topk_sweep_report.md
figures/
01_repro_pipeline.png
02_32k_correctness.png
03_correctness_matrix.png
04_8k_topk_sweep.png
05_negative_cases.png
如果模型权重可以重新下载,权重本身不一定要和这些资料放在一起。真正关键的是脚本、补丁、参数、日志摘要、结果表和截图,它们决定了这次实验能不能被完整恢复。
AMD GPU 大模型部署经验
做完这轮实验后,我总结出几条对 AMD GPU / ROCm 大模型部署很有帮助的经验:
- 不要把“服务启动”当成“长上下文可用”,必须做 needle retrieval;
- ROCm fallback 能帮助建立 baseline,但不能替代真正的 kernel 优化;
- topk 是 correctness 和性能之间的关键旋钮,不同上下文长度需要分别验证;
- 负例同样重要,它能说明哪些参数方向不可行;
- 复现资料应该包括环境、补丁、脚本、日志摘要、结果表和截图,而不只是启动命令;
- 性能对照要先统一硬件、并发、脚本和输入输出口径;在口径不同的情况下,公开结果更适合作为优化参照,而不是直接下结论。
结论和下一步
这一轮研究,我已经完成了 DeepSeek-V4-Flash 在 AMD ROCm DSW 上的研究型部署:
- vLLM OpenAI API 服务可以启动;
- 8K 长上下文语义验证通过;
- 32K correctness 在
index_topk=4096下通过; - 8K 性能在
index_topk=2048下有约 17% 首 token 延迟改善; - 部署脚本、补丁链、实测报告和证据图已经沉淀为可复现资料。
同时我也会把边界说清楚:它还不是生产级高性能部署,而是一条以 correctness 和复现为优先的研究型 baseline。当前性能瓶颈已经比较明确,后续需要继续沿着 ROCm-native kernel 和调度优化方向推进。
下一步我认为最有研究价值的方向,是把 sparse MLA、top-k、mHC、MoE、FlashMLA 等关键路径逐步替换为 ROCm-native Triton 或 AITER kernel,并继续研究 DeepSeek-V4-Flash + 投机解码在 AMD GPU 上的可行性。
对我来说,这次实验最大的意义,是把 DeepSeek-V4-Flash 在 AMD ROCm 上的部署问题拆解成了可复现的工程链路、可验证的 correctness gate 和清晰的 kernel 优化方向。后续继续研究 AMD GPU 大模型推理优化时,我可以在这个 baseline 上往下推进,而不是每次都从“能不能启动”重新开始。
参考资料
- DeepSeek-V4-Flash 模型卡:https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash
- vLLM DeepSeek-V4-Flash recipe:https://recipes.vllm.ai/deepseek-ai/DeepSeek-V4-Flash
- vLLM ROCm DeepSeek-V4 tracking issue:https://github.com/vllm-project/vllm/issues/41820
- Lambda DeepSeek-V4-Flash benchmark:https://lambda.ai/inference-models/deepseek-ai/deepseek-v4-flash