LLM 网关生产化:架构、权衡与惨痛教训 — Kanish Manuja, Twilio

AAI Engineer
Computing/SoftwareManagementInternet Technology

Transcript

00:00:00我是 Ganesh Manuja,Twilio 的首席工程师。我们先举个手统计一下。
00:00:20在座有谁见过“出错了,请重试”这条消息?
00:00:27看来有几位比较幸运,还有几位可能刚吃了一顿美餐。
00:00:33那么,这个简单提示的背后,实际上是一个非常复杂的系统;即便在模型提供商宕机的情况下,它依然能为你提供服务。
00:00:45这就是我们今天要将其实际投入生产,或者说今天我们要讨论的投产内容。
00:00:50那么,什么是 LLM 网关?
00:00:52LLM 网关是你的应用程序与背后模型提供商之间的入口点或中间件。
00:00:58它负责处理很多事情,包括路由、身份验证、故障转移、速率限制以及你能想到的各种治理。
00:01:08而网关的核心,其实是四种因素之间的博弈。
00:01:13也就是可用性、延迟、安全护栏和成本。
00:01:18在发生性能降级时,你无法将这四点全部做到极致。
00:01:22你必须有所取舍。
00:01:25因此,通过这次演讲,如果你正在使用 LLM 网关,我希望帮你在你的用例中做出这种权衡。
00:01:35而如果你在设计网关,我希望你能为调用者和客户设计或提供这些调节杆,让你的客户感到满意。
00:01:46我们首先从可用性开始谈起。
00:01:50如果你只有一个模型提供商,他们的上限就是你的上限。
00:01:56他们的故障就是你的故障。
00:02:03因此,在典型的软件工程中,应对不可靠依赖项的方法就是重试。
00:02:11通过指数退避和随机抖动进行重试。
00:02:15当所有这些都失败时,你会使用一个断路器,在遭遇足够多的失败后触发它,然后停止调用该死的东西。
00:02:24但这对于大语言模型(LLM)来说是不够的。
00:02:26LLM 与那些你可以随意重试的快速、廉价的 API 有很大不同。
00:02:32重试 LLM API 会非常迅速地消耗你的延迟预算。
00:02:38而且,当你还有另一个完全正常的模型提供商可以路由时,却偏偏触发断路器,这是毫无道理的。
00:02:47你应该使用第二个模型提供商。
00:02:48第三,正如我所说,这些调用既缓慢又昂贵。
00:02:53因此,盲目重试只会成倍增加你的成本和长尾延迟。
00:02:58那么,这里更好的主意是什么?
00:03:02实际上是按请求进行故障转移(Fallback)。
00:03:05这意味着你可以先尝试模型提供商 A,如果对模型提供商 A 的请求失败,则按顺序尝试模型提供商 B。
00:03:14这里需要考虑的另一种选择是,你可以同时向两个提供商发送请求,但这仅在你极度、极度追求低延迟的情况下适用,因为这会使你的成本翻倍。
00:03:26一些类似熔断的模式也适用于这里的 LLM。
00:03:32如果你知道主提供商已经连续失败了一段时间,再次尝试它就没有任何意义了。
00:03:40你需要把它从负载均衡器或请求路径中移除,放入冷却期,然后在几分钟过去后,再尝试把它加回来。
00:03:51这里你必须做出的一项有趣选择是:你的失败计数应该存放在哪里。
00:03:59你可以决定将失败计数保存在处理流量的实例的内存中,或者使用共享信息,让失败计数在整个实例集群中共享。
00:04:12这两者各有权衡。
00:04:14如果你想要快速的故障转移,那么集群范围的共享会有所帮助。
00:04:19而对于使用本地状态计数器的实例,你将遇到的问题是,每当你更改部署规模时,你的配置和预期也会随之改变。
00:04:30所以这是需要考虑的一点。
00:04:34刚才那张整洁的架构图并没有真正向你展示一些其他潜在的陷阱,接下来我要讨论这些内容。
00:04:39故障转移并不是完全透明的。
00:04:41尽管行业正在向兼容 OpenAI API 的格式靠拢,但我可以说,其中仍然存在细微的差异。
00:04:49因此你需要对故障转移进行充分的测试。
00:04:52它们在工具调用架构(schemas)、令牌限制、停止原因以及其他方面可能会有所不同。
00:04:58因此,借助 LLM 网关,你可以拥有一个标准化层,以确保你也能实现跨提供商的故障转移。
00:05:08另一件事是流式传输(Streaming)。
00:05:15基本上,没有人愿意等待 30 秒,然后眼前才突然出现一整面墙的文本。
00:05:22因此有些用例是绝对需要流式传输的。
00:05:26但这是有代价的。
00:05:27你放弃了你的控制手段。
00:05:29你不能——一旦你决定使用提供商 A,你就必须继续使用提供商 A。
00:05:36你无法在流传输中途更换提供商。
00:05:39无论发送给客户端什么内容,都已经定型了。
00:05:42这就是为什么会出现“出错了”的提示信息。
00:05:46这就是你看到的那个提示。
00:05:48这不是因为懒惰。
00:05:49你看到它是由于设计使然。
00:05:52这也是各种权衡之一。
00:05:54我想指出另一件事,我见过团队在这上面一次又一次地跌倒。
00:06:00他们对主提供商的配置和测试做得非常充分。
00:06:05但第二个提供商,也就是故障转移提供商,却不一定得到同等程度的重视。
00:06:11而我认为,你的第二个提供商或故障转移提供商的吞吐量、容量或冗余度甚至应该更高。
00:06:21因为那是你的最后一道防线。
00:06:23如果它倒下了,你的应用程序也就宕机了。
00:06:29接下来我们讨论延迟。
00:06:31可用性故障是显而易见的。
00:06:34它们直接失败。
00:06:36你会收到警报。
00:06:37你会被呼叫(page)。
00:06:38但高延迟可能是悄无声息的。
00:06:42比起仅仅为了可用性而调优服务,我认为延迟更需要得到关注。
00:06:49有一点需要指出。
00:06:54网关可能会运行混合工作负载。
00:06:58你可能会有耗时不到一秒的嵌入(embedding)请求。
00:07:04你可能会有耗时不到一秒的分类请求。
00:07:07你会有耗时三秒的聊天请求。
00:07:10还有需要很长时间的推理请求。
00:07:13大家举个手。
00:07:15如果你们测量整个服务的总体延迟,请举个手。
00:07:20好吧,这是一个陷阱问题。
00:07:23抱歉。
00:07:24你们不应该这么做。
00:07:25这没有意义。
00:07:26这是个谎言。
00:07:27你应该按每个模型、每个路由来追踪你的 P99,而不是看整个网关的笼统数字。
00:07:32网关级别的数字没有任何意义,特别是当你运行混合工作负载时。
00:07:36我希望刚刚举手的人其实并没有这样做。
00:07:40另一件我怎么强调都不为过的事情是:要针对每个模型类别、每个路由设置超时。
00:07:49这就是导致你遭遇静默故障的头号根源。
00:07:54如果你没有设置超时,你的网关会以为请求正在顺利服务,而实际上并没有。
00:08:00关于延迟,我最后留给你们这样一句话。
00:08:05推理模型的正常表现,实际上就是聊天模型的故障。
00:08:09所以你绝对需要按路由追踪延迟。
00:08:13好的,这是最令人头疼、也让我最担心的幻灯片,也就是推理和路由模型。
00:08:24这也是延迟真正变得不可预测的地方。
00:08:29推理模型不会给你……它们高度不确定,比常规模型更具不确定性。
00:08:40在许多情况下,你无法将温度(temperature)设为零。
00:08:43同一个提示词可能需要 2 秒到 60 秒不等的时间。
00:08:48我们在生产中也见过这种情况:毫无理由地,P99 突然飙升到了 60 秒。
00:08:53因此,虽然没有神奇的解决方案,但我建议你至少从修复每个路由的推理级别开始。
00:09:03对于路由模型,它们将这种抽象隐藏在你的背后。
00:09:08比如,它们来选择运行哪些模型。
00:09:11我强烈建议你,在面对一个不确定的系统时,尽可能让请求变得确定性更高一些。
00:09:23另一个想法是对冲长尾延迟(Hedging the tail)。
00:09:26如果你的主请求实际上已经消耗了你延迟预算的 P90 左右,你可以再发送另一个请求。
00:09:37这可以真正为你的服务对冲掉 P99 的长尾延迟。
00:09:44好了,这是我最喜欢的话题之一。
00:09:47为了保证模型的安全,你需要设置安全护栏(Guardrails)。
00:09:53因此,安全护栏对于防止服务遭受提示词注入攻击、落实 PII(个人身份信息)过滤器、实施毒性过滤器以及防止大语言模型对你的客户爆粗口是必不可少的。
00:10:08所有这些都是好事情。
00:10:11但就像模型提供商一样,这里也有权衡。
00:10:15安全护栏就像是另一个服务。
00:10:18它也可能会宕机。
00:10:19它也可能不可靠。
00:10:21这就是你需要做出选择的地方。
00:10:24你是选择“故障放行(Fail open)”还是“故障拦截(Fail close)”?
00:10:27当我说是故障放行时,即使你的安全护栏挂了,你依然可以提供请求服务。
00:10:32而故障拦截则是阻断请求并说:嘿,我目前不可用。
00:10:36在某种程度上,这就是可用性与安全性之间的权衡。
00:10:41虽然没有标准答案,但这完全取决于你的具体用例。
00:10:45你可以决定,例如有害内容过滤器如果没有正常运行,你依然可以处理该请求。
00:10:53因此,默认的选择应该是你能承受的最坏情况。
00:11:02面对安全护栏失效以及管理安全护栏自身的不稳定性时,你实际上可以采取一些措施来改善系统的行为。
00:11:17首先是时间预算。
00:11:20你的请求绝不应该被安全护栏的耗时所束缚。
00:11:25决定速率的步骤应该始终是大语言模型本身。
00:11:29因此,请确保设置了超时机制,并且这些安全护栏在特定的时间预算内运行。
00:11:38另一个重要的事情是降级备用方案。
00:11:40你可能听过——你也知道,我之前谈到过,我们总是讨论关于模型提供商的降级备用方案。
00:11:47但安全护栏同样是关键服务,你也可以考虑降级备用方案,拥有备用提供商、备用检查、缓存决策,以便在安全护栏提供商宕机时保持服务可用。
00:12:03关于安全护栏,另一个有趣的抉择是安全护栏的部署位置。
00:12:10通常,你可以通过三种方式放置安全护栏。
00:12:15你可以设置一个前置钩子,让安全护栏实际对输入内容进行检查。
00:12:19你可以这样做——这可能是最安全的,但它确实会为你的请求增加串行延迟。
00:12:26另一种方式是并行运行。
00:12:29这是我的最爱之一,但需要说明的是,流式传输在这里采用并行方式效果不太好。
00:12:35因此,如果你特别需要生成结构化输出,请不要对它们进行流式传输。
00:12:40尝试节省延迟,并为你的结构化输出并发运行这些安全护栏。
00:12:46另一种是后置钩子。
00:12:48这些最适合用于输出监控、审计你的输出等等。
00:12:58所以,到目前为止,我们都——我已经讨论了关于我们依赖项可能出错的所有情况。
00:13:06我们还没有讨论的是,我们实际上在请求路径本身中添加了另一个依赖项,即核心——或者说大语言模型网关本身。
00:13:15如果你正在开发大语言模型网关或正在使用网关,我们有一些曾吃过亏并吸取了教训的事项想要与你分享。
00:13:25第一点是共享限制。
00:13:28确保你的API密钥按路由、按用例进行隔离,做到你能想象到的最细颗粒度。
00:13:40拥有一个吵闹的租户可能是这里最大的问题之一。
00:13:47第二点是负载脱落。
00:13:50作为运行手册和应急演练的一部分,这是一个你应该具备的功能,要确保你使用的网关支持负载脱落。
00:13:59因为当你遭遇重试风暴时,单纯进行横向扩展会变得非常困难。
00:14:03你无法简单地扩展正处于重试风暴之中的服务。
00:14:07所有这些Web服务器都有一个内部队列,并且它们是可配置的。
00:14:13确保它们是有界的,并且它们不能接受无限制的请求。
00:14:19如果你想加入一些自定义逻辑,甚至可以在这里进行流量优先级排序,以确保在高负载下你最重要的用例能够得到良好的服务。
00:14:29我想讨论的最后一件事是中央网关的整个概念。
00:14:38它是一个单点故障。
00:14:40因此,如果你正考虑为全公司的大语言模型部署一个中央网关,我建议你重新考虑一下,并弄清楚你想要它的原因是什么。
00:14:52我注意到,在大多数情况下,他们想要的并不是中央网关。
00:14:57他们想要的是集中化治理。
00:15:00有一种折中的发展路径,你实际上可以去中心化网关,同时依然集中化治理。
00:15:07所以,不要试图集中你的流量,但你可以通过插件、自定义代码来实现治理的集中化。
00:15:17治理可以采用成本跟踪、速率限制管理等形式,同时还有其他可能的解决方案。
00:15:24因此,在着手为整个公司建立一个中央网关之前,请先探索这些方案。
00:15:30它可以由单个团队管理,但我不建议将它作为全公司的单个部署实例来运行,即使它是分布式的。
00:15:43话虽如此,我想以一个个人话题来结束这次演讲。
00:15:47今天是我的儿子的生日,而我却在这里跟陌生人谈论熔断机制。
00:15:54所以,你能为我做的最起码的事情,就是请为我和你的客户阻止一次故障事故。
00:16:02谢谢大家。
00:16:03如果你有什么问题,请讲。
00:16:06.

