共计 2327 个字符,预计需要花费 6 分钟才能阅读完成。
问题背景
GLM(General Language Model)作为一种通用语言模型,在处理长文本时依赖完整的上下文信息来维持生成质量。但在实际使用 Claude Code 调用 GLM 模型时,我们发现模型不会自动压缩历史上下文,这会导致两个严重问题:

- 内存占用线性增长:随着对话轮次增加,所有历史 token 都保留在内存中,32 轮对话后显存占用可达 12GB
- 推理速度下降:每次生成都需要处理完整上下文,第 50 轮对话的推理延迟比首轮增加 300%
通过分析 GLM 的 attention_mask 实现机制,发现其默认配置中 keep_first_round_context=True 是根本原因。这种设计虽然保证了学术测评的公平性,却不符合生产环境的需求。
技术方案
我们设计了一套动态窗口 + 优先级标记的混合策略:
- 滑动窗口机制
- 设置基础窗口大小(如 2048 tokens)
- 保留最新 N 个 token 的完整注意力关系
-
窗口外内容转为 key-value 缓存
-
关键信息标记
- 允许开发者通过特殊标记(如
<keep> 重要信息 </keep>)指定保留内容 - 被标记内容不受窗口大小限制
-
自动维护跨轮次的标记信息关联
-
动态压缩策略
- 当上下文超过阈值时触发压缩
- 使用 TF-IDF 算法识别低信息量片段
- 对非关键内容进行摘要生成(保留原意但减少 token)
代码实现
以下是 Python 实现的核心逻辑(需配合 transformers>=4.28 使用):
class SmartContextCompressor:
def __init__(self, model, window_size=2048):
self.model = model
self.window = window_size
self.kept_chunks = [] # 保存被标记的关键信息
def compress(self, full_context):
"""执行上下文压缩"""
# 第一步:提取关键标记内容
kept_spans = self._extract_tagged_content(full_context)
# 第二步:处理非关键内容
if len(full_context) > self.window:
truncated = self._apply_sliding_window(full_context)
summary = self._generate_summary(truncated)
return kept_spans + [summary]
return full_context
def _extract_tagged_content(self, text):
"""使用正则提取 <keep> 标签内容"""
pattern = r'<keep>(.*?)</keep>'
return re.findall(pattern, text)
def _generate_summary(self, text):
"""生成摘要的简化实现"""
inputs = self.model.tokenizer(
"Summarize this:" + text,
return_tensors="pt",
max_length=1024,
truncation=True
)
summary_ids = self.model.generate(
inputs.input_ids,
max_new_tokens=150
)
return self.model.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
集成到 Claude Code 的示例:
glm_model = AutoModelForCausalLM.from_pretrained("THUDM/glm-large")
compressor = SmartContextCompressor(glm_model)
# 在对话循环中调用
def chat_round(new_input, history):
full_context = history + "\n" + new_input
if len(full_context) > 2000: # 触发压缩阈值
compressed = compressor.compress(full_context)
return model.generate(compressed)
return model.generate(full_context)
性能对比
测试环境:NVIDIA V100 32GB,GLM-large 模型
| 指标 | 原始方案 (50 轮) | 优化方案 (50 轮) |
|---|---|---|
| 峰值显存占用 | 14.8GB | 6.2GB |
| 平均响应延迟 | 2.4s | 1.1s |
| 内容相关性评分 | 92% | 88% |
关键发现:
1. 显存占用降低 58%,主要得益于及时清理过期上下文
2. 延迟改善主要来自 attention 计算的 token 减少
3. 质量损失控制在 4% 内,因关键信息保留机制生效
生产环境建议
在实际部署中我们遇到几个典型问题:
- 标记泄漏问题
- 现象:
<keep>标签意外出现在最终输出中 -
解决方案:在 tokenizer 中添加特殊 token 并设置
add_special_tokens=True -
长文档摘要失真
- 现象:超过 5000 字的文档压缩后丢失核心论点
-
改进:采用分层摘要策略,先按段落摘要再整体摘要
-
多轮对话关联断裂
- 现象:压缩后忘记之前的对话主题
- 应对:维护对话主题向量,作为压缩时的参考因素
方案扩展
这个方案可适配其他主流 LLM,只需调整:
1. 对 Llama 系列模型:需修改 attention_mask 的计算方式
2. 对 GPT 类模型:可以利用其固有的 token 位置编码特性
3. 在多模态场景:需要联合考虑文本和图像的压缩策略
最终建议通过 hook 机制实现通用集成,而非硬编码到具体模型。未来可以探索基于强化学习的动态窗口调整策略,让模型自主决定最佳上下文保留范围。
