第 3 课
← 返回系列列表

硬件适配:GB10 统一内存 — 128GB 不是「显存」,是「总预算」

MiniMax-H3 × DGX Spark — 六期原理实战

GB10 不是一台「显存很大的 GPU」,而是一台「内存共享的 SoC」。SM121 的四个「不支持」决定了量化与内核的全部选型。

下载视频 (MP4)

硬件适配:GB10 统一内存 128GB 不是"显存",是"总预算"

T AGENT 开发纪实 · EP32 · MiniMax-H3 六期原理课 ③
上一期 FP8 把 134GB 压到 67GB,可 67GB 还是装不进任何"显存"——因为它根本没住在显存里。这一期讲硬件:SM121 的四个"不支持",和那块没有独立显存的统一内存。
Compute Capability
12.1
统一内存实测
127.6GB
nvidia-smi 显存
N/A
个"不支持"
4

💡 本节要点:EP32:硬件适配——GB10 的 SM121 上,128GB 不是'显存'是'统一内存总预算';nvidia-smi 显存查询返回 N/A;四个'不支持'(INT8/Triton FP8/FA4 变长注意力/独立大显存)决定了量化与内核的选型

起点问题

四个问题:这台机器的"内存"为什么特殊?

硬件画像

这台机器的实测身份:没有显存数字的 GPU

维度 实测值 说明
GPU NVIDIA GB10 DGX Spark 单芯片,Blackwell 架构
算力等级 Compute Capability 12.1 SM121 :有 FP8、无 INT8 指令
显存查询 nvidia-smi → N/A 没有独立 VRAM——传统"显存"概念在这里失效
物理内存 MemTotal 127.6GB 128GB 统一内存(CPU/GPU 共享)
CPU Cortex-X925 + A725 ARM 大小核 ×20,aarch64,非 x86
CUDA 13.0 驱动与运行时版本
第一个信号: 一台 nvidia-smi 查不到显存 的 GPU——它申请内存的方式、驻留权重的方式,都和传统 GPU 不一样。后面所有"装不下"和"装得下"都从这里开始。
> 💡 本节要点:硬件画像(本机实测):GPU NVIDIA GB10 / compute cap 12.1(SM121)/ 显存查询 N/A / 物理内存 127.6GB / CPU ARM 大小核 20 核 / CUDA 13.0

核心机制

统一内存:GPU 和 CPU 住在同一块内存里

GPU SM121 FP8 矩阵乘 计算单元 → 统一内存 128GB LPDDR5x GPU 与 CPU 共享 无独立 VRAM → CPU ARM ×20 aarch64 宿主进程
- 🧱 · 没有"显存"这个概念 · GPU 缺页时从统一内存按需换入,不是从独立 VRAM 里取——所以 nvidia-smi 报 N/A。
- 🗂️ · 128GB 是"总预算" · 不是"一次能装下的量",而是"能寻址 + 按需驻留的池子"。常驻 ~102GB、留 18-19GB 余量。
- ⚡ · C2C 高带宽互联 · GPU 与 CPU 直连共享内存,页粒度换入换出,比 PCIe 搬运高一个量级——这是能"住下"的前提。

💡 本节要点:统一内存架构:GB10 的 GPU 与 CPU 通过 NVLink-C2C 共享同一块物理内存(LPDDR5x,128GB),没有显存/内存之分;128GB 是'总预算'不是'显存上限'

对比视角

传统 GPU 的 134GB:显存是一堵"墙"

