patch_eagle3.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #!/usr/bin/env python3
  2. """Target-side EAGLE3/aux-hidden-state support for Glm5Next (DFlash2 needs it).
  3. The DFlash2 drafter consumes the TARGET's hidden states at layers
  4. 5/14/24/33/42 (draft config.json dflash_config.target_layer_ids). The vLLM
  5. spec-decode runner gates that on supports_eagle3(target) and on the model
  6. forward emitting aux states (EagleModelMixin). The day-0 nvidia Glm5Next
  7. model implements neither; the fork (local-inference-lab/vllm
  8. dev/jovian-judgement @ da4d7be6, Apache-2.0, see UPSTREAM.md) does. A full
  9. file swap is NOT possible: the fork diverges from our day-0 base in ~150
  10. lines (b12x backends, weight remaps) and our base carries fixes the fork
  11. lacks. So this is a surgical port of exactly the fork's:
  12. * EagleModelMixin on Glm5NextModel + SupportsEagle3 on both roots
  13. * dflash_capture flag (fork: speculative_config.use_dflash(), absent on
  14. our base -> method == "dflash" equivalent)
  15. * _prepare_aux_hidden_state (mHC: hc_post then hc_contract for dflash)
  16. * forward-loop aux collection + tuple return (after self.norm, fork parity)
  17. Idempotent; every anchor asserted exactly-once; py_compile-checked.
  18. """
  19. import py_compile
  20. B = "/usr/local/lib/python3.12/dist-packages/vllm/models/glm5next/nvidia"
  21. PATH = f"{B}/model.py"
  22. EDITS = [
  23. # 1. imports
  24. (
  25. """from vllm.model_executor.models.interfaces import (
  26. HasInnerState,
  27. IsHybrid,
  28. MixtureOfExperts,
  29. SupportsPP,
  30. )""",
  31. """from vllm.model_executor.models.interfaces import (
  32. EagleModelMixin,
  33. HasInnerState,
  34. IsHybrid,
  35. MixtureOfExperts,
  36. SupportsEagle3,
  37. SupportsPP,
  38. )""",
  39. ),
  40. # 2. model gets the mixin (runner sets aux layers through it)
  41. (
  42. "class Glm5NextModel(nn.Module):\n",
  43. "class Glm5NextModel(nn.Module, EagleModelMixin):\n",
  44. ),
  45. # 3. dflash capture flag in Glm5NextModel.__init__
  46. (
  47. """ self.is_sequence_parallel = (
  48. vllm_config.parallel_config.use_sequence_parallel_moe
  49. )
  50. """,
  51. """ self.is_sequence_parallel = (
  52. vllm_config.parallel_config.use_sequence_parallel_moe
  53. )
  54. _spec = vllm_config.speculative_config
  55. # fork parity: dflash drafts consume hc_contract-ed aux states
  56. # (SpeculativeConfig.use_dflash() only exists on the fork).
  57. self.dflash_capture = (
  58. _spec is not None and _spec.method == "dflash"
  59. )
  60. """,
  61. ),
  62. # 4. aux extraction method (fork body, verbatim apart from type-name quote)
  63. (
  64. """ def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
  65. return self.embed_tokens(input_ids)
  66. """,
  67. """ def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
  68. return self.embed_tokens(input_ids)
  69. def _prepare_aux_hidden_state(
  70. self,
  71. layer: "Glm5NextDecoderLayer",
  72. hidden_states: torch.Tensor,
  73. residual: torch.Tensor | None,
  74. post: torch.Tensor | None,
  75. comb: torch.Tensor | None,
  76. ) -> torch.Tensor:
  77. if not layer.mhc or residual is None:
  78. return hidden_states
  79. assert post is not None and comb is not None
  80. aux_hidden_state = layer.hc_post(hidden_states, residual, post, comb)
  81. if self.dflash_capture:
  82. return hc_contract(aux_hidden_state, layer.n)
  83. return aux_hidden_state.flatten(1)
  84. """,
  85. ),
  86. # 5. forward return type
  87. (
  88. """ ) -> torch.Tensor:
  89. if get_pp_group().is_first_rank:
  90. """,
  91. """ ) -> (
  92. torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]
  93. ):
  94. if get_pp_group().is_first_rank:
  95. """,
  96. ),
  97. # 6. aux collection in the layer loop (fork semantics: capture the state
  98. # ENTERING layer_idx+1, i.e. the OUTPUT of layer_idx)
  99. (
  100. """ for layer in self._active_layers:
  101. hidden_states, residual, post, comb = layer(
  102. positions, hidden_states, residual, post, comb
  103. )
  104. """,
  105. """ aux_hidden_states: list[torch.Tensor] = []
  106. if self.start_layer in self.aux_hidden_state_layers:
  107. aux_hidden_states.append(hidden_states)
  108. for layer_idx, layer in enumerate(
  109. self._active_layers, start=self.start_layer
  110. ):
  111. if layer_idx + 1 in self.aux_hidden_state_layers:
  112. aux_hidden_state = self._prepare_aux_hidden_state(
  113. layer, hidden_states, residual, post, comb
  114. )
  115. if self.is_sequence_parallel:
  116. aux_hidden_state = sp_all_gather(aux_hidden_state)[
  117. :full_num_tokens
  118. ]
  119. aux_hidden_states.append(aux_hidden_state)
  120. hidden_states, residual, post, comb = layer(
  121. positions, hidden_states, residual, post, comb
  122. )
  123. """,
  124. ),
  125. # 7. tuple return after norm (fork parity — logits stay well-formed)
  126. (
  127. """ hidden_states = self.norm(hidden_states)
  128. return hidden_states
  129. def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
  130. stacked_params_mapping = [
  131. """,
  132. """ hidden_states = self.norm(hidden_states)
  133. if aux_hidden_states:
  134. return hidden_states, aux_hidden_states
  135. return hidden_states
  136. def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
  137. stacked_params_mapping = [
  138. """,
  139. ),
  140. # 8. both registered roots advertise eagle3 (the runner checks the root)
  141. (
  142. """ nn.Module, HasInnerState, SupportsPP, MixtureOfExperts, IsHybrid
  143. ):
  144. """,
  145. """ nn.Module, HasInnerState, SupportsPP, MixtureOfExperts, IsHybrid,
  146. SupportsEagle3
  147. ):
  148. """,
  149. ),
  150. (
  151. """ Glm4vForConditionalGeneration, HasInnerState, IsHybrid
  152. ):
  153. """,
  154. """ Glm4vForConditionalGeneration, HasInnerState, IsHybrid, SupportsEagle3
  155. ):
  156. """,
  157. ),
  158. ]
  159. src = open(PATH).read()
  160. if "dflash_capture" in src:
  161. print(f"already patched: {PATH}")
  162. else:
  163. for i, (old, new) in enumerate(EDITS, 1):
  164. n = src.count(old)
  165. assert n == 1, f"edit {i}: anchor {n}x in {PATH} — review me"
  166. src = src.replace(old, new, 1)
  167. open(PATH, "w").write(src)
  168. py_compile.compile(PATH, doraise=True)
  169. print(f"patched: {PATH}")
  170. print("patch_eagle3 OK")