Coverage for transformer_lens/model_bridge/supported_architectures/llada2_moe.py: 93%
48 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-21 19:27 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-21 19:27 +0000
1"""LLaDA 2.0 MoE architecture adapter.
3Ant Group's LLaDA 2.x (``LLaDA2MoeModelLM``, remote code): masked
4block-diffusion language models on a DeepSeek-V3-style MoE decoder —
5fused ``query_key_value`` attention with full-width query/key layernorms,
6per-expert routed MLPs behind a bias-corrected router plus shared
7experts, and dense MLPs on the first ``first_k_dense_replace`` layers.
9Attention is bidirectional (``is_causal = False``) and generation is
10block-diffusion sampling via the model's own ``generate``, reached through
11``bridge.diffusion_generate``; attention delegates to HF and the bridge's
12autoregressive generation stays off. The fused QKV ships no
13HookedTransformer-format weight conversions, so LN folding is disabled.
15The remote forward validates attention_mask strictly: it must be the 4D
16block-diffusion form (batch, 1, seq, seq) — full-ones for full
17bidirectional visibility — not a 2D padding mask. A forward pre-hook
18drops all-ones 2D masks (informationless) and rejects padded ones with a
19clear error instead of the remote validator's opaque failure.
20"""
22from typing import Any
24from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
25from transformer_lens.model_bridge.generalized_components import (
26 AttentionBridge,
27 BlockBridge,
28 EmbeddingBridge,
29 LinearBridge,
30 MoEBridge,
31 RMSNormalizationBridge,
32 RotaryEmbeddingBridge,
33 UnembeddingBridge,
34)
35from transformer_lens.model_bridge.generalized_components.base import (
36 GeneralizedComponent,
37)
38from transformer_lens.model_bridge.supported_architectures.dream import (
39 _register_default_rope_init,
40)
43class _LLaDA2FusedAttentionBridge(AttentionBridge):
44 """Fused query_key_value projection: no separate q/k/v submodules to
45 alias — expose the fused output instead."""
47 hook_aliases = {
48 "hook_qkv": "qkv.hook_out",
49 "hook_z": "o.hook_in",
50 }
53class LLaDA2MoeArchitectureAdapter(ArchitectureAdapter):
54 """Architecture adapter for LLaDA2MoeModelLM models."""
56 applicable_phases: list[int] = [1, 2, 3, 4]
57 supports_generation: bool = False
58 # Bidirectional masked-denoising objective; shifted causal CE is undefined.
59 supports_causal_loss: bool = False
60 # Semi-autoregressive block remasking, shipped on the model class.
61 native_sampler: str = "generate"
62 # Fused query_key_value with no per-projection conversions to fold into.
63 supports_fold_ln = False
64 # Delegated attention computes rotary inside HF; nothing to wire.
65 _testing_eager = None
66 _testing_wire_rotary = False
68 def native_sampler_kwargs(self, max_new_tokens: int, prompt_len: int) -> dict:
69 """gen_length must cover whole blocks; block_length caps at the budget."""
70 block_length = min(32, max_new_tokens)
71 blocks = max(1, -(-max_new_tokens // block_length))
72 return {
73 "gen_length": blocks * block_length,
74 "block_length": block_length,
75 "steps": max_new_tokens,
76 }
78 def __init__(self, cfg: Any) -> None:
79 """Initialize the LLaDA 2.0 MoE architecture adapter."""
80 super().__init__(cfg)
82 self._set_rms_rotary_defaults()
84 self.weight_processing_conversions = {}
86 self.component_mapping = {
87 "embed": EmbeddingBridge(name="model.word_embeddings"),
88 "rotary_emb": RotaryEmbeddingBridge(name="model.rotary_emb"),
89 "blocks": BlockBridge(
90 name="model.layers",
91 config=self.cfg,
92 submodules={
93 "ln1": RMSNormalizationBridge(name="input_layernorm", config=self.cfg),
94 "ln2": RMSNormalizationBridge(name="post_attention_layernorm", config=self.cfg),
95 # Bidirectional diffusion attention with fused QKV; the
96 # bridge reimplementation assumes causal masking, so
97 # delegate to HF. The fused projection and dense output
98 # are hookable; q/k layernorms are full-width.
99 "attn": _LLaDA2FusedAttentionBridge(
100 name="attention",
101 config=self.cfg,
102 submodules={
103 "qkv": LinearBridge(name="query_key_value"),
104 "o": LinearBridge(name="dense"),
105 "q_norm": RMSNormalizationBridge(
106 name="query_layernorm", config=self.cfg
107 ),
108 "k_norm": RMSNormalizationBridge(name="key_layernorm", config=self.cfg),
109 },
110 maintain_native_attention=True,
111 ),
112 # Dense on the first first_k_dense_replace layers, routed
113 # MoE elsewhere — gate and shared_experts are optional so
114 # setup skips them on dense layers (deepseek_v3 pattern).
115 "mlp": MoEBridge(
116 name="mlp",
117 config=self.cfg,
118 sparse_required=("gate",),
119 submodules={
120 "gate": GeneralizedComponent(name="gate", optional=True),
121 "shared_experts": self._gated_mlp(name="shared_experts", optional=True),
122 # Dense-layer projections (absent on MoE layers).
123 "dense_gate": LinearBridge(name="gate_proj", optional=True),
124 "dense_in": LinearBridge(name="up_proj", optional=True),
125 "dense_out": LinearBridge(name="down_proj", optional=True),
126 },
127 ),
128 },
129 ),
130 "ln_final": RMSNormalizationBridge(name="model.norm", config=self.cfg),
131 "unembed": UnembeddingBridge(name="lm_head"),
132 }
134 def prepare_loading(self, model_name: str, model_kwargs: dict) -> None:
135 """Restore the v4 'default' rope init the remote code looks up (Dream shim)."""
136 _register_default_rope_init()
137 super().prepare_loading(model_name, model_kwargs)
139 def setup_hook_compatibility(self, bridge: Any) -> None:
140 """Guard the remote forward against auto-passed 2D padding masks."""
141 model = getattr(bridge, "original_model", None)
142 if model is None or getattr(model, "_llada2_mask_guard", False): 142 ↛ anywhereline 142 didn't jump anywhere: it always raised an exception.
143 return
145 def _mask_guard(module: Any, args: Any, kwargs: Any) -> Any:
146 import torch
148 mask = kwargs.get("attention_mask")
149 if mask is None:
150 # Remote forward calls attention_mask.size() unconditionally;
151 # synthesize the full-visibility 4D mask from the input shape.
152 ids = kwargs.get("input_ids", args[0] if args else None)
153 if isinstance(ids, torch.Tensor) and ids.ndim == 2: 153 ↛ 156line 153 didn't jump to line 156 because the condition on line 153 was always true
154 batch, seq = ids.shape
155 kwargs["attention_mask"] = torch.ones(batch, 1, seq, seq, device=ids.device)
156 return args, kwargs
157 if isinstance(mask, torch.Tensor) and mask.ndim == 2:
158 if not bool(mask.all()):
159 raise NotImplementedError(
160 "LLaDA2's remote forward rejects 2D padding masks; "
161 "batched padded inputs are unsupported — pass "
162 "equal-length sequences."
163 )
164 batch, seq = mask.shape
165 kwargs["attention_mask"] = torch.ones(batch, 1, seq, seq, device=mask.device)
166 return args, kwargs
168 model.register_forward_pre_hook(_mask_guard, with_kwargs=True)
169 model._llada2_mask_guard = True