Skip to content

Commit 35be5a1

Browse files
committed
Document Laguna support and stabilize meta tests
1 parent f8deb06 commit 35be5a1

2 files changed

Lines changed: 53 additions & 31 deletions

File tree

README.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align=center>
22
<img width="50%" alt="image" src="https://github.com/user-attachments/assets/f801617b-8959-474a-a565-6b8897e2fcbf" />
3-
<h1 align="center">Defuser</h1>
3+
<h1 align="center">Defuser 🔧</h1>
44
</div>
55

66
<p align="center">
@@ -11,47 +11,47 @@
1111
<a href="https://huggingface.co/modelcloud/"><img src="https://img.shields.io/badge/🤗%20Hugging%20Face-ModelCloud-%23ff8811.svg"></a>
1212
</p>
1313

14-
Defuser converts select Hugging Face Transformers `5.3.0+` fused or stacked MoE and MLP blocks back into plain, per-expert `nn.Linear` modules. It keeps the forward math intact while exposing individual projections again so quantizers, activation capture, debugging hooks, and checkpoint tooling can work against a simple module layout instead of fused expert tensors.
14+
🧩 Defuser converts select Hugging Face Transformers `5.3.0+` fused or stacked MoE and MLP blocks back into plain, per-expert `nn.Linear` modules. It keeps the forward math intact while exposing individual projections again so quantizers, activation capture, debugging hooks, and checkpoint tooling can work against a simple module layout instead of fused expert tensors.
1515

16-
Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range.
16+
Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range.
1717

18-
## Purpose
18+
## 🎯 Purpose
1919

2020
Defuser exists for cases where newer Transformers modeling code optimizes model structure in ways that are good for runtime, but harder for tooling that needs direct access to individual projections.
2121

2222
Depending on the model family, Defuser can:
2323

24-
- patch a supported model class before load so HF instantiates a defused block directly
25-
- split fused tensors such as `gate_up_proj` into `gate_proj` + `up_proj`
26-
- convert 3D expert tensors, including registered expert buffers, into numbered expert `nn.Linear` modules
27-
- preserve the original fused math while presenting a naive module structure again
24+
- 🧵 patch a supported model class before load so HF instantiates a defused block directly
25+
- ✂️ split fused tensors such as `gate_up_proj` into `gate_proj` + `up_proj`
26+
- 🧱 convert 3D expert tensors, including registered expert buffers, into numbered expert `nn.Linear` modules
27+
- 🧮 preserve the original fused math while presenting a naive module structure again
2828

29-
Public API:
29+
🛠️ Public API:
3030

3131
```python
3232
from defuser import convert_model, replace_fused_blocks
3333
```
3434

35-
- `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
36-
- `convert_model(model, cleanup_original=False, max_layers=None, filter=None)` converts an already loaded model in place. This is the runtime defusion path for supported post-load expert and MLP conversions, including `qwen3_5_moe` style checkpoints.
37-
- Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range. Older versions log a warning on these public APIs and are skipped as unsupported.
38-
- Some model families appear in both support tables. Full models can be prepatched with `replace_fused_blocks(...)`, while standalone fused expert modules from those same families can still be runtime-defused with `convert_model(...)`.
35+
- 🧰 `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
36+
- 🔄 `convert_model(model, cleanup_original=False, max_layers=None, filter=None)` converts an already loaded model in place. This is the runtime defusion path for supported post-load expert and MLP conversions, including `qwen3_5_moe` style checkpoints.
37+
- 🧪 Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range. Older versions log a warning on these public APIs and are skipped as unsupported.
38+
- 🧭 Some model families appear in both support tables. Full models can be prepatched with `replace_fused_blocks(...)`, while standalone fused expert modules from those same families can still be runtime-defused with `convert_model(...)`.
3939

