From fef9505848e3385622a60cf64c7f081a02f96b5a Mon Sep 17 00:00:00 2001 From: Oleksandr Pichak Date: Mon, 24 Aug 2026 12:32:15 +0200 Subject: [PATCH] fix(classifier): adopt the tokenizer pad_token_id when the model config lacks one Decoder-only sequence-classification heads (Qwen2/Qwen3ForSequenceClassification) raise `Cannot handle batch sizes > 1 if no padding token is defined.` when config.pad_token_id is unset. michaelfeil/Qwen3-Reranker-0.6B-seq, produced by docs/lm_head_to_classifier/convert_lm.py, ships without that key, so infinity crashes during warmup at batch_size=32 before serving a request. No-op for every checkpoint that already carries pad_token_id. --- .../transformer/classifier/torch.py | 20 +++++++++ .../classifier/test_torch_classifer.py | 41 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/libs/infinity_emb/infinity_emb/transformer/classifier/torch.py b/libs/infinity_emb/infinity_emb/transformer/classifier/torch.py index 3c9e7045e..97f21fdb1 100644 --- a/libs/infinity_emb/infinity_emb/transformer/classifier/torch.py +++ b/libs/infinity_emb/infinity_emb/transformer/classifier/torch.py @@ -18,6 +18,24 @@ import torch +def _set_pad_token_id_if_missing(model, tokenizer) -> None: + """Copy the tokenizer's pad_token_id onto the model config when it is missing. + + Decoder-only sequence-classification heads (e.g. Qwen2/Qwen3ForSequenceClassification) + raise `Cannot handle batch sizes > 1 if no padding token is defined.` when + `config.pad_token_id` is unset, and several published checkpoints ship without it. + """ + if getattr(model.config, "pad_token_id", None) is not None: + return + pad_token_id = getattr(tokenizer, "pad_token_id", None) + if pad_token_id is None: + return + logger.info( + f"model config has no pad_token_id, adopting the tokenizer's pad_token_id={pad_token_id}" + ) + model.config.pad_token_id = pad_token_id + + class SentenceClassifier(BaseClassifer): def __init__( self, @@ -45,6 +63,8 @@ def __init__( model_kwargs=model_kwargs, ) + _set_pad_token_id_if_missing(self._pipe.model, self._pipe.tokenizer) + if ls.quantization_dtype is not None: self._pipe.model = quant_interface( # TODO: add ls.quantization_dtype and ls.placement self._pipe.model, engine_args.dtype, device=Device[self._pipe.model.device.type] diff --git a/libs/infinity_emb/tests/unit_test/transformer/classifier/test_torch_classifer.py b/libs/infinity_emb/tests/unit_test/transformer/classifier/test_torch_classifer.py index 814536d74..653e5ed3c 100644 --- a/libs/infinity_emb/tests/unit_test/transformer/classifier/test_torch_classifer.py +++ b/libs/infinity_emb/tests/unit_test/transformer/classifier/test_torch_classifer.py @@ -1,8 +1,13 @@ +from types import SimpleNamespace + import torch from transformers import pipeline # type: ignore from infinity_emb.args import EngineArgs -from infinity_emb.transformer.classifier.torch import SentenceClassifier +from infinity_emb.transformer.classifier.torch import ( + SentenceClassifier, + _set_pad_token_id_if_missing, +) def test_classifier(model_name: str = "SamLowe/roberta-base-go_emotions"): @@ -40,3 +45,37 @@ def test_classifier(model_name: str = "SamLowe/roberta-base-go_emotions"): if pred_orig_i["score"] > 0.005: assert pred_orig_i["label"] == pred_i["label"] + + +def test_set_pad_token_id_if_missing_adopts_tokenizer_value(): + """decoder-only seq-cls checkpoints often ship without config.pad_token_id. + + transformers then raises `Cannot handle batch sizes > 1 if no padding token is + defined.`, which infinity hits during warmup (batch_size 32) before serving a + single request. michaelfeil/Qwen3-Reranker-0.6B-seq is such a checkpoint. + """ + model = SimpleNamespace(config=SimpleNamespace(pad_token_id=None)) + tokenizer = SimpleNamespace(pad_token_id=151643) + + _set_pad_token_id_if_missing(model, tokenizer) + + assert model.config.pad_token_id == 151643 + + +def test_set_pad_token_id_if_missing_is_noop_when_already_set(): + """strict no-op for every model that works today (BERT rerankers, mxbai-*-seq).""" + model = SimpleNamespace(config=SimpleNamespace(pad_token_id=0)) + tokenizer = SimpleNamespace(pad_token_id=151643) + + _set_pad_token_id_if_missing(model, tokenizer) + + assert model.config.pad_token_id == 0 + + +def test_set_pad_token_id_if_missing_tolerates_tokenizer_without_pad(): + model = SimpleNamespace(config=SimpleNamespace(pad_token_id=None)) + tokenizer = SimpleNamespace(pad_token_id=None) + + _set_pad_token_id_if_missing(model, tokenizer) + + assert model.config.pad_token_id is None