在大模型应用中,系统提示词往往会随着规则、工具说明、安全策略和业务上下文不断增长。即使这些内容在每次请求中完全相同,模型仍然需要反复处理它们。Shopify 工程团队介绍的 gisting 技术提供了另一条路径:将长提示词压缩到一小组经过学习的“gist token”中,以减少推理阶段的输入长度,提升吞吐量并降低成本。
它不是简单地把文字改写得更短,而是让模型学习一种可复用的内部表示。这一区别决定了 gisting 的收益,也划定了它的工程边界。
Gisting 压缩的究竟是什么
典型的 LLM 请求可以抽象为:
[长期稳定的系统提示词] + [本次用户输入] -> [模型输出]
系统提示词可能包含角色定义、输出格式、拒答要求、工具协议和商家政策。当同一套规则被用于大量请求时,这段固定前缀会持续占用上下文窗口与计算资源。
Gisting 的目标是把它改造成:
[少量已学习的 gist token] + [本次用户输入] -> [模型输出]
这些 gist token 可以理解为一组经过训练得到的压缩状态。训练过程中,模型需要学会让它们承载原始提示词中与后续任务相关的信息;推理时,应用不再重复提交完整的长提示词,而是使用其对应的压缩表示。
这里有三个容易混淆的概念:
- 不是文本摘要:摘要仍然是自然语言 token,可能遗漏细粒度规则;gist token 是模型学习到的表示。
- 不等同于 KV cache:前缀缓存复用已有计算,而 gisting 试图缩短后续实际需要处理的表示。两者可以解决不同层面的问题。
- 不能靠提示词技巧直接获得:真正的 learned token 通常涉及训练或适配模型,而不是在 API 请求中写一句“请压缩以上内容”。
收益取决于复用频率,而不只是压缩率
假设原始系统提示词包含 4,000 个 token,压缩后需要 64 个 gist token。单次请求减少的输入量约为:
4000 - 64 = 3936 tokens
如果这套提示词每天被复用 100 万次,理论上的输入缩减会很可观。但这并不代表账单一定按相同比例下降。真实收益还受到模型架构、注意力计算、批处理方式、缓存命中率、输出长度和服务商计费规则影响。
可以先用下面这个可运行的 Python 脚本做容量估算。它不是 gisting 的训练实现,只用于判断某个场景是否值得投入实验。
from dataclasses import dataclass
@dataclass(frozen=True)
class Workload:
requests_per_day: int
prompt_tokens: int
gist_tokens: int
input_price_per_million: float
def estimate(workload: Workload) -> None:
if workload.gist_tokens >= workload.prompt_tokens:
raise ValueError("gist_tokens must be smaller than prompt_tokens")
saved_per_request = workload.prompt_tokens - workload.gist_tokens
saved_per_day = saved_per_request * workload.requests_per_day
estimated_saving = (
saved_per_day / 1_000_000 * workload.input_price_per_million
)
print(f"Saved tokens/request: {saved_per_request:,}")
print(f"Saved tokens/day: {saved_per_day:,}")
print(f"Estimated saving/day: ${estimated_saving:,.2f}")
if __name__ == "__main__":
estimate(
Workload(
requests_per_day=1_000_000,
prompt_tokens=4_000,
gist_tokens=64,
input_price_per_million=2.50,
)
)
运行前应把请求量、原始提示长度、目标 gist 长度和实际输入价格替换为自己的数据:
python estimate_gisting.py
这项计算只给出 token 账面上的上限估计,尚未扣除训练、评测、存储多个 gist 版本以及模型部署带来的成本。
可以怎样设计一条实验流水线
来源摘要没有给出可直接调用的训练 API,因此下面是一套可改造的伪项目接口,重点展示工程边界,而不是声称复现 Shopify 的内部实现。
from typing import Protocol
class GistModel(Protocol):
def encode_prompt(self, system_prompt: str) -> list[list[float]]: ...
def generate(
self,
gist_embeddings: list[list[float]],
user_message: str,
) -> str: ...
def build_gist(model: GistModel, system_prompt: str) -> list[list[float]]:
# encode_prompt 假设由经过 gisting 训练的模型提供。
gist = model.encode_prompt(system_prompt)
if not gist:
raise RuntimeError("model returned an empty gist")
return gist
def answer(model: GistModel, gist: list[list[float]], message: str) -> str:
return model.generate(gist_embeddings=gist, user_message=message)
实际系统还需要为每个压缩结果绑定不可变元数据,例如:
gist_id: merchant-support-v3
source_prompt_sha256: "replace-with-real-prompt-hash"
model_revision: "replace-with-training-checkpoint"
gist_length: 64
created_at: "2025-01-15T10:00:00Z"
evaluation_suite: "support-policy-regression-v7"
这样做是为了避免两个常见问题:系统提示词已经更新,线上仍在使用旧 gist;或者底层模型版本变化后,旧的压缩表示不再兼容。部署单元应当是“模型版本 + gist 版本 + 评测结果”,而不是一个脱离上下文的 embedding 文件。
最大风险是规则被压缩掉却不易察觉
自然语言系统提示词至少可以被人直接审阅,learned token 则缺少这种可解释性。压缩后的模型可能在常见样本上表现正常,却在边界条件下遗漏拒答规则、JSON 字段约束或业务例外。
评测时不能只比较平均质量分数。更稳妥的测试集应覆盖:
- 必须严格保持的安全和合规行为;
- 输出结构、字段类型及必填项;
- 提示词中优先级较低但影响严重的例外规则;
- 对抗性输入、超长输入和多轮对话;
- 原始提示词方案与 gist 方案的延迟、吞吐量和失败率。
对于金额、权限、医疗建议或政策执行等高风险任务,应设置明确的回退条件。例如,当结构校验失败或置信指标异常时,重新使用完整系统提示词处理请求,而不是强行接受压缩路径的输出。
采用前的判断清单
Gisting 更适合系统提示词很长、内容相对稳定、请求量大,而且团队能够控制模型训练或部署链路的场景。如果主要依赖封闭式模型 API,无法注入 learned token 或中间表示,那么前缀缓存、提示词裁剪和检索式上下文通常更容易落地。
进入生产环境前,至少确认以下事项:
- 固定提示词在总输入 token 中占有足够高的比例;
- 同一份提示词会被大量重复使用;
- 压缩表示与模型版本可以一起发布和回滚;
- 已建立面向规则保持能力的回归测试,而不只是通用质量评测;
- 节省的推理成本高于训练、验证和运维成本;
- 系统保留完整提示词路径,能够灰度切换和故障回退。
Gisting 的价值不在于把每一段提示词都变成不可读的向量,而在于将高频、稳定且昂贵的上下文预先编译成模型可消费的短表示。只有当评测证明关键行为没有随长度一起消失时,这种压缩才真正转化为工程收益。