阶段 6 · 让模型跑起来

硬件与算力账本:同一个算法
换个形状快 300 倍

上一章《微调实战决策树》帮你选好了微调的路子,但从没有一章讲过这些东西跑在什么上面。
这一章回答两个很具体的问题:你写的 Python 到那块芯片之间,压着哪几层;以及 为什么“矩阵乘快、逐元素慢”不是玄学,而是可以提前算出来的。
算它的工具只有两个数字——机器每秒能算多少次,和 每秒能搬多少字节。

1

算法写出来,不代表跑得动

同一块显卡,同一批数字,换个写法,速度差 300 倍。 更反直觉的是:快的那一种,浮点运算量(一共做了多少次乘、加)其实是慢的那一种的 1000 倍。 先猜一下。

互动 · 先猜,再看答案

能把这件事提前算出来,靠的是两个速度:

互动 · 同一台机器上,两种形状真实能跑多快

🎯 类比

把 GPU 想成一家中央厨房。逐元素运算像是"来一箱菜、切一刀、装箱送走"—— 厨师手再快,也一直在等下一箱菜送进来。
矩阵乘像是"来一箱菜,反复配几十道菜,最后装一大车送走"—— 同一箱菜被用了几百次,送料的速度就不再是问题,厨师的手速才是。
两种活都叫"做饭",但卡住它们的是两件完全不同的东西。
这个类比只管到"谁在等谁"为止,不管厨房里还有没有别的仓库。

2

先认一认:从芯片到框架的几层

前面几十章的「工业界怎么写」里,你已经见过 nn.Linear、loss.backward() 几十次, 但从来没人告诉你,从这几行往下还压着六层东西。
这一节先认脸:GPU 和驱动、CUDA 和 cuDNN、PyTorch 和 HuggingFace 不是同一类东西,它们叠在一起。
下面这四句话,如果你有一句看不懂,缺的就是这张图。

你在哪见过你当时可能在想翻回来查这里
CUDA error: out of memory我 24G 显存,模型才 3G,谁吃了剩下 21G?框架层 · 外链《分布式训练与集群》第一节的显存账
nvidia-smi 说 CUDA 12.4,torch.version.cuda 说 12.1到底哪个是真的?我装重了吗?本节「只有一层必须你自己装」:驱动和运行时是两层
有人跟你说「换块 H100 就快了」那为什么我先把 batch 开大就快了?芯片层 · 本页第 4 节的脊点(算力墙和带宽墙的交点)、第 8 节:一次训练要烧多少电
老板问「我们该不该上 JAX」这问题到底在问什么?框架层 · 本节「谁在做这些层」的对照表

互动 · 六层的栈

点任意一层:它下面的层会一起亮起来,因为上面那层要干活,必须靠下面这几层撑着。 每一层都在做同一件事——把上一层的意图,翻译成下一层能干的活。 你写的每一行 model(x),都同时穿过了其中的四层。

只有一层必须你自己装

六层听起来很麻烦,你真正要管的只有一层。从第四层往上,Python 包里都给你装好了: 敲一句 pip install torch,装进来的那个包自带一套 CUDA 运行时,也自带 cuDNN 和 cuBLAS;中间三层你一辈子都不用碰。
但驱动住在操作系统里,包管理器替你装不了。它必须和你的显卡型号、系统内核对上,所以只能你自己装、自己升级。

图 · 两个版本号,一个说“支持到哪”,一个说“在跑哪版”

驱动(住在操作系统里) nvidia-smi:CUDA 12.4 这块卡最多支持到哪一版 torch 包(住在 Python 环境里) torch.version.cuda:12.1 包里装的运行时版本 ≤ 12.1 ≤ 12.4 ✓ 对得上 反过来(包里比驱动还新)才会报 CUDA driver version is insufficient

两个数字不一样,不是装重了:前者是「这块卡最多能配合到哪一版」,后者是「包里装的是哪一版」。只要后者不高于前者就对。

你敲的命令它装了什么装不了什么
pip install torch 框架 + 算子库 + CUDA 运行时(包里写着 +cu121 就是这个意思) 驱动。它不属于 Python 环境
装显卡驱动 只有驱动:让操作系统能跟这块卡说话 框架、算子库、运行时,它是一个都不给你
⚠️ 环境出问题时,第一个要看的是这一条

