资讯动态

Tokenizer Padding全解析:训练与推理阶段的正确用法与踩坑经验

发布时间:2026/10/20 7:40:07 来源:尧图企业网站定制
我最初接触 Tokenizer Padding 时以为它只是“给短句子末尾补一堆没意义的占位符”直到有一次训练 loss 曲线莫名抖动、单独预测和批量预测结果对不上才意识到这个环节藏着不少约定。这篇笔记就是我在某短文本分类项目、某序列标注项目和后续生成任务里反复踩坑之后整理出来的训练与推理阶段 padding 用法总结。内容围绕三件事展开padding 到底在解决什么问题、训练和推理两个阶段各自的正确用法、以及一旦没处理好会出现哪些典型事故和对应的排查思路。最后附上一套可以直接改动使用的代码模板。适合刚接触预训练模型的同学也适合已经跑了几个项目但总在 batch 环节卡住的工程师。1. Padding 不是凑数的先弄清楚三种等待模式在解决什么问题1.1 矩阵计算要求每个 batch 内长度一致这是 padding 存在的根本原因Transformer 模型内部绝大部分计算都是张量运算attention 的核心操作依赖(batch, seq_len, hidden_dim)这样的形状。一个 batch 里如果同时出现长度为 3、5、9 的句子这三条样本根本没法叠成一个规整的矩阵参与矩阵乘法。你不是在“给模型补词”你是在“把一张长短不齐的桌子修成矩形”。但这里有一个很容易被忽略的矛盾点形状上补齐了语义上不能补。padding token 只是为了让长度对齐而存在的空位它在输入序列里并不代表任何真实信息。所以 Transformer 里才有对应的attention_mask模型在计算注意力分数时要把这些空位的位置权重遮掉。打个比方一桌人合影为了拍出一张整齐的矩形照片个子矮的人脚下要垫箱子但垫箱子不是让照片里多一个人后期必须把箱子修掉。padding 就是垫箱子attention mask 就是后期修图。前者解决整齐问题后者解决“别让人误以为箱子是人”的问题。1.2 三种常见 padding 模式longest、max_length、动态分桶我自己第一次写代码时只知道paddingTrue后来才发现这背后其实有几种不同的取舍分别对应不同的训练场景。模式基准长度是否定长显存波动实现难度适用场景longest当前 batch 内最长样本不定每个 batch 不同较大低训练阶段长度差异大的任务max_length固定长度如 512恒定小低分布式训练、需固定 shape 的部署动态分桶每个桶内最长样本桶内定长桶间不定中等中大批量训练、吞吐优先paddinglongest的好处是几乎没有多余计算缺点是每个 batch 的序列长度都在变GPU 利用率会有波动。paddingmax_length的好处是 shape 稳定坏处是短样本被白白补一大截长样本又可能被截断。动态分桶是折中方案先把样本按长度分到若干桶里每个桶内部再做 longest padding这样既减少了无意义的 pad 占比又不会剧烈波动。从实践来看训练阶段我会优先使用 longest 或动态分桶除非是分布式多卡训练中对固定 shape 有硬性要求。推理阶段则要单独考虑后面专门讲。1.3 pad token 本质上是一个真实的符号不是数字 0padding 并不是简单往input_ids里填 0。pad_token_id是词汇表里一个实际存在的 token id它有自己的 embedding 向量。你填进去的是一个有向量表示的符号只是模型通过 mask 知道这个位置不该产生真实信息。很多人会把input_ids里的 pad 位置和attention_mask里的 0 混为一谈。实际上input_ids里填的是pad_token_idattention_mask里对应位置才是 0。一个是内容符号一个是参与计算的开关两回事。还要注意并不是所有 tokenizer 都自带 pad token。我用过的某些开源模型Tokenzier 默认没有设置pad_token如果不处理训练时就会直接报错或者生成奇怪的结果。此时需要手动添加特殊 token具体做法在后面代码模板里会写。2. 训练阶段真正做对了才不会被维度错误教做人2.1 tokenizer 参数的正确组合padding、truncation、max_length 缺一不可训练阶段最常见的写法是这样encoded tokenizer( batch_texts, paddinglongest, # 或者直接写成 paddingTrue truncationTrue, max_length256, return_tensorspt, )这里要注意几个容易混淆的细节。paddingTrue是paddinglongest的等价写法它会根据当前这一批数据的最大长度来补。paddingmax_length则不同它需要配合max_length作为补齐目标长度。max_length这个参数同时也承担 truncation 的长度上限所以一旦设了它短文本会被补长长文本会被截断整套行为都在一个参数里控制。训练时我建议至少把truncationTrue一直开着。否则当某个 batch 里出现一条超长文本而你又设置了max_length却没有 truncationtokenizer 可能不会像你预期的那样截断而是把超出的内容原样返回直接打乱后面的形状假设。另外提一句tokenizer 的 padding 是针对一次调用里的多条文本而言的。如果你单条文本传入tokenizer(text)就算写了paddingTrue也不会有什么效果因为单条文本不需要对齐它是“自己和自己对齐”。2.2 labels 同样要 padding而且 pad 值必须用 -100padding 不只是输入侧的事。序列标注、文本生成这类 token 级任务里labels也是序列长度必须和input_ids对齐否则 forward 阶段计算 loss 时维度对不上。但 labels 里 padding 位置不能随便填最标准的做法是填-100。PyTorch 的CrossEntropyLoss默认把ignore_index设为-100也就是说这个位置不参与梯度计算。Transformers Trainer 的大部分任务也都沿用了这个约定。举个例子某序列标注样本经过 padding 后input_ids: [101, 2023, 2003, 1996, 102, 0, 0] labels: [101, 2023, 2003, 1996, 102, -100, -100]这里的0是 pad token id而 labels 里对应位置是-100。如果 labels 里也填 0相当于告诉模型“padding 这个位置我要预测 id 为 0 的 token”模型会把大量计算浪费在学 padding 位置的规律上训练曲线会变得非常不稳。提示分类任务的 labels 是标量不需要 padding。需要 padding 的是那些 labels 本身也是 token 序列的任务比如序列标注、摘要生成、机器翻译、文本生成。2.3 DataCollator 是训练侧管理 padding 的核心但别完全依赖默认行为在 Transformers 训练流程里DataLoader每次取出的原始样本长度各不相同需要一个collate_fn把它们整合成 batch。DataCollatorWithPadding就是为了这个场景设计的它内部会调用tokenizer.pad把 input 侧处理得很干净。from transformers import DataCollatorWithPadding collator DataCollatorWithPadding(tokenizertokenizer)但我后来发现一个问题它处理 input 没问题但 labels 的 pad 值替换成-100这件事在不同版本和不同任务里并不总是自动发生。为了让自己睡得踏实我最终选择了自定义 collator把 labels 处理逻辑写在明面上。如果你不用 Transformers 这套 Trainer而是手写 PyTorch 训练循环通常会用到torch.nn.utils.rnn.pad_sequencefrom torch.nn.utils.rnn import pad_sequence input_ids pad_sequence( [torch.tensor(item[input_ids]) for item in batch], batch_firstTrue, padding_valuetokenizer.pad_token_id, ) attention_mask pad_sequence( [torch.tensor(item[attention_mask]) for item in batch], batch_firstTrue, padding_value0, ) labels pad_sequence( [torch.tensor(item[labels]) for item in batch], batch_firstTrue, padding_value-100, )pad_sequence只会帮你把序列补齐不会自动选择合理的 pad 值input 的 pad 值、label 的 pad 值、mask 的 pad 值全靠手动传。这里最容易漏的就是 mask 那一行我之前就漏过后面仔细讲。3. 推理阶段同样的 padding为什么坑比训练还多3.1 attention mask 一旦丢失批量推理结果会出现系统性的偏差很多人觉得模型训练完就稳定了推理时少传点参数无所谓。这个想法在 padding 场景特别危险。推理阶段如果不传attention_mask模型仍然会执行完整的 attention 计算padding 位置会被当作真实 token 参与注意力权重分配。后果不是直接报错而是结果悄悄变差。具体表现有几种序列 mean pooling 时被 padding 稀释注意力权重被吸引到无意义的 pad 位上分类边界附近的样本预测被系统性拉偏。我遇到过的情况是同一个文本单独预测的结果是对的放进 batch 之后预测反而错了。后来排查出来就是因为推理脚本里组装 batch 时只放了input_ids没放attention_mask。验证方法也很简单拿一条长度差异明显的样本单独跑一次再塞进一个只有短文本的 batch 里跑一次对比两次 logits。如果结果不一致八成就是 mask 或者 padding 相关问题。3.2 左 padding 与右 padding生成模型的最后一位必须是真实内容padding 方向这事训练阶段不太容易感知但推理阶段会直接影响生成质量。对 decoder-only 这类自回归模型来说生成逻辑是从输入序列的末尾继续往后预测。如果使用的是右 padding真实内容会被挤到序列开头最后一个位置是 pad token 的隐状态。当你依靠最后一个位置的隐状态去预测下一个 token 时拿到的就是一个无意义位置的特征。这也是为什么很多生成任务的推理批次里要求左 padding。tokenizer.padding_side left设置了左 padding 以后真实内容靠右最后一个非 pad 位置就是输入的真实末尾生成起点是正确的位置。不过左 padding 也不是设置完就万事大吉。有些实现里position_ids仍然从 0 开始编号左侧的 pad 位置也会被分配一个位置编号这会导致位置信息整体偏移。签名侧的策略是改padding_side之后自己打印一次input_ids、attention_mask、position_ids确认最终喂进模型的三个张量在语义上是对齐的。3.3 单条推理与批量推理策略不同别复用同一段代码单条推理不需要 padding必要性为零。批量推理则必须处理长度对齐但我不建议无脑固定一个max_length。固定长度的问题有两个短文本被白白 pad 一截长文本又被截断导致关键信息丢失。更好的做法还是按当前 batch 内最长样本做动态 padding把 truncation 上限设高一点只用来兜底。代码上就是paddinglongest配合truncationTrue。另一个经验是训练阶段用 longest推理阶段也尽量保持 longest。虽然理论上模型能适应不同 padding 方式但完全一致的预处理策略会让训练和推理之间的分布偏移最小。4. 踩坑实录三次 padding 事故的完整排查链路4.1 事故一attention mask 忘传线上分类精度掉了一截当时某个二分类模型在训练评估时指标正常部署到线上推理服务后发现中长文本的预测结果总是偏向某一个类别。短文本基本正常长文本越是后面 pad 多偏差越明显。排查链路我如实记在这里先怀疑后处理逻辑单独构造一条短文本测试预测正常。再把这条短文本和一条长文本放进同一个 batch发现同样的内容预测结果变了。打印 batch 里的input_ids短文本后面补了一串 pad token。检查送入模型的 dict发现只有input_ids和token_type_ids没有attention_mask。修复方案是在推理脚本里显式传入attention_mask。修复后再跑同一条样本单测和 batch 结果完全一致。这也是我后来一直坚持的经验只要看到“单独跑对batch 跑不对”第一反应就是去查 padding 和 mask而不是先去调模型参数。4.2 事故二labels 没有 padding形状对不上导致 loss 计算崩溃做序列标注任务时有一次我偷懒没有自定义 collator直接用了框架默认的数据整理流程。前几个 batch 正常后来某个 batch 里所有样本长度都比较短而最长样本和最短样本差异很大loss 计算报错提示 logits 和 labels 的形状不一致。排查链路看报错信息定位到loss criterion(logits.view(-1, num_labels), labels.view(-1))这一行。打印 logits 形状发现 seq_len 维度对应的是 batch 内最长 padding 后的长度。打印 labels 形状发现它停留在每条样本的原始长度没有被补齐。根因是训练数据构造时 labels 没有经过 padding 逻辑。修复方案是在数据处理阶段让 labels 走一遍和 input_ids 完全相同的 padding 流程并且把新增位置填为-100。之后我每次写训练数据处理都会把 input 和 labels 放到同一个函数里统一处理避免出现“一个补了另一个没补”的状态。4.3 事故三固定 max_length 导致显存被 padding 吃满吞吐上不去有一个文本生成项目数据长度长尾分布严重。大部分样本只有几十个 token少部分样本接近几百甚至上千 token。我一开始图省事直接设了max_length512训练时显存一直贴着上限batch size 不敢调大吞吐低得让人着急。排查链路对数据集的 token 长度做分布统计发现中位数只有 6495 分位才是 512 左右。计算 padding 占比发现整个训练过程中超过六成的计算都在处理无意义的 pad 位置。把paddingmax_length改成paddinglongest显存占用立刻降下来batch size 可以往上提。后续我又进一步做了长度分桶把相似长度的样本放在一起同一桶内再做 longest padding。吞吐比固定长度提升了将近一倍且显存波动在可控范围内。这个教训让我形成习惯任何用固定max_length的方案都得先问一句“我的数据长度分布真的支持这个固定值吗”。如果分布很集中固定长度没问题如果长尾严重动态 padding 几乎一定是更优解。4.4 三次事故的共同根因回头看三个事故其实指向同一个本质padding 有“形状补齐”和“语义屏蔽”两层含义任何一层没做好都会出问题。事故症状根因修复分类精度掉点单独推理正确batch 推理偏移attention mask 丢失显式传attention_maskloss 崩溃logits 与 labels 形状不匹配labels 未同步 paddinglabels 补到同长度并填 -100显存吃满GPU 占用率高但有效计算少固定长度导致大量 pad动态 padding 或长度分桶5. 一套可直接抄的训练 / 推理 padding 代码模板5.1 训练侧自定义一个可靠的 collator先确认 tokenizer 有没有 pad token没有就手动加。这里有一个容易忽略的点和模型 embedding 的关系如果加载模型之后才新增了 pad token需要重新调整 embedding 矩阵大小。from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments model_name some-base-model tokenizer AutoTokenizer.from_pretrained(model_name) if tokenizer.pad_token is None: tokenizer.add_special_tokens({pad_token: [PAD]}) # 如果模型是在添加 pad token 之前加载的必须 resize embedding model AutoModelForSequenceClassification.from_pretrained(model_name, num_labels2) if len(tokenizer) ! model.config.vocab_size: model.resize_token_embeddings(len(tokenizer))然后定义一个自定义 collator把 input 和 labels 的 padding 逻辑放在同一个地方避免出现“只补 input 不补 label”的问题。import torch class UnifiedCollator: def __init__(self, tokenizer): self.tokenizer tokenizer def __call__(self, features): # 让 tokenizer 按 batch 内最长长度补齐所有序列字段 batch self.tokenizer.pad( features, paddingTrue, return_tensorspt, ) if labels in batch: # 把 labels 中 padding 占位统一替换为 -100 batch[labels] batch[labels].masked_fill( batch[labels] self.tokenizer.pad_token_id, -100 ) return batchTrainer 里直接指定trainer Trainer( modelmodel, argsTrainingArguments(output_dir./output), train_datasettrain_dataset, eval_dataseteval_dataset, data_collatorUnifiedCollator(tokenizer), )在使用 Trainer 时很多同学会直接用默认 collator结果就是 labels 的处理规则不透明。写上这个自定义版本之后整个 padding 链路就在你自己掌控下了。5.2 推理侧批量分类与批量生成分类推理就是按 batch 动态 padding注意把attention_mask也放进 model 的入参def batch_predict(texts, tokenizer, model, device, max_len256): encoded tokenizer( texts, paddinglongest, truncationTrue, max_lengthmax_len, return_tensorspt, ) encoded {k: v.to(device) for k, v in encoded.items()} with torch.no_grad(): outputs model(**encoded) probs outputs.logits.softmax(-1) return probs生成任务则要先检查 padding 方向。对 decoder-only 模型推理批量生成前把padding_side切到lefttokenizer.padding_side left encoded tokenizer( texts, paddingTrue, truncationTrue, max_length512, return_tensorspt, ) encoded {k: v.to(device) for k, v in encoded.items()} generated_ids model.generate( **encoded, max_new_tokens64, )这里最心虚的地方是position_ids。部分模型在 left padding 时会正确生成部分则不会建议打印一次序列送入前后的位置索引确认真实 token 的位置编号正确。5.3 性能优化pad_to_multiple_of 与长度分桶如果动态 padding 已经用上了还想继续优化有两个方向值得试。第一个是pad_to_multiple_of。这个参数可以把 padding 后的长度对齐到某个数字的倍数。大部分 GPU 加速库对对齐后的形状处理更友好实测在小 batch 场景下有一定加速效果虽然没有夸张到翻倍但属于零成本收益。encoded tokenizer( texts, paddinglongest, pad_to_multiple_of8, return_tensorspt, )第二个是长度分桶。做法是先统计所有训练样本的 token 长度按长度排序后切成若干桶每个桶内动态 padding并且每个 epoch 开始前打乱桶的顺序。这样既能避免浪费又不破坏训练随机性。def build_buckets(texts, bucket_size100): lengths [len(tokenizer(text)[input_ids]) for text in texts] order sorted(range(len(texts)), keylambda i: lengths[i]) buckets [order[i : i bucket_size] for i in range(0, len(order), bucket_size)] return buckets对大批量训练来说长度分桶带来的吞吐提升往往比调整学习率还明显。我测试过的某个序列标注任务里固定长度改成动态分桶后训练耗时降了大概一半。6. 我的几条实操体会写了这么多最后还是想把自己最直接的几条经验留在这里比代码更值钱。第一把 padding 当成一套完整的协议来对待input_ids负责形状attention_mask负责内容参与度labels里的-100负责梯度屏蔽。三条缺一不可每次组装 batch 时按这个清单自查。第二训练和推理阶段的 padding 策略尽量保持一致。你可以训练时用 longest、推理时用固定长度但这么做省不了多少事反而引入不必要的分布偏移。我自己已经改成两套代码全部走动态 padding。第三不要盲信默认 collator。训练脚本里如果发现数据处理环节有模糊不清的地方优先写一个显式的 collator把 padding 规则打印出来确认哪怕多花十分钟也值得。第四排障时记住一个优先级先查 data再查 model最后才调超参。凡是出现“单条对、batch 错”“训练时好时坏”“显存莫名偏高”这类问题基本都能在 padding 和 mask 这一步找到答案。第五pad token 缺失的问题在任何模型上都有可能遇到。函复的适配方式就两行代码但少了这两行后续所有 padding 逻辑都会建立在沙地上。整理完这份笔记之后我自己再写数据处理逻辑时已经把这些规则当成了默认动作。如果你顺着这套流程走一遍应该也能少踩很多原本看不到的坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