
【Bug已解決】Llama3.2 Allow batch to have 解決方案一、現(xiàn)象長什么樣用 Llama 3.2 做批量生成一次把多條 prompt 拼成一個 batch 送進model.generate時出現(xiàn)兩類故障from transformers import AutoModelForCausalLM, AutoTokenizer tok AutoTokenizer.from_pretrained(meta-llama/Llama-3.2-3B-Instruct) model AutoModelForCausalLM.from_pretrained(meta-llama/Llama-3.2-3B-Instruct) prompts [翻譯你好, 寫一首詩, 總結今天天氣晴朗適合出門散步] batch tok(prompts, paddingTrue, return_tensorspt).to(model.device) out model.generate(**batch, max_new_tokens64)故障現(xiàn)象批量生成的結果里短 prompt 的回復混進了長 prompt 的內(nèi)容或結尾錯位某些樣本生成出亂碼、提前 EOS而單條生成完全正常報錯RuntimeError: position_ids shape ... does not match ...或attention_mask相關 shape 錯加上paddingTrue后模型把 padding token 也當成要生成的內(nèi)容回復里出現(xiàn)pad或重復。最迷惑的是單條generate一切正常一上 batch 就亂。這是典型的「批量 padding 位置對齊」問題。二、背景自回歸模型做批量生成時batch 內(nèi)各樣本長度不同必須 padding 到同一長度。padding 有兩種左 paddingleft-padding在序列前面補 pad讓所有樣本的「最后一個 token」對齊到同一列。這是model.generate的默認因為生成時模型基于「最右列」預測下一個 token左 padding 保證每個樣本的有效末尾在同一位置。右 paddingright-padding在序列后面補 pad。普通tokenizer(paddingTrue)默認是右 padding。問題就出在Llama 3.2 的tokenizer默認padding_side可能是right或用戶沒顯式設left于是 batch 用的是右 padding。但generate的 KV 緩存和位置編碼是按「左 padding」假設的——右 padding 下每個樣本的有效末尾不在同一列position_ids和attention_mask與實際 token 錯位導致短樣本的有效 token 被 pad 隔開注意力算錯解碼時模型從錯誤的位置繼續(xù)生成錯位/亂碼不加pad_token_id時模型可能把 pad 當普通 token 預測回復含pad。另外Llama 3.2 的pad_token_id常被設成eos_token_id或干脆沒設batch 生成時更需要顯式處理。三、根因根因一句話Llama 3.2 批量生成時tokenizer的 padding 側(cè)默認 right與generate期望的左側(cè)對齊left不一致加上pad_token_id未正確設置導致position_ids/attention_mask與有效 token 錯位批量生成結果混亂。三點展開padding 側(cè)錯位右 padding 下各樣本有效末尾不在同列generate的緩存/位置假設失效。pad_token_id 缺失沒設pad_token_id模型把 pad 當普通 token回復含pad或提前停。position_ids 未對齊右 padding 讓絕對位置與真實 token 偏移自回歸解碼錯位。不是模型不會批量是「padding 契約」在批量路徑?jīng)]對齊。四、最小可運行復現(xiàn)不依賴真實模型模擬「右 padding vs 左 padding 在批量解碼時錯位」import torch def simulate_decode(padding_side, seqs): # seqs: 各樣本的有效 token 列表用非 0 表示有效0 表示 pad max_len max(len(s) for s in seqs) batch [] for s in seqs: if padding_side right: padded s [0] * (max_len - len(s)) # 右補 pad(0) else: padded [0] * (max_len - len(s)) s # 左補 pad(0) batch.append(padded) # generate 假設「最右列」是各樣本的有效末尾 last_col [row[-1] for row in batch] # 右 padding 時短樣本的最右列是 pad(0)模型從 pad 繼續(xù) - 錯位 broken any(v 0 for v in last_col) return batch, last_col, broken seqs [[5, 6, 7], [8, 9]] # 兩個樣本長度 3 和 2 right simulate_decode(right, seqs) left simulate_decode(left, seqs) print(右 padding 錯位:, right[2]) # True - 錯位 print(左 padding 錯位:, left[2]) # False - 正確跑出來右 padding 下短樣本最右列是 pad(0)模型從 pad 繼續(xù) → 錯位左 padding 下所有樣本有效末尾對齊 → 正確。這就是「批量生成亂」的精確復現(xiàn)。五、解決方案第一層最小直接修復最小修復批量生成前把 tokenizer 的padding_side設為left并顯式設置pad_token_id通常等于eos_token_id。from transformers import AutoModelForCausalLM, AutoTokenizer tok AutoTokenizer.from_pretrained(meta-llama/Llama-3.2-3B-Instruct) model AutoModelForCausalLM.from_pretrained(meta-llama/Llama-3.2-3B-Instruct) # 關鍵1批量生成用左 padding讓各樣本有效末尾對齊 tok.padding_side left if tok.pad_token is None: tok.pad_token tok.eos_token # 關鍵2確保有 pad_token prompts [翻譯你好, 寫一首詩, 總結今天天氣晴朗適合出門散步] batch tok(prompts, paddingTrue, return_tensorspt).to(model.device) out model.generate( **batch, max_new_tokens64, pad_token_idtok.pad_token_id, # 關鍵3顯式傳 pad_token_id ) # 解碼時跳過 prompt 部分用每個樣本實際長度切片 input_lens batch[attention_mask].sum(dim1) for i, ids in enumerate(out): reply tok.decode(ids[input_lens[i]:], skip_special_tokensTrue) print(f樣本{i}:, reply)要點tok.padding_side left讓generate的緩存/位置假設成立批量不再錯位。tok.pad_token tok.eos_token或?qū)iT的 pad確保 padding 有合法 id。pad_token_idtok.pad_token_id顯式傳入避免模型把 pad 當普通 token 預測。解碼時用attention_mask.sum得到每個樣本實際長度精準切片不把 pad 當回復。這一步單獨就讓 Llama 3.2 批量生成穩(wěn)定。六、解決方案第二層結構性改進第一層是「在批量入口改 padding_side」。但多個批量入口、多模型都需一致處理。更穩(wěn)的做法把「批量生成的 padding/解碼契約」收斂成單一策略對象。from dataclasses import dataclass, field from typing import List import torch from transformers import PreTrainedModel, PreTrainedTokenizerBase dataclass class LlamaBatchPolicy: Llama 3.2 批量生成對齊的單一策略。 # 批量生成必須用左 padding padding_side: str left # pad 是否復用 eos pad_is_eos: bool True def prepare(self, model: PreTrainedModel, tokenizer: PreTrainedTokenizerBase, prompts: List[str], max_new_tokens: int 64): # 統(tǒng)一設左 padding tokenizer.padding_side self.padding_side if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token if self.pad_is_eos else |pad| batch tokenizer(prompts, paddingTrue, return_tensorspt).to(model.device) gen_kwargs { max_new_tokens: max_new_tokens, pad_token_id: tokenizer.pad_token_id, } return batch, gen_kwargs def decode_replies(self, tokenizer, generated, batch): # 用每個樣本實際長度精準切片跳過 prompt 與 pad input_lens batch[attention_mask].sum(dim1).tolist() replies [] for i, ids in enumerate(generated): reply tokenizer.decode(ids[input_lens[i]:], skip_special_tokensTrue) replies.append(reply) return replies # 用法 policy LlamaBatchPolicy() batch, gen_kwargs policy.prepare(model, tok, prompts, max_new_tokens64) out model.generate(**batch, **gen_kwargs) replies policy.decode_replies(tok, out, batch)結構收益單一策略padding 側(cè)、pad_token、解碼切片都集中在LlamaBatchPolicy批量入口不再各自寫錯。可校驗prepare保證padding_sideleft且pad_token存在避免遺漏。可復用所有批量生成推理服務/評測共用行為一致。七、解決方案第三層斷言 / CI 守護寫 pytest 守三條(1) 批量 padding 用 left(2) pad_token 已設置(3) 解碼切片跳過 prompt 不含 pad。import torch import pytest from your_lib import LlamaBatchPolicy from transformers import AutoTokenizer pytest.fixture def policy(): return LlamaBatchPolicy(padding_sideleft, pad_is_eosTrue) def test_padding_side_is_left(policy): tok AutoTokenizer.from_pretrained(gpt2) # 模擬 prepare 設 padding_side tok.padding_side policy.padding_side assert tok.padding_side left def test_pad_token_resolved(policy): tok AutoTokenizer.from_pretrained(gpt2) if tok.pad_token is None: tok.pad_token tok.eos_token if policy.pad_is_eos else |pad| assert tok.pad_token is not None assert tok.pad_token_id is not None def test_decode_skips_prompt(): policy LlamaBatchPolicy() tok AutoTokenizer.from_pretrained(gpt2) # 構造 batch兩條長度不同的 input_ids a tok(hello, return_tensorspt) b tok(hello world, return_tensorspt) max_len max(a.input_ids.shape[1], b.input_ids.shape[1]) # 右 padding 構造 mask 示意 mask torch.cat([torch.ones(1, a.input_ids.shape[1]), torch.ones(1, b.input_ids.shape[1])], dim0) # 解碼切片長度 mask.sum lens mask.sum(dim1).tolist() assert lens[0] a.input_ids.shape[1] assert lens[1] b.input_ids.shape[1] def test_batch_consistent_across_lengths(): # 不同長度樣本應能同 batch 生成而不錯位結構校驗 policy LlamaBatchPolicy() prompts [短, 這是一條明顯更長的提示詞用于測試批量對齊是否生效] # 僅校驗策略能產(chǎn)出統(tǒng)一的 padding 配置 assert policy.padding_side leftCI 常駐跑這四條后任何「又用右 padding 批量生成」「pad_token 缺失」的回歸都會立刻爆紅。八、排查清單Llama 3.2 批量生成「亂 / 錯位」時按順序查先確認是不是「單條正常、批量亂」——是的話高度懷疑 padding 對齊。檢查tokenizer.padding_side批量生成必須left不是默認的right。確認tokenizer.pad_token不為 None必要時設tok.pad_token tok.eos_token。生成時顯式傳pad_token_idtok.pad_token_id避免模型預測 pad。解碼時用attention_mask.sum(dim1)得到每個樣本實際長度精準切片跳過 prompt/pad。多入口推理服務/評測/benchmark都過LlamaBatchPolicypadding 行為一致。升級 transformers 后跑「不同長度批量生成」冒煙斷言各樣本回復不串味、不含pad。九、小結Llama 3.2 批量生成「亂 / 錯位」的根子是tokenizer默認右 padding 與generate期望的左對齊不一致加上pad_token_id未正確設置導致position_ids/attention_mask與有效 token 錯位。修復三層次第一層批量生成前設tok.padding_sideleft、確保pad_token存在、顯式傳pad_token_id、按attention_mask精準切片第二層用LlamaBatchPolicydataclass 把 padding/pad/解碼契約收斂為單一策略第三層用 pytest 守「左 padding」「pad_token 存在」「解碼跳過 prompt」。工程啟示自回歸模型做批量生成padding 側(cè)必須用 left否則緩存與位置編碼全部錯位。這是 LLM 推理服務最高頻的坑——單條永遠正常、批量必亂記住「批量即左 padding 顯式 pad_token_id 按 mask 切片」三件套即可穩(wěn)過。