推理是和训练不同的工作
到现在你已经训练过模型:你在数据集上跑了 梯度下降,看着 损失 下降,并保存了 权重。提供服务是生命周期的另一半,而 训练与推理之间的区别 影响深远。训练只发生一次(或定期一次),在离线的大型任务里进行,你乐意等上几个小时;推理则要发生上百万次,在线进行,此时有人或别的系统正等着这个答案。
推理时没有反向传播:没有 反向传播,没有梯度,没有优化器状态。你只是跑一遍前向传播,把输出读出来。这听上去更便宜,单次确实如此——但经济账反了过来,因为你要持续不断地为它付钱。一个只训练两周的模型,之后可能服务好几年,所以整个生命周期的 推理成本 通常远远盖过训练账单。
延迟和吞吐量朝相反方向拉扯
两个数字主宰着一个服务系统,而 延迟与吞吐量之间的张力 正是核心的权衡。延迟是单个请求要花多久——单个用户感受到的等待。吞吐量是你每秒在所有用户之间完成多少个请求。它们不是一回事,而且压住一个往往会伤到另一个。
延迟要报成一个分布,而不是平均值。真正要紧的通常是尾部百分位——p99,也就是最慢的那 1% 请求——因为那才是你最倒霉的用户感受到的,而平均值会悄悄把它掩盖掉。一个服务平均看上去很快,却可能有十分之一的用户在等两秒。设一个*预算*(比如 p99 低于 200 毫秒),并把每一项优化都看成在为这个预算买出余量。
为什么会拔河?一块 GPU 最开心的事是一口气做一次大的 矩阵乘法。如果一次只处理一个请求,芯片在请求之间大半时间都闲着——吞吐量低,但每个答案回得很快。把请求凑在一起,芯片就能一直忙着——吞吐量高——可第一个请求现在得等它的同伴们。这种凑批是服务中最大的那根杠杆,所以它单独占一节。
批处理:喂饱芯片,留意等待
请求批处理 把若干输入叠成一个张量,让它们一起穿过网络。因为权重只从内存里加载一次、然后被整个批次复用,你每搬动一个字节就能做更多有用的计算——而在现代加速器上,瓶颈通常是搬运内存,而不是做算术。批次越大,吞吐量越好,一直好到你内存用尽、或撑爆延迟预算为止。
最简单的做法叫*静态批处理*:等到凑齐 N 个请求(或到一个很短的超时)再一起跑。但对一个逐 token 生成的 大语言模型 来说,静态批处理很浪费:短回复早早结束,它们的槽位就空着,而最长的那条回复还在拖。*连续批处理*解决了这一点——在每一步把一个已完成的请求换出、一个新请求换入,让批次始终满载。这一点,再加上把过去注意力的键和值存起来、使每个新 token 都很便宜的 KV 缓存,正是现代 LLM 服务器能达到那种吞吐量的原因。
static batching: [req A]......done (slot idle)
[req B]...............done
waste = idle slots while B finishes
continuous batching: A finishes -> C jumps into A's slot
batch stays full every step向量数据库:提供的不只是权重,还有事实
模型的 权重 在训练时就被冻结了,所以它不可能知道今天的新闻或你的私有文档——而要它回忆具体细节,反而招来 自信满满的编造。解法是在请求发生时*检索*相关文本,把它作为上下文交给模型。这就是 检索增强生成,它的引擎就是向量数据库。
机制是这样的。每篇文档都被转成一个 嵌入向量——一个把含义相近者在空间中放得彼此靠近的向量。一个 向量数据库 存下上百万个这样的向量,并极快地回答一个问题:*哪些已存的向量离这条查询向量最近?*在嵌入的几何里最近,就意味着含义最接近,于是你取回的是真正相关的段落,而不仅仅是共享关键词的那些。
在大规模下精确扫描每个向量太慢了,所以这些数据库使用近似最近邻索引(基于图的 HNSW 之类很常见)。它们用很小一部分时间返回*几乎*最优的匹配——一次刻意用精度换速度的取舍,正是你在服务里反复遇到的那笔交易。为了低延迟,检索这一步有它自己的预算:把索引放在内存里,并记住你塞满检索文本的 上下文窗口 不是免费的,因为每多一个 token,都会在每个生成的 token 上花掉算力。
边缘部署:把模型搬到数据旁边
到目前为止我们都假设模型住在数据中心里。边缘部署 把这一点反了过来:模型跑在手机、摄像头、汽车、传感器上——就在数据诞生的地方。动机很少只是单纯的速度。它省掉的是一次往返(不必跨网络跳到服务器)、是你从不外发的数据(隐私,而且能离线工作),以及你不再支付的带宽和服务器账单。
难处在于预算。一台边缘设备的内存和功耗只是数据中心 GPU 的零头,所以模型往往得先瘦身——通过 量化(把权重存成 8 位或 4 位,而不是 32 位)以及下一篇会讲的其他 压缩 技巧。然后一个可移植的运行时,比如 ONNX Runtime,让同一个导出的模型能在五花八门的边缘芯片上跑。精度降低可能带来一点准确率下滑,所以你要去测量它——绝不要假设它是免费的。
把它们串起来
低延迟服务是一连串诚实的权衡,而不是某个单一的妙招。你从一个冻结的模型出发,针对要紧的那个百分位设定延迟预算,然后有意识地把它花掉:用批处理喂饱芯片,用缓存避免重复劳动,用检索把答案锚定在新鲜的事实上,并在往返本身就是成本时压缩模型或把它搬到边缘。这些没有一个是银弹——每一个都是用付出另一样东西来换取一样东西。
而且把服务上线并不是终点线。真实世界的输入会随时间渐渐偏离你的训练分布,所以接下来的指南会讲如何把模型进一步瘦身,以及用 监控 盯住它、提防 数据漂移与概念漂移。一个服务很快却悄悄出错的模型,比一个慢的还要糟——只有在答案始终正确的前提下,速度才值得去追。