小模型原地打转:为什么模型会陷入”反复循环”以及如何解决
引言
使用小模型(7B/9B/13B)的时候,你有没有遇到过这种情况:
用户:请写一个 Python 函数计算斐波那契数列
模型:我来写一个 Python 函数计算斐波那契数列
模型:首先我们需要定义函数
模型:让我再想想,我们可以用递归
模型:或者用循环更高效
模型:好的,我来写一个 Python 函数
模型:这个函数应该接受参数 n
模型:首先我们需要定义函数
...(陷入循环,重复上述内容)
小模型在处理复杂任务时,容易出现”反复循环”的问题。 这不是模型”傻了”,而是有明确的技术原因。
一、什么是”反复循环”
典型表现
- 内容重复:反复说同一段话,每次都略有变化但整体循环
- 无法收尾:生成了几百个 token 还在”考虑”,迟迟不给最终答案
- 自我否定:先给出一个方案,然后否定,再给另一个,再否定
- 忘记上下文:说了后面的忘了前面的,导致逻辑无法推进
在高、低参数模型中的表现差异
| 对比项 | 大模型(35B+) | 小模型(7B-14B) |
|---|---|---|
| 循环发生率 | 低 | 较高 |
| 复杂任务 | 能稳定推进 | 容易卡住 |
| 自我修正 | 修正后继续推进 | 修正后可能循环 |
| 上下文利用 | 充分利用上下文 | 容易”走失” |
二、根本原因分析
原因一:注意力机制的”局部锁定”
小模型的注意力头数量有限,当上下文变长时,注意力分布倾向于聚焦在最近的 token 上,导致模型”忘记”了较早的输出内容,从而无意识地重复之前的内容。
正常的注意力分布:
[我][要][写][一][个][函][数][...][def][fib][(][n][)]
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
分散关注,能看到全局
循环时的注意力分布:
[我][要][写][一][个][函][数][...][def][fib][(][n][)]
↑↑↑↑↑↑↑↑↑↑
只盯着最近的内容,忘了前面已经写过了
原因二:模型容量不足
- 大模型(35B):总参数多,知识容量大,能更好地理解任务的完整结构
- 小模型(7B):参数少,模型的”工作记忆”有限,处理长程依赖关系时能力不足
用一个比喻:
- 大模型像资深程序员:看一眼需求就知道整体结构,不会写到一半忘记前面
- 小模型像新手:写到后面忘了前面,需要反复确认”我刚才写了什么”
原因三:概率采样的”随机游走”
模型生成 token 本质是概率采样。小模型的概率分布更”扁平”——即在多个可能的 token 之间概率接近,容易在不同选项之间来回切换:
在生成某个步骤时:
- "递归实现" 概率 35%
- "循环实现" 概率 32%
- "用数学公式" 概率 28%
→ 模型在不同方案间反复横跳
大模型的概率分布更”尖锐”:
- "循环实现" 概率 72% ← 明确的选择
- "递归实现" 概率 18%
- "用数学公式" 概率 10%
→ 迅速确定方向,继续推进
原因四:缺乏”闭环能力”
复杂任务需要模型具备 “规划-执行-检查-完成” 的闭环能力。小模型的规划能力弱,容易在”执行”环节迷失方向。
三、实际案例:我们在 9B 模型上的观察
在之前部署的体验中,9B 级别模型在处理以下任务时容易出现循环:
| 任务类型 | 循环表现 |
|---|---|
| 写代码(多函数) | 写到第二个函数时忘记第一个函数的逻辑 |
| 多步骤推理 | 推理到第三步时忘了第一步的结论 |
| 长文本总结 | 总结到后半部分内容开始重复前半部分的表述 |
| 工具调用(Function Calling) | 反复调用同一个工具,说”让我再查一下” |
而切换到 35B A3B 模型 后,同样的问题明显减少——不是因为架构变好了,而是总参数量的增加提供了更大的”工作记忆容量”。
四、解决方法
方法一:调低温度(Temperature)
最直接有效的方法:
# 循环时——降低温度,让输出更"确定"
temperature = 0.3 # 从默认的 0.7-0.8 降低
- 低温(0.1~0.3):输出更确定,减少随机游走
- 中温(0.5~0.7):平衡创意和稳定
- 高温(0.8~1.0):容易陷入循环,小模型慎用
原理:低温让高概率 token 更突出,减少在不同选项间的摇摆。
方法二:增加重复惩罚(Repetition Penalty)
# 在 llama.cpp 中
llama-server \
--repeat-penalty 1.15 \ # 默认 1.0,适当提高
--frequency-penalty 0.1 \
--presence-penalty 0.1
repeat-penalty 1.1~1.2:对已经出现过的 token 施加惩罚,有效减少循环- 但不要太高(>1.3),否则会导致语言不自然
方法三:缩短上下文窗口
小模型不适合超长上下文。如果发现循环,尝试:
- 减少
--ctx-size:从 32K 降到 16K 或 8K - 减少
history_max_length:让 Agent 不要塞入太多历史
给模型减负,让它专注在当前任务上。
方法四:优化提示词(Prompt Engineering)
让小模型”提前规划”:
❌ 不好的提示词:
"写一个计算器程序。"
✅ 好的提示词(引导模型按步骤推进):
"请按以下步骤写一个计算器程序:
1. 先定义加减乘除四个函数
2. 再写主菜单循环
3. 最后组装成完整程序
在完成步骤 1 之前不要跳到步骤 2。"
明确的步骤引导能有效防止模型”走丢”。
方法五:使用大模型验证/修正
在 Agent 架构中,可以用小模型生成初稿,大模型做最终验证:
小模型(9B)→ 生成初稿(快,可能有循环)
大模型(35B)→ 检查并修正(慢,但稳定)
这也是我们最终使用 35B A3B 配合 9B Agent 的思路。
五、不同场景的推荐配置
| 场景 | 推荐模型 | 温度 | 重复惩罚 | 上下文 |
|---|---|---|---|---|
| 简单问答 | 小模型(7-9B) | 0.7 | 1.0 | 8K |
| 代码生成 | 中/大模型(14-35B) | 0.3 | 1.15 | 16K |
| 长文总结 | 大模型(35B+) | 0.5 | 1.1 | 32K+ |
| 多轮对话 | 中/大模型 | 0.6 | 1.05 | 16K |
| 工具调用 | 中/大模型 | 0.3 | 1.1 | 8K |
六、总结
小模型循环的根本原因是”容量不足”——总参数限制了工作记忆和规划能力。
| 解决手段 | 效果 | 难度 |
|---|---|---|
| 降低温度 | ⭐⭐⭐ | 低(改参数) |
| 增加重复惩罚 | ⭐⭐⭐ | 低(改参数) |
| 缩短上下文 | ⭐⭐ | 低 |
| 优化提示词 | ⭐⭐⭐⭐ | 中 |
| 大模型验证 | ⭐⭐⭐⭐⭐ | 高(需双模型) |
| 换更大的模型 | ⭐⭐⭐⭐⭐⭐ | 终极方案 |
如果条件允许,35B A3B 这样的 MoE 模型是解决循环问题的最佳选择——既保持小模型的速度(仅激活 3B),又拥有大模型的容量(总参数 35B)。