deepseek_v4.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import copy
  4. from typing import Any
  5. from transformers import PreTrainedTokenizerFast
  6. from vllm.entrypoints.chat_utils import ChatCompletionMessageParam
  7. from .deepseek_v4_encoding import encode_messages
  8. from .hf import HfTokenizer, get_cached_tokenizer
  9. from .protocol import TokenizerLike
  10. def get_deepseek_v4_tokenizer(tokenizer: HfTokenizer) -> HfTokenizer:
  11. """
  12. Wraps a tokenizer to use the custom DeepSeek V4 chat template encoding.
  13. """
  14. dsv4_tokenizer = copy.copy(tokenizer)
  15. added_vocab = tokenizer.get_added_vocab()
  16. added_vocab_size = len(added_vocab)
  17. tokenizer_vocab_size = tokenizer.vocab_size
  18. class _DeepseekV4Tokenizer(tokenizer.__class__): # type: ignore
  19. def apply_chat_template(
  20. self,
  21. messages: list["ChatCompletionMessageParam"],
  22. tools: list[dict[str, Any]] | None = None,
  23. **kwargs,
  24. ) -> str | list[int]:
  25. thinking = kwargs.get("thinking", False)
  26. enable_thinking = kwargs.get("enable_thinking", False)
  27. # Honor the model-card API too: callers may pass either `thinking`
  28. # (bool) or `thinking_mode` ("thinking"/"chat"). Previously `thinking_mode`
  29. # was silently dropped, so a request asking for thinking ran in chat mode.
  30. thinking = thinking or enable_thinking or kwargs.get("thinking_mode") == "thinking"
  31. thinking_mode = "thinking" if thinking else "chat"
  32. conversation = kwargs.get("conversation", messages)
  33. messages = conversation.copy()
  34. if tools is not None and len(tools) > 0:
  35. messages.insert(0, {"role": "system"})
  36. messages[0]["tools"] = tools # type: ignore[typeddict-unknown-key]
  37. reasoning_effort = kwargs.get("reasoning_effort")
  38. if not isinstance(reasoning_effort, str):
  39. reasoning_effort = None
  40. elif reasoning_effort == "none":
  41. thinking_mode = "chat"
  42. reasoning_effort = None
  43. elif reasoning_effort in ("max", "xhigh"):
  44. reasoning_effort = "max"
  45. elif reasoning_effort not in ("low", "high", "max"):
  46. reasoning_effort = None
  47. encode_config = dict(
  48. thinking_mode=thinking_mode,
  49. drop_thinking=kwargs.get("drop_thinking", True),
  50. reasoning_effort=reasoning_effort,
  51. )
  52. prompt_str = encode_messages(messages, **encode_config) # type: ignore
  53. if kwargs.get("tokenize", True):
  54. tokenizer_kwargs = {
  55. k: kwargs[k] for k in ("truncation", "max_length") if k in kwargs
  56. }
  57. return self.encode(
  58. prompt_str,
  59. add_special_tokens=False,
  60. **tokenizer_kwargs,
  61. )
  62. return prompt_str
  63. def num_special_tokens_to_add(self) -> int:
  64. return len(self.encode(""))
  65. def __len__(self) -> int:
  66. return tokenizer_vocab_size + added_vocab_size
  67. def get_added_vocab(self) -> dict[str, int]:
  68. return added_vocab.copy()
  69. def __reduce__(self):
  70. return get_deepseek_v4_tokenizer, (tokenizer,)
  71. _DeepseekV4Tokenizer.__name__ = f"DSV4{tokenizer.__class__.__name__}"
  72. dsv4_tokenizer.__class__ = _DeepseekV4Tokenizer
  73. return dsv4_tokenizer
  74. class DeepseekV4Tokenizer(TokenizerLike):
  75. @classmethod
  76. def from_pretrained(cls, *args, **kwargs) -> HfTokenizer:
  77. tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs)
  78. return get_cached_tokenizer(get_deepseek_v4_tokenizer(tokenizer))