跳到主要内容

80 篇博文 含有标签「infrastructure」

查看所有标签

你的编程代理基于落后 Main 分支三周的代码版本重建的代码库索引

· 阅读需 11 分钟
Tian Pan
Software Engineer

你团队中的一个 AI 编程 Agent 提交了一个 PR,在两个文件中调用了四次 parseUserToken()。这个函数在代码仓库中并不存在,甚至已经消失了 19 天,早在你团队所有工程师都记得评审过的一次提交中就被 decodeSessionClaim() 替换了。Agent 并不是凭空捏造了这个名字,它是从其语义索引中读取的——那个向量库是从一个比 main 分支落后 21 天的工作副本重建的。相比之下,Agent 的编辑步骤在会话开始时运行了 git pull,操作的是最新的代码。对同一个代码库的两个视角,相隔三周,而 Agent 却自信地用一段无法针对任何真实环境编译的代码桥接了它们。

这是一种不会自我宣告的失败模式。Agent 运行了。测试看起来通过了。PR 合并了。第一位评审者之所以注意到,仅仅是因为一个被删减的函数与一个无关的辅助函数重名,触发了 linter 报错。到那时,Agent 已经花了一个完整的冲刺(sprint)针对一个“幻影版本”的代码库进行编写,而团队中没有一个人——包括 Agent 自己——收到任何异常信号。

KV Cache 驱逐:供应商称其为“缓存压力”,而你的账单则称其为“双倍前缀费用”

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的应用程序开启了一个包含 4 万 token 系统提示词和完整工具库的长对话。第一轮对话按写入费率为前缀付费,且提供商的 KV 缓存(KV cache)开始预热。第二轮对话在 90 秒后到来。你假设这会命中缓存。有时确实如此。但有时,同样的 4 万 token 会再次以未缓存的价格出现在你的账单上,而你的代码在第一轮和第二轮之间没有任何改动。

改变的是别人的流量。KV 缓存是共享基础设施。你的租户被分配到一个推理节点上,而在你的两轮对话之间的 90 秒内,该节点接纳了足够多的其他租户,从而将你的前缀从内存中驱逐。提供商的控制台会将其描述为“缓存压力(cache pressure)”。你的财务团队会将其描述为一项翻倍的支出项。这两种描述都是准确的,但原因都不在你的代码里。

供应商将你的模型标识符重定向到特定租户的微调模型,而其他人使用的却是基础模型

· 阅读需 12 分钟
Tian Pan
Software Engineer

客户支持团队升级了一个问题:“你的助手以前能正确处理退款资格问题。但上周开始出错了。”值班工程师调取了对话记录,在开发账号中使用相同的模型标识符回放了完全相同的提示词(prompt),得到了正确的回答,于是以“无法复现”为由关闭了工单。两周后,另一名客户提出了同样的投诉。工程师再次在同一个开发账号中进行回放,结果依然正确。团队开始归咎于没人做过的提示词更改。

请求中的模型标识符从未改变。响应字段中的字符串与请求字段中的字符串匹配。评估套件在六周内一直保持绿色。生产流量使用的模型权重与评估套件使用的模型权重是两套不同的集合,而且在该账号的整个生命周期中一直如此——直到过去这六周,它们变成了同一套权重,而团队注意到这一点仅仅是因为客户先发现了。

被反向代理剥离的 SSE Keep-Alive,以及你支付了两次费用的 Prompt

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 Agent 调用了一个耗时 35 秒的工具。在这 35 秒内,没有任何 Token 从模型流回浏览器。Provider 的 SSE 流仍然开启。你的工具仍在运行。用户的加载动画也在旋转。而在路径中间的某个你无法控制的反向代理认为连接静默时间过长,关闭了它,随后你的客户端重连逻辑尽职地从头重新启动了整个请求。

第一次响应产生了 4,200 个 Prompt Token 和 600 个 Completion Token。第二次响应也是 4,200 个 Prompt Token 和 600 个 Completion Token。用户得到了一个答案。而你的账单却收到了两份。

批处理负载挤占了你的实时路径:GPU 预留的惨痛教训

· 阅读需 10 分钟
Tian Pan
Software Engineer

