LLM 全栈总览:七层各解决一个问题,最终都落到算力、带宽和容量三个硬件指标上¶
从模型到芯片可以分成七层,每层只解决一个问题:
- 模型定义算什么。
- 训练和推理决定怎么跑。
- 框架把公式变成张量运算。
- 编译器把计算图改写成适合硬件的程序。
- 算子把单个运算做到接近峰值。
- 硬件提供算力、带宽和容量。
对 SoC 架构师来说,各层的优化最终都表现为三个数字:峰值算力(TOPS/TFLOPS)、内存带宽(GB/s)和存储容量(片上 SRAM 与 DRAM)。看懂哪一层在消耗哪个数字,就能判断 XPU 该怎么设计。
| 情境 | LLM 由 Transformer 堆叠而成,训练和推理都跑在 GPU、NPU 等 XPU 上。 |
|---|---|
| 冲突 | 模型作者写的是数学公式,芯片只执行指令;中间隔着框架、编译器和算子库,架构师很难判断是哪一层决定了 XPU 的实际利用率。 |
| 问题 | 每一层分别解决什么问题?它们的需求怎样落到 XPU 的设计指标上? |
1. 七层分工:上层提出需求,下层负责高效执行¶
这张图说明:七层自上而下传递需求,越往下越接近硬件,优化目标也越具体。
| 层 | 解决的问题 | 输入 → 输出 | 代表实现 | 对 SoC 的含义 |
|---|---|---|---|---|
| ① 模型 | 用什么结构表示语言规律 | 设计 → 结构与超参 | GPT、Llama、DeepSeek | 参数量和上下文长度决定计算量与内存需求的上限 |
| ② 训练 | 怎样让参数从数据里学到知识 | 数据 → 权重 | nanoGPT、Megatron | 需要高算力、大容量和高速片间互连 |
| ③ 推理 | 怎样又快又省地生成回答 | 权重 + 提示 → token | llama.cpp、vLLM、SGLang | 首字延迟看算力,生成速度看带宽 |
| ④ 框架 | 怎样用张量写模型并自动求导 | 公式 → 张量运算序列 | PyTorch、JAX、micrograd | 决定哪些运算交给 XPU、哪些留在 CPU |
| ⑤ 编译器 | 怎样把整张计算图改写得适合某款硬件 | 计算图 → 优化后的程序 | XLA、Inductor、TVM、MLIR | 新 XPU 需要编译器后端,否则上层框架用不上 |
| ⑥ 算子 | 怎样把单个运算做到接近峰值 | 一个运算 → kernel 二进制 | cuBLAS、FlashAttention、Triton | kernel 的切块方式由 SRAM 容量、带宽和指令集决定 |
| ⑦ 硬件 | 提供多少算力、带宽和容量 | 指令 → 结果 | GPU SM、TPU MXU、NPU MAC 阵列 | 三个指标的配比决定哪类负载跑得快 |
2. 训练和推理共用这套栈,但瓶颈不同¶
训练的瓶颈在算力和通信,推理的瓶颈又分两段:Prefill 受算力限制,Decode 受带宽限制。这是 SoC 上 NPU 设计取舍的出发点。
2.1 训练:每步做三倍于推理的计算,还要存下整套优化器状态¶
- 计算量约为
6 × 参数量 × token 数:前向 2 份,反向 4 份。 - 显存方面,混合精度 AdamW 每个参数约占 16 字节(BF16 权重 2 + 梯度 2 + FP32 主权重 4 + 两个动量 8)。一个 7B 模型光这部分就要约 112 GB,所以必须多卡切分(数据并行、张量并行、流水并行、ZeRO)。
- 多卡之间每步都要同步梯度或激活,互连带宽决定扩展效率。
2.2 推理:Prefill 是大矩阵乘,Decode 是“读一遍全部权重才算出一个 token”¶
- Prefill(首字延迟):N 个输入 token 一起算,是标准的大矩阵乘,受算力限制。以 8B 模型、1000 个 token 的提示为例,约需 16 TFLOP;NPU 若以 50% 利用率跑出 20 TOPS,约 0.8 秒出首字。
- Decode(生成速度):每步只算 1 个 token,却要把全部权重从 DRAM 读一遍,算术强度在 FP16 下只有约 1 FLOP/字节(INT4 约 4),远低于 NPU 数百 FLOP/字节的 roofline 拐点。以 8B 模型 INT4 量化(约 4 GB)、128-bit LPDDR5X-8533(约 136 GB/s)为例,生成速度上限约 34 token/s,此时只用到约 0.5 TFLOPS 的算力。
- KV Cache:把之前 token 的 K、V 存下来,避免重复计算,代价是占内存。Llama-3-8B 每个 token 约 128 KiB(32 层 × 8 个 KV 头 × 128 维 × K/V 两份 × 2 字节),8K 上下文约 1 GiB。
结论:手机或 PC SoC 上的 NPU 跑单用户对话时,TOPS 大多闲置,DRAM 带宽才决定体验。批处理、投机解码和量化之所以有效,是因为它们提高了每读一字节权重所能做的计算量,或者减少了要读的字节数。
3. 编译器和算子:让一行代码少走 DRAM¶
编译器和算子的核心任务是把数据留在片上,减少 DRAM 往返。下面以一行注意力代码为例,展示它落到硬件的五步。
- 模型代码:开发者只写
softmax(q·kᵀ)·v,不关心硬件。 - 计算图:框架(
torch.compile、jax.jit)记录下运算顺序、形状和数据类型,后续优化才有依据。 - 图优化:编译器把能合并的运算融合成一个,中间结果不再写回 DRAM。
- Kernel:按片上 SRAM 的大小把矩阵切成 tile,安排搬运和计算的流水,让 MAC 阵列不停顿。
- 硬件执行:矩阵乘交给 MAC 阵列(Tensor Core、MXU),softmax、归一化交给向量单元。
编译器和算子库的分工可以这样理解:
| 角色 | 擅长 | 不擅长 |
|---|---|---|
| 编译器(XLA、Inductor、TVM) | 自动融合大量小运算,快速适配新模型和新硬件 | 关键大算子很难自动做到峰值 |
| 手写算子(cuBLAS、FlashAttention) | 把 GEMM、Attention 这类热点做到接近峰值 | 每换一款硬件或一种数据类型都要重写 |
| Triton、CUTLASS、TileLang 等 DSL | 介于两者之间:人决定切块,编译器生成细节 | 抽象仍然绑定某类硬件模型 |
4. SoC 架构师的检查清单:用三个指标反推 XPU 设计¶
把以上各层的需求汇总成硬件指标,可以得到下面这张清单。
| 指标 | 主要消耗者 | 要回答的设计问题 |
|---|---|---|
| 峰值算力 | 训练、Prefill、大批量推理 | 支持哪些数据类型(BF16、FP8、INT8、INT4、FP4)?MAC 阵列多大? |
| 内存带宽 | Decode、KV Cache 读写、未融合的算子 | DRAM 带宽多少?算力与带宽之比(roofline 拐点)是否和目标负载匹配? |
| 片上容量 | 算子切块、KV Cache、权重常驻 | SRAM 能放下多大的 tile?是否需要大容量系统缓存? |
| 互连与软件 | 多卡训练、异构协同、编译器后端 | NPU 与 CPU/GPU 是否共享内存?有没有能接入 PyTorch 的编译器和算子库? |
后续章节按学习路线从底层的自动微分开始,逐层自己动手实现一遍,每章的代码练习放在 learn-llm-lab 仓库。