DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Transformers 的 generate 结果为什么包含输入提示词

decoder-only 的 generate 返回输入加新增 token。按实际 input_ids 张量宽度裁剪,区分左补齐批次、结构化返回、pipeline 与 encoder-decoder。

把 model.generate() 的返回值整段解码后,结果从输入提示词开始,后面才接模型生成的回答。对使用 input_ids 的常规 decoder-only 文本生成,这通常是返回序列的正常结构,不足以证明模型在重复回答。

下面按 Transformers 4.57.1 的 PyTorch 接口说明。先确认 model.config.is_encoder_decoder:decoder-only 与 T5 这类 encoder-decoder 模型的返回结构不同,不能共用同一段裁剪逻辑。

在解码之前裁掉输入 token

假定 model 和 tokenizer 已匹配加载,下面的示例适用于普通文本、单条输入和 decoder-only 模型:

if model.config.is_encoder_decoder:
    raise ValueError("此示例仅适用于 decoder-only 模型")

inputs = tokenizer("Explain what a tokenizer does.", return_tensors="pt")
inputs = inputs.to(model.device)
input_width = inputs["input_ids"].shape[1]

outputs = model.generate(**inputs, max_new_tokens=64)
new_ids = outputs[:, input_width:]
answers = tokenizer.batch_decode(new_ids, skip_special_tokens=True)
print(answers[0])

input_width 是 token 数,取自实际提交给模型的张量。它不是 Python 字符串长度,也不是 UTF-8 字节数。先解码再用 text[len(prompt):] 截字符串,可能受到特殊标记跳过、空格清理和分词还原的影响。先切 token 再解码,可以避开这些差异。

如果开启了结构化返回,要先取 sequences,再按同样的宽度切:

result = model.generate(
    **inputs,
    max_new_tokens=64,
    return_dict_in_generate=True,
)
answers = tokenizer.batch_decode(
    result.sequences[:, input_width:],
    skip_special_tokens=True,
)

max_new_tokens 限制新生成的 token 数,不包括这里裁掉的输入。它不会改变返回序列保留输入的规则。

批量左补齐后,边界是张量宽度

普通 decoder-only 模型批量生成时,一般使用左侧补齐,并把 attention_mask 一起传入。先确认 tokenizer 已有适合模型的 PAD;缺失时参考 没有 padding token 的处理分支。

tokenizer.padding_side = "left"
inputs = tokenizer(
    ["Say hello.", "Explain tokenization in one sentence."],
    padding=True,
    return_tensors="pt",
).to(model.device)

input_width = inputs["input_ids"].shape[1]
outputs = model.generate(**inputs, max_new_tokens=48)
answers = tokenizer.batch_decode(
    outputs[:, input_width:],
    skip_special_tokens=True,
)

这时每行的原始文本长度可以不同,但提交的 input_ids 已补齐为相同宽度。新增 token 从这个统一宽度之后开始。不要逐行用 attention_mask.sum() 当裁剪起点:它只统计非补齐 token,左侧 PAD 仍占据返回序列的位置,按这个较小的数字切会留下部分输入。

多条候选输出也保留各自的输入前缀。启用 num_return_sequences 后,还需按该参数对应的输出顺序整理回答与输入之间的映射;不要默认一行输入始终只返回一行结果。

pipeline 有自己的返回选项

如果使用的是 pipeline("text-generation"),字符串输入可以在调用时要求只返回新增文本:

# generator 是已创建好的 text-generation pipeline。
result = generator(
    "Explain what a tokenizer does.",
    max_new_tokens=64,
    return_full_text=False,
)
print(result[0]["generated_text"])

return_full_text 是这个 pipeline 的后处理选项,不是直接传给任意模型 generate() 的通用参数。对话消息输入的返回值可能是消息列表,也不能照字符串结果机械拼接或截取。

如果裁掉输入之后,新增部分仍连续重复同一句话,才需要继续排查模型选择、对话模板和生成参数。encoder-decoder 输出、仅传 inputs_embeds 的调用、多模态模型和流式输出都需要各自核对边界,不能直接套用本文的 input_ids.shape[1]。