TGI 生成中文文本出现重复截断的 repetition_penalty 与 no_repeat_ngram_size 调参

文章导读
TGI 生成中文文本时遇到重复和截断,通常要区分两个来源:重复是采样参数不合适,截断是生成长度限制或停止条件设置不当。repetition_penalty 惩罚已经生成的 token,no_repeat_ngram_size 禁止连续 n 个 token 重复。中文没有词边界,ngram 计算的是模型 token,不是汉字短语,所以一开始不要设得太小。
📋 目录
  1. 一 前置条件
  2. 二 配置生成参数
  3. 三 调整顺序与边界
  4. 四 验证与回退
A A

TGI 生成中文文本时遇到重复和截断,通常要区分两个来源:重复是采样参数不合适,截断是生成长度限制或停止条件设置不当。repetition_penalty 惩罚已经生成的 token,no_repeat_ngram_size 禁止连续 n 个 token 重复。中文没有词边界,ngram 计算的是模型 token,不是汉字短语,所以一开始不要设得太小。

前置条件

检查你能否访问 TGI 服务的 /info 接口,确认模型名称和当前加载的版本。准备一组长中文提示词,提示词结尾要明确,比如“请接着写一段关于城市交通治理的建议”。调用时关闭流式,避免观察时被输出截断。

配置生成参数

用 curl 直接请求 /generate,在 parameters 中同时传入 repetition_penalty 和 no_repeat_ngram_size。以下是一个可复制的起始配置。

curl http://127.0.0.1:8080/generate -X POST \
  -H 'Content-Type: application/json' \
  -d '{
    "inputs": "请继续写一段关于城市交通治理的中文短文,要求逻辑清晰:",
    "parameters": {
      "max_new_tokens": 256,
      "repetition_penalty": 1.2,
      "no_repeat_ngram_size": 4,
      "temperature": 0.8,
      "do_sample": true
    }
  }'

如果返回 400 或错误信息提示未知参数,说明当前 TGI 版本不支持 no_repeat_ngram_size。优先保留 repetition_penalty,把它的值降到 1.1,再次请求。这不是保险式配置,而是为了先确定重复是否由 token 级惩罚不足引起。

调整顺序与边界

  • repetition_penalty:适用场景是文本中同一个字或词连续出现。操作动作是从 1.1 开始,每次增加 0.05,直到重复减少。验证方式是对比同一提示词生成结果中重复短语的出现次数。风险边界是值超过 1.5 后,句子会变得生硬,甚至漏掉标点。
  • no_repeat_ngram_size:适用场景是句子没有连续重复字,但上下文里反复出现整句结构。操作动作是从 3 或 4 开始,每次增加 1。验证方式是用上面 curl 请求返回的文本,人工找出长度等于当前 n 的重复片段。风险边界是值超过 6 后,内容可能被切碎,读起来不连贯。
  • max_new_tokens:不属于调参范围。当你发现每次生成都在相同长度停止,并且末尾是一个不完整句,先查看 TGI 服务端是否配置了最大输出长度。如果服务端限制是 256,客户端传 512 也不会超过。

验证与回退

验证要有固定条件。生成时加入 seed 参数,让随机采样可复现,否则无法判断是参数效果还是随机波动。

"parameters": {
  "seed": 42,
  "max_new_tokens": 256,
  "repetition_penalty": 1.2,
  "no_repeat_ngram_size": 4,
  "temperature": 0.8
}

用同一组参数生成三次,每次 seed 不同,人工检查重复和截断是否每次都出现。如果加入 seed 后重复消失,说明问题来自采样随机性,不需要继续调参。如果 no_repeat_ngram_size 不可用,回退方案是只保留 repetition_penalty,并在请求中加入 stop_sequences,让生成在完整句子处主动结束,而不是被截断在重复片段上。

另外,可以写一个 20 行内的 Python 脚本,统计输出中重复的 n-gram 数量,作为验证和回退前后的对比数据。注意这个脚本只统计可见字符,不负责判断语义。

from collections import Counter

def repeated_ngrams(text, n=4):
    text = ''.join(text.splitlines())
    seq = list(text.replace(' ', ''))
    ngrams = [tuple(seq[i:i+n]) for i in range(len(seq)-n+1)]
    return {g: c for g, c in Counter(ngrams).items() if c > 1}

print(repeated_ngrams('你的生成结果文本', 4))