尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Bug已解决】GPTNeo Error Attempting to Generate Text 解决方案

【Bug已解决】GPTNeo Error Attempting to Generate Text 解决方案 【Bug已解决】GPTNeo Error Attempting to Generate Text 解决方案一、现象长什么样用 GPT-NeoEleutherAI 的 GPT-Neo如EleutherAI/gpt-neo-125M调用model.generate(...)时常见几种报错ValueError: Cannot use past_key_values with a length ! input_ids length或IndexError: index out of range in self又或者RuntimeError: Expected attention_mask to have length X but got Y还有一种不报错但结果异常的情况generate 出来的全是重复 token 或一个固定 token看似生成了但内容无意义。GPT-Neo 的特殊点在于它的注意力实现它用GPTNeoSelfAttention的局部全局注意力类似 Sparse Transformer并依赖attention_mask同时做因果掩码和长度控制。在generate的自回归循环里每步新生成的 token 需要与past_key_values对齐而 GPT-Neo 的掩码逻辑在从第二步起只喂一个新 token时容易因为attention_mask长度没同步缩短、或position_ids没递增导致形状/索引错误。最迷惑的是第一次前向prompt 编码正常一进入 generate 的自回归第二步就炸——典型的单步 OK、自回归失败。二、背景generate的工作方式是先用 prompt 跑一次前向得到past_key_valuesKV cache之后每一步只把新生成的 1 个 token喂进去并复用 KV cache。这就要求每一步的输入长度1且attention_mask/position_ids都与当前步对齐。GPT-Neo 的注意力因为含全局注意力头某些 head 看完整序列对attention_mask的处理比标准因果注意力更挑剔past_key_values长度校验GPT-Neo 在forward里会检查past_key_values的序列维是否和当前input_ids累积长度一致。如果 generate 时attention_mask仍保持初始 prompt 长度没按步裁剪校验会失败。position_ids未递增GPT-Neo 用绝对位置编码generate 第二步需要position_ids last_pos 1若沿用 prompt 的 position_ids索引越界或取到错误位置。全局注意力头的长度假设全局头期望看到完整序列KV cache 拼接后长度变化若没同步到掩码会导致 mask 与 query 长度不符。下面用可运行代码复现generate 第二步 attention_mask 长度未同步导致报错的机制。三、根因根因一句话GPT-Neo 的generate自回归循环中past_key_values与attention_mask/position_ids的长度/索引未正确同步——第二步只喂 1 个新 token但掩码仍按 prompt 长度、position 未递增导致形状校验失败或索引越界。三个具体失配attention_mask 未随步裁剪第二步attention_mask长度应与当前累积序列一致而非停在 prompt 长度。position_ids 未递增绝对位置编码下新 token 的 position 应是上一步 1。全局注意力头对长度敏感GPT-Neo 的全局头要求掩码与 query 长度对齐否则 mask 形状校验失败。四、最小可运行复现用纯 Python 模拟generate 第二步input_ids 长度1但 attention_mask 仍prompt 长度导致校验失败from dataclasses import dataclass from typing import List dataclass class GenState: input_len: int past_len: int mask_len: int def check_step(state: GenState): 模拟 GPT-Neo forward 对 past_key_values 与 mask 的校验。 # 自回归第二步input_ids 长度1累积长度 past_len 1 expected state.past_len 1 if state.input_len ! 1: raise RuntimeError(f自回归步 input_ids 长度应为 1实际 {state.input_len}) if state.mask_len ! expected: raise RuntimeError( fattention_mask 长度 {state.mask_len} ! 累积长度 {expected} fpast 与 mask 不同步 ) def main(): # 错误第二步 input_ids1但 mask 还停在 prompt 长度 5past5 bad GenState(input_len1, past_len5, mask_len5) try: check_step(bad) except RuntimeError as e: print(复现到 generate 报错:, e) # 正确mask 同步为 6 good GenState(input_len1, past_len5, mask_len6) check_step(good) print(修正后mask 与 past 同步generate 第二步通过) if __name__ __main__: main()运行会打印复现到 generate 报错: attention_mask 长度 5 ! 累积长度 6 ...正是 GPT-Neo generate 第二步掩码未同步的本质。五、解决方案第一层最小直接修复最立竿见影的修复在自回归循环里每步把attention_mask正确扩展到累积长度并把position_ids递增 1。也就是不要依赖model.generate的默认行为有时 GPT-Neo 的特定配置会让默认行为漏掉而是用prepare_inputs_for_generation的正确返回。import torch def step_generate(model, input_ids, attention_mask, past_key_values, position_ids): 修复版自回归单步mask 与 position 正确同步。 # 新 token 的 position 上一步最后一个 position 1 next_position position_ids[:, -1:] 1 # attention_mask 追加一位新 token 可见 next_mask torch.cat([attention_mask, torch.ones_like(input_ids)], dim1) out model( input_idsinput_ids, attention_masknext_mask, position_idsnext_position, past_key_valuespast_key_values, use_cacheTrue, ) return out, next_mask, next_position def main(): # 示意prompt 长度 5第一步后得到 past第二步用长度 1 的 input prompt_len 5 position_ids torch.arange(prompt_len).unsqueeze(0) # [1, 5] attention_mask torch.ones(1, prompt_len) # 第二步input_ids 长度 1mask 应为 6position 应为 5 new_ids torch.randint(0, 100, (1, 1)) out, mask, pos step_generate(None, new_ids, attention_mask, None, position_ids) # 实际应 print(mask.shape, pos.shape) 验证同步 print(修复关键第二步 mask 长度6, position[5]与 past(5)1 对齐) if __name__ __main__: main()第一层修复让 mask 与 position 在自回归每步同步消除 generate 第二步的校验失败。六、解决方案第二层结构性改进把自回归每步必须同步 mask/position/past收口成一个ARState状态机封装advance方法调用方只管喂新 token同步逻辑全在内部。import torch from dataclasses import dataclass, field dataclass class ARState: input_ids: torch.Tensor attention_mask: torch.Tensor position_ids: torch.Tensor past_key_values: object None def advance(self, new_token: torch.Tensor): # 同步mask 追加、position 递增、input 换成新 token self.attention_mask torch.cat( [self.attention_mask, torch.ones_like(new_token)], dim1) self.position_ids torch.cat( [self.position_ids, self.position_ids[:, -1:] 1], dim1) self.input_ids new_token return self def ready_for_step(self): assert self.input_ids.shape[1] 1, 自回归步 input 长度应为 1 assert self.attention_mask.shape[1] self.position_ids.shape[1] return { input_ids: self.input_ids, attention_mask: self.attention_mask, position_ids: self.position_ids, past_key_values: self.past_key_values, } def main(): st ARState( input_idstorch.randint(0, 100, (1, 1)), attention_masktorch.ones(1, 1), position_idstorch.zeros(1, 1, dtypetorch.long), ) for _ in range(3): st.advance(torch.randint(0, 100, (1, 1))) inp st.ready_for_step() print(ARState 同步后mask 长度 , inp[attention_mask].shape[1], position 长度 , inp[position_ids].shape[1]) if __name__ __main__: main()第二层的关键是ARState.advance把mask 追加 position 递增固化ready_for_step还带了断言任何一步不同步都会被立即发现。七、解决方案第三层断言 / CI 守护加 pytest 守护(1) 第二步input_ids长度必须为 1(2)attention_mask长度必须等于past_len1(3)position_ids必须随步递增。import torch import pytest class ARState: def __init__(self): self.input_ids torch.randint(0, 100, (1, 1)) self.mask torch.ones(1, 1) self.pos torch.zeros(1, 1, dtypetorch.long) self.past_len 0 def advance(self, tok): self.mask torch.cat([self.mask, torch.ones_like(tok)], 1) self.pos torch.cat([self.pos, self.pos[:, -1:] 1], 1) self.input_ids tok self.past_len 1 def test_step_input_len_one(): st ARState() st.advance(torch.randint(0, 100, (1, 1))) assert st.input_ids.shape[1] 1 def test_mask_aligned_with_past(): st ARState() st.advance(torch.randint(0, 100, (1, 1))) assert st.mask.shape[1] st.past_len 1 def test_position_increments(): st ARState() for _ in range(3): st.advance(torch.randint(0, 100, (1, 1))) assert st.pos[0, -1].item() 3 # 第 4 个位置索引应为 3 if __name__ __main__: pytest.main([__file__, -q])CI 里test_mask_aligned_with_past通过就能保证自回归每步 mask 与 past 同步防止 GPT-Neo generate 的回归。八、排查清单GPT-Neogenerate报错时按此顺序查看是第一步还是第二步炸第一步正常、第二步炸基本锁定 past/mask/position 同步问题。打印第二步的attention_mask.shape与past_key_values序列维不等长就是根因。检查position_idsGPT-Neo 用绝对位置确认每步 position 递增 1。确认use_cacheTrue且 past 被传入没传 past 会每步重算全序列长度对不上。注意全局注意力头GPT-Neo 的全局头对 mask 长度敏感mask 必须严格等于当前累积序列长。优先用model.generate的标准调用多数情况框架会处理好若手动循环用ARState封装同步。升级 transformers部分 GPT-Neo generate 问题已在较新版本修复。九、小结GPT-Neogenerate报错根因不在模型装错而在自回归循环里past_key_values与attention_mask/position_ids的长度/索引未同步第二步只喂 1 个新 token但注意力掩码还停在 prompt 长度、位置编码没递增GPT-Neo 的全局注意力头对长度敏感于是形状校验失败或索引越界。表现常为prompt 编码正常、generate 第二步炸——典型的单步 OK、自回归失败。修复三层第一层在自回归每步把attention_mask扩展到累积长度、position_ids递增 1第二层用ARState状态机把 mask/position/past 同步封装ready_for_step带断言第三层用 pytest 断言第二步 input 长度1、mask 与 past 对齐、position 递增。记住GPT-Neo 自回归past、mask、position 三步必须齐步走少同步一个第二步就炸。
返回列表