芥末
发布于 2025-12-28 / 0 阅读
0
0

LLMLingua:微软开源的 Prompt 压缩方案与使用指南

使用大型语言模型(LLM,Large Language Model)做应用时,Prompt 迟早会变长。

在检索增强生成(RAG,Retrieval-Augmented Generation)系统里,用户问一句话,后端可能要拼上多段检索文档;在聊天机器人里,为了保留上下文,历史对话会不断追加;在 Agent 场景里,工具说明、约束规则、中间状态也会进入 Prompt。Prompt 越长,问题越明显:

问题具体影响
成本上升API 通常按输入 token 和输出 token 计费,输入越长越贵
延迟增加模型需要处理更多上下文,首 token 时间和总响应时间都会变长
容易超上下文窗口超过模型最大上下文后,请求会失败或必须截断
信息被稀释大模型不一定能稳定关注长上下文里的关键位置,尤其是中间段落

LLMLingua 解决的是这个问题:在 Prompt 发给目标大模型之前,先做一次压缩,只保留对任务有用的信息。

它不是简单地把前半段删掉,也不是让大模型再总结一遍,而是用一个更小、更便宜的模型或专门训练出的 token 分类器,判断哪些内容可以删、哪些内容必须留。

典型调用链可以理解成这样:

flowchart LR
    A[用户问题] --> B[检索文档 / 历史对话 / 工具信息]
    B --> C[原始长 Prompt]
    C --> D[LLMLingua Prompt 压缩器]
    D --> E[压缩后的 Prompt]
    E --> F[目标大模型<br/>GPT-4 / Claude / LLaMA 等]
    F --> G[最终回答]

压缩发生在目标大模型之前,所以它可以减少真正昂贵的那部分输入 token。根据论文实验,LLMLingua 在一些任务上可以做到约 20 倍压缩,同时保持较小的效果损失。实际压缩比例要看任务类型、文本冗余程度、压缩模型和目标 token 数,不能只看一个固定数字。

Prompt 压缩和普通摘要有什么区别

很多人看到“压缩 Prompt”,第一反应是让一个模型先总结上下文。但 LLMLingua 的思路更接近“删冗余”,而不是“重写全文”。

两者差别很大:

方法做法风险
摘要把长文本改写成短文本可能改写事实、丢掉数字、改变语气或引入幻觉
截断只保留前 N 个或后 N 个 token关键证据可能刚好被截掉
Prompt 压缩尽量删掉低价值 token,保留任务相关片段压缩过强时仍可能丢信息,需要评估

LLMLingua 更适合那些“信息很长但有冗余”的输入,例如 RAG 检索结果、会议记录、长对话历史。它的目标不是生成一段漂亮的摘要,而是让目标大模型仍然能完成原任务。

LLMLingua 系列的技术演进

LLMLingua 不是一个单点方案,而是一组逐步演进的 Prompt 压缩技术。主要可以分成四条线:

timeline
    title LLMLingua 系列演进
    2023 : LLMLingua
         : 使用小型因果语言模型估计 token 重要性
         : 粗到细三层压缩框架
    2024 : LongLLMLingua
         : 面向长文档和 RAG
         : 解决长上下文中间信息容易被忽略的问题
    2024 : LLMLingua-2
         : 从困惑度方法转向 token 分类
         : 使用 GPT-4 蒸馏数据训练专用压缩模型
    2025 : SecurityLingua
         : 把安全感知压缩用于越狱攻击防御
         : 通过压缩暴露提示中的真实意图

LLMLingua:粗到细的三层压缩

早期 LLMLingua 的核心思想是:用一个较小的语言模型来估计 Prompt 中每个 token 的重要性。

它使用的指标之一是困惑度(Perplexity)。如果某些 token 对小模型来说非常容易预测,说明它们通常信息量较低,删除后对整体语义影响可能较小;如果某些 token 难以预测,往往包含名称、数字、关键实体、推理条件或任务相关内容,更应该保留。

完整压缩流程不是直接按 token 排序删除,而是分成三层:

flowchart TD
    A[原始 Prompt] --> B[预算控制器]
    B --> C[粗粒度筛选<br/>段落 / 示例 / 文档块]
    C --> D[细粒度压缩<br/>逐 token 判断保留或删除]
    D --> E[压缩 Prompt]
    E --> F[目标大模型]

    B -.-> B1[决定总压缩率<br/>或目标 token 数]
    C -.-> C1[先删明显低价值片段]
    D -.-> D1[再处理句子内部冗余 token]