绝大多数「昨天还好好的,今天跑不起来了」,都是有人在你机器上动了驱动, 或者你换了个 Python 环境、里面的 torch 是另一个 CUDA 版本。
先跑 nvidia-smi 和 torch.version.cuda 对一下版本,再去看别的。顺序反了会浪费一整天。

谁在做这些层

这六层不是一个整体,是几家公司各做各的,靠接口拼在一起。 知道每一层的边界在哪,你就知道「换个框架」到底换掉了什么。

层谁在做换掉它要动多少东西
框架PyTorch(Meta)· TensorFlow / Keras(Google)· JAX(Google)只重写模型定义的几百行。算法思路一行不用改
算子库cuDNN / cuBLAS / NCCL(NVIDIA)· FlashAttention、Triton(开源)通常由框架替你选,你只管开不开加速开关
运行时CUDA(NVIDIA)· ROCm(AMD)· Metal(Apple)换它基本等于换硬件。这是整张图里最硬的一道墙
驱动各家显卡厂,闭源换它不用改代码,但改错了什么都跑不起来
芯片NVIDIA · AMD · Google(TPU)· AWS · Apple · 各家 NPU见本页第 9 节那张厂商谱系表

为什么 CUDA 这一层被反复提起?因为它上面已经堆了二十年: 六百多万开发者、几百个库,新论文的官方实现也默认它是首选。 性能优势可以被追平,但「别人已经写好的东西」不能一夜之间搬走。 2026 年有很多讨论说这道墙正被产能和成本顶,你只要知道它在哪一层就行。

JAX 和 PyTorch 的差别不在栈的这一层:它们都调 CUDA、都调 cuDNN,差别在怎么写代码。 所以「该不该上 JAX」真正在问的是你的团队更习惯哪种写法。

这张图还有一个前提

上面那张栈图默认你要用神经网络。表格数据(一行一个样本,几十到几百列)占真实工作的大头, 工具箱是 scikit-learn · XGBoost · LightGBM · CatBoost,默认跑在 CPU 上,下面四层根本不用管; 而在表格数据上,梯度提升树至今稳定打赢神经网络。一个只有几张 Excel 表的问题,上神经网络是浪费。

3

一张图判定“快不快”

下面这张图叫 Roofline(屋顶线): 横轴是算术强度(每搬进一个字节做几次运算),纵轴是每秒实际能跑多快。
图上有两条线:一条斜的、一条平的。它们合起来像一个屋顶—— 所有运算都被压在这个屋顶下面,谁也顶不破。图例里那三种运算是逐元素运算(SAXPY)、读缓存(KV Cache)和矩阵乘(GEMM);fp32 / bf16 / fp8 是三种数字格式,区别是每个数占几个字节,第 6 节细讲。先拖一下那颗小球。

核心大图 · 拖动小球,看它顶不破这条线

① SAXPY 逐元素 ② KV Cache 读取 ③ 矩阵乘 GEMM 1024³ 阴影区 = 物理上做不到的地方
— —
每个数几字节
跳到真实运算
算术强度 AI
—
搬运字节数
—
浮点运算次数
—
这个形状的上限
—

💡 三个真实运算,落在图上三个位置

这张表里的数都是现算的;本页演示卡峰值 100 TFLOP/s、带宽 1 TB/s(第 4 节两个滑块的默认值)。
表里的字节数是这么数出来的:SAXPY 每个元素要读 x、读 y、写 y 三个数,bf16 一次 6 字节,只做 2 次运算; GEMM 里同一个数被反复用,每搬进一个字节就能做 341 次运算。

真实运算算 / 搬算术强度判定与上限
① SAXPY
y = a·x + y,100 万个元素(bf16)
2×10⁶ FLOP
÷ 6×10⁶ 字节
0.333 严重带宽受限
0.333 TFLOP/s —— 只有峰值的 1/300
② KV Cache 读取
1 个 token × 32 层 × 4096 维(bf16)
每个字节只参与一次乘加,两者正好相等
5.24×10⁵ FLOP
÷ 5.24×10⁵ 字节
1.0 带宽受限
1 TFLOP/s —— 峰值的 1/100
③ 矩阵乘 GEMM
两个 1024×1024 矩阵相乘(bf16)
2.15×10⁹ FLOP
÷ 6.29×10⁶ 字节
341 算力受限
100 TFLOP/s —— 跑满
⚠️ 这张图只说了两件事,但它已经把结论给完了

