v2 (inheriting SupportsEagle3 as a base) taught the real lesson: the isinstance
check PASSES and the class then dies at creation with
'Cannot create a consistent method resolution order (MRO) for bases ...'
-- GLM's outer wrapper already inherits vLLM Protocols (SupportsPP,
MixtureOfExperts, HasInnerState, IsHybrid) whose ordering is incompatible with
appending another Protocol base.
v3 avoids the MRO entirely: declare
supports_eagle3: ClassVar[Literal[True]] = True
as a class attribute on Glm5NextForCausalLM. Runtime-checkable Protocols do
presence-based isinstance, so the bare flag satisfies supports_eagle3(model)
without touching the MRO. The aux-layer plumbing stays on the trunk via
EagleModelMixin.
base 008eeaa2... -> patched 4c9a632b..., apply-verified against the pinned
image (patch -p1 reproduces the sha256 and matches the mounted dflash-patch
file byte for byte).
122 lines
6.1 KiB
Diff
122 lines
6.1 KiB
Diff
# glm53-dflash-01-aux-hidden-states.patch — give the GLM trunk the EAGLE3 / DFlash2
|
|
# auxiliary-hidden-state interface.
|
|
#
|
|
# STATUS: WIP, and INDEPENDENT of the compile changelist (01-05). It patches the same
|
|
# file as glm53-compile-01-model-decorator.patch, so if both are ever applied, apply 01
|
|
# first. Not part of the deployed shape.
|
|
#
|
|
# WHY. `--speculative-config '{"method":"dflash","model":"/path/to/drafter", ...}'` with
|
|
# incoai/GLM-5.3-Flash-DFlash2 is accepted -- the config parses, the drafter loads -- and
|
|
# then the worker dies at init with:
|
|
#
|
|
# RuntimeError: Model does not support EAGLE3 interface
|
|
#
|
|
# raised by v1/worker/gpu/spec_decode/eagle/eagle3_utils.py:20 through
|
|
# `supports_eagle3(model)` == `isinstance(model, SupportsEagle3)` (a runtime-checkable
|
|
# Protocol). Nothing about the drafter, the method name or the payload is wrong:
|
|
# `glm5next` implements neither half of the contract.
|
|
#
|
|
# THE CONTRACT (read out of this image, not assumed):
|
|
# * interfaces.py:1663 -- `supports_eagle3: ClassVar[Literal[True]] = True`. THE FLAG
|
|
# IS A REQUIRED MEMBER for runtime-checkable presence-based isinstance, so v1
|
|
# (methods only) failed identically. BUT v2 (inheriting the Protocol) taught the
|
|
# sharper lesson: adding SupportsEagle3 to Glm5NextForCausalLM's bases passes
|
|
# isinstance and then dies at class creation with
|
|
# "Cannot create a consistent method resolution order (MRO) for bases ..."
|
|
# -- because GLM's outer wrapper already inherits vLLM Protocols that are NOT
|
|
# Protocol-derived mixins in a compatible order. The fix that avoids the MRO
|
|
# entirely: declare the flag as a class attribute on the wrapper:
|
|
# supports_eagle3: ClassVar[Literal[True]] = True
|
|
# which satisfies presence-based isinstance without touching the MRO, and pairs
|
|
# with the EagleModelMixin on the trunk that supplies the real aux-layer plumbing.
|
|
# (SupportsEagleBase's has_own_lm_head/has_own_embed_tokens default to False, which
|
|
# is what a wrapper that delegates embed/lm_head to its trunk wants.)
|
|
# * interfaces.py:1673-1719 -- the inherited defaults unwrap
|
|
# `holder = getattr(parent_ref, "model", parent_ref)`, assert
|
|
# `isinstance(holder, EagleModelMixin)`, set `holder._set_aux_hidden_state_layers`, and
|
|
# default to (2, L // 2, L - 3). So the INNER trunk must inherit EagleModelMixin,
|
|
# which supplies `aux_hidden_state_layers` and the slot helpers.
|
|
# * eagle3_utils.py:26-31 -- layers come from the spec config when it carries them
|
|
# (this drafter ships target_layer_ids [5, 14, 24, 33, 42]), else from the default.
|
|
# * deepseek_v2.py:1551-1585 and v1/worker/gpu/cudagraph_utils.py:621 -- the trunk
|
|
# appends one tensor per aux layer while looping and returns the TUPLE
|
|
# (hidden_states, aux_hidden_states) only when that list is non-empty.
|
|
#
|
|
# WHAT IT ADDS for GLM: the mixin on Glm5NextModel; aux capture in the layer loop, where
|
|
# the aux state is the single-stream `hidden_states` ENTERING that layer (GLM's loop
|
|
# carries the already-summed single-stream state -- its non-mHC branch re-splits
|
|
# `residual = hidden_states` inside the layer -- so this is the equivalent of
|
|
# deepseek_v2's `hidden_states + residual`); the conditional tuple return; and
|
|
# SupportsEagle3 among Glm5NextForCausalLM's bases.
|
|
#
|
|
# HONEST SCOPE: this makes the interface PRESENT, which is exactly what the engine
|
|
# checks -- it does not make drafts GOOD, and per the measured profile it is not a
|
|
# decode-speed fix (a verification pass pays pointwise cost proportional to its token
|
|
# count, which is why MTP4 measured flat). It is the prerequisite for driving DFlash2
|
|
# on this endpoint at all.
|
|
#
|
|
# TARGET: vllm/models/glm5next/common/model.py (inside the pinned image)
|
|
# base sha256 008eeaa2a07e091465319f86900b5a321d921238723ccbe22e6b9e971e1d2b28
|
|
# patched sha256 4c9a632b5173fdefe0672b810b54f91b88ed65decf0f4a76fde92527970bee8e
|
|
#
|
|
# APPLY: `patch -p1` from the vllm package root, or bind-mount the patched file over
|
|
# site-packages (which is what a scratch-launcher DFLASH_PATCH=1 does).
|
|
--- orig/vllm/models/glm5next/common/model.py 2026-09-30 15:57:08.910029614 -0700
|
|
+++ new/vllm/models/glm5next/common/model.py 2026-09-30 17:04:20.104521484 -0700
|
|
@@ -95,6 +95,12 @@
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
+from vllm.model_executor.models.interfaces import (
|
|
+ EagleModelMixin,
|
|
+ SupportsEagle3,
|
|
+)
|
|
+
|
|
+
|
|
class Glm5NextMLP(nn.Module):
|
|
def __init__(
|
|
self,
|
|
@@ -623,7 +629,7 @@
|
|
)
|
|
|
|
|
|
-class Glm5NextModel(nn.Module):
|
|
+class Glm5NextModel(nn.Module, EagleModelMixin):
|
|
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
|
super().__init__()
|
|
|
|
@@ -730,7 +736,14 @@
|
|
if self.is_sequence_parallel:
|
|
hidden_states = sp_shard(hidden_states)
|
|
|
|
- for layer in self._active_layers:
|
|
+ aux_hidden_states: list[torch.Tensor] = []
|
|
+ for local_idx, layer in enumerate(self._active_layers):
|
|
+ if (getattr(self, "start_layer", 0) + local_idx) in self.aux_hidden_state_layers:
|
|
+ # Auxiliary hidden state for EAGLE-3 / DFlash2 drafts: the
|
|
+ # single-stream state ENTERING this layer, before its input norm --
|
|
+ # the same semantic deepseek_v2 implements by appending
|
|
+ # hidden_states + residual at this point in its loop.
|
|
+ aux_hidden_states.append(hidden_states)
|
|
hidden_states, residual, post, comb = layer(
|
|
positions, hidden_states, residual, post, comb
|
|
)
|
|
@@ -749,6 +762,8 @@
|
|
hidden_states = sp_all_gather(hidden_states)[:full_num_tokens]
|
|
|
|
hidden_states = self.norm(hidden_states)
|
|
+ if len(aux_hidden_states) > 0:
|
|
+ return hidden_states, aux_hidden_states
|
|
return hidden_states
|
|
|
|
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
|
@@ -937,6 +952,8 @@
|
|
class Glm5NextForCausalLM(
|
|
nn.Module, HasInnerState, SupportsPP, MixtureOfExperts, IsHybrid
|
|
):
|
|
+ supports_eagle3: ClassVar[Literal[True]] = True
|
|
+
|
|
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
|
super().__init__()
|
|
self.model_config = vllm_config.model_config
|