大模型前缀缓存技术,有望将服务成本降低90%

译者 | 核子可乐审校 | 重楼是不是总感觉ChatGPT响应缓慢? 大家可能没有留意,大模型应用程序的提示词重复率高达70%,问天气、问翻译和问节日安排的内容大量出现,且每次都要消耗算力进行处理。 这样的情况在分布式集群的各节点上被无数次放大,白白烧掉宝贵的能源和金钱。

大模型前缀缓存技术,有望将服务成本降低90%

译者 | 核子可乐

审校 | 重楼

是不是总感觉ChatGPT响应缓慢?

大家可能没有留意,大模型应用程序的提示词重复率高达70%,问天气、问翻译和问节日安排的内容大量出现,且每次都要消耗算力进行处理。这样的情况在分布式集群的各节点上被无数次放大,白白烧掉宝贵的能源和金钱。

为此,Anthropic日前详细介绍了如何利用提示词缓存技术将推理成本降低90%。其实不少开源大模型运行时(包括vLLM、TRT-LLM和SGLang等)都拥有自动前缀缓存(也称上下文缓存)功能,负责将相同前缀请求中的输入提示词自动缓存起来。

前缀缓存的工作原理

为了更好地理解前缀缓存,我们先来聊聊大模型推理的工作原理。

推理过程在宏观上分为两个步骤:

  • 通过正向传递处理给定的输入标记序列,即预填充阶段。
  •  解码阶段,从首个token连续生成至最后一个token,且当前token依赖于上一token。

大模型前缀缓存技术,有望将服务成本降低90%

图一

由于此过程的自回归属性(即新token依赖于前一token),因此有效的内存管理非常重要。多数大模型会采取为中间状态保留KV缓存的做法。与简单提示词或语义缓存的不同之处在于,其不会将全文输入和输出保存在数据库内,因为这样就只有完全匹配(或者几乎完全相同的查询)才能立即命中缓存并收到响应。

在预填充阶段,在大模型处理token时会计算“注意力”,即每个token与其他token的关系。计算过程会为每个token生成键-值矩阵。如果不经任何KV缓存,那么模型每次回顾此前token时都需要重新计算这些矩阵。KV缓存在设计上只支持一次生成,即只会在生成一条输出的过程中捕捉中间状态。

如果有两条具有相同前缀的请求,该怎么处理?

KV缓存的基本思路启发并衍生出了前缀缓存,确保在生成包含相同前缀的提示词时给出不同的响应。简单类比一下,假设已经计算过2 * 6的结果,那么对于2 * 6 * 3 * 5 这个新问题,可以直接复用之前的答案,避免在序列中重复计算。

这对应用程序有何帮助?

我们可以使用以下最佳实践来充分发挥前缀缓存的优势:

为提示词结构设计策略

可以将系统提示词、基础指令或者共享上下文等常量元素放在提示词的开头(图二),从而为多条查询建立可复用基础。其他动态或特殊内容则可放在末尾。

大模型前缀缓存技术,有望将服务成本降低90%

图二

对请求进行重新分组

将共享通用结构/前缀的请求捆绑在一起(图三)。例如,在处理以常见问候语或称呼开头的多条客户查询时,可以尝试将它们分为一组,尽可能提高计算过程的缓存和复用率。

大模型前缀缓存技术,有望将服务成本降低90%

图三

监控缓存利用率

另外,需要注意跟踪缓存利用率。

包括命中率与未命中率:

  • 找出哪些前缀比其他一般前缀更重要
  • 识别缓存未命中的模式

依托这些见解,就能优化提示词结构以获得最佳性能。

简单示例

以下示例为当多条查询共享相同的上下文时,前缀缓存如何优化大模型推理。我们使用一份简单的员工数据库表,并对其中内容进行不同查询。

Python
import time
from vllm import LLM, SamplingParams

# A small table containing employee information
LONG_PROMPT = """You are a helpful assistant that recognizes content in markdown tables. Here is the table:

| ID | Name          | Department | Salary  | Location    | Email                |
|----|---------------|------------|---------|-------------|---------------------|
| 1  | Alice Smith   | Engineering| 85000   | New York    | [email protected]   |
| 2  | Bob Johnson   | Marketing  | 65000   | Chicago     | [email protected]     |
| 3  | Carol White   | Sales      | 75000   | Boston      | [email protected]   |
| 4  | David Brown   | Engineering| 90000   | Seattle     | [email protected]   |
| 5  | Eve Wilson    | Marketing  | 70000   | Austin      | [email protected]     |
"""

def get_generation_time(llm, sampling_params, prompts):
    start_time = time.time()
    output = llm.generate(prompts, sampling_params=sampling_params)
    end_time = time.time()
    print(f"Output: {output[0].outputs[0].text}")
    print(f"Generation time: {end_time - start_time:.2f} seconds")

# Initialize LLM with prefix caching enabled
llm = LLM(
    model='lmsys/longchat-13b-16k',
    enable_prefix_caching=True
)

sampling_params = SamplingParams(temperature=0, max_tokens=50)

# First query - will compute and cache the table
get_generation_time(
    llm,
    sampling_params,
    LONG_PROMPT + "Question: What is Alice Smith's salary? Your answer: Alice Smith's salary is "
)

# Second query - will reuse the cached table computation
get_generation_time(
    llm,
    sampling_params,
    LONG_PROMPT + "Question: What is Eve Wilson's salary? Your answer: Eve Wilson's salary is "
)

运行以上代码,即可查询不同查询间的实际时间差异。第二条查询明显更快,因为其复用了缓存中的表上下文。具体时间将根据硬件和设置而有所浮动。

总结

前缀缓存是一项强大的大模型应用优化技术。实施上述最佳实践将帮助开发人员显著降低推理成本,且不致影响响应质量。参考示例也表明其操作难度极低,推荐大家马上在自己的应用程序中试一试。

原文标题:90% Cost Reduction With Prefix Caching for LLMs,作者:Mahak Shah

相关资讯