- 解释为什么在评估非 NVIDIA 加速器时,算子覆盖率和迁移成本比裸峰值 FLOPs 更重要。
- 用近似的 2025 年公开数据,把国产 AI 硬件格局(华为昇腾、寒武纪、海光、摩尔线程、百度昆仑、阿里含光)与 NVIDIA CUDA + vLLM 生态做对比。
- 用第一性原理估算推理显存(权重 + KV cache)与训练显存(6ND / 模型状态 / 激活值)。
- 构建并运行一个硬件选型评分器,按显存适配、算力适配、生态成熟度、成本和机房功率对加速器排名。
- 解读评分器给出的淘汰原因(
memory、ecosystem、budget、power)在真实采购场景中的含义。
问题
你在规划一个新的训练或推理集群。默认答案是"NVIDIA H100 + CUDA + vLLM"。然后三件事之一发生了。
供应消失了。 H100 交期拉长、价格飙升,或者配额政策限制进口。你被要求评估第二供应商:华为昇腾、寒武纪、海光、摩尔线程、百度昆仑或阿里含光。
规格表纸上很好看。 某国产卡宣称 FP16 TFLOPS 相当、HBM 更大。但 TFLOPS 只有在你的模型的算子在那个栈上有优化过的 kernel 时才算数。一个缺失的 flash_attention、不支持的 bfloat16、或一个没实现的自定义 loss,能把一张快卡变成一个科研立项。
迁移成本是隐性的。 把 CUDA 代码移植到另一个栈,很少只是改语法。它是一连串算子级和数值精度上的意外:torch.matmul 可能分发到不同的 kernel,默认 dtype 行为可能不同,分布式训练集合通信的性能悬崖位置也可能不同。成本在覆盖率和精度对齐上,不在把 cuda() 改名成 npu() 上。
本课构建一个把权衡逼到台面上的采购评分器。产物是一个小程序:读一张 JSON 卡表,把每个候选卡对一个负载画像打分,同时给出排名短名单和每张被拒卡落的坑。
概念
加速器只是产品的一半
一个训练或推理节点 = 硅片 + 软件。同一块硬件可以不可用、很慢、或很有竞争力,取决于栈:
| 加速器家族 | 近似软件栈 | 备注(截至 2025) |
|---|---|---|
| NVIDIA H100 / A100 | CUDA、cuDNN、NCCL、PyTorch、vLLM | vLLM 与社区算子覆盖最高;对照基线。 |
| 华为昇腾 910B / 910C | CANN、torch_npu、MindSpore | 国产主力;torch_npu 提供 PyTorch 设备抽象。算子覆盖在增长但与 CUDA 不同。 |
| 寒武纪 MLU(思元系列) | NeuWare、PyTorch 适配层 | 训练与推理兼顾;迁移需要逐算子验证。 |
| 海光 DCU | DTK,ROCm / HIP 兼容路线 | 类 ROCm 编程模型;兼容层降低移植语法成本,但不保证 kernel 对等。 |
| 摩尔线程 MTT S4000 / S5000 | MUSA、CUDA 移植工具 | CUDA 兼容移植路线;成熟度与算子覆盖仍在建设。 |
| 百度昆仑 | XPU SDK、Paddle / PyTorch 桥接 | 推理与训练产品;生态小于昇腾。 |
| 阿里含光 | AliNN、PAI 集成 | 云优先推理加速器;自建集群中较少见。 |
表中所有数字都是近似值,应视为截至 2025 年的快照。价格、峰值 TFLOPS 和软件成熟度变化很快;本课交付一张 JSON 卡表,正是为了让你能更新它们。
算子覆盖率决定"能不能跑",而不是峰值 FLOPs
峰值 TFLOPS 是理想利用下的张量核速度。真实负载是显存带宽、kernel 启动开销、通信和算子可用性的混合。要先问的问题是:
我的模型前向和反向图里的每一个算子,这个栈上是否都有正确且优化过的实现?
如果答案是否,这张卡不是一张慢一点的 H100;它是一个移植项目。所以评分器把生态分(0–1 的近似成熟度评分)当作一等输入,而不是平手裁决项。
兼容层降低的是语法成本,不是语义成本
几个国产栈宣称 CUDA 兼容:
torch_npu让 PyTorch 代码调用torch.Tensor.to("npu"),基本复用 PyTorch API。- 摩尔线程的 MUSA 提供类 CUDA 编程接口和迁移工具。
- 海光 DCU 走 ROCm / HIP 路线,很多 CUDA kernel 可以用
hipify编译。
这些层消除了机械改写,但不保证:
- 每个算子有相同的数值精度,
- 相同的融合 kernel 存在,
- 分布式集合通信性能一致,
bfloat16或梯度缩放行为相同。
真实的迁移成本是端到端验证计算图并测量精度漂移。
显存:第一道硬门
推理时,每卡显存占用近似为:
其中 N 是参数量,b 是每参数字节数(fp16/bf16 为 2),KV cache 为:
B 是批大小、S 是序列长度、H 是隐藏维度、L 是层数、b 是精度字节数。因子 2 来自 key 和 value 张量。
训练时,复用第 35 课的显存账单。混合精度 Adam 的模型状态约为每参数 16 字节:
激活值再加一项,与批大小、序列长度、隐藏维度和层数成正比。评分器用简化的激活值估计,保持代码自包含。
如果模型加 KV cache 放不下,再多的 TFLOPS 也救不了。当所需卡数超过实际上限时,评分器以 memory 原因拒绝该卡。
算力:过了生态和显存之后再算
显存放得下之后,再检查可用算力是否满足时延或墙钟目标。在算力受限假设下的推理:
P 是每卡峰值 TFLOP/s,\eta 是实际达到的 MFU,G 是 GPU 数。目标 tokens/s 是 B \cdot 1000 / \text{ms-per-token}。
训练时,总 FLOPs 近似为:
墙钟时间为:
评分器用这些经验法则(保守的 MFU 默认值)来估计一个候选能否满足时延或训练时长目标。
成本与功率是采购约束
评分器把每卡时价和机房总功率当作硬门。一张快且放得下的卡,如果超预算或顶破机柜功率,照样被拒。
构建它
本课交付一个命令行硬件选型评分器。它读一张 JSON 卡表(默认 code/cards.json,或你给的任意路径),把每个加速器对 WorkloadProfile 打分。
运行:
consolepython3 main.pydemo 评估三个采购场景:
- 7B 推理、要求 vLLM、H100 缺货。 从候选表里移除 H100 来模拟缺货。小显存卡因显存被拒,非 NVIDIA 卡因生态被拒,最便宜的可行 NVIDIA 卡升到成本榜第一。
- 13B 微调。 训练显存主导;小显存消费卡因所需卡数超过实际上限被拒。
- 昇腾机房的 7B 训练。 要求
torch_npu,NVIDIA 卡因生态被拒。昇腾候选之间,评分器权衡显存余量、总成本和机柜功率。
JSON 卡表结构如下:
json{
"name": "Huawei Ascend 910B 64GB",
"peak_fp16_tflops": 320.0,
"vram_gb": 64.0,
"memory_bw_gb_s": 1600.0,
"interconnect_bw_gb_s": 392.0,
"ecosystem_score": 0.75,
"supports_vllm": false,
"supports_torch_npu": true,
"hourly_usd": 1.50,
"tdp_kw": 0.40
}编辑这个文件、加你自己的卡,或者把自定义表路径传给评分函数。
验证它
code/tests/test_main.py 锁住评分器的决策逻辑:
- 显存过小的卡以
memory原因被淘汰; - 不支持所需框架(
vLLM或torch_npu)的卡以ecosystem原因被淘汰; - 超出每卡预算的卡以
budget原因被淘汰; - 超出机房功率限制的配置以
power原因被淘汰; - JSON 卡表的存取是无损的;
- demo 脚本端到端运行并退出 0。
工程师为什么需要它
- 第二供应商是真实需求。 供应不确定性、采购规则或机房约束都可能让 NVIDIA 不可得。决策不是"它有 H100 好吗?"而是"它能以可接受的成本和工作量跑我的负载吗?"
- 软件生态是主导变量。 一张 TFLOPS 更高但缺 kernel 的卡不是更快的卡;它是一个移植加验证任务。先要算子覆盖矩阵,再要 benchmark。
- 迁移成本在覆盖率与精度对齐上。 CUDA 兼容语法解决的是最容易的 20% 工作;剩下 80% 是验证每个算子、dtype 和分布式原语行为一致。
- 显存是第一道门,算力第二,成本和功率第三。 评分器强制这个顺序。第一道门都过不了的卡,不该出现在性能讨论里。
- 把卡表放进版本控制。 价格、可得性和驱动成熟度都在变。团队持续更新的 JSON 表比一次性表格有用。
提示
- 先建一张只含你真正拿得到的加速器的最小卡表,再加别的卡做敏感性分析。
- 卡因
memory被拒时,先试着减批大小或序列长度再放弃它;显存常常是最容易拧的旋钮。 - 卡因
ecosystem被拒时,向厂商要你的模型架构对应的具体算子清单,而不是一般性的兼容声明。 - 默认 MFU 假设偏保守。拿到硬件后,用有代表性的微基准实测 MFU 替换它。
- 采购不确定时,用乐观和悲观两套生态分各跑一遍评分器,看哪些候选对成熟度的小幅波动稳健。
关键术语
| 术语 | 人们说 | 实际含义 |
|---|---|---|
| 生态分 | "栈成熟度怎么样" | 针对特定负载,对驱动、框架、算子和工具链成熟度的 0–1 近似评分。 |
| 兼容层 | "CUDA 原生替换" | 降低代码改动的翻译层或设备抽象,但不保证 kernel 对等或数值等价。 |
| 算子覆盖 | "它支持 PyTorch" | 你模型前向和反向图中的每个算子是否在目标栈上有正确且优化过的实现。 |
| KV cache | "推理显存" | 自回归解码时缓存的逐 token key/value 张量;随批大小和序列长度增长。 |
torch_npu | "昇腾版 PyTorch" | 华为的 PyTorch 设备后端,把 PyTorch 算子映射到昇腾 CANN kernel。 |
| MFU | "GPU 利用率" | 实际吞吐与硬件峰值吞吐之比;用于把纸面 FLOPs 换算成墙钟时间。 |