You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用HuggingFace微调SmolLM3模型时遇张量尺寸不匹配RuntimeError

解决SmolLM3-3B-Base SFT微调中的令牌配置错误与张量维度不匹配问题

问题现象

  • 令牌配置警告:tokenizer的PAD/BOS/EOS令牌与模型配置、生成配置不一致,系统对齐后令牌为{'bos_token_id': None, 'pad_token_id': None}
  • 运行时错误:RuntimeError: The size of tensor a (4) must match the size of tensor b (8) at non-singleton dimension 0

报错栈信息

The tokenizer has new PAD/BOS/EOS tokens that differ from the model config and generation config. The model config and generation config were aligned accordingly, being updated with the tokenizer's values. Updated tokens: {'bos_token_id': None, 'pad_token_id': None}.
  0%|          | 0/1000 [00:00<?, ?it/s]
Traceback (most recent call last):
  File "/tmp/script.py", line 199, in <module>
    trainer.train()
  File "/root/.cache/uv/environments-v2/script-912247c0edd68a55/lib/python3.12/site-packages/transformers/trainer.py", line 2328, in train
    return inner_training_loop(
           ^^^^^^^^^^^^^^^^^^^^
  File "/root/.cache/uv/environments-v2/script-912247c0edd68a55/lib/python3.12/site-packages/transformers/trainer.py", line 2672, in _inner_training_loop
    tr_loss_step = self.training_step(model, inputs, num_items_in_batch)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.cache/uv/environments-v2/script-912247c0edd68a55/lib/python3.12/site-packages/trl/trainer/sft_trainer.py", line 1189, in training_step
    return super().training_step(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.cache/uv/environments-v2/script-912247c0edd68a55/lib/python3.12/site-packages/transformers/trainer.py", line 4009, in training_step
    loss = self.compute_loss(model, inputs, num_items_in_batch=num_items_in_batch)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.cache/uv/environments-v2/script-912247c0edd68a55/lib/python3.12/site-packages/trl/trainer/sft_trainer.py", line 1123, in compute_loss
    entropy = torch.sum(per_token_entropy * attention_mask) / attention_mask.sum()
                        ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
RuntimeError: The size of tensor a (4) must match the size of tensor b (8) at non-singleton dimension 0
  0%|          | 0/1000 [00:01<?, ?it/s]

触发错误的代码

from trl import SFTTrainer, SFTConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from typing import Optional, Dict, Any

# Load model and tokenizer
model = AutoModelForCausalLM.from_pretrained("HuggingFaceTB/SmolLM3-3B-Base")
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM3-3B-Base")

# Load dataset
dataset = load_dataset("HuggingFaceTB/smoltalk2", "SFT", split="s1k_1.1_think")

SYSTEM_PROMPT = "You are a helpful assistant."

CHAT_TEMPLATE = (
    "<|im_start|>system\n{system_prompt}<|im_end|>\n"
    "<|im_start|>user\n{instruction}{input_section}<|im_end|>\n"
    "<|im_start|>assistant\n"
)

CHAT_CONFIG = {
        "custom_instructions": "",
        "enable_thinking": True,
        # Tool lists are accepted but unused by this simple SFT preprocessing.
        "python_tools": [],
        "xml_tools": [],
}

def get_system_prompt(chat_config: Dict[str, Any]) -> str:
        """Compose the short system prompt from `CHAT_CONFIG`.

        - If `custom_instructions` is provided, it is used instead of the default
            `SYSTEM_PROMPT`.
        - If `enable_thinking` is True we append a short instruction that allows
            the model to 'think' or include chain-of-thought style tokens. This is a
            lightweight mechanism used only to alter the system prompt text.
        """
        prompt = chat_config.get("custom_instructions") or SYSTEM_PROMPT
        if chat_config.get("enable_thinking"):
                # A minimal, explicit hint that encourages internal 'thinking' — keep it
                # short so tokenization and label alignment remain straightforward.
                prompt += "\nYou may think through steps before answering."
        return prompt

def build_prompt(instruction: str, input_text: Optional[str], system_prompt: Optional[str] = None) -> str:
    """Construct the prompt (system + user) but stop before the assistant response.

    The returned string ends with the assistant start marker so the response can be
    concatenated and tokenized as a single sequence.
    """
    input_section = f"\n{input_text}" if input_text and len(input_text) > 0 else ""
    # If a system_prompt is explicitly passed use it; otherwise build one from
    # the `CHAT_CONFIG` so callers can control `custom_instructions` and
    # `enable_thinking` settings without changing this file.
    system_prompt = system_prompt or get_system_prompt(CHAT_CONFIG)
    return CHAT_TEMPLATE.format(instruction=instruction, input_section=input_section, system_prompt=system_prompt)

def tokenize_for_sft(example: Dict[str, Any], max_length: int = 512) -> Dict[str, Any]:
    """Tokenize a single example for SFT.

    Expects the dataset example to contain one of these response fields: `response`, `output`,
    `answer`, or `text`. For the prompt it prefers `instruction` and optional `input`.

    Returns a dict with `input_ids`, `attention_mask`, and `labels` where label tokens
    corresponding to the prompt are set to -100.
    """
    # Find prompt fields
    instruction = example.get("instruction") or example.get("prompt") or example.get("query") or ""
    input_text = example.get("input") or example.get("context") or ""

    # Find response field
    response = (
        example.get("response")
        or example.get("output")
        or example.get("answer")
        or example.get("text")
        or ""
    )

    prompt = build_prompt(instruction, input_text)
    assistant_end = "<|im_end|>\n"
    full_text = prompt + response + assistant_end + (tokenizer.eos_token or "")

    # Ensure pad_token_id is set (some tokenizers/models don't have it)
    if tokenizer.pad_token_id is None:
        # Prefer eos_token_id as pad_token_id fallback
        tokenizer.pad_token_id = tokenizer.eos_token_id if tokenizer.eos_token_id is not None else 0
        model.config.pad_token_id = tokenizer.pad_token_id

    # Tokenize prompt and full_text separately so we can find prompt length
    tokenized_full = tokenizer(
        full_text,
        truncation=True,
        max_length=max_length,
        padding="max_length",
        return_tensors=None
    )

    tokenized_prompt = tokenizer(
        prompt,
        truncation=True,
        max_length=max_length,
        padding=False,
        return_tensors=None,
    )

    input_ids = tokenized_full["input_ids"]
    attention_mask = tokenized_full["attention_mask"]

    # Build labels: copy input_ids then mask prompt tokens with -100
    labels = list(input_ids)
    prompt_len = len(tokenized_prompt["input_ids"])
    # If prompt_len >= max_length then there's no room for the response; labels become all -100
    for i in range(min(prompt_len, len(labels))):
        labels[i] = -100

    return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels}

# Map preprocessing over the training dataset and keep the processed dataset for trainer
# Note: we remove other columns so the dataset only contains tensors needed by the trainer.
max_seq_length = 512
processed_train = dataset.map(
    lambda ex: tokenize_for_sft(ex, max_length=max_seq_length),
    remove_columns=[c for c in dataset.column_names],
)


# Configure training

config = SFTConfig(
    output_dir="./smollm3-jobs-sft",
    # Balanced GPU Memory
    per_device_train_batch_size=4,
    gradient_accumulation_steps = 4,
    learning_rate=1e-4,
    max_steps=1000,
    # Short sequences
    max_length = 1024,
    #max_length=4096,
    #packing=True,
    #assistant_only_loss=True,
    logging_steps=50,
    save_steps=200,
    push_to_hub=False,
    hub_model_id="hubsnippetai/smollm3-jobs-sft"
)
# Train
trainer = SFTTrainer(
    model=model,
    train_dataset=processed_train,
    args=config,
)
trainer.train()

解决方案

1. 修复令牌配置问题

SmolLM3的tokenizer默认未设置pad_token和bos_token,需手动指定并同步到模型配置:

# 加载模型和tokenizer后立即执行
model = AutoModelForCausalLM.from_pretrained("HuggingFaceTB/SmolLM3-3B-Base")
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM3-3B-Base")

# 设置pad_token为eos_token,bos_token也用eos_token替代(SmolLM3无单独bos_token)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.bos_token = tokenizer.eos_token

# 同步模型配置
model.config.pad_token_id = tokenizer.pad_token_id
model.config.bos_token_id = tokenizer.bos_token_id
model.config.eos_token_id = tokenizer.eos_token_id

2. 统一序列长度参数

报错核心原因是预处理时的max_length(512)与SFTConfig中的max_length(1024)不一致,导致训练时张量维度冲突。修改为统一值:

# 预处理部分
max_seq_length = 512
processed_train = dataset.map(
    lambda ex: tokenize_for_sft(ex, max_length=max_seq_length),
    remove_columns=[c for c in dataset.column_names],
)

# SFTConfig部分
config = SFTConfig(
    output_dir="./smollm3-jobs-sft",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    learning_rate=1e-4,
    max_steps=1000,
    max_length=512,  # 和预处理时保持一致
    logging_steps=50,
    save_steps=200,
    push_to_hub=False,
    hub_model_id="hubsnippetai/smollm3-jobs-sft"
)

3. 优化标签掩码逻辑(可选)

原代码的标签掩码逻辑存在冗余,可简化为更直观的写法,避免索引越界:

def tokenize_for_sft(example: Dict[str, Any], max_length: int = 512) -> Dict[str, Any]:
    # ... 其他代码保持不变 ...

    input_ids = tokenized_full["input_ids"]
    attention_mask = tokenized_full["attention_mask"]

    # 初始化标签为全-100,仅保留响应部分的标签
    labels = [-100] * len(input_ids)
    prompt_len = len(tokenized_prompt["input_ids"])
    
    # 确保prompt_len不超过序列长度
    if prompt_len < len(input_ids):
        labels[prompt_len:] = input_ids[prompt_len:]

    return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels}

内容的提问来源于stack exchange,提问作者Kingsley Uchunor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 06:35:55