news 2026/9/29 16:48:12

从零手搓AI工程:推理引擎、KV Cache与连续批处理实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手搓AI工程:推理引擎、KV Cache与连续批处理实战

1. 从零手搓AI工程:为什么“调包”救不了你

很多人对AI工程的理解,停留在“装个transformers库,调个pipeline,跑通一个demo”这个层面。我刚开始接触这块的时候也这样,觉得模型能输出结果就算完事。直到有一次,线上服务在高峰期直接雪崩,显存溢出、请求排队、响应时间从200毫秒飙到8秒,我才意识到——能跑通和能扛住,中间隔着一整个工程体系。

ai-engineering-from-scratch这个方向,核心不是教你调API,而是让你从底层理解一个AI系统到底由哪些部件组成、每个部件的瓶颈在哪、怎么在资源受限的情况下做出可用的东西。它适合那些不满足于“会调包”、想真正搞明白推理引擎怎么调度、显存怎么管理、算子怎么优化的人。说白了,就是从“会用”到“会造”的那条路。

这篇文章我会按我自己踩过的坑和实际搭建经验,把从零构建AI工程能力的关键环节拆开讲。不堆术语,不抄文档,只讲那些你真正动手时绕不开的东西。

2. 先搞清楚AI工程到底在工程什么

2.1 训练、推理、服务化是三件不同的事

新手最容易犯的错,是把“AI工程”当成一个整体。实际上它至少分成三层,每层的关注点完全不同:

  • 训练层:关注梯度是否正确、损失是否收敛、数据管道是否高效。核心指标是吞吐量和收敛速度。
  • 推理层:关注延迟、显存占用、批处理效率。核心指标是首token延迟和每秒生成token数。
  • 服务层:关注并发、限流、容错、监控。核心指标是可用性和P99延迟。

我见过太多人把训练脚本直接改吧改吧就上线,结果连基本的并发都扛不住。训练时你可以慢慢跑,推理时用户可不会等你。

2.2 为什么必须从底层理解

有人会问:现在不是有vLLM、TensorRT-LLM这些现成方案吗,为什么还要从零学?

我的回答是:工具越高级,出问题时你越无能为力。当vLLM的显存碎片导致OOM,当TensorRT的算子不支持你的自定义层,当批处理策略和你的业务流量模式不匹配——如果你不理解底层的KV Cache管理、连续批处理、算子融合这些机制,你连日志都看不懂。

从零构建的意义在于,你亲手实现过一个简化版的推理引擎之后,再看那些工业级框架,就能一眼看出它在哪个环节做了什么取舍。这种判断力,是调包调不出来的。

2.3 最小可用AI系统的组成清单

一个能跑起来的最小AI工程系统,至少包含以下模块:

模块职责常见实现
模型加载器读取权重、初始化参数safetensors、pickle
计算图/算子执行矩阵运算、激活函数PyTorch、手写CUDA
内存管理显存分配与回收缓存分配器、内存池
调度器请求排队与批处理连续批处理、动态批处理
服务接口对外提供APIHTTP、gRPC
监控指标采集与告警Prometheus、日志

这张表看着简单,但每一个模块往下挖都是一堆坑。接下来我按实际搭建顺序,逐个拆解。

3. 模型加载与内存布局:第一个性能分水岭

3.1 权重加载不只是“读文件”

很多人以为加载模型就是torch.load()一下完事。实际上,权重加载方式直接决定了你的冷启动时间和内存峰值。

我实测过一个7B参数的模型,用pickle加载峰值内存会到模型大小的2倍以上,因为反序列化过程中会同时存在序列化数据和反序列化后的张量。换成safetensors之后,峰值内存降到1.1倍左右,加载时间也缩短了将近40%。

原因在于safetensors是零拷贝的,它直接把文件映射到内存,不需要额外的反序列化缓冲区。这个细节在本地开发时感知不强,但在容器化部署、内存受限的环境里就是生死线。

3.2 显存分配策略决定你能跑多大模型

显存管理是AI工程里最容易被低估的环节。PyTorch默认的缓存分配器会预留显存,这导致一个现象:你明明只用了8GB,但nvidia-smi显示占了12GB。

这里的关键概念是显存碎片。当你频繁申请和释放不同大小的张量时,显存里会出现很多不连续的小空洞。这些空洞单个看都不够用,但加起来可能有好几个GB。

我的处理经验是:

  • 推理场景尽量预分配固定大小的显存池,避免运行时动态申请
  • 如果必须动态分配,用torch.cuda.memory._set_allocator_settings调整分配策略
  • 监控torch.cuda.memory_summary()里的碎片率,超过15%就要警惕

注意:不要迷信torch.cuda.empty_cache(),它只是把缓存还给驱动,频繁调用反而会拖慢性能。真正要解决的是分配策略问题。

3.3 量化不是万能药,选错格式反受其害

量化能显著降低显存占用,但不同量化格式的代价差异很大。我整理了一个实际对比:

