·3238 words·7 mins
之前写了 GPU 存储层级、Register Tiling 和 Cache Tiling、FlashAttention 的分块策略——这些文章都在讨论"怎么减少数据搬运"、“怎么让数据在片上多用几次”。但我发现自己一直跳过了一个更前面的问题:GPU 到底怎么执行这些 kernel?
·4020 words·9 mins
最近在看 CMU 15-442 的 Hardware Acceleration。课件翻到 Register Tiling,一个 2×3 output tile 的例子。我盯着那页 slide 看了一会儿——Register Tiling 这个词不陌生,但要我说清楚它和 Cache Tiling 的区别,以及它们到底在优化什么,我发现自己的理解一直停在"把矩阵切成小块"这个表面。
·2492 words·5 mins
上一篇从高斯消元推到了低秩分解,最后用 LoRA 举了个例子——\(\Delta W = BA\)。当时想清楚的是数学层面为什么两个小矩阵可以代替一个大矩阵,但 LoRA 本身没有展开。
·5745 words·12 mins
PagedAttention 那篇写完之后,我觉得 KV Cache 的内存管理问题算是理清了——分页、按需分配、Block Table 映射,思路很干净。末尾我提到下一步要看 Continuous Batching 和 Chunked Prefill,也就是在多个并发请求之间怎么调度 GPU 的计算资源。
·6444 words·13 mins
上一篇理清了 Prefill 和 Decode 的因果链之后,我开始翻 Decode 阶段的加速方案。结论已经很清楚了:在单请求、小 Batch 的典型场景下,Decode 阶段是 memory-bound 的——每生成一个 token 就要把整个模型权重从显存搬一遍,计算单元大部分时间在等数据(Batch 较大或模型结构不同时,瓶颈也可能转向 compute-bound,但单次请求的延迟优化场景下 memory-bound 是常态)。
·5882 words·12 mins
前一篇写完之后,KV Cache 在我脑子里的形象已经变了——它不再是一次 forward 里的临时中间量,而是一个跨多轮存活、随 Decode 持续增长、每个请求各自独立的状态。
但"知道它是有状态的"和"知道怎么管它"是两件事。当我开始想具体的内存管理问题的时候,发现事情比想象的复杂。
·5127 words·11 mins
最近在从传统模型 Serving 转向 LLM Inference,一个让我停下来想了很久的问题是:为什么 LLM 的推理系统看起来和我之前熟悉的那套完全不一样?
我之前做推荐模型和 CV 模型的部署,排序模型的 Serving 架构虽然在工程上也有不少复杂性(远程 embedding 读取、稀疏参数缓存等),但核心计算模式不难概括:一个请求进来,跑一次 forward,返回结果。LLM Serving 里冒出来一大堆新概念——推理要分成 Prefill 和 Decode 两个阶段,中间要维护 KV Cache,请求调度要做 Continuous Batching,显存管理要用 PagedAttention——我花了些时间才把它们背后的因果链串起来。
·5343 words·11 mins
写完 Multi-Head Attention 和 Causal Attention 之后,我一直觉得 Attention 这条线已经差不多了——公式拆过了,routing space 的 factorization 也想明白了,Causal Mask 的因果约束也理清了。
直到最近看 Llama 2 的模型配置,发现一行让我停下来的参数:
·2423 words·5 mins
最近学习 MIT 6.S191 Lecture 9,从数据并行一路看到模型并行、序列并行和专家并行,一共过了七种策略。
单独看每种都不难理解,但当它们的缩写一起出现时,很容易混淆:每种并行到底在切什么?为什么需要这么多种?实际训练时怎么组合?
·8985 words·18 mins
上一篇把单个 MoE Layer 的执行流走了一遍:Router 选 Expert、Token Dispatch 按 Expert 重排、Grouped GEMM 统一调度、Combine 加权合回原始 Token 顺序。整条链路假设所有 Expert 都在同一张 GPU 上。
·5127 words·11 mins
上一篇 MoE 笔记把架构家族理了一遍——稀疏、稠密、Soft MoE,gate 在前还是在后,决定了算力形态和适用场景。
但那篇有一段伪代码一直让我不太舒服:
1for prob, idx in zip(topk_probs.unbind(1), topk_idx.unbind(1)): 2 out += prob.unsqueeze(1) * self.experts[idx](x) 逻辑上没问题。但想象一下真实场景:一个 batch 有几千个 token,每个 token 选 2 个 expert,就是上万个 token-expert 计算任务。用 Python for loop 逐个调 expert 的 forward,GPU 利用率会低得离谱。
·5188 words·11 mins
上一篇 TP 写完之后,我对 Megatron 的 Column Parallel + Row Parallel 已经比较清楚了:前一层按输出特征切分,后一层按输入特征切分,两层之间不需要 AllGather,只在最后做一次 AllReduce 把部分和加起来。
·4618 words·10 mins
起因 # 上一篇搞清楚了 Pipeline Parallelism 的调度机制:GPipe 先 Forward 后 Backward,1F1B 通过交替调度降低 Activation 暂存压力,但理想条件下 Bubble Time 相同。
·5476 words·11 mins
起因 # 上一篇把 ZeRO 的三个 Stage 走了一遍之后,我以为关于参数分片这件事已经想得差不多了——ZeRO-3 / FSDP 把参数、梯度和 Optimizer States 全部分片存储到多张 GPU 上,单卡 Model States 从 112 GB 降到约 28 GB,问题解决。
·5007 words·10 mins
起因 # 上一篇 Tensor Parallelism 写完之后,我对 TP 的基本原理有了底:把单个算子内部的矩阵乘法拆到多张 GPU 上执行,每张卡只算部分输出,最后通过 AllReduce 聚合。TP 分片的是计算本身。
·7389 words·15 mins
起因 # 上一篇搞清楚了 AllReduce、ReduceScatter 和 AllGather 三种通信原语,末尾提到 DDP 和 ZeRO Stage 2 选择了不同的通信原语组合,根本原因是参数更新的责任分配不同。
·4919 words·10 mins
起因 # 之前几篇文章从模型显存组成一路算到 7B 模型的训练账本,结论很直接:BF16 mixed precision + AdamW 的 Model States 就有 112 GB,一张 80 GB A100 连放都放不下。Checkpointing + Offloading 两个一起用可以勉强塞进去,但训练速度要被 PCIe 传输和额外重计算拖累。
·6738 words·14 mins
起因 # 上一篇讨论完 Checkpointing 和 Offloading 之后,结尾留了一句:单卡优化的代价已经不可接受时,下一步自然是考虑让多张 GPU 分担训练状态。
于是我开始看 DDP(DistributedDataParallel)。一开始觉得 DDP 的核心思想很直白——每张卡各算各的梯度,然后 All-Reduce 同步一下,用平均梯度更新参数。看起来最朴素的做法就是把所有梯度打包成一个大 tensor 做一次 All-Reduce,简单干净。
·5977 words·12 mins
起因 # 上一篇算完了 7B 模型的训练显存账本。结论很直接:BF16 mixed precision + AdamW 的 Model States 就有 112 GB,加上 30 GB 的 Activations,单卡至少需要 145 GB——一张 80 GB 的 A100 连 Model States 都放不下。
·4077 words·9 mins
起因 # 之前分别写了推理和训练的显存心智模型和混合精度训练中的显存组成,反复出现一个粗估:BF16 mixed precision + AdamW 大约 16 bytes/parameter。但这个数字每次都只是"1B × 16 = 16 GB"的单位换算,没有真正对着一个具体的 Transformer 架构把账从头到尾算一遍。