每晚的微调任务在 UTC 时间 02:00 开始。它进入共享 GPU 池,占用它能找到的每一个槽位并持续持有。到 09:30,当工作日的首波推理流量到达时,自动扩缩器(autoscaler)试图声明已被连续占用七个半小时的容量。早晨的前 90 分钟,系统运行在约为基准 p99 延迟四倍的水平上。仪表盘报告了一个“喧闹的早晨尾部(noisy morning tail)”,推理团队将其归因于用户行为,因为实际的资源争用发生在一个推理团队并不拥有的任务队列中。

这是你在容量评审的成本归因幻灯片中无法捕捉到的 GPU 共享失败模式。共享被宣传为利用率的胜利——晚上训练,白天服务,填补低谷。实际交付的却是直到池按延迟类别(而非按团队或按时间)进行分区之前,你都无法摆脱的延迟长尾。

供应商配额在你的全球流量从未选中的时区重置

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的每月 Token 配额在 00:00 UTC 重置。你最大的客户在东京,他们在 21:00 UTC(即当地时间第二天早上 6:00)达到峰值负载。当重置时刻到来时,东京的工作日已经在配额耗尽的降级方案中消耗了该周期的最后六个小时。429 错误看起来只是“偶发”,因为你仪表板上的 UTC 日历轴将每日重置边界隐藏在了普通的时间戳之中。

这不是速率限制(rate limit)的 Bug。这是一个日历 Bug。供应商为了结算方便选择了一个重置时钟,而你流量的地理分布决定了哪些客户会分配到周期末尾的空窗期。那些将配额定价为统一资源的团队,正基于一个用户从未见过的日历来进行配额分配。

你增加的 Reranker:对召回率的拖累超过了对精准度的提升

· 阅读需 12 分钟
Tian Pan
Software Engineer

离线评估的结果非常明确。在向量搜索的前 50 个结果之上叠加一个交叉编码器(cross-encoder)后,nDCG@5 提升了 4 个点。团队在周二上线了该功能。到了周四,p99 检索延迟已超过 SLO(服务水平目标)700 毫秒,客户成功团队也开始转发空结果页面的截图,而这些页面在旧的流水线下本应是有内容的。真正关键的指标——用户感知的回答质量——下降了。重排序器(reranker)实际上是一个被团队冠以“改进”之名的性能退化,而评估标准则是将这种退化隐藏在众目睽睽之下的幕后黑手。

这是生产环境检索中最常见的失效模式之一,且很少被准确描述为:一个评估缺陷(evaluation bug)。重排序器完成了它的宣传任务:以更细的粒度对前 50 个结果进行了重新排序。问题在于,用于证明其合理性的指标——在无限预算下针对完整重排序列表计算的离线 nDCG——描述的是一个生产系统并不存在的理想世界。在生产环境中,最终输出的答案并非评分最高的重排序列表,而是系统在请求截止时间前所能返回的任何内容。一旦你以此方式重新定义指标,重排序器的贡献就不再是 4 个点的提升,而是一条曲线。

你的后端基础设施并非为流式响应而设计

· 阅读需 13 分钟
Tian Pan
Software Engineer

流式传输(Streaming)是一项产品决策。设计团队的某个人看到竞争对手的聊天 UI 像打字机一样逐个吐出 Token,看到用户在第 200 毫秒看到第一个字符出现时肩膀放松了下来,而不是盯着 4 秒钟的空白屏幕发呆,于是决策就此达成:我们要做流式传输。这个拉取请求(PR)修改了 API 网关中的三个文件。现在,模型输出通过服务器发送事件(Server-Sent Events,SSE)增量刷新。功能在周二上线,周三的满意度评分就有了明显的提升。没人向基础设施团队提工单。

一个月后,值班工程师盯着三个互不一致的仪表盘发愁。自动扩缩容(Autoscaler)配置的 Pod 数量是 CPU 图表显示需求量的两倍。P99 延迟仪表盘坏了——不是出了故障,而是变得无法解读,因为直方图分桶(Histogram Buckets)止步于 5 秒,而现在大多数 Span 都落在溢出区间。上一季度定价时的容量模型显示,该服务每节点每秒可处理 1200 个请求。而值班人员面前的图表显示,它在处理 400 个请求时就已经难以为继。

按摄入日期分片的向量索引

· 阅读需 11 分钟
Tian Pan
Software Engineer