量化方式显存节省精度损失推理加速适用场景
FP16基准无基准通用
INT8约50%较小1.5-2x对精度不敏感
INT4约75%明显2-3x边缘部署
GPTQ约75%中等2-3x消费级显卡
AWQ约75%较小2-3x推荐优先尝试

我的建议是:先跑通FP16,再考虑量化。很多人的问题是模型还没跑起来就想着量化,结果量化后的精度问题排查起来更痛苦。AWQ目前是我用下来精度和速度平衡最好的方案,但需要模型本身支持。

4. 推理引擎的核心:KV Cache与批处理调度

4.1 KV Cache为什么是推理优化的命门

自回归生成的特点是:每生成一个token,都要用到之前所有token的Key和Value。如果不做缓存,每步都要重新计算整个序列的注意力,计算量随序列长度平方增长。

KV Cache的思路很直接:把已经算过的Key和Value存下来,下一步直接复用。这样每步只需要计算新token的注意力,复杂度从O(n²)降到O(n)。

但代价是显存。以7B模型为例,FP16精度下每个token的KV Cache大约占0.5MB。如果并发100个请求、每个请求平均500个token,光KV Cache就要25GB。这就是为什么长上下文和高并发很难同时满足。

4.2 连续批处理:吞吐量和延迟的平衡术

静态批处理的问题是:一批请求必须等最慢的那个生成完才能释放。如果批里有个请求要生成1000个token,其他只生成10个的请求就得干等。

连续批处理的思路是:每个生成步都重新组批。已经生成完的请求立刻退出,新来的请求立刻加入。这样GPU利用率大幅提升,但实现复杂度也上去了。

我手写过一个简化版的连续批处理调度器,核心逻辑是:

class Scheduler: def __init__(self, max_batch_size): self.running = [] # 正在生成的请求 self.waiting = [] # 等待加入的请求 self.max_batch_size = max_batch_size def step(self): # 移除已完成的请求 self.running = [r for r in self.running if not r.finished] # 尽可能多地加入新请求 while self.waiting and len(self.running) < self.max_batch_size: self.running.append(self.waiting.pop(0)) # 执行一步生成 if self.running: self._forward(self.running)

这段代码看着简单,但实际要处理的问题很多:不同请求的序列长度不同怎么对齐、padding怎么处理、显存不够时怎么抢占。每一个都是坑。

4.3 PagedAttention解决了什么

vLLM的PagedAttention是我认为近几年推理优化里最巧妙的设计之一。它借鉴了操作系统虚拟内存的分页思想,把KV Cache切成固定大小的块,不需要连续存储。

这样做的好处是:

  • 消除外部碎片:不再需要为每个请求预留连续显存
  • 支持内存共享:多个请求如果前缀相同,可以共享KV Cache块
  • 灵活扩容:序列变长时按需分配新块,不用提前预留

我实测下来,同样的硬件配置,用PagedAttention能把并发数提升2-4倍。这个提升在长上下文场景下更明显。

提示:如果你在用vLLM,gpu_memory_utilization这个参数很关键。默认0.9,但如果你的服务还有其他显存开销,建议降到0.85左右,留出余量避免OOM。

5. 服务化与线上稳定性:从能跑到能扛

5.1 请求队列设计:别让用户无限等待

我见过最离谱的线上事故,是服务没有队列上限,请求一直堆积,最后内存爆掉整个进程挂掉。用户那边看到的是连接超时,体验极差。

正确的做法是设置多级队列:

  • 快速队列:短请求优先,保证交互体验
  • 批量队列:长请求或离线任务,可以等
  • 拒绝策略:队列满了直接返回429,让客户端重试

队列长度怎么定?我的经验公式是:队列长度 = 平均QPS × 可接受等待时间。比如QPS是50,用户最多等2秒,那队列长度设100左右。超过就拒绝。

5.2 超时与重试:不是所有失败都值得重试

超时设置要分两段:首token超时和总超时。首token超时通常设短一些(比如5秒),因为如果模型连第一个token都出不来,大概率是卡住了。总超时根据业务定,但要有上限。

重试策略上,我的原则是:

  • 连接失败可以重试
  • 超时重试要谨慎,因为可能服务端还在处理,重试会加重负载
  • 模型返回错误不要重试,那是逻辑问题

5.3 监控指标:没有度量就没有优化

线上服务必须监控的指标:

指标含义告警阈值建议
首token延迟P99用户感知的响应速度> 2s
每秒生成token数吞吐能力低于基线30%
显存使用率资源水位> 90%
队列等待时间拥塞程度> 1s
错误率服务质量> 1%

这些指标我建议用Prometheus采集,Grafana做面板。不要等出事了才去看日志,那时候已经晚了。

6. 那些只有动手才会遇到的坑

6.1 显存泄漏:最隐蔽的杀手

显存泄漏在Python里特别难查,因为垃圾回收机制和CUDA缓存交织在一起。我遇到过一次,服务跑几个小时就OOM,重启就好。

