为什么需要前缀感知路由
在大多数 LLM 应用里,一次请求的提示词通常由两部分组成:开头一段固定的指令、背景资料或对话历史(可能长达数千 token),以及末尾随用户输入变化的少量 token。vLLM、TensorRT-LLM 等推理框架都支持前缀缓存:第一次算完的开头部分会被存下来,下一次遇到相同开头可以直接复用计算结果,跳过重复工作。
问题出在多实例部署时。请求会被负载均衡随机分到不同实例,原本可以复用的一段前缀被分散到多台机器上,每台机器都因为命中不足而反复重算同一段长前缀。前缀缓存功能存在,但路由层把请求打得太散,缓存始终暖不起来。
前缀感知路由正是为了解决这个问题。它会查看请求的开头内容,把相同前缀的请求持续送到同一台实例上,使该实例的 KV 缓存能稳定累积并被复用。
这项能力对用户意味着什么
从测试结果看,长上下文场景下启用前缀感知路由后的收益非常明显:
- P50 首 token 时间(TTFT)下降 71%–77%,P90 TTFT 下降 33%–37%。
- KV 缓存命中率从约 25% 提升到 80% 以上。
- 整体吞吐量提升约 15%–16%。
- 路由层额外开销仅 1.3–1.9 毫秒,相对于几十到几百毫秒的模型 TTFT 几乎可以忽略。
- 流量在实例之间分布依然均衡,没有热点。
短上下文场景(例如可变长度的对话)虽然也能享受到 13%–37% 的 TTFT 改善和约 2% 的吞吐量提升,但因为共享前缀较短,单位请求节省的计算量较小。结论是:共享前缀越长、单条前缀被重复请求的频率越高,收益越大。
适用场景与选择标准
前缀感知路由发挥作用的前提是:多个请求之间存在稳定且较长的共享前缀。典型的适用模式包括:
- RAG 应用:检索到的文档被拼到用户问题之前,多个用户针对同一篇文档提问时共享同一长前缀。
- 多轮对话:每一轮都包含前面所有轮次的历史,对话越长,共享的历史前缀越长。
- 模板化的机器人与助手:固定的指令块、格式要求、人设定义随每条请求一起发送。
- 代码补全:开发者编辑同一文件时,每次补全请求都共享该文件的全部内容。
如果请求之间没有稳定前缀、或者使用非 LLM 模型、又或者请求可互换且无差别,使用默认的随机路由即可。如果不同请求的处理时间差异较大、希望每台实例负载尽量均衡,则 LEASTOUTSTANDINGREQUESTS 更合适。
判断要不要启用,可以问自己三个问题:
- 请求是否有稳定的长前缀?
- 推理框架的前缀缓存是否已经开启?
- 是否部署在至少两台实例上?
三项都满足,前缀感知路由才有意义。任何一项不满足,建议保持默认策略。
如何用起来
启用方式是在创建或更新端点配置时指定路由策略,不需要修改模型容器或推理框架代码。配置项主要有两个:
- PrefixLength:取值 1024–65536。对于 SageMaker 原生 Invoke API,是请求体的字节数;对于 OpenAI 兼容 API,是从消息文本中取的字符数。建议设置为共享前缀长度加上适量缓冲,覆盖到能区分不同工作负载的边界即可。
- ConcurrencyThreshold:取值 1–1024。目标实例同时在飞的请求数上限,达到上限时新请求会被路由到其他较闲的实例,避免单点过载。
一段命令行示例:
aws sagemaker create-endpoint-config \
--endpoint-config-name example-llm-config \
--production-variants '[{
"VariantName": "AllTraffic",
"ModelName": "example-llm-model",
"InitialInstanceCount": 3,
"InstanceType": "ml.p5.48xlarge",
"RoutingConfig": {
"RoutingStrategy": "PREFIX_AWARE",
"PrefixAwareRoutingConfig": {
"PrefixLength": 4096,
"ConcurrencyThreshold": 10
}
}
}]'
接着像往常一样创建端点即可。调用方式没有变化,原生 InvokeEndpoint、InvokeEndpointWithResponseStream 以及 OpenAI 兼容的 Chat Completion 接口都可以照常使用。
该策略也支持推理组件端点和动态加载的 LoRA 适配器:在推理组件场景中行为与单模型端点一致;在 LoRA 场景下,会在已加载该适配器的实例集合内基于前缀做选择。
如果需要按租户隔离缓存,可以在原生 API 请求头加上 X-Amzn-SageMaker-Prefix-Aware-Id(最多 64 个 ASCII 字符),或是在 OpenAI 兼容 API 的请求体中加 prompt_cache_key 字段,相同前缀但不同 ID 的请求会被路由到不同实例,避免租户之间共享缓存上下文。
落地建议与成本考量
启用前后的成本结构值得提前算清楚。前缀感知路由本身不增加实例数量,只是让已有实例的缓存命中率提升,从而在同等硬件上承接更多请求或降低响应延迟。换句话说,节省的并非直接的算力费用,而是延迟下降带来的用户体验改善和单实例吞吐提升后潜在的实例数缩减可能。
落地过程中有几点值得提前注意:
- 先确认框架开了前缀缓存。vLLM 在近期版本中默认开启,其他框架可能需要手动配置。如果框架本身不缓存 KV,路由再精准也无效。
- 保持请求序列化一致。原生 Invoke API 的 PrefixLength 按原始字节计算,JSON 的空格、键顺序、字段排列都会影响匹配。建议统一使用同一套序列化方式。
- PrefixLength 不要拍脑袋设。设短了,所有短前缀相同的请求都会被塞到同一台实例,触发溢出;设长了,温度参数等小差异又会打散本该聚合的请求。建议从共享前缀长度 + 小缓冲起步,再根据监控数据调整。
- 至少两台实例。单实例部署下,任何路由策略结果都一样。
- 做好监控。建议开启 SageMaker 详细可观测性,跟踪模型级的 KV 缓存命中率,确认路由策略对实际工作负载是否真的有效。
路由策略可以按生产变体分别设置,也可以在不重新部署模型的前提下,通过更新端点配置来切换 RANDOM、LEASTOUTSTANDINGREQUESTS、PREFIX_AWARE 三种策略。先在灰度环境验证收益,再切到生产,是比较稳妥的做法。
总体来看,前缀感知路由是一项门槛不高、收益清晰的能力:只要你跑的是 LLM 长上下文工作负载,推理框架已经支持前缀缓存,并且部署了多实例,把它打开几乎是一件没有理由不做的事情。