上一篇从高斯消元推到了低秩分解,最后用 LoRA 举了个例子——\(\Delta W = BA\)。当时想清楚的是数学层面为什么两个小矩阵可以代替一个大矩阵,但 LoRA 本身没有展开。
最近在看 LoRA(Low-Rank Adaptation)的时候,碰到了一个公式:
$$ \Delta W = BA $$其中 \(B \in \mathbb{R}^{m \times r}\),\(A \in \mathbb{R}^{r \times n}\),中间那个维度 \(r\) 被称为 Rank。
我对 Rank 这个词并不陌生——以前在 ACM-ICPC 里写高斯消元的时候,算自由变量个数就用到了矩阵的秩。但当时的关注点全在怎么求解、怎么判断无解多解、怎么把实际问题转化成线性方程组。至于 Rank 本身代表什么数学结构、跟低秩分解有什么关系,说实话没有认真想过。
PagedAttention 那篇写完之后,我觉得 KV Cache 的内存管理问题算是理清了——分页、按需分配、Block Table 映射,思路很干净。末尾我提到下一步要看 Continuous Batching 和 Chunked Prefill,也就是在多个并发请求之间怎么调度 GPU 的计算资源。
上一篇理清了 Prefill 和 Decode 的因果链之后,我开始翻 Decode 阶段的加速方案。结论已经很清楚了:在单请求、小 Batch 的典型场景下,Decode 阶段是 memory-bound 的——每生成一个 token 就要把整个模型权重从显存搬一遍,计算单元大部分时间在等数据(Batch 较大或模型结构不同时,瓶颈也可能转向 compute-bound,但单次请求的延迟优化场景下 memory-bound 是常态)。
前一篇写完之后,KV Cache 在我脑子里的形象已经变了——它不再是一次 forward 里的临时中间量,而是一个跨多轮存活、随 Decode 持续增长、每个请求各自独立的状态。
但"知道它是有状态的"和"知道怎么管它"是两件事。当我开始想具体的内存管理问题的时候,发现事情比想象的复杂。
最近在从传统模型 Serving 转向 LLM Inference,一个让我停下来想了很久的问题是:为什么 LLM 的推理系统看起来和我之前熟悉的那套完全不一样?
我之前做推荐模型和 CV 模型的部署,排序模型的 Serving 架构虽然在工程上也有不少复杂性(远程 embedding 读取、稀疏参数缓存等),但核心计算模式不难概括:一个请求进来,跑一次 forward,返回结果。LLM Serving 里冒出来一大堆新概念——推理要分成 Prefill 和 Decode 两个阶段,中间要维护 KV Cache,请求调度要做 Continuous Batching,显存管理要用 PagedAttention——我花了些时间才把它们背后的因果链串起来。
写完 Multi-Head Attention 和 Causal Attention 之后,我一直觉得 Attention 这条线已经差不多了——公式拆过了,routing space 的 factorization 也想明白了,Causal Mask 的因果约束也理清了。
直到最近看 Llama 2 的模型配置,发现一行让我停下来的参数:
最近在看 MIT 6.S191 Lecture 9 讲 Context Parallelism 的时候,碰到了一个词:Causal Attention。
我对 Self-Attention 的公式并不陌生——之前那篇已经把 QKV 和 routing score table 拆得比较清楚了。但 Causal Attention 这个概念我一直没有认真想过。回头看,我写 Attention 公式、写 Multi-Head、写 FlashAttention 的时候,脑子里跑的都是"所有 token 都能看到所有 token"的 Full Self-Attention。
起因 # 最近在过 MIT 6.S191 和分布式训练相关的材料,各种 Norm 反复出现。LayerNorm 在写 Transformer Block 那篇时已经接触过,BatchNorm 在做 CV 的那几年天天调,RMSNorm 在看 LLM 代码时到处都是。单独看每个都不算陌生。
起因 # 之前写 Next-Token Prediction的时候,我把条件概率链式分解、Softmax 输出概率分布这些东西理了一遍。当时觉得逻辑非常自洽:模型在每个位置输出整个词表上的概率分布,概率最高的那个 token 就是模型最有信心的预测。
起因 # 过去几篇笔记一直在 Lecture 6 的范围里打转。Distribution Shift 那篇让我意识到离线测试集的 98% 准确率没法平移到生产环境,Algorithmic Bias 那篇又告诉我总体准确率可以掩盖群体间的巨大差异,对抗攻击那篇则让我看到泛化好的模型仍然可以被微小扰动骗过。
起因 # 上一篇结尾留了一个问题:Pre-training Loss 通常随模型规模平滑改善,但一些下游 benchmark 上的准确率似乎在某个规模区间突然大幅提升。
起因 # 上一篇理清了 Pre-training 和 Post-training 的关系——Pre-training 让模型学会语言建模,SFT 和 RLHF 再让它学会遵循指令。
起因 # 最近继续跟 MIT 6.S191,加上工作中或多或少接触到一些 LLM 训练相关的讨论,我开始试着把大模型的训练流程从头到尾理一遍。
起因 # 上一篇把 RNN 拆成了两层:state transition 和 output projection。State transition 维护 hidden state,output projection 从 hidden state 中映射出当前任务需要的预测结果。
最近在做 Stanford CS336(Language Modeling from Scratch)的 Assignment 1,实现了一个 BPE Tokenizer。最初的 trainer 跑 TinyStories 要约 8 分钟,改完后降到约 36 秒。
耗时主要花在 merge 循环里。要判断哪些工作可以省,得先弄清每次 merge 改变了哪些统计量。下面先交代 BPE 的表示和合并规则,再回到这段实现。
起因 # 机缘使然,最近在工作中接触到了 MoE(Mixture of Experts)。
有意思的是,我接触到的用法,和现在大模型里的 MoE 长得完全不一样。