传统 GPU(A100 / H100) GB10(DGX Spark)
内存模型 显存(VRAM)与系统内存 物理分离 统一内存,CPU/GPU 共享
容量上限 80GB VRAM 硬墙 127.6GB 总预算
装 134GB 权重 单卡不可能 → 2 卡张量并行 / 量化 + PCIe offload FP8 减半 + 统一内存按需换页
搬运路径 PCIe 搬运(带宽瓶颈) NVLink-C2C 共享内存(页粒度驻留)
结论 "塞进显存才算数" "页表能寻址 + 带宽跟得上,就能跑"
关键反转: 传统 GPU 上"装得下"= 权重必须整体进 VRAM;GB10 上"装得下"= 内存能寻址、用到哪页换入哪页。 装 134GB 的物理载体不是显存容量,是内存寻址与换页。
> 💡 本节要点:为什么传统 GPU 装不下:A100/H100 显存 80GB 物理上限,134GB 单卡不可能;多卡张量并行或量化+PCIe offload;而 GB10 的内存是'池子'不是'墙'

核心机制 2

Demand Paging:权重"用到哪页,换入哪页"

67GB FP8 权重 (页表可寻址) 13 个 shard 不整体进 GPU → 按需换页 demand paging 前向用到哪层 就换入哪页 → 常驻 ~102GB 留 18-19GB 余量 峰值不超红线 系统不 OOM
- 🗜️ · 加载并发受控 · --num-weight-load-threads 2:两个线程慢慢换入,避免一次性把内存打满。
- 📦 · 分配段可扩展 · PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True:显存分配按段伸缩,不一次性预留。
- 🛟 · 余量是生命线 · 18-19GB 是给激活、KV、宿主进程留的呼吸空间——统一内存是共享池,抢完就 OOM。

💡 本节要点:demand paging 按需驻留:67GB FP8 权重按页换入 GPU,用到才进;常驻红线 ~102GB,留 18-19GB 余量;配合 --num-weight-load-threads 2 控制加载并发、expandable_segments 管理分配

踩坑 1 · 监控失效

坑 1:显存查不到,怎么盯内存水位?

$ nvidia-smi --query-gpu=memory.total
N/A          ← 传统显存监控直接失效
$ cat /proc/meminfo | grep MemAvailable
MemAvailable:  19896696 kB   ← 换这个

现象: 没有独立显存, nvidia-smi 的显存读数全是 N/A ——用显存监控 GPU 内存的思路在这台机器上不存在。
- 统一内存的水位计 · · /proc/meminfo :MemTotal 127.6GB / MemAvailable(宿主可用) · vLLM 内部分配 :worker 报告 92,957 MiB · swap 用量 :2.8GB / 15GB(换出但没崩) · 结论:盯"总池子"的水位,而不是"显存"
理解: 统一内存把"显存/内存"两个水位计合并成一个。 监控对象从 GPU 显存换成整个内存池 ,余量就是 MemAvailable。

💡 本节要点:踩坑 1:nvidia-smi 显存 N/A——监控方式失效;改用 /proc/meminfo(MemTotal 127.6GB / MemAvailable)+ vLLM 内部分配 + swap 用量来盯内存水位

踩坑 2 · 不支持清单

坑 2:SM121 没有 INT8 指令——量化只能走 FP8

# 请求到达 INT8 量化路径时,后端直接拒绝
File "vllm/_core/execution", line ...
RuntimeError: dispatch_scaled_mm: INT8 not supported on SM121

踩坑 3 · 不支持清单

坑 3:Triton 编不出 FP8——量化器改绑原生 CUDA

# Triton 编译激活量化器 → SM121 上无法 lower
TritonError: codegen for FP8 scalar
             not supported on SM121

# 兼容层:把量化器绑定到原生 CUDA op
for module in model.modules():
    if isinstance(module, QuantFP8):
        module.activation_quantizer.forward_cuda  # 原生路径

现象: vLLM 自带的 QuantFP8 量化器走 Triton 编译, SM121 的 Triton 后端编不出 FP8 标量 ——编译期就失败。
修复: 同一份代码里有个原生 CUDA 的 scaled_fp8_quant 能跑。兼容层在构建期发现量化器,把它们全部绑定到 forward_cuda (Cutlass 内核)——实测绑了 260 个。
- 同一操作,两条路 · Triton(编译型)在 SM121 失败;原生 CUDA(预编译)可用。 结论:FP8 内核必须走原生 CUDA,不碰 Triton。