① SAXPY 差脊点 300 倍,GEMM 超脊点 3.4 倍。 差别不在硬件、不在编程水平、不在魔法——在"每搬一字节做了几次运算"这一个比值上。
这就是你后面会反复看到的那句话的出处:"FlashAttention 的瓶颈是 IO,不是算力"、 "量化最本质的作用是抬高算术强度"。它们都是这一张图的推论。

4

两堵墙,和一个决定命运的拐点

屋顶由两堵墙拼成:斜的那堵说"我每秒只能送进来这么多字节", 平的那堵说"我每秒最多只能算这么多次(算力)"。所有运算都撞在较矮的那堵上。

👆 先悬停,再拖那个滑块

悬停公式里的 峰值算力,下面图的滑块会亮起来;把它从 20 拖到 400,看脊点往右跑多远。

互动 · 拖动两个参数,看拐点往哪走、三个运算谁掉到墙左边

两条墙的交点叫 脊点(ridge point)。它的意思非常直白: 过了它,加算力有用;没过它,加算力白搭。 你把峰值算力往上拖会发现:三个点里只有 ③ GEMM 更快了,①② 一动不动——它们撞在带宽墙上,提高算力没用。

5

为什么搬比算贵 700 倍

上一节说"两堵墙"——但没人规定它们必须一样高。 在真实的芯片上,搬运和计算的价格差得离谱。 一张常被引用的表数的是能量: 在 45 纳米(芯片工艺的尺度,越小越先进)下,做一次运算和从显存取一个数,各要烧掉多少能量——单位是 pJ(皮焦耳,万亿分之一焦耳)。

内存金字塔 · 越往下容量越大、每搬一个数越贵

越往下:容量越大、速度越慢、每搬一个数越贵 寄存器 Register 0.9 pJ 256 KB / SM 共享内存 / L1(SRAM) 5 pJ 228 KB / SM L2 缓存 ≈ 15 pJ 40 MB / 卡 HBM 显存 640 pJ 80 GB / 卡 从 HBM 搬一个数(640 pJ)≈ 做 711 次浮点加法(0.9 pJ)

互动 · 真实能量账(Horowitz 2014,45nm)

条形长度画的是能量对数(线性画法下 0.1 pJ 会短到看不见)。量级取自 Horowitz 2014 / Eyeriss 2016,不同工艺下数字会变。
搬一个数(640 pJ)≈ 算 711 次(0.9 pJ)——所以少搬一点,永远比少算一点划算。

6

精度:位宽减半,屋顶抬高一层

既然贵的是搬运,那把每个数占的字节数减半,是不是就省一半? 是。而且这里有个"两头都赚"的好事:位宽减半,既少了搬运量,又让张量核心(芯片上专门做矩阵乘的那块硬件)的吞吐翻倍。 下面两个表都是现算的。(这一节说的就是量化里最常用的一招。)

互动 · 同一个矩阵乘,三种精度各能跑多快

算术强度怎么跟着精度变:1024³ 矩阵乘运算量固定 2N³,搬运量是 3N² × 字节数;字节数减半 → 算术强度翻倍。再加上张量核心在低精度下吞吐更快,fp32 → fp8 快了 4 倍,算法一行没改。

精度每个数几字节70 亿参数模型的权重显存1024³ GEMM 的算术强度张量核心峰值(相对)它适合干什么
fp32428 GB1711× 数值最稳,但慢、占地方。现在只用在优化器状态和少数敏感算子上
bf16214 GB3412× 训练和推理的默认选择。指数位和 fp32 一样宽,所以不用做额外的缩放
fp817 GB6834× 推理的第一选择;训练要用得配一套缩放技巧(因为它的范围太窄,容易溢出)
⚠️ 低精度不是白拿的

bf16 之所以成为默认,是因为它"畸形"得很聪明:一个浮点数由指数位和尾数拼成,指数位决定它能有多大、多小,尾数决定能精确到第几位。bf16 的指数位和 fp32 一样多(8 位),尾数被砍到 7 位。 这意味着它能表示的范围和 fp32 一样大,只是精度粗——神经网络对"粗"的容忍度远高于对溢出的容忍度。
fp8 的问题正好相反:指数只有 4~5 位,稍微大点的数就爆掉,用它必须逐层单独算缩放系数——这就是"省带宽"要付的代价。

