从凌晨三点的一条告警说起。那天我正在睡,手机连着震了十几下,监控平台告诉我:核心推理服务P99延迟从80毫秒飙到了1.8秒,持续五分钟还没恢复。我当时第一反应是"流量突增",但拉出面板一看,QPS根本没怎么动。真正变化的是一组GPU利用率曲线——A100的利用率从65%掉到了20%以下,然后又突然拉满,反复横跳。那是我转到AI-Infra方向之后处理的第一个真正意义上的疑难杂症。在这之前,我做过后端中间件,做过微服务治理,自我感觉"稳定性"这件事我已经玩明白了。但那天晚上的排查过程让我意识到,AI基础设施和传统后端稳定性建设完全是两套逻辑——它的热点是动态的,它的资源是异构的,它的故障根因经常藏在"看起来都正常"的缝隙里。也是从那次之后,我决定系统地把这条路走下去。这也成了写这个系列的初衷:一个一线工程师视角下的AI基础设施,不聊PPT层面的大蓝图,只聊我实际踩过、拆过、重构过的东西。这一章先讲清楚我理解的AI-Infra到底包含什么,以及一个最典型的实战案例——推理服务延迟毛刺的完整排查链路。1. 为什么一线工程师需要重新理解"基础设施"过去几年大家聊基础设施,脑子里冒出来的往往是这四个组件:容器、Kubernetes、微服务、分布式存储。这四样东西解决的是"软件怎么跑、怎么扩、怎么不挂"的问题,它们有一个共同假设——算力是相对标准化的资源,CPU是CPU,内存是内存,磁盘是磁盘。AI时代的计算负载把这个假设彻底打破了。一张GPU卡不是一个通用计算单元,它的显存容量、算力规格、卡间互联带宽、甚至驱动和固件版本,都会直接影响业务代码的真实表现。同样是跑一个7B模型推理服务,A100上能稳定压到100毫秒以内的延迟,放到某款推理卡上可能直接飙到500毫秒,而这中间的差异不是代码问题,是基础设施层面的适配问题。还有一个大家容易忽略的变化:传统后端服务对"错"的容忍度是比较高的。请求超时了可以重试,消息丢失了可以从日志补偿,哪怕有少量脏数据进入链路,靠兜底逻辑也能兜住大部分风险。但在AI推理路径上,一个超时可能意味着整个会话状态的失效;一次批量调度失败可能导致几十个任务同时重算;模型服务OOM更是会直接把所有在途请求全部打断。容错窗口变窄,基础设施的稳定性和可诊断性就被提到了前所未有的高度。所以一线工程师如果想继续在业务侧做后端,AI-Infra可能是那个帮你建立全局视角的绝佳切入点;如果你本来就在做平台和运维,不补AI负载的原理知识,很快会发现传统套路在这块经常失灵。这不是贩卖焦虑,是我在实际工作中反复撞出来的认知。1.1 传统稳定性手段在AI负载上的失灵现场举两个我很早就遇到过的例子。第一个是扩容。传统后端流量上涨,扩容是个很自然的动作,新起的Pod很快就能接流量。但在大模型推理场景里,一个新的推理副本起来了,它需要加载模型权重,做完预热,才能对外服务。7B模型的权重加载加预热,顺利的话也得一两分钟;如果是更大的模型,加载个十分钟都属正常。那就出现了一个尴尬窗口:流量已经在涨了,服务能力却还在初始化,扩容的生效滞后性非常明显。这个问题从业务侧看是"扩容不够快",从AI-Infra侧看,本质是生命周期管理逻辑需要重新设计——而不仅仅是把副本数调大。第二个是限流。传统后端限流看QPS、看线程数、看内存水位,这些指标在推理服务上当然也要看,但它们跟真实的"剩余服务能力"之间隔着很大一段距离。推理服务当前的吞吐能力取决于已加载的模型实例数、每实例的并发度、batch size策略以及显存余量。同一份代码,请求数相同,显存里KV Cache的空间大小直接决定了当前还能不能开新的并发。只盯着QPS做限流,很容易在显存快打满的时候还在继续放量。这些失灵现场给我带来的转变是:做AI-Infra不能只懂资源的"有"和"无",还得理解工作负载的"状态"和"水位"——模型权重在显存里的布局、KV Cache的占用趋势、算子的计算模式,这些是传统监控里根本不会出现的指标。1.2 这个系列会覆盖什么既然叫第一章,我先交代一下整个系列的路线图,方便你判断哪些章节对你自己更有用。大的方向上,我会围绕五个块面展开:第一是大模型推理服务的稳定性和性能调优,这可能是目前一线团队最头疼的部分;第二是训练任务的调度与容错,重点是长稳训练中的断点处理、显存抖动、通信瓶颈这一类问题;第三是异构资源池的管理,包括多型号GPU混部、调度策略、隔离策略;第四是面向LLM的数据与评测管道,这里面的工程强度被严重低估;第五是成本与容量治理,也就是常说的"怎么把单位Token的成本打下来"。这一章先不铺太宽,我会先把推理服务这条线上最经典的问题——延迟毛刺——完整复盘一遍。因为它麻雀虽小五脏俱全,牵涉到的知识点几乎覆盖了AI推理基础设施的整个核心。2. 推理服务延迟毛刺:根因排查的完整链路回到开头说的那一次事故。当时我们的架构并不复杂:一个标准的在线推理服务,使用了当时主流的推理框架,模型是7B量级的,服务部署在8卡A100的节点上。模型本身是热的,流量模型也相对平稳,属于一个典型的"跑了一阵子、看起来挺稳"的状态。延迟毛刺出现在凌晨两点左右,持续了大约半个小时,然后自动恢复了。网管平台上能看到P99还好,P999直接拉出长尾。从监控看:指标毛刺前毛刺期间QPS稳定在80左右稳定在80左右GPU利用率均值60%~70%25%~75%周期波动P99延迟80ms300~2000ms显存占用稳定出现缓慢上升后回落节点CPU30%出现周期性飙升第一眼看过去,CPU周期性飙升挺像"有定时任务在抢CPU",但查了节点的Cron和周期脚本,什么都没有。显存缓慢上升又回落,倒是有点像缓存被周期性清理。这两条线索放在一起,其实已经暗示了一个方向——显存状态和算子执行之间可能存在耦合关系,但当时的我还没想到那么深。2.1 从"看起来正常"的日志里找突破口真正让我往前走一步的,其实是NVIDIA的系统日志和推理框架的算子Trace。传统后端排障,先看应用日志,再看中间件状态,最后看系统资源。但推理服务的应用日志里只有请求进出和耗时统计,没有太多信息。真正有价值的是推理框架自带的profiling数据,能看出来每次请求在Pre填充阶段和解码阶段分别耗时多少。我拉了一组毛刺期间的请求明细,发现一个有意思的现象:出问题的请求几乎都是长序列请求,且都是解码阶段的单Token耗时异常。Pre填充阶段的耗时相对正常,而解码阶段每个Token的平均生成时间从正常的50毫秒涨到了400毫秒以上。这就把范围收窄到了解码路径。解码阶段的核心操作是:把当前已有的KV Cache拼接上新的Key/Value,做注意力计算,然后采样出新Token。这个阶段对显存的带宽和KV Cache的连续性非常敏感。2.2 根因确认:动态显存碎片化与缓存驱逐的连锁反应在推理服务里,我们给KV Cache分配的空间是按请求动态申请的,不同请求的序列长度不一样,申请大小也不同。当时我们用的是"按需增长"的策略,即每个请求的KV Cache空间随生成进度逐步扩大,内存不够时就触发一次重新分配和拷贝。问题就出在这里。当多个请求以不同节奏增长时,显存里会形成大量的碎片。碎片化本身不一定致命,但一旦新的空间需要连续分配而失败,框架会触发缓存驱逐——把一些低优先级请求的KV Cache先腾出去,腾出去之后又把它们的上下文重新计算一遍,这在解码阶段等于把已经算过的Prefix编码重新算一次,Cost极高。于是整个链路就变成了:显存碎片增多 → 新请求分配连续KV Cache失败 → 触发驱逐和重算 → GPU利用率先跌后猛涨 → 解码延迟剧烈抖动的恶性循环。为什么半小时后自动恢复?因为那段时间请求序列普遍变短,碎片被回收合并,KV Cache需求量下降,系统自己缓过来了。3. 问题背后的推理引擎机制:为什么Torch显存模型不能直接照搬很多做后端出身的人第一次看到这类问题的第一反应是:"显存碎片化我懂,重启一下进程不就完了吗?"或者"把最大显存预留出来,预先分配好不就行了?"这些思路放在传统服务上没错,但在推理引擎里有几层机制需要重新理解。3.1 显存分配器的工作方式决定了碎片问题的形态在显存分配器的层面,碎片化分两种:显存物理碎片和分配器内部碎片。GPU显存物理上是一整块连续空间,所谓碎片更多是指分配器无法找到足够大的连续块来满足大块请求。CUDA自身的分配器在底层做了不少优化,但推理框架用到的显存大头是模型权重、激活值、KV Cache,其中KV Cache的增长模式跟模型权重完全不同——模型权重是一次性分配的,激活值和KV Cache是随请求动态变化的,这就给分配器造成了混合压力。当时我们看到的碎片,主要就是KV Cache动态分配造成的。一个请求开始时分配比如256MB的KV块,生成过程中扩展成512MB、1GB,每个请求的扩展节奏不同,自然带来了参差不齐的"内存空洞"。这里可以做一个粗略的量化:假设显存总容量60GB(A100的80GB版本减掉其他开销),模型权重占约15GB,剩下45GB供KV Cache和激活使用。如果每请求平均KV Cache是1GB,但分配粒度是256MB的块,那么最坏情况下,四分之一左右的块可能处于"无法合并"的零散状态。在这样的状态下,一个需要连续1.5GB空间的请求,分配器找了半天找不到合适的块,触发了释放和重排,连锁反应就开始。3.2 KV Cache的长生命周期和缓存驱逐策略设计传统后端里的缓存驱逐,比如Redis的LRU,驱逐一个Key的代价很低,最多就是下一次访问时去后端再查一次。但推理服务里KV Cache驱逐的代价不只是"重新算一遍"那么简单:如果被驱逐的是某个还在活跃生成中的请求,恢复时除了重算前缀,还可能造成本次对话上下文的截断和状态丢失。这是线上事故级别的影响。有经验的团队会设计"先守底再优化"的策略:比如预先按最坏情况给每个请求预留KV Cache空间,虽然显存利用率低了,但不会触发驱逐;更精细的团队会引入分页式KV Cache管理,把长序列的KV按固定大小的块分散存储,显著降低连续空间要求。我们当时走的弯路,正是没有在业务快速增长之前主动介入这一层的设计。现在回过头看,一个好推理引擎最重要的不是单次算子快多少,而是在动态请求模式下,显存和计算这两个资源能不能平滑协同。Torch本身是一个通用计算框架,它对长期运行、高并发、复杂序列的推理场景支持并不自主——这也是为什么社区里不断有新的推理框架冒出来,它们本质上都是在回答同一个问题:如何在动态负载下把硬件资源用到极致。4. 修复方案设计与落地:从规避到重构的三步走问题定位清楚之后,接下来就是怎么改。我没有一上来就推到重构,而是分了三步,每一步都是当时团队可以快速落地的方案。4.1 第一步:把KV Cache分配策略从"动态增长"改为"分块预留"这是见效最快的一步。我们把每个请求的KV Cache分配策略改成按固定块分配,块大小设为256MB,请求首次分配时先拿一块,后续增长时只追加新块,不再要求物理连续。这个改动其实就是在框架层面把KV Cache的逻辑地址空间和物理地址空间解耦。效果很明显:显存碎片化的概率大幅下降,连续分配失败的告警基本消失。改动之后的压力测试里,同样的流量模型下,P999从毛刺期的接近2秒回落到120毫秒以内。这一步让我确认了之前的根因判断是对的。那一周我们的操作顺序是:先在灰度环境压测,确认没有正确性影响之后,再逐步推全。要注意的是,分块预留不等于无脑预留,块的大小和数量上限要根据模型的上下文窗口和显存总量做估算。块设得太大,浪费显存;设得太小,分配次数太多,反而增加管理开销。我们的经验是,按线上请求的P50序列长度预留出基础块,再允许按P95序列长度追加额外块,既稳又省。4.2 第二步:引入缓存驱逐的优先级分层虽然分块预留解决了大部分碎片问题,但长尾场景下缓存驱逐还是会发生。我们做了一个比较克制的设计:把请求分成几个优先级等级——VIP业务流和正在付费生成的请求最高优先级;内部测试流量和可重放的任务次之;离线批处理请求最低。驱逐时先动低优先级、已完成的KV Cache,严格避免驱逐还在生成中的请求的KV Cache,宁可牺牲低优先级请求的生成连续性,也不让核心链路的请求承担重算代价。这一步提升的是系统的"行为可预期性",也就是说:即使资源吃紧到必须牺牲一部分流量,牺牲的也是我们可以接受的部分。传统稳定性工程里这叫"优雅降级",放到推理服务里其实也一样,只是降级的颗粒度和代价模型不同了。4.3 第三步:把动态批处理策略改成"显存感知"模式推理服务为了提升吞吐,通常会把多个请求拼成一个batch在GPU上跑。批处理策略对显存的亲和性影响很大。一个常见问题:某个batch里的请求如果序列特别长,KV Cache占用高,就会把其它请求的显存空间挤掉,导致连锁驱逐。我们的优化是让batch组装时不仅看请求的到达时间,还参考每个请求的预估KV Cache占用。长短序列混批容易造成"木桶效应"——最长的请求拖慢整个batch。改成"显存感知调度"之后,把长序列请求单独调度或者分到不同的批次,可以明显减少驱逐和重算的发生率。这三步做完,整体耗时大约三周,其中第一周就拿到了主要收益。复盘下来,核心的变化其实就一句话:从"让请求去适应分配器"改成"让分配器去适应请求"。听起来不复杂,但需要深入框架代码去确认它的显存管理路径,这一步绕不开。5. 一线AI-Infra工程师的日常工具箱:我常用的诊断路径排障之外,我平时也花了不少时间在诊断工具链的建设上。AI推理服务的观测体系跟传统后端有显著区别,很多问题用传统监控根本"看不见"。这里分享几个我实际用得最频繁的诊断路径。5.1 GPU墙上的事情,看什么指标第一层是资源层指标:利用率、显存占用、显存带宽、SM活跃度、PCIe传输量。很多人只看利用率和显存,但其实利用率和延迟之间的关系并不线性——利用率高了可能是算子排队,也可能是显存带宽打满了,两种情况的解法不一样。显存带宽的使用情况需要靠硬件计数器去读,光看利用率曲线是不够的。第二层是推理框架的Metrics:排队长度、批次大小分布、解码速度、KV Cache占用与命中率。这些是业务层和资源层之间的桥梁,能帮助我们判断是"计算不够"还是"存储不够快",还是"调度策略有问题"。第三层才是业务指标:首Token延迟、Token间延迟、端到端延迟、不同窗口长度的延迟分布。业务指标变化通常是结果,前两层的指标才是原因。排障的时候我从下往上查,定位性能瓶颈时则从上往下压测。5.2 五条"一线经验"备忘有几个细节是文档很少写的。第一,NVIDIA的指标里有相当一部分在虚拟化或共享场景下是不可靠的,必须落到物理机上验证;第二,推理框架的profiling工具对请求量大的场景会显著增加开销,长稳压测时不要全程开着profiling,只看聚合统计;第三,稍微大一点的模型,权重加载阶段很吃内存带宽,尽量不要让它和在线推理流量抢占同一台机器的带宽;第四,多实例部署时,不同实例的显存分配器状态各不相同,重启单个实例要评估"会不会引发其它实例的缓存驱逐";第五,很多看起来像"推理框架bug"的问题,最终都定位到驱动或固件版本和框架版本之间的兼容性上,升级驱动前先在灰度环境跑一遍压测。这些经验原本散落在我们团队的聊天记录和事故复盘里,我一直想把它沉淀成一套更成体系的参考手册。这个系列也算是我自己的一次整理。6. 关于AI-Infra工程师的成长路径:什么能力真正值钱最后聊一点职业层面的体会。很多朋友问我,想转AI-Infra,是不是先去学某种深度学习框架、某个调度引擎就够了。我的看法是:框架和算法只是入口,真正拉开差距的,是系统性地理解和诊断"计算-显存-通信"三者的协同关系。AI负载不同于传统后端,它的核心矛盾几乎都集中在资源调度和状态管理上。一张GPU卡既是计算单元又是存储单元,通信带宽决定了多卡协同的上限,显存的分配和释放决定了服务生命周期的稳定性。这三者的协同能力,没法通过读几篇文档获得,只能在一次次真实故障中建立体感。如果你正要入门,我建议从自己手头能接触到的最小的推理服务开始,刻意制造故障:把batch size调大,显存别预留,请求并发拉高,然后仔细看延迟曲线的形态变化。用最小代价建立对异常现象的敏感度,比背一百个框架的API都有用。这一章到这里刚好是一个完整的回路:从一个凌晨的告警,到一个动态碎片化的根因定位,再到三步修复方案,最后回到诊断工具和成长路径。下一章我打算拆解训练场景下的任务调度和长稳容错,那里面又有另一套完全不同的逻辑,但很多思维方式和这一章是贯通的。
从凌晨三点的一条告警说起。那天我正在睡,手机连着震了十几下,监控平台告诉我:核心推理服务P99延迟从80毫秒飙到了1.8秒,持续五分钟还没恢复。我当时第一反应是"流量突增",但拉出面板一看,QPS根本没怎么动。真正变化的是一组GPU利用率曲线——A100的利用率从65%掉到了20%以下,然后又突然拉满,反复横跳。那是我转到AI-Infra方向之后处理的第一个真正意义上的疑难杂症。在这之前,我做过后端中间件,做过微服务治理,自我感觉"稳定性"这件事我已经玩明白了。但那天晚上的排查过程让我意识到,AI基础设施和传统后端稳定性建设完全是两套逻辑——它的热点是动态的,它的资源是异构的,它的故障根因经常藏在"看起来都正常"的缝隙里。也是从那次之后,我决定系统地把这条路走下去。这也成了写这个系列的初衷:一个一线工程师视角下的AI基础设施,不聊PPT层面的大蓝图,只聊我实际踩过、拆过、重构过的东西。这一章先讲清楚我理解的AI-Infra到底包含什么,以及一个最典型的实战案例——推理服务延迟毛刺的完整排查链路。1. 为什么一线工程师需要重新理解"基础设施"过去几年大家聊基础设施,脑子里冒出来的往往是这四个组件:容器、Kubernetes、微服务、分布式存储。这四样东西解决的是"软件怎么跑、怎么扩、怎么不挂"的问题,它们有一个共同假设——算力是相对标准化的资源,CPU是CPU,内存是内存,磁盘是磁盘。AI时代的计算负载把这个假设彻底打破了。一张GPU卡不是一个通用计算单元,它的显存容量、算力规格、卡间互联带宽、甚至驱动和固件版本,都会直接影响业务代码的真实表现。同样是跑一个7B模型推理服务,A100上能稳定压到100毫秒以内的延迟,放到某款推理卡上可能直接飙到500毫秒,而这中间的差异不是代码问题,是基础设施层面的适配问题。还有一个大家容易忽略的变化:传统后端服务对"错"的容忍度是比较高的。请求超时了可以重试,消息丢失了可以从日志补偿,哪怕有少量脏数据进入链路,靠兜底逻辑也能兜住大部分风险。但在AI推理路径上,一个超时可能意味着整个会话状态的失效;一次批量调度失败可能导致几十个任务同时重算;模型服务OOM更是会直接把所有在途请求全部打断。容错窗口变窄,基础设施的稳定性和可诊断性就被提到了前所未有的高度。所以一线工程师如果想继续在业务侧做后端,AI-Infra可能是那个帮你建立全局视角的绝佳切入点;如果你本来就在做平台和运维,不补AI负载的原理知识,很快会发现传统套路在这块经常失灵。这不是贩卖焦虑,是我在实际工作中反复撞出来的认知。1.1 传统稳定性手段在AI负载上的失灵现场举两个我很早就遇到过的例子。第一个是扩容。传统后端流量上涨,扩容是个很自然的动作,新起的Pod很快就能接流量。但在大模型推理场景里,一个新的推理副本起来了,它需要加载模型权重,做完预热,才能对外服务。7B模型的权重加载加预热,顺利的话也得一两分钟;如果是更大的模型,加载个十分钟都属正常。那就出现了一个尴尬窗口:流量已经在涨了,服务能力却还在初始化,扩容的生效滞后性非常明显。这个问题从业务侧看是"扩容不够快",从AI-Infra侧看,本质是生命周期管理逻辑需要重新设计——而不仅仅是把副本数调大。第二个是限流。传统后端限流看QPS、看线程数、看内存水位,这些指标在推理服务上当然也要看,但它们跟真实的"剩余服务能力"之间隔着很大一段距离。推理服务当前的吞吐能力取决于已加载的模型实例数、每实例的并发度、batch size策略以及显存余量。同一份代码,请求数相同,显存里KV Cache的空间大小直接决定了当前还能不能开新的并发。只盯着QPS做限流,很容易在显存快打满的时候还在继续放量。这些失灵现场给我带来的转变是:做AI-Infra不能只懂资源的"有"和"无",还得理解工作负载的"状态"和"水位"——模型权重在显存里的布局、KV Cache的占用趋势、算子的计算模式,这些是传统监控里根本不会出现的指标。1.2 这个系列会覆盖什么既然叫第一章,我先交代一下整个系列的路线图,方便你判断哪些章节对你自己更有用。大的方向上,我会围绕五个块面展开:第一是大模型推理服务的稳定性和性能调优,这可能是目前一线团队最头疼的部分;第二是训练任务的调度与容错,重点是长稳训练中的断点处理、显存抖动、通信瓶颈这一类问题;第三是异构资源池的管理,包括多型号GPU混部、调度策略、隔离策略;第四是面向LLM的数据与评测管道,这里面的工程强度被严重低估;第五是成本与容量治理,也就是常说的"怎么把单位Token的成本打下来"。这一章先不铺太宽,我会先把推理服务这条线上最经典的问题——延迟毛刺——完整复盘一遍。因为它麻雀虽小五脏俱全,牵涉到的知识点几乎覆盖了AI推理基础设施的整个核心。2. 推理服务延迟毛刺:根因排查的完整链路回到开头说的那一次事故。当时我们的架构并不复杂:一个标准的在线推理服务,使用了当时主流的推理框架,模型是7B量级的,服务部署在8卡A100的节点上。模型本身是热的,流量模型也相对平稳,属于一个典型的"跑了一阵子、看起来挺稳"的状态。延迟毛刺出现在凌晨两点左右,持续了大约半个小时,然后自动恢复了。网管平台上能看到P99还好,P999直接拉出长尾。从监控看:指标毛刺前毛刺期间QPS稳定在80左右稳定在80左右GPU利用率均值60%~70%25%~75%周期波动P99延迟80ms300~2000ms显存占用稳定出现缓慢上升后回落节点CPU30%出现周期性飙升第一眼看过去,CPU周期性飙升挺像"有定时任务在抢CPU",但查了节点的Cron和周期脚本,什么都没有。显存缓慢上升又回落,倒是有点像缓存被周期性清理。这两条线索放在一起,其实已经暗示了一个方向——显存状态和算子执行之间可能存在耦合关系,但当时的我还没想到那么深。2.1 从"看起来正常"的日志里找突破口真正让我往前走一步的,其实是NVIDIA的系统日志和推理框架的算子Trace。传统后端排障,先看应用日志,再看中间件状态,最后看系统资源。但推理服务的应用日志里只有请求进出和耗时统计,没有太多信息。真正有价值的是推理框架自带的profiling数据,能看出来每次请求在Pre填充阶段和解码阶段分别耗时多少。我拉了一组毛刺期间的请求明细,发现一个有意思的现象:出问题的请求几乎都是长序列请求,且都是解码阶段的单Token耗时异常。Pre填充阶段的耗时相对正常,而解码阶段每个Token的平均生成时间从正常的50毫秒涨到了400毫秒以上。这就把范围收窄到了解码路径。解码阶段的核心操作是:把当前已有的KV Cache拼接上新的Key/Value,做注意力计算,然后采样出新Token。这个阶段对显存的带宽和KV Cache的连续性非常敏感。2.2 根因确认:动态显存碎片化与缓存驱逐的连锁反应在推理服务里,我们给KV Cache分配的空间是按请求动态申请的,不同请求的序列长度不一样,申请大小也不同。当时我们用的是"按需增长"的策略,即每个请求的KV Cache空间随生成进度逐步扩大,内存不够时就触发一次重新分配和拷贝。问题就出在这里。当多个请求以不同节奏增长时,显存里会形成大量的碎片。碎片化本身不一定致命,但一旦新的空间需要连续分配而失败,框架会触发缓存驱逐——把一些低优先级请求的KV Cache先腾出去,腾出去之后又把它们的上下文重新计算一遍,这在解码阶段等于把已经算过的Prefix编码重新算一次,Cost极高。于是整个链路就变成了:显存碎片增多 → 新请求分配连续KV Cache失败 → 触发驱逐和重算 → GPU利用率先跌后猛涨 → 解码延迟剧烈抖动的恶性循环。为什么半小时后自动恢复?因为那段时间请求序列普遍变短,碎片被回收合并,KV Cache需求量下降,系统自己缓过来了。3. 问题背后的推理引擎机制:为什么Torch显存模型不能直接照搬很多做后端出身的人第一次看到这类问题的第一反应是:"显存碎片化我懂,重启一下进程不就完了吗?"或者"把最大显存预留出来,预先分配好不就行了?"这些思路放在传统服务上没错,但在推理引擎里有几层机制需要重新理解。3.1 显存分配器的工作方式决定了碎片问题的形态在显存分配器的层面,碎片化分两种:显存物理碎片和分配器内部碎片。GPU显存物理上是一整块连续空间,所谓碎片更多是指分配器无法找到足够大的连续块来满足大块请求。CUDA自身的分配器在底层做了不少优化,但推理框架用到的显存大头是模型权重、激活值、KV Cache,其中KV Cache的增长模式跟模型权重完全不同——模型权重是一次性分配的,激活值和KV Cache是随请求动态变化的,这就给分配器造成了混合压力。当时我们看到的碎片,主要就是KV Cache动态分配造成的。一个请求开始时分配比如256MB的KV块,生成过程中扩展成512MB、1GB,每个请求的扩展节奏不同,自然带来了参差不齐的"内存空洞"。这里可以做一个粗略的量化:假设显存总容量60GB(A100的80GB版本减掉其他开销),模型权重占约15GB,剩下45GB供KV Cache和激活使用。如果每请求平均KV Cache是1GB,但分配粒度是256MB的块,那么最坏情况下,四分之一左右的块可能处于"无法合并"的零散状态。在这样的状态下,一个需要连续1.5GB空间的请求,分配器找了半天找不到合适的块,触发了释放和重排,连锁反应就开始。3.2 KV Cache的长生命周期和缓存驱逐策略设计传统后端里的缓存驱逐,比如Redis的LRU,驱逐一个Key的代价很低,最多就是下一次访问时去后端再查一次。但推理服务里KV Cache驱逐的代价不只是"重新算一遍"那么简单:如果被驱逐的是某个还在活跃生成中的请求,恢复时除了重算前缀,还可能造成本次对话上下文的截断和状态丢失。这是线上事故级别的影响。有经验的团队会设计"先守底再优化"的策略:比如预先按最坏情况给每个请求预留KV Cache空间,虽然显存利用率低了,但不会触发驱逐;更精细的团队会引入分页式KV Cache管理,把长序列的KV按固定大小的块分散存储,显著降低连续空间要求。我们当时走的弯路,正是没有在业务快速增长之前主动介入这一层的设计。现在回过头看,一个好推理引擎最重要的不是单次算子快多少,而是在动态请求模式下,显存和计算这两个资源能不能平滑协同。Torch本身是一个通用计算框架,它对长期运行、高并发、复杂序列的推理场景支持并不自主——这也是为什么社区里不断有新的推理框架冒出来,它们本质上都是在回答同一个问题:如何在动态负载下把硬件资源用到极致。4. 修复方案设计与落地:从规避到重构的三步走问题定位清楚之后,接下来就是怎么改。我没有一上来就推到重构,而是分了三步,每一步都是当时团队可以快速落地的方案。4.1 第一步:把KV Cache分配策略从"动态增长"改为"分块预留"这是见效最快的一步。我们把每个请求的KV Cache分配策略改成按固定块分配,块大小设为256MB,请求首次分配时先拿一块,后续增长时只追加新块,不再要求物理连续。这个改动其实就是在框架层面把KV Cache的逻辑地址空间和物理地址空间解耦。效果很明显:显存碎片化的概率大幅下降,连续分配失败的告警基本消失。改动之后的压力测试里,同样的流量模型下,P999从毛刺期的接近2秒回落到120毫秒以内。这一步让我确认了之前的根因判断是对的。那一周我们的操作顺序是:先在灰度环境压测,确认没有正确性影响之后,再逐步推全。要注意的是,分块预留不等于无脑预留,块的大小和数量上限要根据模型的上下文窗口和显存总量做估算。块设得太大,浪费显存;设得太小,分配次数太多,反而增加管理开销。我们的经验是,按线上请求的P50序列长度预留出基础块,再允许按P95序列长度追加额外块,既稳又省。4.2 第二步:引入缓存驱逐的优先级分层虽然分块预留解决了大部分碎片问题,但长尾场景下缓存驱逐还是会发生。我们做了一个比较克制的设计:把请求分成几个优先级等级——VIP业务流和正在付费生成的请求最高优先级;内部测试流量和可重放的任务次之;离线批处理请求最低。驱逐时先动低优先级、已完成的KV Cache,严格避免驱逐还在生成中的请求的KV Cache,宁可牺牲低优先级请求的生成连续性,也不让核心链路的请求承担重算代价。这一步提升的是系统的"行为可预期性",也就是说:即使资源吃紧到必须牺牲一部分流量,牺牲的也是我们可以接受的部分。传统稳定性工程里这叫"优雅降级",放到推理服务里其实也一样,只是降级的颗粒度和代价模型不同了。4.3 第三步:把动态批处理策略改成"显存感知"模式推理服务为了提升吞吐,通常会把多个请求拼成一个batch在GPU上跑。批处理策略对显存的亲和性影响很大。一个常见问题:某个batch里的请求如果序列特别长,KV Cache占用高,就会把其它请求的显存空间挤掉,导致连锁驱逐。我们的优化是让batch组装时不仅看请求的到达时间,还参考每个请求的预估KV Cache占用。长短序列混批容易造成"木桶效应"——最长的请求拖慢整个batch。改成"显存感知调度"之后,把长序列请求单独调度或者分到不同的批次,可以明显减少驱逐和重算的发生率。这三步做完,整体耗时大约三周,其中第一周就拿到了主要收益。复盘下来,核心的变化其实就一句话:从"让请求去适应分配器"改成"让分配器去适应请求"。听起来不复杂,但需要深入框架代码去确认它的显存管理路径,这一步绕不开。5. 一线AI-Infra工程师的日常工具箱:我常用的诊断路径排障之外,我平时也花了不少时间在诊断工具链的建设上。AI推理服务的观测体系跟传统后端有显著区别,很多问题用传统监控根本"看不见"。这里分享几个我实际用得最频繁的诊断路径。5.1 GPU墙上的事情,看什么指标第一层是资源层指标:利用率、显存占用、显存带宽、SM活跃度、PCIe传输量。很多人只看利用率和显存,但其实利用率和延迟之间的关系并不线性——利用率高了可能是算子排队,也可能是显存带宽打满了,两种情况的解法不一样。显存带宽的使用情况需要靠硬件计数器去读,光看利用率曲线是不够的。第二层是推理框架的Metrics:排队长度、批次大小分布、解码速度、KV Cache占用与命中率。这些是业务层和资源层之间的桥梁,能帮助我们判断是"计算不够"还是"存储不够快",还是"调度策略有问题"。第三层才是业务指标:首Token延迟、Token间延迟、端到端延迟、不同窗口长度的延迟分布。业务指标变化通常是结果,前两层的指标才是原因。排障的时候我从下往上查,定位性能瓶颈时则从上往下压测。5.2 五条"一线经验"备忘有几个细节是文档很少写的。第一,NVIDIA的指标里有相当一部分在虚拟化或共享场景下是不可靠的,必须落到物理机上验证;第二,推理框架的profiling工具对请求量大的场景会显著增加开销,长稳压测时不要全程开着profiling,只看聚合统计;第三,稍微大一点的模型,权重加载阶段很吃内存带宽,尽量不要让它和在线推理流量抢占同一台机器的带宽;第四,多实例部署时,不同实例的显存分配器状态各不相同,重启单个实例要评估"会不会引发其它实例的缓存驱逐";第五,很多看起来像"推理框架bug"的问题,最终都定位到驱动或固件版本和框架版本之间的兼容性上,升级驱动前先在灰度环境跑一遍压测。这些经验原本散落在我们团队的聊天记录和事故复盘里,我一直想把它沉淀成一套更成体系的参考手册。这个系列也算是我自己的一次整理。6. 关于AI-Infra工程师的成长路径:什么能力真正值钱最后聊一点职业层面的体会。很多朋友问我,想转AI-Infra,是不是先去学某种深度学习框架、某个调度引擎就够了。我的看法是:框架和算法只是入口,真正拉开差距的,是系统性地理解和诊断"计算-显存-通信"三者的协同关系。AI负载不同于传统后端,它的核心矛盾几乎都集中在资源调度和状态管理上。一张GPU卡既是计算单元又是存储单元,通信带宽决定了多卡协同的上限,显存的分配和释放决定了服务生命周期的稳定性。这三者的协同能力,没法通过读几篇文档获得,只能在一次次真实故障中建立体感。如果你正要入门,我建议从自己手头能接触到的最小的推理服务开始,刻意制造故障:把batch size调大,显存别预留,请求并发拉高,然后仔细看延迟曲线的形态变化。用最小代价建立对异常现象的敏感度,比背一百个框架的API都有用。这一章到这里刚好是一个完整的回路:从一个凌晨的告警,到一个动态碎片化的根因定位,再到三步修复方案,最后回到诊断工具和成长路径。下一章我打算拆解训练场景下的任务调度和长稳容错,那里面又有另一套完全不同的逻辑,但很多思维方式和这一章是贯通的。