hiyouga/LlamaFactory · error · ValueError

Stop words are required to replace the EOS token.

Error message

Stop words are required to replace the EOS token.

What it means

Some templates set replace_eos=True to make the tokenizer's EOS a template-specific stop word (the first entry of stop_words). If stop_words is empty or None for such a template, there is nothing to swap in and LlamaFactory raises this ValueError in fix_special_tokens before tokenization starts.

Source

Thrown at src/llamafactory/data/template.py:194

            return

        is_added = tokenizer.eos_token_id is None
        num_added_tokens = tokenizer.add_special_tokens({"eos_token": eos_token})

        if is_added:
            logger.info_rank0(f"Add eos token: {tokenizer.eos_token}.")
        else:
            logger.info_rank0(f"Replace eos token: {tokenizer.eos_token}.")

        if num_added_tokens > 0:
            logger.warning_rank0("New tokens have been added, make sure `resize_vocab` is True.")

    def fix_special_tokens(self, tokenizer: "PreTrainedTokenizer") -> None:
        r"""Add eos token and pad token to the tokenizer."""
        stop_words = self.stop_words
        if self.replace_eos:
            if not stop_words:
                raise ValueError("Stop words are required to replace the EOS token.")

            self._add_or_replace_eos_token(tokenizer, eos_token=stop_words[0])
            stop_words = stop_words[1:]

        if tokenizer.eos_token_id is None:
            self._add_or_replace_eos_token(tokenizer, eos_token="<|endoftext|>")

        if tokenizer.pad_token_id is None:
            tokenizer.pad_token = tokenizer.eos_token
            logger.info_rank0(f"Add pad token: {tokenizer.pad_token}")

        if stop_words:
            try:
                num_added_tokens = tokenizer.add_special_tokens(
                    dict(additional_special_tokens=stop_words), replace_additional_special_tokens=False
                )
            except TypeError:
                num_added_tokens = tokenizer.add_special_tokens(dict(additional_special_tokens=stop_words))

View on GitHub (pinned to f28afaf635)

Solutions

  1. If you authored the template, provide stop_words=["<your_eos>"] so the first entry can replace the EOS token.
  2. If you do not need EOS replacement, remove replace_eos=True from your register_template call.
  3. If using a built-in template, report the misconfiguration upstream — built-in templates with replace_eos always ship stop_words.

Example fix

# before
register_template(name="custom", ..., replace_eos=True)

# after
register_template(name="custom", ..., stop_words=["<|im_end|>"], replace_eos=True)
Defensive patterns

Strategy: validation

Validate before calling

def template_eos_config_ok(template) -> bool:
    return not template.replace_eos or bool(template.stop_words)

Prevention

When it happens

Trigger: Using a built-in template that declares replace_eos=True but has no stop_words (only possible if the template registry entry was misconstructed, e.g. by a plugin or a modified template); creating a custom template with replace_eos=True while omitting the stop_words argument in register_template; unpickling or copying a Template object whose stop_words were cleared.

Common situations: Writing a custom template and setting replace_eos=True without understanding it consumes stop_words[0] as the new EOS; library version changes that renamed the parameter; subclassing Template and overriding stop_words with None.

Related errors


AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14). Data as JSON: /api/errors/335a78401c446d39. Report an issue: GitHub.