7

算子融合:省掉两次中间结果

既然卡在"搬",那少搬一点就行。最典型的一招叫算子融合—— 把本来要分三个程序做的事,合成一个程序做完,中间结果不用落到显存上。 (这里的 kernel 指一段在 GPU 上跑的小程序,和操作系统内核是两回事。)浮点运算一次都没少,少的是搬运。

不融合 vs 融合 · 中间结果跑了几趟显存

不融合 · 3 个 kernel,中间结果落地两次 HBM kernel 1 t₁ = x·w kernel 2 t₂ = t₁ + b kernel 3 y = gelu(t₂) ↑↓ t₁ 和 t₂ 都要写回 HBM、下一轮再读回来 —— 每个元素多跑 4 趟 融合 · 1 个 kernel,中间结果根本不落地 HBM 一个 kernel y = gelu(x·w + b) 搬运量:7 个数 / 元素 → 3 个数 / 元素

互动 · 融合到底省了多少(真实计算)

不融合 · 搬运
—
融合 · 搬运
—
省下
—
上限速度提高
—

算术在这里:这个运算每个元素要做 3 次运算(乘、加、激活)。 不融合要搬 7 个数(读 x、写 t₁、读 t₁、读 b、写 t₂、读 t₂、写 y), 融合只搬 3 个数(读 x、读 w、写 y)。
用 bf16 算:14 字节 vs 6 字节 → 算术强度 0.214 → 0.5,速度同样提高 2.33 倍。 浮点运算的次数一次都没变。所以融合是纯粹的"少搬"。

⚠️ 融合不是万能的

如果这个运算本来就卡在算力上(比如一个大矩阵乘),融合一点用都没有——它本来就不缺带宽。 融合真正的战场是小算子扎堆的地方:LayerNorm、激活、加 bias、残差相加。它们单独看微不足道,串在一起却把显存带宽吃光,旁边的张量核心在闲着。

8

算力账本:一次训练烧多少电

前面每一节都在讲"一个运算快不快"。把尺度拉到一整次训练,就有了一张真正的账本: 一次训练的总浮点运算量只跟两件事有关——模型有多少参数,和它读了多少 token。

👆 先悬停,再拖那个滑块

悬停公式里的 N、D、卡数、MFU,下面四个滑块会分别亮起来。最值得拖 MFU:从 40% 拉到 50%,卡·天(一张卡跑一天的工作量)掉一截,不换一张卡。

那个 6 是从哪来的:前向一遍要做 2N 次运算(每个参数一次乘、一次加), 反向一遍约等于 4N 次(对输入和对权重各算一遍,都是两次运算)。 2 + 4 = 6,所以"训练一次"≈ 参数量的 6 倍 × token 数。 所以只用前向的推理,比训练便宜三分之二。

互动 · 算力账本(真的乘一遍,再换成卡·天和电费)

总浮点运算量
—
卡 · 小时
—
要跑多少天
—
耗电
—
电费(¥0.6/度)
—

🎯 类比

这就像装修报价单:参数量的 6 倍是工时单价,token 数是干多少天,卡数 × MFU 是雇了几个人、真正干活的比例多高。 最容易被忽略的是最后一项:MFU(算力利用率)通常只有 30%~50%,你花大价钱买来的算力,有一半以上在等数据、等通信、等调度。

这一节算的是「这笔账要花多少」;预算该怎么在参数和数据之间分配,是 《缩放定律》回答的。

M

数学 · 一个比值判生死

前面八节反复说同一句话:快不快,由一个比值说了算。 这个比值上下各一个数——一共做了多少次运算,除以一共搬了多少字节。 下面这张图画的就是它:同一批数字,被你反复用了几次。 默认搬 32 字节(8 个 fp32 = 16 个 bf16),AI 起点 0.25 属 fp32 口径。

互动 · 搬进来一次,用了多少次

9

这些墙都是谁砌的

同样两堵墙,不同芯片砌在不同高度。这就是"训练卡"和"推理卡"分野的原因——它们优化的墙不一样。

