LogoCookLLM Docs
LogoCookLLM Docs
HomeCookLLM

Principles

Tokenization
Tokenization BasicsBPE AlgorithmGPT TokenizersBPE Training Engineering
Model Architecture
Transformer LM
From token ids to logitsEmbedding and LM Head
Attention Mechanisms
From Self-Attention to GQAAttention Sink
Position Encoding
Position Encoding BasicsRoPE Math DerivationRoPE ImplementationLength Extrapolation
GPU Programming Basics
GPU Architecture BasicsTensor LayoutTriton Basics: Vector Add
FlashAttention
Flash Attention PrinciplesFrom Naive Implementation to Auto-TuningBlock Pointers and Multi-Dim SupportCausal Masking OptimizationGrouped Query AttentionBackward Pass Implementation
Distributed Training
Data ParallelismZeRO OptimizerFully Sharded Data ParallelTensor ParallelismPipeline ParallelismMulti-Dimensional Hybrid Parallelism

Hands-on Training

Overview
Pretraining
Pretraining DataTokenizer TrainingModel ArchitectureData PipelineTraining LoopMonitoring and Validation
X (Twitter)

Continuous Batching

Premium

静态批处理让 GPU 大量空转,迭代级调度、selective batching 与 chunked prefill 怎么把它填满

Get code access

上一章我们把单个请求的 decode 优化到了极致:KV cache 让每一步只算新 token。但线上服务从来不是一个请求,而是几十上百个请求同时进来,长度各不相同,随时开始随时结束。

这一章要回答的是:既然单请求已经喂不饱 GPU,把多个请求凑在一起算就好了,为什么真实系统还要为此专门设计一套调度机制?我们会看到朴素的批处理会浪费掉一半以上的算力,然后一步步引入 continuous batching、selective batching 和 chunked prefill,最后落到一个更根本的问题:吞吐和延迟不能同时最大化,到底该优化哪个。

一个请求喂不饱 GPU

多个请求一起算,听上去就是"把它们塞进同一个 batch"这么简单。但要理解后面为什么需要一整套调度机制,得先弄清楚:批处理到底省下了什么?

回到 decode 的一步。模型要为 1 个 token 做一次前向,为此它必须把整份模型权重从显存读进计算单元。这里的不对称非常极端:读的是整个模型,产出的是一个词。

打个比方,这就像开一辆大卡车去送一件快递。油钱(读权重)几乎全花在"把车开出去"上,和你车上装了 1 件还是 32 件货关系不大。既然如此,多装几件就是纯赚。这就是批处理的全部动机:把 32 个请求凑成一批,权重仍然只读一遍,却能同时产出 32 个 token。

Log in to continue reading

This is premium content. Please log in to access the full article.

用真实参数量化一下这个不对称。以 13B 模型 FP16 为例,权重约 26 GB,在 HBM 带宽约 2 TB/s 的 A100 上读一遍要约 13 ms。而这 13 ms 里 GPU 的算力(FP16 约 312 TFLOPS)几乎全在空转,因为"为 1 个 token 算一遍前向"所需的浮点运算远远填不满。绝大部分时间在等数据,不在算数。

但有一样东西不能被摊薄:KV cache 是每个请求私有的。batch 里有 32 个请求,就有 32 份缓存,每一份都得完整读一遍。权重可以共享,缓存不行。这个区别看起来不起眼,却是本章后半段所有麻烦的根源,也是上一章结尾那个张力的来源。

这里有个容易混淆的地方:批处理提升的是吞吐(单位时间总共产出多少 token),不是单请求延迟。对某一个用户来说,他的 token 并不会因为旁边多了 31 个人而更快到达,通常还会略慢一点。批处理换来的是同样的硬件能同时服务更多人。

Table of Contents

一个请求喂不饱 GPU
静态批处理:一批人必须一起走
迭代级调度:每一步都重新决定批次
批处理和迭代级调度天生打架
Selective batching:只对能批的算子批
prefill 会卡住正在跑的 decode
显存不够时,请求会被踢下车
该看吞吐还是延迟:goodput
总结