encoding_compat.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """Publisher-aligned V4.1 prompt encoding: reasoning-effort table, thinking kwargs,
  2. and a completion-token cap.
  3. SGLang's OpenAI chat path only reads ``chat_template_kwargs.thinking``. vLLM and the
  4. EXL3 recipe use ``enable_thinking``. Without an alias, ``enable_thinking: true`` is
  5. ignored (SGLang's default thinking flag is off) and a client that only sends the
  6. vLLM name cannot turn thinking on.
  7. The V4.1 encoder's named effort tiers also disagree with DeepSeek's
  8. ``encoding/encoding.py`` (SGLang: low/high = 25/50; publisher: 50/75). The
  9. sitecustomize hook remaps them; ``xhigh`` stays as 75.
  10. Chat completions that omit ``max_tokens`` fill the remaining context (1M on TP4).
  11. That is how a racing-animation trial ran to 714k tokens. The 2x EXL3 recipe does
  12. not install a server cap either — vLLM does the same fill — but every smoke and
  13. README example there sends ``max_tokens``. This adapter fills *and* clamps at
  14. ``DSV41_MAX_NEW_TOKENS`` (default 32768; 0 restores the uncapped engine behaviour).
  15. """
  16. import logging
  17. import os
  18. logger = logging.getLogger(__name__)
  19. # Publisher encoding/encoding.py REASONING_EFFORT_MAPPINGS, plus SGLang's xhigh alias.
  20. PUBLISHER_REASONING_EFFORT = {'low': 50, 'high': 75, 'xhigh': 75, 'max': 100}
  21. DEFAULT_MAX_NEW_TOKENS = 32768
  22. def normalize_chat_template_kwargs(kwargs):
  23. """Copy ``enable_thinking`` onto ``thinking`` (and the reverse) in a new dict.
  24. ``thinking`` wins when both are present and disagree. ``None`` / empty input
  25. is returned unchanged so SGLang's ``SGLANG_DEFAULT_THINKING`` still applies.
  26. """
  27. if not kwargs:
  28. return kwargs
  29. out = dict(kwargs)
  30. has_thinking = 'thinking' in out
  31. has_enable = 'enable_thinking' in out
  32. if has_thinking and has_enable:
  33. thinking = bool(out['thinking'])
  34. enable = bool(out['enable_thinking'])
  35. if thinking != enable:
  36. logger.warning(
  37. 'chat_template_kwargs thinking=%r disagrees with enable_thinking=%r; '
  38. 'using thinking', out['thinking'], out['enable_thinking'])
  39. out['thinking'] = thinking
  40. out['enable_thinking'] = thinking
  41. elif has_enable:
  42. out['thinking'] = bool(out['enable_thinking'])
  43. out['enable_thinking'] = out['thinking']
  44. elif has_thinking:
  45. out['thinking'] = bool(out['thinking'])
  46. out['enable_thinking'] = out['thinking']
  47. return out
  48. def configured_max_new_tokens():
  49. """Return the completion-token default/cap, or 0 to leave the engine uncapped."""
  50. raw = os.environ.get('DSV41_MAX_NEW_TOKENS', str(DEFAULT_MAX_NEW_TOKENS)).strip()
  51. try:
  52. return int(raw)
  53. except ValueError:
  54. logger.warning('DSV41_MAX_NEW_TOKENS=%r is not an int; using %s',
  55. raw, DEFAULT_MAX_NEW_TOKENS)
  56. return DEFAULT_MAX_NEW_TOKENS
  57. def apply_request_max_tokens(request):
  58. """Fill omitted max_tokens / max_completion_tokens and clamp to the cap.
  59. Mutates ``request`` in place and returns it. ``limit <= 0`` is a no-op.
  60. ``max_completion_tokens`` wins when both fields are set, matching SGLang.
  61. """
  62. limit = configured_max_new_tokens()
  63. if limit <= 0 or request is None:
  64. return request
  65. completion = getattr(request, 'max_completion_tokens', None)
  66. tokens = getattr(request, 'max_tokens', None)
  67. current = completion if completion is not None else tokens
  68. if current is None:
  69. request.max_tokens = limit
  70. return request
  71. if current > limit:
  72. if completion is not None:
  73. request.max_completion_tokens = limit
  74. else:
  75. request.max_tokens = limit
  76. return request
  77. def install_encoder(module):
  78. """Point encoding_dsv41.REASONING_EFFORT_MAPPINGS at the publisher table."""
  79. module.REASONING_EFFORT_MAPPINGS.clear()
  80. module.REASONING_EFFORT_MAPPINGS.update(PUBLISHER_REASONING_EFFORT)
  81. def install_serving_chat(module):
  82. """Rewrite thinking kwargs and apply the completion-token cap on every chat request."""
  83. cls = module.OpenAIServingChat
  84. original = cls._process_messages
  85. def _process_messages(self, request, is_multimodal):
  86. request.chat_template_kwargs = normalize_chat_template_kwargs(
  87. request.chat_template_kwargs)
  88. return original(self, request, is_multimodal)
  89. cls._process_messages = _process_messages
  90. original_convert = getattr(cls, '_convert_to_internal_request', None)
  91. if original_convert is not None:
  92. def _convert(self, request, *args, **kwargs):
  93. apply_request_max_tokens(request)
  94. return original_convert(self, request, *args, **kwargs)
  95. cls._convert_to_internal_request = _convert
  96. limit = configured_max_new_tokens()
  97. logger.warning(
  98. 'DSV41 chat_template_kwargs: enable_thinking is accepted as an alias for thinking'
  99. + (f'; omitted max_tokens defaults and caps at {limit}' if limit > 0
  100. else '; DSV41_MAX_NEW_TOKENS=0 leaves completions uncapped'))