在按时间分区的向量索引中,隐藏着一种特定类型的召回率谎言,而构建离线评估的人通常是最后才发现它的人。仪表盘显示 recall@10 为 0.94。检索器在 94% 的情况下都能提供正确的片段。产品团队正基于这个数字发布更多以检索为基础的功能。接着,客服工单接踵而至:“助手引用的指南与答案不符”、“助手链接到了上周版本的政策”、“助手找不到我两个月前上传的文档”。这些工单都不与 0.94 这个数字冲突。它们证明了 0.94 衡量的是错误的东西。

这种机制很简单,也很容易被忽视。向量索引按摄入日期进行分片,因为这是保持高写入吞吐量、停用旧数据以及将热工作集保留在快速内存中的最简单方法。离线测试集每晚从生产日志中生成,这意味着查询是从最新分片恰好持有的同一个近期窗口中提取的。召回率是根据存在于一两个分片深处的基准真相(ground truth)来衡量的。检索器在这些查询上表现出色,因为在生产环境中,路由层会将这些查询保留在同一个分片内。

那个直到触发时你才察觉的 Token 预算

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的团队与推理提供商协商了月度 Token 配额。合同规定了上限。提供商门户的仪表板显示昨天的使用情况,存在一天的延迟。API 本身返回每分钟速率限制头——anthropic-ratelimit-tokens-remainingx-ratelimit-remaining-requests——而对于你实际需要规划的月度配额桶却只字未提。你的智能体集群没有机制在预算耗尽时减速,因为实时到达的唯一信号是 429 错误——而这个信号在预算已经用完后才出现,且伪装成重试逻辑通常会忽略的瞬时错误。

这是一个与速率限制(rate limiting)性质不同的问题。速率限制是一个快速波动的节流阀,消费者必须在几秒钟内做出反应;响应头告诉你桶里还剩一千个 Token,并在 40 秒内补满,一个编写良好的客户端会退避并重试。月度配额则是一个缓慢变化的预算,消费者必须以周为单位进行规划。这两者之所以容易混淆,是因为它们共享错误代码,有时甚至共享同一个仪表板,但它们需要不同的控制手段——而提供商公开的信息与消费者需求之间的差距,正是本月最严重事故的导火索。

长出胳膊和腿的缓存提示词前缀

· 阅读需 11 分钟
Tian Pan
Software Engineer

六个月前,你的提示词前缀是 4,000 tokens。它稳定、缓存预热,几乎可以摊销到不计成本——系统指令的每次调用附加费,相比每次响应的成本,只是一个舍入误差。今天那个前缀变成了 11,000 tokens,你的缓存命中率从 92% 滑到了 31%,你的推理账单上升了 4 倍。团队里没有人能指出是哪个 PR 干的。没有一条 commit message 写着"将提示词增加 7,000 tokens"。每一次修改都很小,每一次修改都有理有据,每一次修改都干干净净地合入了。

提示词前缀长出胳膊和腿,就像地下室积攒纸箱一样。一个团队需要注入用户的订阅等级,这样 agent 才能解释套餐限制。另一个团队需要用户时区的今天日期,这样"明天提醒我"才能工作。第三个团队把当前 A/B 变体名硬塞进去,这样 eval traces 才能切片。市场团队加进了当前促销 banner,这样 agent 才能适时提及它。合规团队加进了功能标志清单,这样模型才能拒绝那些不在灰度名单里的用户访问 beta 功能。每一条都是一行的添加。每一条单独看都站得住脚。但加起来摧毁了你的缓存。

AI 功能规格说明书中无人提及的碳排放项

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开任何一份 AI 功能评论,你都会听到关于这三个数字的辩论:延迟、Token 成本和准确率。有人调出 P95 图表,有人计算单千次请求的成本,还有人争论评估分数 (eval score) 已经好到可以发布了。没人提到能源。没人提到碳排放。正因为没人提及,该功能的环保足迹仍然被决定了——由谁赢得了那场关于金额的争论所含蓄地决定。

这就是 AI 可持续性中沉默的问题。并不是团队故意选择了高碳设计,而是他们根本没有做出选择。碳足迹成了成本决策的副作用,而成本与碳排放之间仅存在松散的关联。在支出仪表盘上看起来大获全胜的路由规则,可能会悄无声息地让排放量翻倍,而会议室里没人会知道,因为那个能告诉他们真相的数字从未出现在仪表盘上。

这篇文章将能源和碳排放视为它们的真实身份:AI 系统的一个可测量、可掌控的属性,与延迟和成本处于同等地位。它不是企业价值的脚注,而是一个明细项。