vllm-ascend-pitfalls
vLLM-Ascend 实战指南:从环境部署到性能调优的完整避坑手册
第三方实践记录:驱动、固件、CANN、PyTorch、Python 版本严格匹配,是最易出错环节。
打开原始来源一题一页 · 技术方法 · 也叫 国产化适配、信创适配
同一个大模型换一块芯片就跑不起来,因为芯片下面那层软件不一样。适配就是把这层软件补齐、对齐、调快的工程活。
此刻在看:静态解释,这一页的 4 个部分都是这一种。
主张依据 3 类:官方文档、公开报道与实践记录、编辑解释。
核实状态:这一页没有需要核实的结论,所以这一维是空的。
表示此刻展示方式,不是准确性等级。
可多选,每种都有实际引用;「本站实验」需有记录。
附范围、时间及必要原因,不等于全页绝对正确。
这一页没有需要核实的结论,所以这一维是空的——不会因为它挂着论文就替它标一个「已核对」。
一份在英伟达卡上跑通的代码,搬到昇腾卡上改一行设备名。它不会直接跑起来,通常先卡在版本、再卡在算子、再卡在精度。
一句话:模型是通用的,模型下面那层软件不是。
大模型本身只是一堆数字和一串计算步骤,不认哪家的芯片。但这串计算步骤要落到硬件上,中间隔着一层厚厚的软件:算子库、图编译、显存管理、通信库。这层软件在英伟达那边被打磨了十几年,主流开源框架长期把它当默认路径,本站没有统计过具体比例。换一块芯片,这层要重写一遍。
版本。 驱动、固件、软件栈、框架插件、Python,五个版本要互相对得上。对不上的表现往往不是清晰的报错,而是莫名的崩溃。多份实践记录都把这一步列为最耗时的环节。
算子。 模型用到的某个数学动作,这层软件里还没有实现,或者实现了但很慢。前者报错,后者不报错,只是慢。
数字格式。 新模型越来越多用低精度格式发布权重,比如 FP8。芯片不支持这种格式,就得先把整份权重转成支持的格式。FP8 每个数占 8 位,BF16 每个数占 16 位,所以权重占的字节数翻倍。要注意翻倍的只有权重这一项:显存里还有激活值、KV 缓存、临时缓冲和框架自己的开销,总占用不会按同一个倍数走,具体多少取决于部署方式,本站没有实测。即便如此,权重通常是最大的一块,本来刚好装得下的模型可能就装不下了。
精度。 跑通之后还要验证算得对不对。做法是拿同一份输入,在两边逐层比对输出,看余弦相似度和误差。这一步经常发现某个算子实现有细微差别,累积到最后输出就变了样。
不是因为技术新奇,而是因为它决定了一个很实际的问题:一个国家的 AI 应用,能不能不依赖单一供应商的芯片。这层软件的成熟度,比芯片的纸面参数更能说明进展到了哪一步。
按暂停会把这一步的文字整段显示出来。这段字本来就是预先写好的,逐字出现只是展示方式,不是模型正在生成。 键盘:焦点在这个实验里时,← → 切换步骤,空格 播放或暂停。
「跑通」「跑对」「跑得快」是三件事,通常按这个顺序解决。这一整套做完,才算这个模型在这块芯片上「适配好了」。
此刻在看:教学脚本,这一页都是这一种。
主张依据 3 类:官方文档、公开报道与实践记录、编辑解释。
部分核对 核实状态:部分核对 · 核于 2026-09-07。
表示此刻展示方式,不是准确性等级。
可多选,每种都有实际引用;「本站实验」需有记录。
附范围、时间及必要原因,不等于全页绝对正确。
这个单元把「大模型的国产芯片适配」拆成四个坎。它不讲芯片参数,只讲工程师实际会撞上的东西。看完之后,你应该能说出:为什么这件事难的不是模型,而是模型下面那层软件。
想自己动手,看这个 前四步要有卡才能复现,最后的精度对齐脚本任何电脑都能跑
此刻在看:历史记录展示 + 教学脚本 + 静态解释——4 个部分各自标着自己的那一种,没有一个徽章能概括整页。
主张依据 3 类:公开报道与实践记录、本站实验、编辑解释。
作者已逐字核对 核实状态:作者已逐字核对 · 核于 2026-09-08。
表示此刻展示方式,不是准确性等级。
可多选,每种都有实际引用;「本站实验」需有记录。
附范围、时间及必要原因,不等于全页绝对正确。
一个最小的推理脚本。它在英伟达卡上没有任何问题。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
name = "your/model"
tok = AutoTokenizer.from_pretrained(name)
model = AutoModelForCausalLM.from_pretrained(
name, torch_dtype=torch.bfloat16
).to("cuda").eval()
ids = tok("今天天气很好,我们去", return_tensors="pt").to("cuda")
with torch.no_grad():
out = model.generate(**ids, max_new_tokens=16)
print(tok.decode(out[0])) 这一步是标准写法,没有跑过,仅作为迁移的起点。
装上厂商的 PyTorch 适配插件,导入它,然后把设备名从 cuda 换成 npu。看上去就完了。
+ import torch_npu # 厂商提供的 PyTorch 适配插件
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
- ).to("cuda").eval()
+ ).to("npu").eval()
- ids = tok(..., return_tensors="pt").to("cuda")
+ ids = tok(..., return_tensors="pt").to("npu") torch_npu 是昇腾侧的 PyTorch 插件,代码仓库见页尾来源。改动确实就这么少,问题都在后面。
第一次运行,报错往往指不到真正的原因。
python3 run.py Traceback (most recent call last):
File "run.py", line 9, in <module>
out = model.generate(**ids, max_new_tokens=16)
RuntimeError: ACL stream synchronize failed, error code: 507011 报错说的是底层运行时同步失败,但真正的原因通常是版本不匹配。这类报错信息是这条路上最典型的体验。
驱动、固件、软件栈、PyTorch 插件、Python,五个版本要互相对得上。
npu-smi info # 看卡和驱动
cat /usr/local/Ascend/ascend-toolkit/latest/version.cfg
pip show torch torch_npu | grep -E "Name|Version" driver : 24.1.rc3
firmware : 7.5.0.1
toolkit : 8.0.RC2 <-- 插件要求 8.0.RC3
Name: torch Version: 2.1.0
Name: torch_npu Version: 2.1.0.post6 多份公开实践记录都把版本匹配列为最易出错的环节,见页尾来源。
版本对上之后,可能撞到模型用了这块卡还没实现的算子。
python3 run.py NotImplementedError: The operator 'aten::_scaled_dot_product_flash_attention'
is not currently implemented for the NPU device. 这类问题有明确报错,反而好办。更麻烦的是有实现但很慢,不报错,只是慢,只能靠逐层测时间去找。
前面几步需要真的有卡(第三个坎是数字格式,在场景那一页)。但最后这一步的方法本身,在任何一台电脑上都能演示。下面这个脚本在**一台机器上用代码模拟**三条被测路径:一条只把矩阵乘的输入舍入到 bf16 表示、一条把 layernorm 的方差分母写错成 n-1、一条直接把参考输出整体乘以 1.5。它不是两块真实芯片的对比,也没有真实硬件的 bf16 内核;跑的是同一份 Python 代码,差别都是代码里人为加进去的。
curl -O https://你的站点/demo/align_check.py
python3 align_check.py 精度对齐:同一份输入,参考路径和 3 条被测路径差多少
这是什么 一台机器上用代码模拟的对照,不是两块真实芯片的对比
bf16 只对矩阵乘的输入做表示舍入,乘法与累加仍是双精度,不是硬件 bf16 内核
余弦相似度按六位小数显示,1.000000 是显示值,不表示完整精度下相等
门槛随张量尺度、层深和任务变,没有单一指标或统一阈值能判断所有算子
参数 8 层 · 维度 64 · 4 行输入 · 随机种子 0 · 余弦门槛 0.999 · 缩放反例 ×1.5 · layernorm 的 eps 1e-05
输入 random.Random(0):按顺序先生成 8 层 64×64 权重(高斯,均值 0、标准差 64^-0.5),再生成 4×64 输入(高斯,均值 0、标准差 1)
参考计算 参考路径是同一份代码在 Python float(IEEE 754 双精度)下跑出来的输出,不是另一块芯片的输出,也不是更高精度的真值
显示舍入 余弦相似度按 6 位小数显示,其余三列按三位有效数字的科学计数法显示;「1-余弦」那一列专门给出六位小数遮掉的部分
指标口径 最大绝对误差 = max|a-b|,带量纲,随张量整体尺度一起变
均方根误差 = sqrt(mean((a-b)²)),带量纲,不被单个离群点主导
相对均方根误差 = ‖a-b‖₂ ÷ ‖a‖₂,无量纲;被测 = 参考 × c 时精确等于 |1-c|
可复现 固定种子、不测耗时。作者在一台 arm64 macOS 上逐个试过 6 个 CPython:
3.12~3.14 与页面上贴的那份逐字节相同;3.9~3.11 只有情况三的「1-余弦」那列不同
(1e-16 量级),因为 CPython 3.12 起浮点 sum() 改用了补偿求和。
情况三的真值是 0,那一列上的非零全是求和方式带来的舍入噪声。换一种 CPU 架构没有试过。
【情况一 · 只有低精度输入表示(bf16 舍入),算子没写错】
两侧输入舍入到 bf16 表示后再做同一次乘加,累加仍是双精度
层 余弦相似度 1-余弦 最大绝对误差 均方根误差 相对均方根误差 余弦判定
-------------------------------------------------------------------------------------
1 0.999997 2.789e-06 7.264e-03 2.362e-03 2.362e-03 通过
2 0.999994 6.018e-06 1.373e-02 3.469e-03 3.469e-03 通过
3 0.999989 1.141e-05 1.922e-02 4.777e-03 4.777e-03 通过
4 0.999982 1.834e-05 2.103e-02 6.056e-03 6.056e-03 通过
5 0.999977 2.346e-05 2.526e-02 6.850e-03 6.850e-03 通过
6 0.999959 4.078e-05 3.392e-02 9.030e-03 9.031e-03 通过
7 0.999963 3.723e-05 3.316e-02 8.629e-03 8.629e-03 通过
8 0.999957 4.282e-05 3.111e-02 9.254e-03 9.254e-03 通过
余弦判定(门槛 0.999):8/8 层通过
【情况二 · 算子写错了:方差除以 n-1 而不是 n】
layernorm 的方差分母写成 63 而不是 64,其余一模一样
层 余弦相似度 1-余弦 最大绝对误差 均方根误差 相对均方根误差 余弦判定
-------------------------------------------------------------------------------------
1 1.000000 4.441e-16 1.539e-02 7.843e-03 7.843e-03 通过
2 0.999998 1.693e-06 1.183e-02 8.054e-03 8.055e-03 通过
3 0.999995 4.753e-06 1.505e-02 8.423e-03 8.423e-03 通过
4 0.999992 7.717e-06 1.836e-02 8.765e-03 8.765e-03 通过
5 0.999987 1.324e-05 2.046e-02 9.369e-03 9.369e-03 通过
6 0.999979 2.148e-05 2.265e-02 1.020e-02 1.020e-02 通过
7 0.999979 2.069e-05 2.439e-02 1.013e-02 1.013e-02 通过
8 0.999971 2.869e-05 2.738e-02 1.088e-02 1.088e-02 通过
余弦判定(门槛 0.999):8/8 层通过
【情况三 · 缩放反例:把参考输出整体乘以 1.5(人为构造,不是芯片会犯的错)】
被测输出 = 参考输出 × 1.5,方向一个字没变
层 余弦相似度 1-余弦 最大绝对误差 均方根误差 相对均方根误差 余弦判定
-------------------------------------------------------------------------------------
1 1.000000 0.000e+00 9.812e-01 5.000e-01 5.000e-01 通过
2 1.000000 0.000e+00 8.756e-01 5.000e-01 5.000e-01 通过
3 1.000000 0.000e+00 9.860e-01 5.000e-01 5.000e-01 通过
4 1.000000 0.000e+00 9.322e-01 5.000e-01 5.000e-01 通过
5 1.000000 1.110e-16 9.827e-01 5.000e-01 5.000e-01 通过
6 1.000000 0.000e+00 9.021e-01 5.000e-01 5.000e-01 通过
7 1.000000 0.000e+00 8.206e-01 5.000e-01 5.000e-01 通过
8 1.000000 1.110e-16 8.281e-01 5.000e-01 5.000e-01 通过
余弦判定(门槛 0.999):8/8 层通过
三张表放在一起看:余弦判定在 3/3 种情况里都是「全部通过」,
包括第二种真写错的算子和第三种纯缩放——余弦只看方向,看不见整体缩放。
带量纲的最大绝对误差和均方根误差看得见缩放,但数值随张量尺度一起变,换个模型、换一层就要换门槛;
无量纲的相对均方根误差在情况三里精确等于 |1-1.5| = 0.5。
所以真实的对齐脚本会同时看几个指标、逐层看趋势,而不是宣布某一个指标配某一个阈值就能定所有算子的对错。 这段输出实际跑出来: 在哪里跑的:作者本机,arm64 macOS,CPython 3.14.7 你现在打开这一页不会有任何程序被执行。
这 64 行是本页作者在 2026-09-08 用 CPython 3.14.7(arm64 macOS)真实运行得到的,逐字粘贴。脚本用固定随机种子、不测耗时,可复现有边界:作者在同一台机器上试过 6 个 CPython,3.12~3.14 逐字节相同,3.9~3.11 只有情况三那列「1-余弦」不同(1e-16 量级,CPython 3.12 起浮点 sum() 改用了补偿求和)。换一种 CPU 架构作者当时没有试过。`tests/demo/test_align_check.py` 会重跑脚本;在这组已记录环境中逐行逐字比较,其他平台仅允许情况三该列在 8 ulp 内变化,其余字段不放过,所以门禁要求 CPython ≥ 3.12。
情况二是算子真的写错了,方差除以 n-1 而不是 n。但它第 1 层的余弦相似度按六位小数显示是 1.000000,比情况一还漂亮,八层全部「通过」。原因是余弦相似度只看方向不看长度,而这个 bug 在每一行里恰好是一个标量缩放。情况三是把这件事推到极端的缩放反例:直接把参考输出整体乘以 1.5,方向一个字没变,余弦八层全过,相对均方根误差却精确等于 0.5。
两件事要一起记住。第一,`1.000000` 是**六位小数的显示值**,不表示完整精度下相等——所以表里专门有一列「1-余弦」,情况二第 1 层是 4.441e-16,情况一第 1 层是 2.789e-06。第二,只拿余弦相似度当验收标准,会把一个真写错的算子放过去,还给它打满分。
至少要有一个对缩放敏感的指标,比如最大绝对误差、均方根误差或相对均方根误差,并且要看它随层数怎么变化。带量纲的那两个随张量尺度一起变,所以门槛不能跨模型、跨层照抄;无量纲的相对均方根误差在「被测 = 参考 × c」时精确等于 |1-c|。
def check(ref, dev):
return {
"cosine": cosine(ref, dev), # 只看方向,对正比例缩放不敏感
"cos_gap": 1 - cosine(ref, dev), # 六位小数显示遮掉的那部分
"max_abs": max_abs_diff(ref, dev), # 带量纲,随张量尺度一起变
"rmse": rmse(ref, dev), # 带量纲,不被单个离群点主导
"rel_rmse": rel_rmse(ref, dev), # 无量纲,被测 = 参考 × c 时等于 |1-c|
}
# 逐层跑一遍,看几个指标一起怎么变;门槛随张量尺度、层深和任务定,
# 没有哪一个指标配哪一个固定阈值能判断所有算子是对是错。 这就是「跑通不等于跑对」在代码里的样子。这里给的是口径,不是验收线——同一批数字整体放大 1000 倍,余弦和相对均方根误差一个字不变,最大绝对误差会跟着放大 1000 倍。
这一页是「国产芯片适配」的第三级。前面的卡片告诉你它是什么,场景演了一遍它怎么发生,这里给你能真正动手的东西。
第 1 到 5 步是迁移的真实样子,需要有卡才能复现,所以那些输出都标了「示意」。第三个坎(数字格式)没有单独一步,它在场景那一页。第 6 步不需要任何卡,你现在就能跑,输出是真实的。
第 6 步能证明什么、不能证明什么,写在这里:它证明的是这套指标在同一份数字上分别看见了什么——一台机器上用代码模拟出来的三条被测路径,跑的是同一份 Python 代码。它不是两块真实芯片的对比,bf16 那一路只对矩阵乘的输入做表示舍入、乘加仍是双精度,所以它也不是硬件 bf16 内核的行为。表里的 1.000000 是六位小数的显示值,旁边那列「1-余弦」才是它遮掉的量级。这里的余弦门槛 0.999 只是这个玩具配置的演示门槛:门槛随张量尺度、层深和任务变,没有单一指标或统一阈值能判断所有算子是对是错。
还有一件事顺便被这个脚本自己演示了。作者把它在同一台机器上的 6 个 CPython 里各跑一遍:3.12~3.14 与上面那 64 行逐字节相同,3.9~3.11 只有情况三的「1-余弦」那一列不同,差在 1e-16 量级——因为 CPython 3.12 起浮点 sum() 改用了补偿求和。情况三的真值本来就是 0(被测 = 参考 × 1.5,方向完全一致),所以那一列上的非零全是求和方式带来的舍入噪声。换句话说:连「同一份代码、同一台机器、只换了解释器版本」都会让最后几位不一样,这正是精度对齐这件事存在的理由。换一种 CPU 架构作者没有试过。
换芯片难的不是模型,是它下面那层翻译软件:跑通、跑对、跑得快是三件事。
在 8 层 × 维度 64 的代码模拟网络上,余弦相似度全部「通过」,最大绝对误差 1.539e-02 才露出写错的算子。
模型本身一个字都没改。难的是它下面那层翻译软件:版本要对齐、算子要补齐、数字格式要转换、精度要逐层比对。跑通、跑对、跑得快是三件事。
迁移的代码改动可能只有三行,工作量全在后面:对版本、补算子、转格式、验精度。验精度这一步,在这个 8 层 × 维度 64 的代码模拟网络上,一个真写错的算子(方差除以 63 而不是 64)在 8 层里余弦相似度全部「通过」,第 1 层的六位小数显示值还是 1.000000;同一批数字的最大绝对误差 1.539e-02 才把它露出来。
上面那一栏是整条结论的公共条件;下面每一行另有自己的口径,所以单独摘走一行也带着口径。
| 这个数是什么 | 数值 | 它在什么范围内成立 |
|---|---|---|
| 代码模拟网络的层数 | 8 层 | 每层维度 64,权重与输入都是脚本里固定种子生成的,不对应任何真实模型 |
| 第 1 层余弦相似度的六位小数显示值 | 1.000000 | 六位小数的显示值,不能读成两边完全一致;后面几位的差别就藏在这个显示精度以下 |
| 同一批数字的最大绝对误差 | 1.539e-02 | 情况三那一批数字上的最大绝对误差;它随张量尺度变,换一批数字这个数就不作数 |
vllm-ascend-pitfalls
第三方实践记录:驱动、固件、CANN、PyTorch、Python 版本严格匹配,是最易出错环节。
打开原始来源deepseek-fp8-bf16
第三方实践记录:DeepSeek 官方权重为 FP8,910B 不支持,需转为 BF16。属于个人实践,具体型号支持情况以官方为准。
打开原始来源vllm-ascend-docs
搜索结果给出的页面标题与片段;发布说明原文一个字都没读到
vLLM 昇腾后端的官方发布说明。2026-09-07 直接抓取返回 429,页面内容经搜索结果转述,未逐字核对原文。
打开原始来源ascend-910b-practice
第三方实践记录:CANN 分层结构带来的双层排错、版本匹配与显存管理差异。属于个人经验,非官方口径。
打开原始来源torch-npu-repo
PyTorch 的昇腾适配插件,提供 npu 设备后端。版本需与 CANN、驱动匹配。
打开原始来源