Key Takeaway

在生产环境中实现LLM网关需要平衡可用性、延迟、安全护栏与成本,采用按请求故障转移、按路由追踪延迟以及去中心化网关架构来防止单点故障。

Highlights

  • LLM网关的核心由可用性、延迟、安全护栏和成本这四种相互博弈的因素组成。

  • 盲目重试LLM API会成倍增加成本和长尾延迟,因此应采用按请求进行故障转移。

  • 混合工作负载下,全网关级别的延迟数字没有意义,必须按每个模型和每个路由追踪P99延迟。

  • 安全护栏宕机时,系统需要选择‘故障放行’或‘故障拦截’的降级备用方案。

  • 全公司部署单一中央网关会构成单点故障,更佳的做法是去中心化网关并集中化治理。

Timeline

LLM网关的定义与核心权衡

  • LLM网关作为应用程序与模型提供商之间的中间件,处理路由、身份验证、故障转移和速率限制。
  • 网关的核心由可用性、延迟、安全护栏和成本这四种因素构成。

应用程序在面对模型提供商宕机时通常会依赖复杂的系统来维持服务。网关在性能降级时无法将可用性、延迟、安全护栏和成本全部做到极致,因此必须在设计时做出取舍,并为调用者和客户提供调节杆。

可用性与故障转移机制

  • 传统软件的盲目重试会迅速消耗LLM的延迟预算并成倍增加成本。
  • 按请求进行故障转移和冷却期管理能够有效替代传统的断路器模式。
  • 故障转移并非完全透明,不同提供商在工具调用架构、令牌限制和停止原因上存在差异。

