glm53_eagle3_aux.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env python3
  2. """Give GLM-5.3-Flash the EAGLE3 interface its DFlash2 drafter requires.
  3. vLLM's DFlash2 asks the target model for auxiliary hidden states, and refuses to
  4. start without them: `set_eagle3_aux_hidden_state_layers` raises "Model does not
  5. support EAGLE3 interface" (v1/worker/gpu/spec_decode/eagle/eagle3_utils.py).
  6. Upstream's `glm5next` model carries no aux-hidden-state code, so this adds it.
  7. `EagleModelMixin` supplies the bookkeeping. What it cannot supply is what an
  8. aux hidden state *is* for this architecture. Under mHC a non-final layer never
  9. materialises its output: it returns (x, residual, post, comb) and defers hc_post
  10. to the next layer's fused post+pre kernel. So the mixin's default of
  11. `hidden_states + residual` is the wrong tensor here, and picking it would not
  12. crash -- it would quietly cost draft acceptance. For an aux layer we apply that
  13. hc_post standalone, the same kernel the final layer and the PP boundary use,
  14. then contract the streams with hc_contract. That matches the DFlash reference
  15. capture for this model (sglang PR #36708) and DeepSeek V4's dspark capture.
  16. Aux ids are 1-based: eagle3_utils shifts the drafter's target_layer_ids by +1,
  17. so id L is the completed output of layer L-1.
  18. Pipeline parallelism is not handled. PP is off for this model, and the mixin's
  19. own pack/collect helpers cover it if that ever changes.
  20. """
  21. from __future__ import annotations
  22. from pathlib import Path
  23. VLLM = Path("/usr/local/lib/python3.12/dist-packages/vllm")
  24. MODEL = VLLM / "models/glm5next/common/model.py"
  25. LOG = "[glm53-eagle3]"
  26. EDITS: list[tuple[str, str, str]] = [
  27. (
  28. "interfaces import",
  29. """from vllm.model_executor.models.interfaces import (
  30. HasInnerState,
  31. IsHybrid,
  32. MixtureOfExperts,
  33. SupportsPP,
  34. )""",
  35. """from vllm.model_executor.models.interfaces import (
  36. EagleModelMixin,
  37. HasInnerState,
  38. IsHybrid,
  39. MixtureOfExperts,
  40. SupportsEagle3,
  41. SupportsPP,
  42. )""",
  43. ),
  44. (
  45. "Glm5NextModel mixin",
  46. "class Glm5NextModel(nn.Module):",
  47. "class Glm5NextModel(nn.Module, EagleModelMixin):",
  48. ),
  49. (
  50. "aux layer loop",
  51. """ for layer in self._active_layers:
  52. hidden_states, residual, post, comb = layer(
  53. positions, hidden_states, residual, post, comb
  54. )
  55. """,
  56. """ aux_hidden_states: list[torch.Tensor] = []
  57. for idx, layer in enumerate(self._active_layers, start=self.start_layer):
  58. hidden_states, residual, post, comb = layer(
  59. positions, hidden_states, residual, post, comb
  60. )
  61. if idx + 1 in self.aux_hidden_state_layers:
  62. aux = self._completed_layer_output(
  63. layer, hidden_states, residual, post, comb
  64. )
  65. if self.is_sequence_parallel:
  66. aux = sp_all_gather(aux)[:full_num_tokens]
  67. aux_hidden_states.append(aux)
  68. """,
  69. ),
  70. (
  71. "aux return",
  72. """ hidden_states = self.norm(hidden_states)
  73. return hidden_states
  74. """,
  75. """ hidden_states = self.norm(hidden_states)
  76. if self.aux_hidden_state_layers:
  77. # The runner unpacks a tuple whenever use_aux_hidden_state_outputs
  78. # is set, which is exactly when the layers were configured.
  79. return hidden_states, aux_hidden_states
  80. return hidden_states
  81. """,
  82. ),
  83. (
  84. "ForCausalLM SupportsEagle3",
  85. """class Glm5NextForCausalLM(
  86. nn.Module, HasInnerState, SupportsPP, MixtureOfExperts, IsHybrid
  87. ):""",
  88. """class Glm5NextForCausalLM(
  89. nn.Module, HasInnerState, SupportsPP, SupportsEagle3, MixtureOfExperts, IsHybrid
  90. ):""",
  91. ),
  92. (
  93. "ForConditionalGeneration SupportsEagle3",
  94. """class Glm5NextForConditionalGeneration(
  95. Glm4vForConditionalGeneration, HasInnerState, IsHybrid, MixtureOfExperts
  96. ):""",
  97. """class Glm5NextForConditionalGeneration(
  98. Glm4vForConditionalGeneration, HasInnerState, IsHybrid, MixtureOfExperts,
  99. SupportsEagle3
  100. ):""",
  101. ),
  102. ]
  103. COMPLETED = ''' def _completed_layer_output(
  104. self,
  105. layer: Glm5NextDecoderLayer,
  106. hidden_states: torch.Tensor,
  107. residual: torch.Tensor | None,
  108. post: torch.Tensor | None,
  109. comb: torch.Tensor | None,
  110. ) -> torch.Tensor:
  111. """The tensor an aux consumer expects: this layer's completed output.
  112. `post is None` means there is nothing deferred -- a non-mHC layer, whose
  113. hidden_states already carries residual + mlp, or the final mHC layer,
  114. already hc_post'ed and contracted.
  115. """
  116. if post is None:
  117. return hidden_states
  118. recon = layer.hc_post(hidden_states, residual, post, comb)
  119. return hc_contract(recon, layer.n)
  120. '''
  121. FORWARD_ANCHOR = """ def forward(
  122. self,
  123. input_ids: torch.Tensor | None,
  124. positions: torch.Tensor,
  125. intermediate_tensors: IntermediateTensors | None,
  126. inputs_embeds: torch.Tensor | None = None,
  127. **kwargs,
  128. ) -> torch.Tensor:"""
  129. def apply(text: str, label: str, old: str, new: str) -> str:
  130. n_old, n_new = text.count(old), text.count(new)
  131. if n_old == 0 and n_new >= 1:
  132. print("%s %s: skipped" % (LOG, label))
  133. return text
  134. if n_old != 1:
  135. raise SystemExit(
  136. "%s refuse %s (old=%d new=%d); the stock tree moved" % (LOG, label, n_old, n_new)
  137. )
  138. print("%s %s: applied" % (LOG, label))
  139. return text.replace(old, new, 1)
  140. def main() -> None:
  141. text = MODEL.read_text()
  142. for label, old, new in EDITS:
  143. text = apply(text, label, old, new)
  144. # Insert the helper just above the forward it serves.
  145. if "def _completed_layer_output" not in text:
  146. if text.count(FORWARD_ANCHOR) != 1:
  147. raise SystemExit(
  148. "%s refuse helper insert: %d forward anchors"
  149. % (LOG, text.count(FORWARD_ANCHOR))
  150. )
  151. text = text.replace(FORWARD_ANCHOR, COMPLETED + FORWARD_ANCHOR, 1)
  152. print("%s completed-output helper: applied" % LOG)
  153. else:
  154. print("%s completed-output helper: skipped" % LOG)
  155. MODEL.write_text(text)
  156. if __name__ == "__main__":
  157. main()