三层分别承担不同工作:

层级作用
预算控制器根据目标 token 数或压缩率,决定整体压缩强度
粗粒度筛选在段落、示例、文档块级别先删掉明显不重要的部分
细粒度压缩在 token 级别继续删冗余内容,尽量保留语义核心

这种设计比直接逐 token 删除更稳。因为长 Prompt 通常由多个结构组成,比如 instruction、few-shot 示例、检索文档、用户问题。如果不区分结构,可能会把关键问题或重要示例压坏。

LLMLingua 还引入了对齐思路:压缩模型虽然是小模型,但压缩后的文本最终要交给目标大模型理解,因此压缩器需要尽量贴近目标大模型的语言分布。简单说,压缩器删完之后,目标大模型仍然要“看得懂”。

LongLLMLingua:面向长文档和 RAG

长上下文里有一个常见现象:模型更容易关注开头和结尾,对中间位置的信息不够稳定。这个问题常被称为 “Lost in the Middle”,也就是长上下文中间信息丢失。

RAG 场景尤其容易遇到这个问题。检索系统返回多段文档后,关键证据可能排在中间;即使这些证据没有被截断,目标大模型也可能没有充分利用。

LongLLMLingua 针对这个问题做了几件事:

机制解决的问题
问题感知压缩根据用户问题判断哪些文档块更相关,而不是均匀压缩所有内容
文档重排序把重要内容放到更容易被模型关注的位置,例如开头或结尾
动态预算分配给关键文档更多 token 预算,给弱相关文档更高压缩率

它的处理链路可以概括为:

flowchart LR
    A[用户问题] --> B[检索到的长文档集合]
    B --> C[按问题计算相关性]
    C --> D[动态分配压缩预算]
    D --> E[重排序重要文档]
    E --> F[压缩文档内容]
    F --> G[目标大模型生成答案]

LongLLMLingua 的重点不只是“压短”,而是“把有限 token 留给更可能回答问题的内容”。对于 RAG 系统,这一点比单纯追求压缩率更重要。

LLMLingua-2:从困惑度变成 token 分类

LLMLingua-2 的变化更大。它不再主要依赖因果语言模型计算困惑度,而是把压缩任务改成 token 分类任务:对输入里的每个 token 判断“保留”还是“删除”。

flowchart TD
    A[原始文本] --> B[Tokenizer]
    B --> C[双向编码器<br/>如 XLM-RoBERTa]
    C --> D[Token 分类头]
    D --> E{每个 token 是否保留}
    E -->|保留| F[压缩 Prompt]
    E -->|删除| G[丢弃]

这个范式有几个好处。

因果语言模型只能从左到右看上下文,而 BERT 类编码器是双向的,可以同时看到 token 的前文和后文。判断一个 token 是否重要时,双向上下文通常更有优势。例如一个数字、实体名、条件短语是否关键,很多时候要看它所在句子的完整语境。

LLMLingua-2 还使用了数据蒸馏:让 GPT-4 对大量文本生成压缩结果,再用这些结果训练较小的分类模型。这样小模型可以学习强模型的压缩偏好,但推理时成本更低。

对比三代方案可以看得更清楚:

方案主要方法适合场景特点
LLMLingua小型因果语言模型 + 困惑度 + 粗到细压缩通用长 Prompt 压缩思路直观,压缩粒度细
LongLLMLingua问题感知压缩 + 文档重排序 + 动态预算RAG、长文档问答更关注任务相关性和长上下文位置问题
LLMLingua-2GPT-4 蒸馏数据 + 双向编码器 + token 分类多任务通用压缩模型更小,压缩速度更快,任务迁移能力更强

LLMLingua-2 的压缩模型参数规模可以降到数亿级别,相比 7B 级别的压缩模型更容易部署,压缩速度也更适合在线服务。

SecurityLingua:把压缩用于安全防御

Prompt 压缩还可以用于安全场景。SecurityLingua 的思路是:很多越狱提示会混入大量噪声、角色扮演、干扰性指令和绕行话术,用来掩盖真实意图。安全感知压缩可以剥离一部分噪声,让模型更容易识别用户真正想做什么。

它的防御链路大致如下:

flowchart TD
    A[用户输入] --> B[安全感知 Prompt 压缩器]
    B --> C[提取后的真实意图]
    A --> D[原始输入]
    C --> E[安全系统提示]
    D --> F[目标大模型]
    E --> F
    F --> G[安全约束下的回答]

