Index-Translate 翻译时提示长度超限——该截断还是分片?

文章导读
遇到 Index-Translate 报长度超限,先不要动 max_tokens,也不要立刻切句子。第一步是从报错原文里判断这次超限发生在输入侧还是生成侧:报错指向送入模型的源文本/prompt 字段,通常是输入超限,该考虑分片;报错指向输出上限字段,则是生成长度不够,该调生成参数。判错方向,后面做的截断或分片都会让译文更不完整,而不是解决问题。
📋 目录
  1. A 先把报错原文和触发句子一并记录下来
  2. B 用分词器复算这条句子的 token 数
  3. C 确认上限值来自哪里
  4. D 分别试截断和分片两种改法
  5. E 把可接受的参数组合固定到配置里
A A

遇到 Index-Translate 报长度超限,先不要动 max_tokens,也不要立刻切句子。第一步是从报错原文里判断这次超限发生在输入侧还是生成侧:报错指向送入模型的源文本/prompt 字段,通常是输入超限,该考虑分片;报错指向输出上限字段,则是生成长度不够,该调生成参数。判错方向,后面做的截断或分片都会让译文更不完整,而不是解决问题。

长度超限先看报错指向哪一侧:出现 input、prompt、source 类字段,通常是输入超限,优先分片而不是硬截断;出现 max_tokens、output 类字段,是生成长度不足,调生成参数而不是切句子。操作上是先记录报错原文和触发句子,用分词器复算 token 数,再分别跑截断与分片两种改法,对比首尾句是否保留、术语译法是否一致、是否重复。风险边界:分片会打断上下文,术语可能不统一;截断会直接丢内容。上限值以当前模型配置为准,换环境要重新确认。

先把报错原文和触发句子一并记录下来

只记报错文本不够,因为同一句「长度超限」在不同实现里可能来自输入校验,也可能来自生成函数对输出的检查。必须把触发这一条报错的原始句子或原始段落一起存下来,否则你复算 token 数时只能猜是哪一段触发的,后面调参数也没法对比。

建议按下面的字段做一份记录,一个失败样例一行:

报错原文:        # 完整复制,不要只留「length exceeded」这种概括
触发句子:        # 触发报错的源文本,原文照存
句子 token 数:   # 用分词器复算后的数字
当前参数设置:     # 输入侧上限、输出侧上限、是否已开分片
调用位置:        # 哪个函数/哪段流程抛出的

判断归档时可以先粗分两类:报错里出现输入相关字段,归为输入过长;出现输出相关字段,归为生成长度不足。这两类的处理动作完全不同,混在一起记,后面会一直改错位置。

用分词器复算这条句子的 token 数

「长度超限」是相对上限说的,所以要先确认这条句子到底占多少 token,而不是凭字数判断。字符数和 token 数不是一回事,中文尤其明显。下面是一段通用统计骨架,把 tokenizer 换成你实际使用的那一个即可:

Index-Translate 翻译时提示长度超限——该截断还是分片?
from your_tokenizer import load_tokenizer

tok = load_tokenizer(config_path)

def count(text):
    ids = tok.encode(text)
    return len(ids)

samples = {
    "纯中文": "这句话全部是中文,用来测试单字切分的计数量级。",
    "纯英文": "This sentence is all English words for comparison.",
    "中英混合": "这里 mix 了一些 English 单词,用来观察混合切分。",
}
for name, s in samples.items():
    print(name, count(s), "chars=", len(s))

同一句里,中文往往一个字对应一个或多个 token,英文一个单词可能被切成若干子词,中英混合会同时叠加这两种切分方式。所以用「字数」估算上限经常偏小或偏大,需要结合当前分词器实际跑一遍。复算时要用送进模型的完整文本(含系统提示、待翻译正文、拼接模板),只算正文有时会漏掉一大截。

确认上限值来自哪里

改参数前先找上限的真实来源,三个地方分别看:

  • 模型配置:模型或服务声明的最大上下文长度、最大输出长度,通常写在模型配置、服务端参数或调用方初始化里。这里是硬上限,改代码里的截断值绕不过它。
  • 分词器:分词器自身的 model_max_length 一类的属性,有时会和模型配置不一致。以实际加载的分词器为准,别只看文档数字。
  • 生成函数:负责拼接和发送请求的那个函数,输入侧的检查、输出侧的上限参数通常在此设置。报错常在这里抛出。

如果这几处都查不到明确数值,或配置分散难以确认,就用通用脚本骨架探边界:

# 逐步加长输入,观察在哪一步开始报长度错误,不写死具体数值
for n in range(step, hard_guard, step):
    text = "测" * n
    try:
        call_translate(text)
    except LengthError as e:
        print("首次失败长度:", n, "报错:", e)
        break

探出来的边界只对当前环境有效,换模型、换分词器、换服务端配置都要重新确认。

分别试截断和分片两种改法

不要靠猜,把两种改法各跑一遍,用输出结果来选。截断是超出部分直接丢;分片是按句子或段落切开,逐片翻译再合并。

Index-Translate 翻译时提示长度超限——该截断还是分片?
# 截断骨架:按 token 上限裁掉尾部
def truncate(text, tok, limit):
    ids = tok.encode(text)[:limit]
    return tok.decode(ids)

# 分片骨架:按句切,逐片翻译后合并
def split_translate(text, tok, limit, call_translate):
    pieces, buf, out = split_sentences(text), "", []
    for s in pieces:
        if len(tok.encode(buf + s)) > limit and buf:
            out.append(call_translate(buf)); buf = s
        else:
            buf += s
    if buf:
        out.append(call_translate(buf))
    return "".join(out)

跑完后按这几项对比,而不是看谁快:

  • 首句和尾句是否保留——截断最容易丢尾句。
  • 术语译法是否一致——分片后同一术语可能被翻成不同说法。
  • 是否出现重复片段——分片边界处理不当会重复翻译连接处。
  • 段落结构是否还能对应原文。

如果文档结构完整、术语重要,分片通常更合适,但要在合并时做术语统一;如果只是短文本偶发超限、尾部内容可舍弃,截断更省事。两者都试,看哪种丢的信息你更不能接受。

把可接受的参数组合固定到配置里

选定改法后,把参数写进配置文件,让同类文档下次直接复用,不用重新试。下面是一份示例结构,数值按你的环境实际探测填写,不要直接照抄:

translate:
  input_limit: <复算后的输入上限>      # 输入侧允许的最大 token
  output_limit: <生成侧上限>           # 输出侧允许的最大 token
  split_threshold: <分片阈值>          # 超过该值触发分片
  strategy: split                      # split 或 truncate
  retry:
    on_length_error: true
    split_more: true                   # 仍超限时进一步细分
    max_retry: <重试次数>

验证方式是拿之前记录的失败样例重跑:同一批触发句子在固定配置下不再报长度错误,且首尾句、术语一致性通过上面的对比清单。确认后把这批样例留存为回归用例,模型或分词器变更后再跑一次,因为上限值会随环境变化,配置里的数值需要跟着更新。