4040
`filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
4141

42-
- `+:regex` explicitly includes matching candidate module paths
43-
- `-:regex` explicitly excludes matching candidate module paths
44-
- `regex` is shorthand for `+:regex`
45-
- negative rules take priority over positive rules
46-
- when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
42+
- `+:regex` explicitly includes matching candidate module paths
43+
- 🚫 `-:regex` explicitly excludes matching candidate module paths
44+
- `regex` is shorthand for `+:regex`
45+
- 🛡️ negative rules take priority over positive rules
46+
- 🎯 when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
4747

48-
## Supported Models
48+
## Supported Models
4949

5050
Defuser currently supports the following `transformers>=5.3.0` `model_type` values.
5151

52-
### `replace_fused_blocks(model_type)` before load
52+
### 🧰 `replace_fused_blocks(model_type)` before load
5353

54-
| Model type | Defused op performed |
54+
| Model type | Defused op performed ⚙️ |
5555
| --- | --- |
5656
| `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
5757
| `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
@@ -62,11 +62,11 @@ Defuser currently supports the following `transformers>=5.3.0` `model_type` valu
6262
| `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
6363
| `qwen3_omni_moe` | Replaces both thinker and talker text sparse MoE blocks with defused per-expert linear blocks and applies small runtime compatibility patches for text `forward()` and `generate()`. |
6464

65-
### `convert_model(model)` after load
65+
### 🔄 `convert_model(model)` after load
6666

67-
| Pattern | Supported model types | Defused op performed |
67+
| Pattern | Supported model types | Defused op performed ⚙️ |
6868
| --- | --- | --- |
69-
| Standard routed expert tensors | `deepseek_v2`, `dots1`, `ernie4_5_moe`, `ernie4_5_vl_moe`, `exaone_moe`, `flex_olmo`, `glm4_moe_lite`, `glm4v_moe`, `hunyuan_v1_moe`, `jamba`, `lfm2_moe`, `minimax`, `minimax_m2`, `olmoe`, `qwen3_vl_moe`, `solar_open` | Splits fused expert tensors or registered expert buffers into numbered expert `nn.Linear` modules with per-expert `gate_proj`, `up_proj`, and `down_proj`. |
69+
| Standard routed expert tensors 🧱 | `deepseek_v2`, `dots1`, `ernie4_5_moe`, `ernie4_5_vl_moe`, `exaone_moe`, `flex_olmo`, `glm4_moe_lite`, `glm4v_moe`, `hunyuan_v1_moe`, `jamba`, `laguna`, `lfm2_moe`, `minimax`, `minimax_m2`, `olmoe`, `qwen3_vl_moe`, `solar_open` | Splits fused expert tensors or registered expert buffers into numbered expert `nn.Linear` modules with per-expert `gate_proj`, `up_proj`, and `down_proj`. |
7070
| Mixed sparse and shared experts | `deepseek_v3`, `glm_moe_dsa`, `qwen3_5_moe`, `qwen3_5_moe_text` | Runtime expert tensor defusion for routed experts while preserving the model's shared-expert path. |
7171
| Transposed or packed expert tensors | `gpt_oss`, `phimoe` | Splits transposed fused expert `gate_up_proj` tensors into per-expert `gate_proj` + `up_proj`, preserves expert bias when present, and converts expert tensors into numbered expert `nn.Linear` modules. |
7272
| Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
@@ -76,7 +76,7 @@ Defuser currently supports the following `transformers>=5.3.0` `model_type` valu
7676
| Routed experts with identity experts | `longcat_flash` | Defuses routed experts into numbered `gate_proj`, `up_proj`, and `down_proj` modules and preserves zero or identity experts. |
7777
| Fused dense `gate_up_proj` MLPs | `dia`, `glm`, `glm4`, `glm_image`, `glm_ocr`, `phi3`, `phi4_multimodal`, `zamba2` | Splits fused dense `gate_up_proj` layers into `gate_proj` + `up_proj` and updates the block `forward()` to preserve the original MLP math. |
7878

79-
## Workflow Summary
79+
## 🔁 Workflow Summary
8080

8181
Use `replace_fused_blocks()` for model families that Defuser can patch before load:
8282

@@ -103,7 +103,7 @@ print(converted) # True when runtime defusion happened
103103