这种做法不是直接改写用户输入,而是额外提取一条“意图信号”,再把它提供给目标模型或安全模块。它的价值在于让隐藏在噪声里的危险请求更明显。

安全压缩不应该被理解成万能防线。它更适合作为多层防护的一部分,和内容审核、策略模型、系统提示约束、输出过滤一起使用。压缩器如果误删关键信息,也可能产生漏判或误判,所以必须用攻击样本和正常样本一起评估。

安装和基本使用

LLMLingua 已经发布到 PyPI,可以直接安装:

pip install llmlingua

最小使用示例:

from llmlingua import PromptCompressor

# 初始化压缩器
llm_lingua = PromptCompressor()

prompt = """
Sam bought a dozen boxes, each with 30 highlighter pens inside,
for $10 each box. He gave 5 boxes to his classmates and sold the rest...
"""

result = llm_lingua.compress_prompt(
    prompt,
    instruction="",
    question="",
    target_token=200,
)

print(result["compressed_prompt"])
print("origin_tokens:", result["origin_tokens"])
print("compressed_tokens:", result["compressed_tokens"])
print("ratio:", result["ratio"])
print("saving:", result.get("saving"))

返回结果通常是一个字典,结构类似:

{
    "compressed_prompt": "Question: Sam bought a dozen boxes each with 30 highlighter pens...",
    "origin_tokens": 2365,
    "compressed_tokens": 211,
    "ratio": "11.2x",
    "saving": "Saving $0.1 in GPT-4."
}

几个字段的含义:

字段含义
compressed_prompt压缩后的 Prompt
origin_tokens原始 token 数
compressed_tokens压缩后 token 数
ratio压缩倍率
saving按指定目标模型估算的费用节省

如果要指定压缩模型,可以在初始化时传入模型名称:

from llmlingua import PromptCompressor

# 使用更强的压缩模型
llm_lingua = PromptCompressor("microsoft/phi-2")

显存有限时,可以使用量化模型。量化模型通常需要额外依赖:

pip install optimum auto-gptq
from llmlingua import PromptCompressor

llm_lingua = PromptCompressor(
    "TheBloke/Llama-2-7b-Chat-GPTQ",
    model_config={"revision": "main"},
)

如果使用 LLMLingua-2,通常会选择项目提供的专用压缩模型。不同版本的库参数可能略有差异,实际以安装版本和项目文档为准。常见形式如下:

from llmlingua import PromptCompressor

llm_lingua = PromptCompressor(
    model_name="microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank",
    use_llmlingua2=True,
)

result = llm_lingua.compress_prompt(
    prompt,
    rate=0.33,
    force_tokens=["\n", "?", ".", ","],
)

print(result["compressed_prompt"])

force_tokens 可以要求压缩器尽量保留某些符号或结构,对问答、列表、代码片段比较有用。

在 RAG 系统里怎么接入

RAG 里最常见的接入方式,是在检索完成之后、调用目标大模型之前压缩上下文。

sequenceDiagram
    participant U as 用户
    participant A as 应用服务
    participant R as 检索系统
    participant C as LLMLingua
    participant L as 目标大模型

    U->>A: 提问
    A->>R: 检索相关文档
    R-->>A: 返回 Top-K 文档
    A->>C: 压缩文档上下文
    C-->>A: 返回压缩 Prompt
    A->>L: 发送 instruction + question + compressed context
    L-->>A: 生成答案
    A-->>U: 返回结果

示例代码:

from llmlingua import PromptCompressor

llm_lingua = PromptCompressor()

instruction = "根据给定上下文回答问题。如果上下文中没有答案,直接说明无法确定。"
question = "LLMLingua 为什么适合 RAG 场景?"

docs = [
    "文档 1:RAG 系统会把检索到的多个文档片段拼接进 Prompt...",
    "文档 2:长上下文会增加推理成本,并可能导致模型忽略中间信息...",
    "文档 3:Prompt 压缩可以在保留关键语义的同时减少 token 数...",
]

context = "\n\n".join(docs)

compressed = llm_lingua.compress_prompt(
    context,
    instruction=instruction,
    question=question,
    target_token=800,
)

final_prompt = f"""
{instruction}

上下文:
{compressed["compressed_prompt"]}

问题:
{question}
"""