单一模型提供商的上限即为系统的上限。重试机制在LLM场景下效果不佳,应当在主提供商失败时按顺序尝试备用提供商。同时,故障转移提供商的吞吐量和冗余度应当甚至高于主提供商,以确保最后一道防线不会崩溃。

延迟管理与推理模型处理

  • 混合工作负载下,网关级别的笼统延迟数字没有意义,必须按每个模型和路由追踪P99。
  • 未针对每个模型类别和路由设置超时是导致静默故障的头号根源。
  • 推理模型的延迟高度不确定且波动极大,常规提示词的耗时可能在2秒到60秒之间不等。

可用性故障会导致直接警报,而高延迟则是悄无声息的。聊天模型的正常表现实际上就是推理模型的故障,因此团队必须针对不同路由精确追踪延迟,并通过对冲长尾延迟的方法来改善服务表现。

安全护栏的抉择与权衡

  • 安全护栏对于防止提示词注入、实施PII过滤器和毒性过滤至关重要。
  • 安全护栏本身也可能宕机,因此系统需要在故障放行与故障拦截之间做出选择。
  • 安全护栏有前置钩子、并行运行和后置钩子三种部署位置。

安全护栏作为独立的依赖服务同样存在不可靠的风险。请求不应该被安全护栏的耗时无限制地束缚,团队必须设置明确的时间预算和降级备用方案,以在安全护栏服务失效时保持系统的整体可用性。

网关架构的惨痛教训与治理去中心化

  • API密钥必须按路由和用例进行最细颗粒度的隔离,以防止吵闹租户影响系统。
  • 网关应当具备有界的内部队列以支持负载脱落,从而应对重试风暴。
  • 全公司的中央网关属于单点故障,更推荐的做法是去中心化网关并集中化治理。

在请求路径中加入中央网关会引入新的单点故障风险。大多数组织真正需要的并不是集中化的流量网关,而是集中化的治理。通过插件和自定义代码实现成本跟踪和速率限制的去中心化部署,可以避免全公司陷入单点崩溃的困境。

Community Posts

View all posts