104104
`convert_model(model)` also preserves meta-device construction for supported meta-initialized models, so structural validation can run without materializing weights.
105105

106-
Use `filter` when only specific blocks should be defused:
106+
Use `filter` when only specific blocks should be defused 🎯:
107107

108108
```python
109109
from defuser import convert_model
@@ -117,11 +117,11 @@ convert_model(
117117
)
118118
```
119119

120-
## Real Qwen3.5 MoE Example
120+
## 🧪 Real Qwen3.5 MoE Example
121121

122122
The example below is written for the `transformers==5.3.0` public API surface and uses the real Hugging Face model `Qwen/Qwen3.5-35B-A3B-Instruct`. Defuser supports `transformers>=5.3.0`.
123123

124-
### Fused Weights Before And After
124+
### 🔬 Fused Weights Before And After
125125

126126
Before `convert_model(model)`:
127127

@@ -149,7 +149,7 @@ After `convert_model(model)`:
149149
+-----------------------------------------------------------------+--------------------------------------+
150150
```
151151

152-
### Sample 1: Inspect The Conversion In Place
152+
### 🧭 Sample 1: Inspect The Conversion In Place
153153

154154
```python
155155
from defuser import convert_model
@@ -187,7 +187,7 @@ print(after[:6])
187187
# ]
188188
```
189189

190-
### Sample 2: Convert And Keep Using The Model Normally
190+
### 🚀 Sample 2: Convert And Keep Using The Model Normally
191191

192192
```python
193193
import torch

tests/test_meta_model_defusion.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,32 @@ def _build_model_config(case: dict):
167167
elif model_type == "glm_moe_dsa":
168168
config.mlp_layer_types = ["sparse"] * config.num_hidden_layers
169169
elif model_type == "granitemoehybrid":
170-
config.layer_types = ["attention", "mamba"]
170+
# Keep this meta-structure test on the attention path. The mamba path
171+
# lazy-loads optional hub kernels during construction, which is outside
172+
# the Defuser behavior being validated here.
173+
config.layer_types = ["attention", "attention"]
171174
config.shared_intermediate_size = 64
172175
config.mamba_n_heads = 8
176+
elif model_type == "jamba":
177+
# Keep this meta-structure test on the attention path. The mamba path
178+
# lazy-loads optional hub kernels during construction, which is outside
179+
# the Defuser behavior being validated here.
180+
config.attn_layer_period = 1
181+
config.attn_layer_offset = 0
182+
config.expert_layer_period = 1
183+
config.expert_layer_offset = 0
173184
elif model_type == "lfm2_moe":
174185
config.layer_types = ["full_attention", "short_conv"]
175186
config.num_dense_layers = 0
176187
elif model_type == "laguna":
177188
config.layer_types = ["full_attention"] * config.num_hidden_layers
178189
config.mlp_layer_types = ["dense"] + ["sparse"] * (config.num_hidden_layers - 1)
179190
config.num_attention_heads_per_layer = [config.num_attention_heads] * config.num_hidden_layers
191+
elif model_type == "nemotron_h":
192+
# Keep this meta-structure test on MoE blocks. Mamba blocks lazy-load
193+
# optional hub kernels during construction, which is outside the
194+
# Defuser behavior being validated here.
195+
config.layers_block_type = ["moe"] * config.num_hidden_layers
180196
elif model_type == "qwen3_omni_moe":
181197
config.enable_audio_output = True
182198
config.talker_config.spatial_merge_size = 2
@@ -192,6 +208,12 @@ def _build_model_config(case: dict):
192208
num_key_value_heads=1,
193209
head_dim=16,
194210
)
211+
elif model_type == "zamba2":
212+
# Zamba2 always constructs Mamba layers, even for hybrid blocks. Keep
213+
# the optional hub kernels disabled while preserving hybrid MLP targets.
214+
config.use_mamba_kernels = False
215+
config.layers_block_type = ["hybrid"] * config.num_hidden_layers
216+
config.hybrid_layer_ids = list(range(config.num_hidden_layers))
195217

196218
return config
197219

0 commit comments

Comments
 (0)