print(final_prompt)

这里有一个重要习惯:不要把用户问题和核心系统指令压得太狠。通常更稳的做法是压缩检索文档和历史对话,把系统指令、工具约束、用户最终问题保留得更完整。

Hugging Face Demo 的压缩效果

LLMLingua 提供了 Hugging Face Demo,可以直接输入长 Prompt 观察压缩前后的 token 数。下面这个示例展示了一次压缩结果:原始输入为 2428 tokens,压缩后变成 331 tokens。

LLMLingua Demo 压缩效果

这个结果大约是 7.3 倍压缩。它说明两点:一是实际应用里确实存在大量可删冗余;二是压缩倍率不是固定值,同一套工具在不同文本、不同任务、不同目标 token 设置下会有不同结果。

适合和不适合的场景

LLMLingua 适合放在“长输入、高调用成本、允许少量信息损失”的位置。

场景是否适合原因
RAG 多文档问答适合检索结果通常有冗余,压缩能减少输入 token
长对话历史压缩适合旧轮次对话可以按相关性保留
会议记录问答适合长文本里有大量口语填充和重复表达
Agent 工具日志适合中间状态可能很长,很多细节不需要完整传给模型
精确引用合同条款谨慎删除一个否定词、金额或日期都可能改变含义
代码生成和代码审查谨慎标点、缩进、变量名、边界条件都可能关键
安全系统提示不建议强压缩安全规则丢失会直接改变模型行为
极短 Prompt不适合压缩器本身也有开销,收益很小

使用时容易踩的坑

只看压缩率,不看任务效果

压缩率越高,不一定越好。真正要看的指标是下游任务效果,例如问答准确率、引用命中率、人工评审结果、拒答正确率等。

一个实用评估表可以这样设计:

指标说明
token 节省比例输入 token 减少多少
平均延迟加上压缩器后端到端耗时是否下降
回答准确率压缩后答案是否仍然正确
证据覆盖率RAG 中关键证据是否仍在压缩 Prompt 里
失败样本类型哪类问题最容易被压坏

如果压缩器本身耗时很高,而目标模型调用并不贵,端到端收益可能并不明显。高并发线上系统尤其要测整体链路,而不是只测目标模型输入 token。

数字、实体和否定词容易出问题

长文本压缩时,最怕删掉这些内容:

  • 金额、日期、比例、单位;
  • 人名、公司名、产品名;
  • “不”“不能”“除非”“仅当”这类条件或否定词;
  • 代码里的符号、变量名、缩进;
  • 法律、医疗、金融文本中的限定范围。

对这类任务,应该降低压缩强度,或通过 force_tokens、结构化模板、字段抽取等方式保护关键信息。

RAG 文档不要平均压缩

如果 10 个检索文档里只有 2 个真正相关,平均压缩每个文档不是好策略。更合理的做法是先按问题相关性排序,再给高相关文档更多 token 预算。

这也是 LongLLMLingua 更适合 RAG 的原因:它不只是缩短文本,还会围绕用户问题调整压缩策略。

中文场景要选合适模型

如果输入主要是中文,优先选择支持多语言的压缩模型,尤其是 LLMLingua-2 中基于 multilingual BERT 或 XLM-RoBERTa 的模型。英文模型直接压中文,可能会出现 token 粒度不稳定、关键信息误删的问题。

不要压缩所有内容

一条请求里不同部分的重要性不同:

Prompt 部分建议
system prompt尽量不压缩,尤其是安全和格式约束
developer/tool 说明谨慎压缩,保留函数名、参数、约束
用户最终问题尽量不压缩
检索文档适合压缩
历史对话适合按相关性压缩
中间日志适合压缩或摘要化

更稳的做法是分段处理,而不是把所有内容拼成一坨再交给压缩器。

项目和 Demo 地址

LLMLingua 开源项目:

https://github.com/microsoft/LLMLingua

Hugging Face Demo:

https://huggingface.co/spaces/microsoft/LLMLingua
https://huggingface.co/spaces/microsoft/LLMLingua-2

LLMLingua 的核心价值在于,把“长 Prompt 带来的成本和延迟”变成一个可控的工程问题。RAG、长对话和长文档问答里,如果上下文经常膨胀到几千甚至几万 token,Prompt 压缩可以作为模型调用前的一层预处理;但压缩强度、模型选择和评估指标必须结合业务任务来定,不能只追求 token 数字变小。


评论