跳到主要内容

118 篇博文 含有标签「llm-ops」

查看所有标签

那个在你待办事项中悄悄更改的弃用日期

· 阅读需 10 分钟
Tian Pan
Software Engineer

弃用通知在某个周二送达,停用日期定在六个月后。你的平台团队将其记录在依赖跟踪器中,贴上 “Q3 切换” 标签,并标记为黄色严重度。它与队列中已有的另外两个迁移任务汇合。三周后,供应商在同一个 URL 下修改了日期,没有 diff,没有收件箱通知,只有一段悄悄更新的文字,将停用日期提前了 60 天,直接挪到了你的代码冻结期中间。

你视为规划文档的生命周期页面,其实一直是一个合同闹钟。唯一改变的是它控制着哪个团队的日历——而拥有这个日历的团队并不是你的。

KV 缓存预热 Cron 任务只在蓝环境运行而从未进入绿环境,原因竟是主机绑定从未迁移

· 阅读需 12 分钟
Tian Pan
Software Engineer

事故复盘将十二天前的一次部署确定为支出增加 3.6 倍的原因,而当时在场的参与者中,没有一个人在变更发布时参与其中。部署过程非常常规:蓝绿切换,流量按计划转移到绿色环境,蓝色环境停用,流水线变绿,发布工程师关闭了工单。生产环境的 SLO 都没有触发。应用层的告警也没有响起。系统运行得完全符合设计。

原本的设计是一个每五分钟运行一次的 Cron 任务,它每五分钟针对稳定的系统提示词前缀 (system-prompt prefix) 预热提供商的 Prompt 缓存。这种预热为团队在冷启动时带来了 91% 的缓存命中率,并在每个会话的第一次请求中获得了大约 4 倍的成本优势。该 Cron 任务是一年前首次引入蓝绿模式时编写的,其主机选择器 (host selector) 被固定在蓝色池 (blue pool),以避免在重叠窗口期间运行两次预热。当绿色环境变成活跃环境而蓝色环境消失时,该 Cron 任务失去了它的主机,并从“每五分钟运行一次”悄无声息地转变为“永不运行”。随着提供商缓存的 TTL 使预热的前缀过期,缓存命中率在接下来的 36 小时内逐渐下降。成本仪表盘计算的是每日窗口内的平均单次请求成本,平滑了趋势,直到下一个计费周期让问题变得显而易见。

配额窗口机制重写后,这批夜间脚本是如何拖垮你的交互流量的

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个稳定运行了十个月的 cron 任务是你系统中最危险的任务,因为其中的代码没变,你的代码也没变,唯一改变的是别人发布的、你们团队没人读的发行说明(release notes)中的一句话。那个每晚在 00:05 UTC 启动、在十分钟内清空工作队列并重新进入休眠状态的每晚 embedding 刷新任务,曾是教科书般的范例。它通过在用户醒来前占用几分钟刚重置的每分钟配额,并在当天的剩余时间内保持在每日配额之内,从而与白天的交互式流量和谐共存。接着,服务商重写了每日窗口的核算方式,保持了每分钟窗口不变,并保留了你客户端测试的所有签名。批处理任务依然稳定运行。但每晚 00:13 UTC,交互界面开始返回 429 错误。团队一直在追查一个根本不存在的、本应在一周后才开始的上游维护窗口。

Bug 从不在你的代码里。Bug 在于“每日限制”不再是前一天的意思,而你的调度程序固定在了一个与旧定义对齐的时钟边界上。这篇文章讨论的是:速率限制核算(rate-limit accounting)作为一种服务商可以在不破坏任何签名的情况下修改的契约;两个独立正确的调度是如何组合成拒绝服务模式的;以及如何通过架构手段让 cron 任务不再是一个连接到别人时钟上的定时炸弹。

那些将模型未完成的残缺回答存入数据库的流式 UI

· 阅读需 12 分钟
Tian Pan
Software Engineer

这份事后分析读起来像是一份幻觉报告。一名用户根据一份语气笃定的建议采取了行动,但结果证明该建议是错误的——这种错误在模型正常完成输出的情况下是不会出现的。然而,追踪记录显示模型并未完成输出。在预期的 800 个 Token 中,供应商连接在第 412 个 Token 时断开了。客户端的错误处理程序记录了这次失败。但随着 Token 的到达,持久化的部分消息已被写入对话历史,在用户的 UI 中看起来与其他完整的回答毫无二致。于是用户采信了它。支持团队将该工单归类为内容质量问题,花了整整两周时间才将其转交给平台团队。