💡 本节要点:踩坑 3:Triton FP8 编译失败——QuantFP8 的 Triton 后端在 SM121 无法 lower FP8 scalar;量化器必须绑原生 CUDA(forward_cuda / CutlassFP8ScaledMMLinearKernel)

踩坑 4 · 不支持清单

坑 4:FA4 编不出变长注意力——退 PyTorch SDPA

# "不支持" 现象 / 证据 实际走的路
1 INT8 矩阵指令 dispatch_scaled_mm: INT8 not supported on SM121 FP8(原生)
2 Triton FP8 编译 codegen for FP8 scalar not supported 原生 CUDA forward_cuda
3 FA4 变长注意力 CuTe variable-length kernel compile failed PyTorch SDPA(逐段)
4 "独立大显存" nvidia-smi memory.total = N/A 统一内存 demand paging
共同规律: 四个"不支持"来自同一个架构事实—— SM121 是 Blackwell 的统一内存 SoC,不是传统独立显存的 GPU 。每个"不支持"都对应一次选型修正:FP8、原生 CUDA、SDPA、按需驻留。
> 💡 本节要点:踩坑 4:FA4 变长注意力编译失败(CuTe 内核对 H3 packed sequence 编不出来)→ 退 PyTorch SDPA;四个'不支持'汇总:INT8 指令 / Triton FP8 / FA4 变长内核 / 独立大显存

验证

内存账本:数字全部落在红线内

▌ 总预算 MemTotal · 127.6 GB
▌ 常驻红线 · ~102 GB(留 18-19GB 余量)
▌ 实测加载占用 · 89.17 GiB / 532s
▌ 请求后分配 · 92,957 MiB(低于红线 ✅)
- 🧠 · 宿主可用内存 · 请求后 21 GiB——系统没被模型挤死
- 💾 · swap 用量 · 2.8GB / 15GB——有换出,但没崩
- 🎯 · 结论 · 102GB 红线守住了,账本对得上

💡 本节要点:验证:内存账本——MemTotal 127.6GB 总预算 / 常驻红线 ~102GB / 实测加载 89.17GB / 请求后 92,957 MiB / 宿主可用 21GB / swap 2.8GB——全在红线内

总结

硬件决定决策边界:128GB 是池子,不是墙

—— 没有独立显存,128GB 是 CPU/GPU 共享的总预算,nvidia-smi 显存查询返回 N/A
统一内存
—— 权重"用到哪页换入哪页",常驻 ~102GB,留 18-19GB 余量,峰值 92,957 MiB 不越线
按需驻留
—— INT8 指令、Triton FP8、FA4 变长注意力、独立大显存:每个都逼出一个替代方案
四个"不支持"
—— FP8 / Cutlass / SDPA / demand paging,全是 SM121 这台硬件给出的"唯一可行解"
选型被硬件划定

硬件把路划好了,可每条路都是"先失败、再换路"试出来的—— 那串失败链和兼容补丁怎么写的?
- 🧮 · EP31 · 量化原理 · 在线动态 FP8 ✅
- 🖥️ · EP32 · 硬件适配 · 本期:GB10 统一内存 ✅
- 🧩 · EP33 · 失败链与补丁 · 下期:六连坑 + 兼容补丁
本期证据:本机实测(lscpu / nvidia-smi / /proc/meminfo)· docs/PATCH.md 失败链 · docs/RESULTS.md 内存账本
💡 本节要点:总结:GB10 不是一台'显存很大的 GPU',而是一台'内存共享的 SoC'——128GB 是总预算,四个'不支持'划定选型边界(FP8/Cutlass/SDPA/按需驻留);下期 EP33:失败链与兼容补丁

← 在线动态 FP8 量化:128GB 怎么装下 134GB 权重 失败链与兼容补丁:六个坑,五次换路,没有一次白踩 →