排查过程是这样的:

  1. 先用torch.cuda.memory_summary()看分配情况,发现预留显存持续增长
  2. 用tracemalloc查Python对象,没发现异常
  3. 最后定位到是一个全局缓存字典,每次请求都往里塞东西,从来没清理

教训是:任何全局状态都要有清理机制。推理服务里,请求级别的数据绝对不要放到全局变量里。

6.2 批处理反而变慢:小批量的陷阱

批处理不是越大越好。我实测过一个场景,batch size从1加到8,吞吐量提升明显;但从8加到32,吞吐量几乎没变,延迟却翻倍了。

原因是GPU算力已经饱和,再加批量只是让每个请求等更久。最优批量取决于模型大小和硬件,需要实际压测找拐点。我的做法是从batch size 1开始,每次翻倍,记录吞吐和延迟,找到吞吐不再明显增长的那个点。

6.3 精度问题:量化后的模型可能“胡说八道”

量化后的模型有时候不是直接报错,而是输出变得莫名其妙。这种问题最难查,因为代码没bug,是数值精度的问题。

我的排查清单:

  • 对比量化前后同一输入的输出,看偏差是否在可接受范围
  • 检查是否有层对精度特别敏感(通常是attention的softmax和layer norm)
  • 尝试混合精度,敏感层保持FP16,其他层量化

注意:INT4量化在小于7B的模型上精度损失往往不可接受,建议至少13B以上再考虑INT4。

7. 从零构建的学习路径建议

如果你真想走ai-engineering-from-scratch这条路,我的建议是按这个顺序来:

第一阶段:手写推理循环。不用任何推理框架,用PyTorch手动实现一个GPT的生成循环,包括KV Cache。这一步能让你彻底理解自回归生成的计算过程。

第二阶段:实现批处理。在第一步的基础上加入静态批处理,然后改成连续批处理。体会调度器设计的取舍。

第三阶段:内存优化。实现一个简单的分页KV Cache,理解PagedAttention的原理。

第四阶段:服务化。用FastAPI或gRPC把上面的东西包起来,加入队列、限流、监控。

第五阶段:性能调优。用profiler找瓶颈,尝试算子融合、量化、编译优化。

每一步都不需要做到工业级强度,但必须亲手写一遍。我自己的经验是,写完第一阶段的代码后,再看vLLM的源码,理解速度至少快了三倍。

这个方向没有捷径,但每一步的收获都是实打实的。等你亲手实现过一个能扛住并发的推理服务,再回头看那些“调包”教程,会有完全不同的感受。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 16:46:22

财务系统建设最难啃的硬骨头:业务规则、技术架构与避坑指南

做企业数字化做了这些年&#xff0c;有一个领域是公认的硬骨头——财务系统。市面上讲产品功能的文章很多&#xff0c;讲技术架构的也不少&#xff0c;但真正把难点掰开揉碎讲透的确实不多。财务系统难&#xff0c;不是难在哪个具体功能上&#xff0c;而是难在一套系统要同时满…

作者头像 李华
网站建设 2026/9/29 16:45:37

从零搭建AI工程体系:环境、数据、训练、部署与监控全链路实战

从零搭建AI工程能力这件事&#xff0c;我前前后后折腾过好几轮。最早的时候我也走过弯路——上来就装框架、跑Demo、调API&#xff0c;结果模型一换、数据一多、并发一上来&#xff0c;整个项目就散架了。后来我才慢慢想明白一个道理&#xff1a;AI工程不是"会调模型"…

作者头像 李华
网站建设 2026/9/29 16:44:20

starnet桌面AI Agent框架:MCP协议与OpenRouter模型路由实战

1. 从“starnet”这个名字说起&#xff1a;它到底想解决什么问题 第一次看到“starnet”这个项目标题&#xff0c;加上旁边一串热搜词——AI agents、desktop、OpenRouter、MCP——我脑子里第一反应是&#xff1a;这又是一个想把“AI 智能体”和“本地桌面环境”缝在一起的东西…

作者头像 李华
网站建设 2026/9/29 16:44:19

一文搞懂IPEX、SMA、U.FL射频连接器区别与选型

干我们这行&#xff0c;最常被小白问到的不是电路怎么画&#xff0c;而是天线接口怎么认。IPEX、SMA、U.FL这三个词&#xff0c;看着像三兄弟&#xff0c;实则是完全不同路子的连接器&#xff0c;但很多商家和教程又喜欢把IPEX和U.FL混着叫&#xff0c;导致你拿着卡尺量半天&am…

作者头像 李华
网站建设 2026/9/29 16:43:56

量子力学与材料力学:从密度泛函理论到弹性常数预测

1. 当材料力学开始问“为什么”&#xff1a;经典模型面对尺度极限时的空白材料力学这门学科&#xff0c;传统上是靠连续介质假设吃饭的。我们习惯把一块金属看成均匀的、连续的物质&#xff0c;用应力、应变、弹性模量去描述它在外力下的行为。这种思路在宏观尺度下极其成功——…

作者头像 李华