流派(为谁设计)代表强在哪弱在哪
通用 GPU
训练 + 推理
NVIDIA H100 / B200 · AMD MI300 生态最全、张量核心最快、"什么模型都能跑"贵、功耗高、还经常买不到
云自研训练芯片
训练
Google TPU v5p / v6 片间互联特别强(几千张卡连成一个大芯片的感觉)只在自家云上,通用性差
云自研通用芯片
训练 + 推理
AWS Trainium2 便宜,和自家云绑定卖生态要自己从头搭
推理专用 ASIC
只做推理
Groq LPU 延迟极低、吞吐极高不能训练,模型稍大就放不下
晶圆级引擎
训练 + 推理
晶圆 = 一整片硅圆盘,平时切成许多小芯片,这里整片做一块
Cerebras CS-3 整片晶圆做一块芯片,能训超大规模模型软件生态自成一套,迁移成本高
边缘 NPU
端侧推理
手机 / 车机里的 NPU 极省电,断网也能用算力小两三个数量级,通常只跑量化后的小模型
统一内存
本地推理 / 微调
Apple M 系列 内存和显存不分家,能"装下"很大的模型峰值算力比数据中心卡低一个数量级

下面这张表更有用:把每一代卡的脊点真的算出来。脊点 = 峰值算力 ÷ 带宽。

芯片年份半精度峰值(稠密 = 不利用稀疏加速)显存带宽脊点 AI*
V1002017125 TFLOP/s0.9 TB/s139
A100(40GB)2020312 TFLOP/s1.55 TB/s201
H100 SXM2022989 TFLOP/s3.35 TB/s295
TPU v5p2023459 TFLOP/s2.8 TB/s164
B20020242250 TFLOP/s8.0 TB/s281
Apple M4 Pro(本地推理)2024≈ 17 TFLOP/s0.273 TB/s62
💡 把这张表和第 3 节那三个数放在一起看

八年里算力涨了 18 倍,带宽只涨了 9 倍——所以脊点从 139 挪到 281,往右移了一倍。
这意味着一个大概的趋势:大体上,换越新的卡,会有更多的运算落到带宽墙上。
逐元素运算的算术强度是 0.333,比表里最低的那张卡还低两个数量级—— 这不是换卡能解决的,只能改运算的形状。

诚实标注:表里是厂商标称峰值,实际通常只跑到 30%~50%。

10

这张图会在哪里骗你

这一章有两张容易被当成全貌的图:Roofline 是上界,不是预测;六层栈把连续画成分格。下面是它们瞒着你的部分。

容易踩的误会实际上是
性能可以照着它预测 Roofline 是一条上界。内存没对齐、分支发散、寄存器不够、活跃线程太少——任何一条都会让你的程序远低于这条线,而图上不会显示这一点
瓶颈一定在这个 kernel 里 图只看一个 kernel。真实的训练里,瓶颈可能是数据加载跟不上、是卡间通信、是调度器在发呆——这些都不在这一张图上
启动开销可以忽略 一个只跑 10 微秒的小算子,kernel 启动本身就要几微秒。融合在这类场景下的收益有时来自"少启动几次",而不是少搬几个字节
反正瓶颈都在卡上 卡间互联是另一条带宽墙,而且比显存带宽矮得多。《分布式训练与集群》整章都在跟它打架
FLOPs 就是成本 同样的浮点运算量,用三代前的卡和最新的卡,电费可能差好几倍。算力不是成本,算力 ÷ 能效才是成本
层是分好的,各管各的 边界正在模糊。PyTorch 会用 Triton 现场生成 CUDA 核(框架层直接往下写运行时的代码); FlashAttention 既算「算子库」又算「运行时」。边界是给人看的,不是代码里的砖墙。
从下往上,越往下越重要 反了。你 90% 的时间会花在最上面两层。栈越往下越不该碰—— 越往下,你越没有能力改,改了收益也越不属于你。
只有这一条栈 Apple 的 MLX、华为昇腾、各家 NPU 走的是另一套名字,形状一样、专有名词全不同。 这一章画的是 2026 年最主流的那条。
换个框架要重学 要重学的只是怎么写模型定义。卷积、注意力、反向传播和优化器, 这门课前面的每一章,在三个框架里都叫同一个东西。
HuggingFace 是栈里的一层 它不在计算路径上。它是「模型的图书馆」:存模型,存数据集,发模型。 你的代码多半从它那儿拿一个模型,但真正算的时候,它在旁边看。
⚠️ 一句诚实的话