这条链路中没有任何环节属于模型故障。模型对生成的 412 个 Token 表现得非常正确。失败的原因在于流式 UI 和持久化对话历史在“什么才算是一条消息”的问题上产生了隐秘的分歧。而正是这种流式传输本应缓解的故障模式,导致这一分歧成为了权威记录。

这是乐观渲染(Optimistic Rendering)与持久化存储之间的契约。大多数聊天产品只是从教程或框架中继承了这种模式,而从未将其视为一项契约,这种鸿沟最终表现为一系列看似模型 Bug 实则不然的尾部故障。

你在供应商支持呼叫中通过屏幕共享泄露的系统提示词

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的 AI 团队将系统提示词(System Prompt)视为专有知识产权(IP)。部署流水线会将其从每一个客户可见的界面中剥离。针对生产环境调试的操作手册(Runbook)要求工程师在任何故障产物(Incident artifact)离开作战室(War room)之前,通过 grep 将其过滤掉。你的上一次安全审查捕获并封堵了三条不同的提示词泄露路径:过于冗余的 API 响应、发布到错误层级的调试请求头(Debug header),以及将提示词嵌入到消息中的堆栈跟踪(Stack-trace)端点。

但在某个早晨,这一切都变得毫无意义:一名工程师加入了一个关于无关账单争议的供应商支持电话,通过屏幕共享展示他们的终端以演示堆栈跟踪信息。而该回溯信息中包含了一行详细的日志,完整打印了完全解析后的提示词——每一个注入的变量都已被替换,包括针对特定客户的业务规则和内部模型路由提示。供应商的支持工程师按照标准支持工作流录制了通话。录音随后进入了供应商的工单管理系统。现在,提示词被清晰地存储在了一个第三方 SaaS 中,而你的安全审查与其没有签订合同,没有签署数据处理协议(DPA),也没有审计权。

使所有 Prompt 缓存前缀失效的分词器升级

· 阅读需 10 分钟
Tian Pan
Software Engineer

发布说明只有两行。“改进了多语言分词(Tokenization)。模型输出无破坏性变更。”一共不到二十个字。你的评估(Evals)确认了这一点:相同的提示词,相同的生成内容,相同的评分。你的平台团队在周五下午批准了升级。到了周二早上,你的缓存命中率从 80% 下降到 4%,每日推理费用翻了两番,而凌晨 6 点把你叫醒的轮值工程师在你的代码里找不到任何一行改动。

你的代码确实没有任何改动。但服务商发布了一个新的分词器,它对某个 Unicode 字符的一个字节划分与旧版本不同。你系统中每个缓存的前缀现在都是基于一个已不再存在的 Token 序列生成的指纹。模型的表现完全一致 —— 这确实是事实。但发布说明中未曾提及的缓存层,却为此付出了全额代价。

供应商重新校准后,你的智能体所信任的转录置信度得分

· 阅读需 11 分钟
Tian Pan
Software Engineer

语音智能体有一个门控机制。转录置信度高于 0.85 的任何内容都会直接进入规划步骤;低于该值的内容则会被路由给人工。该阈值是六个月前针对标记的真实客户通话语料库进行调优的,随后被固定在配置文件中并被遗忘。在六个月的时间里,它确实履行了职责。然后,转录服务提供商发布了模型升级——同样的 API、同样的响应形式、同样的延迟范围、同样记录在案的准确率——但在接下来的两周里,该智能体开始向错误的人授权电汇。

“给妈妈转账 50 美元”变成了“给 Tom 转账 5,000 美元”。新的转录结果返回的置信度为 0.91,远高于门控阈值。下游规划器看到了一个置信度很高的转录结果并据此执行。客户的申诉最终暴露了这个 Bug,但到那时,支持队列已经将一周内类似的事件作为欺诈纠纷过滤掉了。复盘分析将差距追溯到团队从未明确做出的一个决定:旧模型的 0.85 和新模型的 0.85 是同一个数字。

你的评测套件也是生产负载:当每晚测试耗尽线上流量配额时

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个团队最成功的 AI 功能在周二凌晨 2:14 宕机了。传呼机显示模型 API 在稳态下返回 429 错误。模型是健康的。供应商是健康的。团队自身的生产流量也是正常的。蚕食额度的是每晚运行的评测套件(eval suite)——正是团队在前一周引以为傲并进行扩展的那个套件。评测系统和产品共享同一个组织密钥(organization key),在那个夜晚,评测系统成了那个打破室友宁静的“吵闹邻居”。

