算法和infra协同,这个组合词最近两年在技术社区里出现的频率越来越高。算法模型训练出来只是个开始,真正让它稳定跑在生产环境、承受住真实流量,靠的是基础设施团队和算法团队之间那条既看不见又极其关键的协作链条。我同时蹲过算法组和平台组,两边都待过,深知这套协同做不好,再漂亮的模型指标都是空中楼阁,线上出问题时两边还容易互相甩锅。这篇文章我想把真实项目里遇到的协作痛点、职责边界、流水线设计、性能博弈和监控拉通这些事,掰开揉碎讲清楚,希望能帮正在被这类问题困扰的团队少走几条弯路。
1. 本地一切正常,一上生产就翻车:差异到底藏在哪里
这个问题几乎每个月都会发生一次。算法同学在本地或离线训练环境里把模型调得漂漂亮亮,评估指标全线达标,结果一部署到线上推理服务,延迟飙高、显存溢出、甚至直接OOM崩溃,第一反应就是“infra那边有问题”。但多数时候问题根源不在基础设施本身,而在训练环境和推理环境之间那些不起眼的差异。
1.1 训练环境与推理环境的“微小”偏差
先说CUDA和驱动。训练集群的驱动版本通常是新部署的,GPU型号也相对统一,比如V100、A100。但线上推理集群可能是不同批次采购的,混着T4、P40这种老卡。同一个PyTorch模型,在不同算力、不同驱动版本下,算子的kernel选择逻辑会发生变化,结果就是本地benchmark延迟10毫秒,线上却跑出40毫秒,而且没人说得清这30毫秒差在哪。
再说Python依赖。训练代码里的numpy、pandas版本和线上推理镜像里的版本很难保证完全一致,尤其是那些用Cython写的库,版本升级后同一段代码执行路径可能完全不同。还有CPU指令集,训练时多数运算在GPU上,但数据预处理、padding、tokenizer这些逻辑跑在CPU上,线上容器的CPU型号如果少了某个AVX指令集,性能会肉眼可见地下降。
最隐蔽的是数据形状变动。训练时你习惯用固定batch size,比如128,但线上服务收到的请求数量是实时变化的,推理框架会动态拼batch。动态shape会让GPU算子频繁做kernel重编译,这一下就把延迟从“平稳”变成“过山车”。我记得有次把padding长度从固定64改成动态截断,本意是省算力,结果线上p99延迟反而涨了3倍,就是因为kernel重编译和显存碎片化。
1.2 一次延迟突刺的完整复盘
那次事故我印象很深。某个推荐模型的p99延迟从日常20毫秒突然跳到120毫秒,持续了将近半小时。infra同事第一时间看网络、看容器CPU、看磁盘IO,全都没异常;算法同学看GPU利用率,发现只有30%,就认定“资源肯定够,是infra调度的问题”。
两边僵了一个小时,最后我把模型推理日志和infra监控拉到同一张时间轴上,才发现那半小时里请求文本长度分布发生了明显右移,长文本比例从5%涨到35%。模型对长文本的注意力计算复杂度是平方级上升的,同时动态padding又导致GPU算子频繁重建,两件事叠加,延迟自然爆炸。根子在于算法团队做离线评估时用的数据集是“平均长度”,从没考虑到线上长度长尾分布。
那次之后我们定了一个规矩:任何模型上线前,算法必须提供“线上输入分布分析”,infra根据这个分布决定batch策略、显存预留和弹性伸缩阈值。这比出了事再排查高效得多。
2. 算法和infra的职责边界:谁的模型谁负责,但中间地带必须有人管
很多团队一开始会把算法和infra当成纯粹的上下游:算法把模型文件一交,剩下的全是infra的事。这种边界意识看似清晰,实则埋雷。
2.1 常见的两类错误认知
第一类,算法觉得“我只出模型,部署是infra的事”。于是模型文件丢过来,没有文档、没有版本说明、没有输入输出约束,infra同学只能靠猜。第二类,infra觉得“我只管服务稳定,模型效果和我无关”。结果线上A/B实验效果负向,两边互相推诿,算法说“是infra容量不够导致超时”,infra说“模型自己不行”。
真实情况是,模型的服务化效果是两边的共同产物。算法决定“做什么”和“做多好”,infra决定“跑多快”和“花多少钱”,中间重叠地带恰恰是决定线上成败的关键。
2.2 正确的边界地图
我总结了一张职责划分表格,每次新项目启动会都拿出来对齐,效果比嘴上说一百遍都强:
| 职责域 | 算法团队 | infra团队 | 中间地带(必须协同) |
|---|---|---|---|
| 模型效果指标(AUC、召回率、线上业务指标) | 负责 | 不负责,但需要知情 | 效果波动时的归因分析 |
| 模型正确性(输入输出逻辑、特征处理一致性) | 负责 | 不负责 | 特征上线前的字段评审 |
| 推理延迟性能 | 负责提供模型性能基线 | 负责部署调优 | 性能预算的制定与遵守 |
| 服务可用性(SLA、容灾、扩容) | 不负责 | 负责 | 容量规划时算法需提供QPS预估 |
| 资源成本 | 不负责 | 负责 | 成本与收益的权衡评估 |
这张表的核心是:任何一项职责都不是单方面的,中间地带要有一个明确的接口人。我们在每个项目里会指定“算法对接人”和“infra对接人”,凡是跨域问题,只需要找对接人,不需要各自拉群扯皮。
2.3 中间地带的协调机制
中间地带最常见的问题是“性能预算”缺失。所谓性能预算,就是线上环境下单个推理请求允许消耗的最大资源,比如“单次推理不超过50毫秒、显存不超过2GB”。算法在模型选型时就要背着这个预算去做,而不是做一个精度很高但重到跑不动的模型,然后指望infra硬扛。
我见过一个反例,算法同学选了一个700M参数的Transformer模型,离线精度确实比小模型高1.5个点,但线上单卡只能扛5 QPS,而业务要求是200 QPS,最后只能拆成多机分布式推理,成本翻了8倍。如果一开始就定好性能预算,算法大概会换一个更轻量的模型加上蒸馏方案。
3. 从训练产物到线上服务:一条能对齐双方的流水线长什么样
如果每次上线都依赖“算法口头说明 + infra临场猜测”,协同成本高到没法看。我比较认同的做法是把交付过程标准化,让模型上线像软件发版一样有章可循。
3.1 交付物规范化:模型卡与依赖固定
所谓模型卡,就是一份模板化的说明文档,里面必须写清楚模型用途、输入输出定义、特征依赖、离线评估结果、资源需求、已知风险这六项。它不是什么科研产出,而是给infra同学看的“安装说明书”。
依赖固定更关键。训练一份模型时,必须把Python包版本、CUDA版本、模型框架、推理引擎都锁定下来。一个很实用的做法是训练环境生成一份requirements-lock.txt,同时把推理镜像构建放在CI流程里,任何依赖变更都自动触发重新构建,避免“本地能跑,镜像不能跑”的尴尬。
3.2 推理接口协议:输入输出schema、错误码、健康检查
算法和infra之间最容易出问题的就是接口字段的隐式约定。算法觉得“这个字段命名很自然”,infra却不知道它的取值范围和边界。我们后来约定所有模型推理接口必须显式定义JSON Schema,字段含义、类型、是否可空、取值范围、默认值全部写清楚。类似这样:
{ "model_name": "click_rank_v3", "model_version": "3.2.1", "inputs": [ { "name": "user_features", "type": "array", "items": {"type": "number"}, "description": "用户侧特征向量,长度固定为128" }, { "name": "item_list", "type": "array", "items": { "type": "object", "properties": { "item_id": {"type": "string"}, "item_features": {"type": "array", "items": {"type": "number"}} } } } ], "outputs": { "scores": {"type": "array", "items": {"type": "number"}} } }健康检查的接口也要统一约定。infra的负载均衡器需要知道服务是否真正可用,不能只返回“进程还在”就万事大吉。健康检查接口应该做一次最小化推理,或者至少校验模型文件是否加载完整、依赖的特征服务是否连通,避免流量打到一台“半死不活”的节点上。
3.3 发布流程:离线评估和在线验证怎么衔接
模型发布不能是“一把梭”,我比较推荐的流程是“离线评估 → 影子流量 → 金丝雀发布 → 全量上线”四步走。
影子流量阶段,把线上真实请求复制一份打到新模型服务上,但不影响真实返回结果,用来观察新模型在真实负载下的推理耗时和显存占用,看看有没有性能问题。金丝雀发布阶段,把1%-5%的真实流量切到新模型,同时盯着业务指标和系统指标,确认没问题再逐步放量。如果效果指标异常,要能一键回滚到旧版本,这个回滚能力必须在第一次发布前就验证过,别等出了事再研究怎么回滚。
4. 性能与成本博弈:算法同学和infra同学如何用数据对话
算力资源永远不够,预算永远紧张。算法想用更大的模型、更宽的特征;infra想控制成本、提高资源利用率。两者碰撞是常态,但有一点可以确定:靠感觉争论没有意义,只有用数据对话才解决得了问题。
4.1 谁来决定GPU卡型和副本数
这是两个团队之间最常见的分歧。算法希望分到一张大显存卡,infra希望能用多张小卡跑更多服务。合理做法是算一笔账:先确定单请求的推理延迟SLA,再看单卡能支撑的最大QPS,反推出需要的副本数。
比如业务要求单请求算法部分不超过30毫秒,模型在T4上单卡单batch是12毫秒,支持动态batch后单卡可以扛40 QPS;业务高峰期需要400 QPS,那就至少需要10个副本,加上冗余至少12个。这些数字都算清楚,申请资源时就不用“我觉得”了。
4.2 加速手段的收益和代价
infra团队经常喜欢用量化、算子融合、蒸馏这些手段来加速推理,但算法团队天然对精度损失敏感。这里需要一个评估机制:加速手段不能单方面决定,要跑一遍离线评估,把精度变化报告拿出来一起决策。
| 加速手段 | 收益 | 风险/代价 | 谁来评估 |
|---|---|---|---|
| FP16半精度推理 | 显存减半,吞吐提升明显 | 可能出现精度损失、数值溢出 | 算法评估精度,infra评估吞吐 |
| INT8量化 | 吞吐提升最大 | 精度损失更明显,需校准数据集 | 算法主导,infra配合 |
| 算子融合 | 减少kernel启动开销,延迟降低 | 工程复杂度高,调试难 | infra主导 |
| 模型蒸馏 | 延迟和显存双降 | 需要重新训练,周期长 | 算法主导 |
我见过最顺滑的协同模式是:infra把加速方案的预期收益和风险写清楚,算法负责评估精度影响,双方共同决定是否上线。从一开始就保持透明,就不会出现“你量化完了模型烂了”这种秋后算账的事。
4.3 自动扩缩容的坑
很多团队一步到位上Kubernetes的HPA(Horizontal Pod Autoscaler),以为设好CPU阈值就万事大吉。但模型推理服务有个特点:请求量上来时,GPU利用率不一定马上升高,因为瓶颈可能在CPU预处理或内存拷贝上。等CPU指标反映出来,流量已经洪峰过境了,扩容完全来不及。
更实际的方案是用自定义指标做扩缩容,比如“排队请求数”或“推理线程池积压长度”。积压超过阈值,立即扩容;积压清空,再逐步缩容。配合最小副本数,避免冷启动延迟和资源抖动。这个方案需要算法和infra共同设计,因为“排队长度”这个指标和模型推理时延强相关。
5. 线上监控和根因定位:算法指标与系统指标必须放在一张图上看
每次线上出问题,算法和infra各看各的监控看板,谁也说服不了谁。我后来学到一个教训:监控必须拉通,两个团队必须习惯在同一个时间轴上排查。
5.1 割裂的监控是问题查不清的根源
有次线上推荐业务点击率掉了10%,算法团队怀疑是特征数据延迟,infra团队怀疑是网络抖动,两边各自查了两小时也没结果。最后把会话日志和系统指标对齐,发现那个时段里特征服务的响应时间从5毫秒涨到了800毫秒,根因是特征服务所在机器磁盘IO被打满,而磁盘IO的波动在infra看板上有明显峰值,只是算法团队从来没看过那块面板。
如果两边从一开始就在同一个看板里看问题,这次故障定位大概只需要十分钟。
5.2 两张必须拉通的看板
我现在做项目都会要求团队搭两张看板,并且放在同一个监控系统里,方便随时联动。
模型指标看板:QPS、推理延迟(p50/p95/p99)、batch大小均值、排队长度、错误率、超时率、输入特征分布漂移、模型预测分数分布漂移。
系统资源看板:CPU使用率、内存使用率、GPU利用率、GPU显存占用、GPU温度、磁盘IO、网络带宽、容器重启次数。
关键不是静态展示,而是要有“同一个时间轴上的事件联动”。比如模型p99延迟上涨时,旁边就能看到GPU利用率是否饱和、batch大小是否突然变化、输入分布是否偏移。这样根因定位从“猜谜”变成了“看图说话”。
5.3 联合排障的SOP
有了一套协同看板之后,还得有一套联合排障的标准动作。我们内部跑过一次演练后总结出来的流程大概是:确认最近变更清单,包括模型版本、代码、配置、特征数据、资源容量,任何一项有变动都要列入嫌疑列表;看整体服务状态,确认是局部实例问题还是全局限流;按“输入 → 模型计算 → 输出 → 下游调用”这条链路逐段检查,每段都有明确负责人;每15分钟同步一次进展,避免“查着查着就跑偏”。
这套SOP看起来很简单,但有和没有完全不一样。
6. 长期协作沉淀:把一次次的“吵架”变成流程和文档
最后聊聊团队文化和长期机制。技术问题盘根错节,但绝大多数摩擦的根源是信息不对称。把踩过的坑变成制度和文档,比在会上喊“大家要多沟通”有用一百倍。
6.1 我踩过的三个典型坑
第一个坑是随机种子和固定数据顺序。算法训练时没固定随机种子,导致每次训练出来的模型虽然指标差不多,但对特定样本的预测行为差异很大。上线后业务方反馈“模型行为变了”,两边查了很久,最后发现只是随机种子变动导致的。这种问题必须在训练脚本里把seed固定下来,并且写进模型卡里。
第二个坑是共享特征服务升级协议不兼容。infra团队升级了一个特征服务的RPC协议,以为“向下兼容没问题”,结果新模型用了新字段,旧模型还在用旧字段,测试时没覆盖到全部模型版本,线上部分请求拿不到特征直接走了兜底分支,模型效果明显下降。从那以后,凡是涉及共享服务的变更,必须有一个“影响面评审”,把所有模型版本和调用方列清楚再动手。
第三个坑是SLA没人定。团队快速扩张期,没人明确说这个推荐服务该保证几个9的可用性、允许的延迟是多少、QPS峰值预估是多少。结果流量翻倍时服务直接雪崩,谁都没提前做过容量规划。现在所有模型服务上线前必须写一个SLA文档,哪怕只是“尽力而为”,白纸黑字写下来,后续责任自然清晰。
6.2 对协作有效的几个小习惯
我现在倾向于在团队里推行几个轻量级习惯,不搞复杂流程和重型平台,先让协作跑起来。
变更日历:所有模型版本、配置改动、推理镜像更新、特征口径调整,提前一天登记到一个共享日历里,关联负责人和影响范围。好处是出问题时能快速排除“最近谁动过东西”这个嫌疑。
故障复盘机制:每次故障不是追责,而是画时间线、列触发点、写改进清单。只有真正把“事故”变成“案例”,协作才可能一次比一次顺滑。
轮值接口人:算法和infra团队各指定一个轮值接口人,负责当周的跨团队需求流转和紧急问题响应。避免每次都是“临时的沟通靠人脉、长期的问题靠运气”。
我现在越来越觉得,算法和infra之间的协同不是简单的上下游交接,而是一套共享目标、共享边界、共享责任的工作方式。模型是算法团队的心血,服务是infra团队的地基,两者拼在一起才是真正能在线上创造价值的产品。每个团队的情况不一样,但把流程标准化、数据透明化、责任明确化这几点做到位,大概率不会跑偏。