Roofline 的用途不是"预测你的程序会有多快",而是回答"我该先停止浪费哪个资源"—— 答案只有两种:要么减少搬运,要么减少计算。具体怎么减得去测,torch.profiler 这类工具才是量真实性能的地方。先用这张图定方向,再用工具量数字。

11

小结

这一章没有新算法、没有新架构。它讲的是一条不能协商的硬约束。

它对应哪条线 ③ 规模会赢——「再大十倍」是一笔能算的买卖:一次训练 ≈ 6 × 参数量 × token 数,而每一次运算都要在带宽 / 算力两堵墙里撞一堵。规模能不能赢,先看这张账本划不划算。
一句话 每一个运算都要在两堵墙里选一堵撞上去。这就是为什么"矩阵乘"成了通用货币——它每搬一个数能做几百次运算,喂得饱算力那面墙。
它牺牲了什么 算法为了迁就硬件,牺牲了自然表达:注意力要做成大矩阵乘(要 padding、要 mask、长序列 O(n²) 爆炸), 低精度省带宽(要额外算缩放系数),融合省搬运(kernel 难读难调)。 每个架构选择背后都有一笔这样的交换——这就是暗线 B。
🎬 检验一下:你现在能指着哪个互动说这句话

回到第 4 节那个小图。把"峰值算力"直接拉到最右边,看哪几个点动了。
① 和 ② 一动不动——它们撞在带宽墙上,算力提高再多也没用。只有 ③ 真的更快了。
那个"拖动滑块却没反应"的瞬间,就是"归纳偏置由硬件决定"这句话的字面意思。

所以这章属于哪条线,为什么不是别的

线为什么这章不是它
① 表达力 vs 泛化硬件不改变模型能学什么,也不改变它会不会过拟合
③ 规模会赢✅ 本章的线:规模会赢的前提就是这张能算的账单——「再大十倍」要多少算力、多少电。
④ 学习即压缩不涉及
⑤ 高维里的低维这一章的反直觉点是"多算一千倍反而更快",但它的原因不是维度,是两堵墙的高度差
② 没有免费午餐沾边——加速的账都换到别处去了(暗线 B),但那是代价,不是这一章的主线

硬件改写了哪几条暗线

而在这一章,暗线 D 第一次成为主角。

暗线这一章的回答
D 跑在什么上 ✅ 这一章就是暗线 D 的总纲。 前面每一章的暗线 D 里都会点一句"这是带宽受限还是算力受限", KV Cache、FlashAttention、 量化、通信都是在跟这两堵墙里的某一堵打架。
E 它假设了什么 假设"算力和带宽各是一个数,而且不会变"。 真实的芯片有缓存、有突发、有调度,所以这张图是上界而不是预测——它假设的是最坏情况。
F 违背了哪个直觉 "换块更大的卡就更快"是错的。 逐元素运算的算术强度是 0.333,所有卡的脊点都在 62 以上——差两个数量级,换什么卡都救不了,只能改运算的形状。

这一章会被哪三章用到

章它会怎么用这一章
梯度下降与反向传播 反向传播在硬件上就是又跑了一遍矩阵乘(前向 1 遍 + 反向 2 遍),这就是 6ND 里那个 6 的来历
MLP 多层感知机 / CNN 卷积 为什么要有 batch?不是数学需要,是硬件需要:一个 batch 的矩阵乘够大才喂得饱张量核心,逐元素操作无论多大都只能撞在带宽墙上。
高效注意力 它那句"FlashAttention 是 IO 瓶颈而非算力瓶颈"在这里有了完整推导:decode 每步只算几个 FLOP,却要把整个 KV cache 搬一遍 → 算术强度极低 → 卡在带宽墙上

它接住了《微调实战决策树》:上一章选好了微调的路子,没说这些方案要花多少显存、跑在什么芯片上。
它给后面留了什么:显存装不下怎么拆到多张卡上,是下一章《分布式训练与集群》;带宽瓶颈则留给更后面的《推理与 KV Cache》。

一句话带走硬件与算力账本

一个运算快不快,只看它的算术强度落在脊点的哪一边—— 落在带宽墙那侧就去融合算子、降精度,落在算力墙那侧才轮到换更快的卡。 换卡只在后一种情况下有用。

12

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式