评测系统并没有异常行为。它正在按照开发者的设计运行:针对生产模型标识符(identifier)进行一千个案例的测试,按节奏、按计划运行——这个计划因为已经静默运行了两年,早就被大家遗忘了。这次最终导致超限的扩展增加了三百个案例。该 PR 经过了评测负责人和 Prompt 负责人的审核。评审线程中没有一个人想到要问:这会消耗多少每日 Token 额度?

那些响应体显示 OK 且被客户端信以为真的 429 错误

· 阅读需 10 分钟
Tian Pan
Software Engineer

故障始于 14:03,服务商返回了 429 错误,并带有一个 JSON 响应体,内容为 {"status": "ok", "data": null}。这个客户端库是六个月前由一个被坑过两次的人匆忙写成的——一次是因为网关返回了带有 error 字段的 HTTP 200,另一次是因为服务商在请求实际成功时返回了 HTTP 500。所以,这个库学会了信任响应体,而不是状态码。状态码要求限流,响应体却说继续。客户端相信了响应体,发出了下一个请求,又得到了一个带有 ok 的 429,再次发送,到 14:11 时,服务商的熔断器已将该账户在该小时的剩余时间内列入了黑名单。

服务商并没有完全撒谎。429 是真实的。但在响应流水线的某个环节,一个默认的封装层覆盖了限流负载——这是一个来自包装服务的通用 {"status": "ok"},用于填充缺失字段,并应用在了一个该包装服务无法识别的错误之上。状态码是正确的,请求头是正确的,响应体是错误的,而响应体正是客户端读取的部分。

以 Token 数量而非结果驱动的 A/B 测试

· 阅读需 14 分钟
Tian Pan
Software Engineer

我曾合作过的一个团队发布了一次 prompt 变更,将输出 token 减少了 22%。实验仪表盘上一片绿意——方差极小,p 值非常清晰,外推后的成本节省每年高达六位数。两周后,一位研究转化漏斗的产品分析师指出,在同一时间段内,下游任务完成率下降了 11%。较短的输出省略了一个澄清步骤,而用户一直默默依赖该步骤来了解下一步该点击哪里。

实验平台没有撒谎。它报告的正是团队配置的核心指标,而且该指标确实朝着正确的方向移动了。问题在于,该指标衡量的是团队实际上并不关心的东西。Token 统计成本低,实验基础设施对其有现成的集成,而衡量结果却很难——因此团队选择了平台提供的便捷方案。结果是仪表盘上的完胜,却是产品层面的退化。

针对你已不再提供服务的模型版本的 Bug 报告

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个客户支持工单在周二送达。客户附上了一张你的产品在 6 周前生成的输出截图。他们声称该输出是错误的、不安全的,或者根本不符合预期,并要求修复。你的支持工程师将提示词粘贴回同一个 API 终端,得到了一个清晰、合理的回答。就系统目前的状态而言,这个 Bug 并不存在。

Bug 是存在的,但产生这张截图的模型已经不在了。自从客户提交工单以来,你的 v1-chat 终端背后的权重已经更换了两次——一次是为了提升质量,一次是为了优化成本——而原始的检查点(checkpoint)已无法访问。客户的“这东西坏了”现在成了一个针对变动目标的无法证伪的断言,支持团队既无法确认它,也无法关闭它。

这不是一个古怪的边缘案例。这是将模型版本控制视为内部 MLOps 问题,而非客户可见的产品合约的必然结果。终端 URL 是稳定的,但它背后的产物(artifact)却不是。在你的支持流程、保留策略和客户合约承认这一差距之前,每一个针对已轮换检查点的 Bug 报告都会掉进同一个分类真空区。

为了节省 Token 而被你剥离的思维链,其实隐藏着一项合规证据要求

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个平台团队发布了一次提示词重构,将平均响应成本降低了 32%。这个改动非常简单:剥离了 “解释你的推理过程” 前导语,要求模型仅返回 JSON 对象,并删除了从模型文本中解析推理逻辑的后处理步骤。仪表板变绿了。季度回顾中的单位经济效益页面从黄色变为了金色。平台团队中没有人想到要咨询风险团队,因为这个改动没有触及客户收到的任何答案。

两个季度后,一位受监管客户的审计员要求提供一份六个月前的贷款拒绝信的决策理由。团队调取了追踪记录。输入在那,输出也在那。推理过程消失了 —— 不是因为有人删除了它,而是因为它在重构发布的那天起就停止生成了。客户的合规计划一直运行在推理逻辑存储在追踪记录库中的假设之上;平台团队一直运行在推理逻辑不是任何人的问题,因为面向客户的答案没有变化的假设之上。孤立来看,这两个假设都是正确的。但结合在一起,它们让客户面临了一项监管违规审计发现,并让平台团队失去了一份合同续签。