@@ -102,6 +102,26 @@ def update(
102102# Config
103103
104104
105+ def validate_eagle_tap_layers (layers : list , num_hidden_layers : int ) -> None :
106+ """Validate EAGLE-3 tap indices (HF/vLLM convention; 0 = embedding output).
107+
108+ Indices must be non-bool ints, unique, ascending, and in
109+ ``[0, num_hidden_layers]``. Order defines the fc concatenation order.
110+ """
111+ if not layers :
112+ return
113+ if any (isinstance (t , bool ) or not isinstance (t , int ) for t in layers ):
114+ raise ValueError (f"eagle_tap_layers must be non-bool ints, got { layers } " )
115+ if len (set (layers )) != len (layers ):
116+ raise ValueError (f"eagle_tap_layers has duplicates: { layers } " )
117+ if any (t < 0 or t > num_hidden_layers for t in layers ):
118+ raise ValueError (
119+ f"eagle_tap_layers { layers } out of range [0, { num_hidden_layers } ]"
120+ )
121+ if list (layers ) != sorted (layers ):
122+ raise ValueError (f"eagle_tap_layers must be ascending (fc order): { layers } " )
123+
124+
105125@dataclass
106126class Gemma4_31BConfig :
107127 # Embedding / shape
@@ -144,6 +164,11 @@ class Gemma4_31BConfig:
144164 # Runtime
145165 max_seq_len : int = 4096
146166
167+ # EAGLE-3 auxiliary hidden-state taps. Indices use the HF/vLLM convention:
168+ # 0 = embedding output, k = output after decoder layer k-1. Empty disables
169+ # tap collection.
170+ eagle_tap_layers : list = field (default_factory = list )
171+
147172 def __post_init__ (self ):
148173 if not self .layer_types :
149174 # Default hybrid pattern: 5 sliding then 1 full, repeated.
@@ -156,6 +181,7 @@ def __post_init__(self):
156181 f"layer_types length { len (self .layer_types )} != "
157182 f"num_hidden_layers { self .num_hidden_layers } "
158183 )
184+ validate_eagle_tap_layers (self .eagle_tap_layers , self .num_hidden_layers )
159185
160186 @staticmethod
161187 def from_hf_config (config_path : str ) -> "Gemma4_31BConfig" :
@@ -466,6 +492,48 @@ def _build_masks(
466492
467493 return sliding_mask , full_mask
468494
495+ def _decode (
496+ self ,
497+ tokens : torch .LongTensor ,
498+ input_pos : torch .LongTensor ,
499+ collect_taps : bool ,
500+ ) -> tuple [torch .Tensor , Optional [torch .Tensor ]]:
501+ """Embed -> decoder layers -> final norm.
502+
503+ Returns the normed hidden states and, when ``collect_taps`` is set, the
504+ concatenated tap features for ``config.eagle_tap_layers`` (in ascending
505+ index order) as ``(B, T, len(tap_layers) * hidden_size)``; else None.
506+
507+ Tap indices follow the HF/vLLM hidden-state convention: index 0 is the
508+ embedding output (before any decoder layer) and index k is the output
509+ *after* decoder layer k-1.
510+ """
511+ x = self .embed_tokens (tokens ) * self .embed_normalizer
512+
513+ tap_layers = self .config .eagle_tap_layers
514+ if collect_taps :
515+ # Revalidate dynamic tap configuration before membership checks.
516+ validate_eagle_tap_layers (tap_layers , len (self .layers ))
517+ taps = []
518+ if collect_taps and 0 in tap_layers :
519+ taps .append (x ) # index 0 == embedding output
520+
521+ sliding_mask , full_mask = self ._build_masks (input_pos )
522+ for i , layer in enumerate (self .layers ):
523+ x = layer (x , input_pos , sliding_mask , full_mask )
524+ if collect_taps and (i + 1 ) in tap_layers :
525+ taps .append (x ) # output of layer i == hidden-state index i+1
526+
527+ if collect_taps and len (taps ) != len (tap_layers ):
528+ raise ValueError (
529+ f"collected { len (taps )} taps but eagle_tap_layers requests "
530+ f"{ len (tap_layers )} ({ tap_layers } ); check the index convention"
531+ )
532+
533+ x = self .norm (x )
534+ taps_out = torch .cat (taps , dim = - 1 ) if taps else None
535+ return x , taps_out
536+
469537 def forward (
470538 self ,
471539 tokens : torch .LongTensor ,
@@ -482,18 +550,41 @@ def forward(
482550 Returns:
483551 (B, 1) sampled token IDs as float.
484552 """
485- x = self .embed_tokens (tokens ) * self .embed_normalizer
486-
487- sliding_mask , full_mask = self ._build_masks (input_pos )
488- for layer in self .layers :
489- x = layer (x , input_pos , sliding_mask , full_mask )
490-
491- x = self .norm (x )
553+ x , _ = self ._decode (tokens , input_pos , collect_taps = False )
492554 last = self .lm_head (x [:, - 1 , :]).float ()
493555 cap = self .logit_softcap .float ()
494556 last = torch .tanh (last / cap ) * cap
495557 return sample (last , temperature )
496558
559+ def set_eagle_tap_layers (self , layers : list ) -> None :
560+ """Set and validate EAGLE-3 tap layers."""
561+ validate_eagle_tap_layers (layers , self .config .num_hidden_layers )
562+ self .config .eagle_tap_layers = list (layers )
563+
564+ def forward_logits_taps (
565+ self ,
566+ tokens : torch .LongTensor ,
567+ input_pos : torch .LongTensor ,
568+ last_logits_only : bool = True ,
569+ ) -> tuple [torch .Tensor , Optional [torch .Tensor ]]:
570+ """Return soft-capped logits and EAGLE-3 tap features.
571+
572+ Defaults to final-position logits. Set ``last_logits_only=False`` to
573+ materialize per-position float32 logits over the full vocabulary.
574+
575+ Returns:
576+ logits: (B, 1, vocab_size) soft-capped float32, or (B, T, vocab_size)
577+ when ``last_logits_only=False``.
578+ taps: (B, T, len(eagle_tap_layers) * hidden_size) or None.
579+ """
580+ x , taps = self ._decode (tokens , input_pos , collect_taps = True )
581+ if last_logits_only :
582+ x = x [:, - 1 :, :]
583+ logits = self .lm_head (x ).float ()
584+ cap = self .logit_softcap .float ()
585+ logits = torch .tanh (logits / cap ) * cap
586+ return logits , taps
587+
497588 # ---------------- checkpoint loading ----------------
498589
499590 @staticmethod
0 commit comments