| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from dataclasses import field |
| 5 | |
| 6 | from vllm.config.model import ModelConfig |
| 7 | from vllm.config.utils import config |
| 8 | from vllm.reasoning import ReasoningParserManager |
| 9 | from vllm.tokenizers import cached_tokenizer_from_config |
| 10 | |
| 11 | |
| 12 | @config |
| 13 | class ReasoningConfig: |
| 14 | """Configuration for reasoning models. |
| 15 | |
| 16 | Set `reasoning_start_str` and `reasoning_end_str` to the strings used to |
| 17 | enter and forcibly terminate reasoning. The end string may include a |
| 18 | transition phrase before the parser's natural reasoning end marker. Token |
| 19 | IDs are derived automatically by `initialize_token_ids`. |
| 20 | """ |
| 21 | |
| 22 | reasoning_parser: str = "" |
| 23 | """The name of the ReasoningParser to use for this model.""" |
| 24 | reasoning_start_str: str = "" |
| 25 | """String that indicates the start of reasoning.""" |
| 26 | reasoning_end_str: str = "" |
| 27 | """String forced when the thinking budget is exhausted.""" |
| 28 | |
| 29 | _reasoning_start_token_ids: list[int] | None = field( |
| 30 | default=None, init=False, repr=False |
| 31 | ) |
| 32 | """Private backing field for `reasoning_start_token_ids`. Set by |
| 33 | `initialize_token_ids`. Not intended to be configured directly.""" |
| 34 | _reasoning_end_token_ids: list[int] | None = field( |
| 35 | default=None, init=False, repr=False |
| 36 | ) |
| 37 | """Private backing field for forced reasoning end token IDs.""" |
| 38 | _natural_reasoning_end_token_ids: list[int] | None = field( |
| 39 | default=None, init=False, repr=False |
| 40 | ) |
| 41 | """Token IDs that naturally terminate reasoning, as defined by the parser.""" |
| 42 | |
| 43 | _enabled: bool = field(default=False, init=False, repr=False) |
| 44 | """Private field indicating whether reasoning token IDs have been initialized. |
| 45 | Set to True by `initialize_token_ids` once token IDs are initialized.""" |
| 46 | |
| 47 | @property |
| 48 | def enabled(self) -> bool: |
| 49 | """Returns True if reasoning is enabled (i.e. if token IDs have been |
| 50 | initialized), False otherwise.""" |
| 51 | return self._enabled |
| 52 | |
| 53 | @property |
| 54 | def reasoning_start_token_ids(self) -> list[int] | None: |
| 55 | """Token IDs derived from `reasoning_start_str`. Set automatically by |
| 56 | `initialize_token_ids`. Not intended to be configured directly.""" |
| 57 | return self._reasoning_start_token_ids |
| 58 | |
| 59 | @property |
| 60 | def reasoning_end_token_ids(self) -> list[int] | None: |
| 61 | """Token IDs forced when the thinking budget is exhausted.""" |
| 62 | return self._reasoning_end_token_ids |
| 63 | |
| 64 | @property |
| 65 | def natural_reasoning_end_token_ids(self) -> list[int] | None: |
| 66 | """Token IDs that indicate the model naturally ended reasoning.""" |
| 67 | return self._natural_reasoning_end_token_ids |
| 68 | |
| 69 | def initialize_token_ids(self, model_config: ModelConfig) -> None: |
| 70 | """Initialize reasoning token IDs from strings using the tokenizer.""" |
| 71 | if ( |
| 72 | self._reasoning_start_token_ids is not None |
| 73 | and self._reasoning_end_token_ids is not None |
| 74 | and self._natural_reasoning_end_token_ids is not None |
| 75 | ): |
| 76 | self._enabled = True |
| 77 | return # Already initialized |
| 78 | |
| 79 | tokenizer = cached_tokenizer_from_config(model_config=model_config) |
| 80 | reasoning_start_str = self.reasoning_start_str |
| 81 | reasoning_end_str = self.reasoning_end_str |
| 82 | natural_reasoning_end_str = "" |
| 83 | if self.reasoning_parser: |
| 84 | parser_cls = ReasoningParserManager.get_reasoning_parser( |
| 85 | self.reasoning_parser |
| 86 | ) |
| 87 | reasoning_parser = parser_cls(tokenizer) |
| 88 | start_token = reasoning_parser.reasoning_start_str |
| 89 | if start_token and not reasoning_start_str: |
| 90 | reasoning_start_str = start_token |
| 91 | |
| 92 | end_token = reasoning_parser.reasoning_end_str |
| 93 | if end_token and not reasoning_end_str: |
| 94 | reasoning_end_str = end_token |
| 95 | natural_reasoning_end_str = end_token or "" |
| 96 | |
| 97 | if not natural_reasoning_end_str: |
| 98 | natural_reasoning_end_str = reasoning_end_str |
| 99 | |
| 100 | if not reasoning_start_str or not reasoning_end_str: |
| 101 | # If we don't have valid strings to tokenize, |
| 102 | # we can't initialize the token IDs. |
| 103 | return |
| 104 | self._reasoning_start_token_ids = tokenizer.encode( |
| 105 | reasoning_start_str, add_special_tokens=False |
| 106 | ) |
| 107 | self._reasoning_end_token_ids = tokenizer.encode( |
| 108 | reasoning_end_str, add_special_tokens=False |
| 109 | ) |
| 110 | self._natural_reasoning_end_token_ids = tokenizer.encode( |
| 111 | natural_reasoning_end_str, add_special_tokens=False |
| 112 | ) |
| 113 | |
| 114 | if ( |
| 115 | not self._reasoning_start_token_ids |
| 116 | or not self._reasoning_end_token_ids |
| 117 | or not self._natural_reasoning_end_token_ids |
| 118 | ): |
| 119 | raise ValueError( |
| 120 | f"ReasoningConfig: failed to tokenize reasoning strings: " |
| 121 | f"reasoning_start_str='{self.reasoning_start_str}', " |
| 122 | f"reasoning_end_str='{self.reasoning_end_str}'. " |
| 123 | "Ensure the strings are valid tokens in the model's vocabulary." |
| 124 | ) |
| 125 | self._enabled = True |