Defuser 0.0.20__tar.gz → 0.0.22__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {defuser-0.0.20 → defuser-0.0.22}/Defuser.egg-info/PKG-INFO +40 -40
  2. {defuser-0.0.20 → defuser-0.0.22}/PKG-INFO +40 -40
  3. {defuser-0.0.20 → defuser-0.0.22}/README.md +39 -39
  4. {defuser-0.0.20 → defuser-0.0.22}/defuser/defuser.py +0 -1
  5. {defuser-0.0.20 → defuser-0.0.22}/defuser/model_registry.py +6 -0
  6. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/replace_modules.py +0 -1
  7. {defuser-0.0.20 → defuser-0.0.22}/pyproject.toml +1 -1
  8. {defuser-0.0.20 → defuser-0.0.22}/tests/test_convert_model.py +79 -0
  9. {defuser-0.0.20 → defuser-0.0.22}/tests/test_meta_model_defusion.py +48 -1
  10. {defuser-0.0.20 → defuser-0.0.22}/Defuser.egg-info/SOURCES.txt +0 -0
  11. {defuser-0.0.20 → defuser-0.0.22}/Defuser.egg-info/dependency_links.txt +0 -0
  12. {defuser-0.0.20 → defuser-0.0.22}/Defuser.egg-info/requires.txt +0 -0
  13. {defuser-0.0.20 → defuser-0.0.22}/Defuser.egg-info/top_level.txt +0 -0
  14. {defuser-0.0.20 → defuser-0.0.22}/LICENSE +0 -0
  15. {defuser-0.0.20 → defuser-0.0.22}/defuser/__init__.py +0 -0
  16. {defuser-0.0.20 → defuser-0.0.22}/defuser/checkpoint_ops.py +0 -0
  17. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/__init__.py +0 -0
  18. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/glm4v.py +0 -0
  19. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/model_patches.py +0 -0
  20. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/moe_experts_interface.py +0 -0
  21. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/runtime_defusion.py +0 -0
  22. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/__init__.py +0 -0
  23. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/common.py +0 -0
  24. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/glm4_moe.py +0 -0
  25. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/glm4_moe_lite.py +0 -0
  26. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/mixtral.py +0 -0
  27. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/qwen2_moe.py +0 -0
  28. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/qwen3_moe.py +0 -0
  29. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/qwen3_next.py +0 -0
  30. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/unfused_moe/qwen3_omni_moe.py +0 -0
  31. {defuser-0.0.20 → defuser-0.0.22}/defuser/modeling/update_module.py +0 -0
  32. {defuser-0.0.20 → defuser-0.0.22}/defuser/utils/__init__.py +0 -0
  33. {defuser-0.0.20 → defuser-0.0.22}/defuser/utils/common.py +0 -0
  34. {defuser-0.0.20 → defuser-0.0.22}/defuser/utils/device.py +0 -0
  35. {defuser-0.0.20 → defuser-0.0.22}/defuser/utils/hf.py +0 -0
  36. {defuser-0.0.20 → defuser-0.0.22}/defuser/utils/model.py +0 -0
  37. {defuser-0.0.20 → defuser-0.0.22}/setup.cfg +0 -0
  38. {defuser-0.0.20 → defuser-0.0.22}/setup.py +0 -0
  39. {defuser-0.0.20 → defuser-0.0.22}/tests/test_candidate_coverage.py +0 -0
  40. {defuser-0.0.20 → defuser-0.0.22}/tests/test_device_utils.py +0 -0
  41. {defuser-0.0.20 → defuser-0.0.22}/tests/test_filter_rules.py +0 -0
  42. {defuser-0.0.20 → defuser-0.0.22}/tests/test_moe_experts_interface.py +0 -0
  43. {defuser-0.0.20 → defuser-0.0.22}/tests/test_replace_modules_tracker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Defuser
3
- Version: 0.0.20
3
+ Version: 0.0.22
4
4
  Summary: Model defuser helper for HF Transformers.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
 
27
27
  <div align=center>
28
28
  <img width="50%" alt="image" src="https://github.com/user-attachments/assets/f801617b-8959-474a-a565-6b8897e2fcbf" />
29
- <h1 align="center">Defuser</h1>
29
+ <h1 align="center">Defuser 🔧</h1>
30
30
  </div>
31
31
 
32
32
  <p align="center">
@@ -37,47 +37,47 @@ Dynamic: license-file
37
37
  <a href="https://huggingface.co/modelcloud/"><img src="https://img.shields.io/badge/🤗%20Hugging%20Face-ModelCloud-%23ff8811.svg"></a>
38
38
  </p>
39
39
 
40
- 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.
40
+ 🧩 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.
41
41
 
42
- Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range.
42
+ ✅ Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range.
43
43
 
44
- ## Purpose
44
+ ## 🎯 Purpose
45
45
 
46
46
  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.
47
47
 
48
48
  Depending on the model family, Defuser can:
49
49
 
50
- - patch a supported model class before load so HF instantiates a defused block directly
51
- - split fused tensors such as `gate_up_proj` into `gate_proj` + `up_proj`
52
- - convert 3D expert tensors, including registered expert buffers, into numbered expert `nn.Linear` modules
53
- - preserve the original fused math while presenting a naive module structure again
50
+ - 🧵 patch a supported model class before load so HF instantiates a defused block directly
51
+ - ✂️ split fused tensors such as `gate_up_proj` into `gate_proj` + `up_proj`
52
+ - 🧱 convert 3D expert tensors, including registered expert buffers, into numbered expert `nn.Linear` modules
53
+ - 🧮 preserve the original fused math while presenting a naive module structure again
54
54
 
55
- Public API:
55
+ 🛠️ Public API:
56
56
 
57
57
  ```python
58
58
  from defuser import convert_model, replace_fused_blocks
59
59
  ```
60
60
 
61
- - `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
62
- - `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.
63
- - 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.
64
- - 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(...)`.
61
+ - 🧰 `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
62
+ - 🔄 `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.
63
+ - 🧪 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.
64
+ - 🧭 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(...)`.
65
65
 
66
66
  `filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
67
67
 
68
- - `+:regex` explicitly includes matching candidate module paths
69
- - `-:regex` explicitly excludes matching candidate module paths
70
- - `regex` is shorthand for `+:regex`
71
- - negative rules take priority over positive rules
72
- - when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
68
+ - ✅ `+:regex` explicitly includes matching candidate module paths
69
+ - 🚫 `-:regex` explicitly excludes matching candidate module paths
70
+ - ➕ `regex` is shorthand for `+:regex`
71
+ - 🛡️ negative rules take priority over positive rules
72
+ - 🎯 when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
73
73
 
74
- ## Supported Models
74
+ ## ✅ Supported Models
75
75
 
76
76
  Defuser currently supports the following `transformers>=5.3.0` `model_type` values.
77
77
 
78
- ### `replace_fused_blocks(model_type)` before load
78
+ ### 🧰 `replace_fused_blocks(model_type)` before load
79
79
 
80
- | Model type | Defused op performed |
80
+ | Model type | Defused op performed ⚙️ |
81
81
  | --- | --- |
82
82
  | `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
83
83
  | `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
@@ -88,21 +88,21 @@ Defuser currently supports the following `transformers>=5.3.0` `model_type` valu
88
88
  | `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
89
89
  | `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()`. |
90
90
 
91
- ### `convert_model(model)` after load
91
+ ### 🔄 `convert_model(model)` after load
92
92
 
93
- | Pattern | Supported model types | Defused op performed |
94
- | --- | --- | --- |
95
- | 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`. |
96
- | 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. |
97
- | 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. |
98
- | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
99
- | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
100
- | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
101
- | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
102
- | 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. |
103
- | 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. |
93
+ | Pattern | Supported model types | Defused op performed ⚙️ |
94
+ | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
95
+ | 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`. |
96
+ | Mixed sparse and shared experts | `deepseek_v3`, deepseek_v4`, `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. |
97
+ | 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. |
98
+ | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
99
+ | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
100
+ | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
101
+ | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
102
+ | 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. |
103
+ | 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. |
104
104
 
105
- ## Workflow Summary
105
+ ## 🔁 Workflow Summary
106
106
 
107
107
  Use `replace_fused_blocks()` for model families that Defuser can patch before load:
108
108
 
@@ -129,7 +129,7 @@ print(converted) # True when runtime defusion happened
129
129
 
130
130
  `convert_model(model)` also preserves meta-device construction for supported meta-initialized models, so structural validation can run without materializing weights.
131
131
 
132
- Use `filter` when only specific blocks should be defused:
132
+ Use `filter` when only specific blocks should be defused 🎯:
133
133
 
134
134
  ```python
135
135
  from defuser import convert_model
@@ -143,11 +143,11 @@ convert_model(
143
143
  )
144
144
  ```
145
145
 
146
- ## Real Qwen3.5 MoE Example
146
+ ## 🧪 Real Qwen3.5 MoE Example
147
147
 
148
148
  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`.
149
149
 
150
- ### Fused Weights Before And After
150
+ ### 🔬 Fused Weights Before And After
151
151
 
152
152
  Before `convert_model(model)`:
153
153
 
@@ -175,7 +175,7 @@ After `convert_model(model)`:
175
175
  +-----------------------------------------------------------------+--------------------------------------+
176
176
  ```
177
177
 
178
- ### Sample 1: Inspect The Conversion In Place
178
+ ### 🧭 Sample 1: Inspect The Conversion In Place
179
179
 
180
180
  ```python
181
181
  from defuser import convert_model
@@ -213,7 +213,7 @@ print(after[:6])
213
213
  # ]
214
214
  ```
215
215
 
216
- ### Sample 2: Convert And Keep Using The Model Normally
216
+ ### 🚀 Sample 2: Convert And Keep Using The Model Normally
217
217
 
218
218
  ```python
219
219
  import torch
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Defuser
3
- Version: 0.0.20
3
+ Version: 0.0.22
4
4
  Summary: Model defuser helper for HF Transformers.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
 
27
27
  <div align=center>
28
28
  <img width="50%" alt="image" src="https://github.com/user-attachments/assets/f801617b-8959-474a-a565-6b8897e2fcbf" />
29
- <h1 align="center">Defuser</h1>
29
+ <h1 align="center">Defuser 🔧</h1>
30
30
  </div>
31
31
 
32
32
  <p align="center">
@@ -37,47 +37,47 @@ Dynamic: license-file
37
37
  <a href="https://huggingface.co/modelcloud/"><img src="https://img.shields.io/badge/🤗%20Hugging%20Face-ModelCloud-%23ff8811.svg"></a>
38
38
  </p>
39
39
 
40
- 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.
40
+ 🧩 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.
41
41
 
42
- Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range.
42
+ ✅ Defuser is designed and CI-tested for `transformers>=5.3.0`, and support is only offered for that version range.
43
43
 
44
- ## Purpose
44
+ ## 🎯 Purpose
45
45
 
46
46
  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.
47
47
 
48
48
  Depending on the model family, Defuser can:
49
49
 
50
- - patch a supported model class before load so HF instantiates a defused block directly
51
- - split fused tensors such as `gate_up_proj` into `gate_proj` + `up_proj`
52
- - convert 3D expert tensors, including registered expert buffers, into numbered expert `nn.Linear` modules
53
- - preserve the original fused math while presenting a naive module structure again
50
+ - 🧵 patch a supported model class before load so HF instantiates a defused block directly
51
+ - ✂️ split fused tensors such as `gate_up_proj` into `gate_proj` + `up_proj`
52
+ - 🧱 convert 3D expert tensors, including registered expert buffers, into numbered expert `nn.Linear` modules
53
+ - 🧮 preserve the original fused math while presenting a naive module structure again
54
54
 
55
- Public API:
55
+ 🛠️ Public API:
56
56
 
57
57
  ```python
58
58
  from defuser import convert_model, replace_fused_blocks
59
59
  ```
60
60
 
61
- - `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
62
- - `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.
63
- - 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.
64
- - 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(...)`.
61
+ - 🧰 `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
62
+ - 🔄 `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.
63
+ - 🧪 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.
64
+ - 🧭 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(...)`.
65
65
 
66
66
  `filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
67
67
 
68
- - `+:regex` explicitly includes matching candidate module paths
69
- - `-:regex` explicitly excludes matching candidate module paths
70
- - `regex` is shorthand for `+:regex`
71
- - negative rules take priority over positive rules
72
- - when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
68
+ - ✅ `+:regex` explicitly includes matching candidate module paths
69
+ - 🚫 `-:regex` explicitly excludes matching candidate module paths
70
+ - ➕ `regex` is shorthand for `+:regex`
71
+ - 🛡️ negative rules take priority over positive rules
72
+ - 🎯 when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
73
73
 
74
- ## Supported Models
74
+ ## ✅ Supported Models
75
75
 
76
76
  Defuser currently supports the following `transformers>=5.3.0` `model_type` values.
77
77
 
78
- ### `replace_fused_blocks(model_type)` before load
78
+ ### 🧰 `replace_fused_blocks(model_type)` before load
79
79
 
80
- | Model type | Defused op performed |
80
+ | Model type | Defused op performed ⚙️ |
81
81
  | --- | --- |
82
82
  | `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
83
83
  | `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
@@ -88,21 +88,21 @@ Defuser currently supports the following `transformers>=5.3.0` `model_type` valu
88
88
  | `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
89
89
  | `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()`. |
90
90
 
91
- ### `convert_model(model)` after load
91
+ ### 🔄 `convert_model(model)` after load
92
92
 
93
- | Pattern | Supported model types | Defused op performed |
94
- | --- | --- | --- |
95
- | 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`. |
96
- | 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. |
97
- | 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. |
98
- | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
99
- | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
100
- | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
101
- | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
102
- | 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. |
103
- | 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. |
93
+ | Pattern | Supported model types | Defused op performed ⚙️ |
94
+ | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
95
+ | 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`. |
96
+ | Mixed sparse and shared experts | `deepseek_v3`, deepseek_v4`, `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. |
97
+ | 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. |
98
+ | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
99
+ | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
100
+ | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
101
+ | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
102
+ | 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. |
103
+ | 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. |
104
104
 
105
- ## Workflow Summary
105
+ ## 🔁 Workflow Summary
106
106
 
107
107
  Use `replace_fused_blocks()` for model families that Defuser can patch before load:
108
108
 
@@ -129,7 +129,7 @@ print(converted) # True when runtime defusion happened
129
129
 
130
130
  `convert_model(model)` also preserves meta-device construction for supported meta-initialized models, so structural validation can run without materializing weights.
131
131
 
132
- Use `filter` when only specific blocks should be defused:
132
+ Use `filter` when only specific blocks should be defused 🎯:
133
133
 
134
134
  ```python
135
135
  from defuser import convert_model
@@ -143,11 +143,11 @@ convert_model(
143
143
  )
144
144
  ```
145
145
 
146
- ## Real Qwen3.5 MoE Example
146
+ ## 🧪 Real Qwen3.5 MoE Example
147
147
 
148
148
  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`.
149
149
 
150
- ### Fused Weights Before And After
150
+ ### 🔬 Fused Weights Before And After
151
151
 
152
152
  Before `convert_model(model)`:
153
153
 
@@ -175,7 +175,7 @@ After `convert_model(model)`:
175
175
  +-----------------------------------------------------------------+--------------------------------------+
176
176
  ```
177
177
 
178
- ### Sample 1: Inspect The Conversion In Place
178
+ ### 🧭 Sample 1: Inspect The Conversion In Place
179
179
 
180
180
  ```python
181
181
  from defuser import convert_model
@@ -213,7 +213,7 @@ print(after[:6])
213
213
  # ]
214
214
  ```
215
215
 
216
- ### Sample 2: Convert And Keep Using The Model Normally
216
+ ### 🚀 Sample 2: Convert And Keep Using The Model Normally
217
217
 
218
218
  ```python
219
219
  import torch
@@ -1,6 +1,6 @@
1
1
  <div align=center>
2
2
  <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>
4
4
  </div>
5
5
 
6
6
  <p align="center">
@@ -11,47 +11,47 @@
11
11
  <a href="https://huggingface.co/modelcloud/"><img src="https://img.shields.io/badge/🤗%20Hugging%20Face-ModelCloud-%23ff8811.svg"></a>
12
12
  </p>
13
13
 
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.
15
15
 
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.
17
17
 
18
- ## Purpose
18
+ ## 🎯 Purpose
19
19
 
20
20
  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.
21
21
 
22
22
  Depending on the model family, Defuser can:
23
23
 
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
28
28
 
29
- Public API:
29
+ 🛠️ Public API:
30
30
 
31
31
  ```python
32
32
  from defuser import convert_model, replace_fused_blocks
33
33
  ```
34
34
 
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(...)`.
39
39
 
40
40
  `filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
41
41
 
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
47
47
 
48
- ## Supported Models
48
+ ## ✅ Supported Models
49
49
 
50
50
  Defuser currently supports the following `transformers>=5.3.0` `model_type` values.
51
51
 
52
- ### `replace_fused_blocks(model_type)` before load
52
+ ### 🧰 `replace_fused_blocks(model_type)` before load
53
53
 
54
- | Model type | Defused op performed |
54
+ | Model type | Defused op performed ⚙️ |
55
55
  | --- | --- |
56
56
  | `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
57
57
  | `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
@@ -62,21 +62,21 @@ Defuser currently supports the following `transformers>=5.3.0` `model_type` valu
62
62
  | `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
63
63
  | `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()`. |
64
64
 
65
- ### `convert_model(model)` after load
65
+ ### 🔄 `convert_model(model)` after load
66
66
 
67
- | Pattern | Supported model types | Defused op performed |
68
- | --- | --- | --- |
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`. |
70
- | 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. |
71
- | 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. |
72
- | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
73
- | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
74
- | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
75
- | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
76
- | 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. |
77
- | 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. |
67
+ | Pattern | Supported model types | Defused op performed ⚙️ |
68
+ | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
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`. |
70
+ | Mixed sparse and shared experts | `deepseek_v3`, deepseek_v4`, `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. |
71
+ | 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. |
72
+ | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
73
+ | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
74
+ | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
75
+ | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
76
+ | 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. |
77
+ | 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. |
78
78
 
79
- ## Workflow Summary
79
+ ## 🔁 Workflow Summary
80
80
 
81
81
  Use `replace_fused_blocks()` for model families that Defuser can patch before load:
82
82
 
@@ -103,7 +103,7 @@ print(converted) # True when runtime defusion happened
103
103
 
104
104
  `convert_model(model)` also preserves meta-device construction for supported meta-initialized models, so structural validation can run without materializing weights.
105
105
 
106
- Use `filter` when only specific blocks should be defused:
106
+ Use `filter` when only specific blocks should be defused 🎯:
107
107
 
108
108
  ```python
109
109
  from defuser import convert_model
@@ -117,11 +117,11 @@ convert_model(
117
117
  )
118
118
  ```
119
119
 
120
- ## Real Qwen3.5 MoE Example
120
+ ## 🧪 Real Qwen3.5 MoE Example
121
121
 
122
122
  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`.
123
123
 
124
- ### Fused Weights Before And After
124
+ ### 🔬 Fused Weights Before And After
125
125
 
126
126
  Before `convert_model(model)`:
127
127
 
@@ -149,7 +149,7 @@ After `convert_model(model)`:
149
149
  +-----------------------------------------------------------------+--------------------------------------+
150
150
  ```
151
151
 
152
- ### Sample 1: Inspect The Conversion In Place
152
+ ### 🧭 Sample 1: Inspect The Conversion In Place
153
153
 
154
154
  ```python
155
155
  from defuser import convert_model
@@ -187,7 +187,7 @@ print(after[:6])
187
187
  # ]
188
188
  ```
189
189
 
190
- ### Sample 2: Convert And Keep Using The Model Normally
190
+ ### 🚀 Sample 2: Convert And Keep Using The Model Normally
191
191
 
192
192
  ```python
193
193
  import torch
@@ -75,7 +75,6 @@ def replace_fused_blocks(model_type: str) -> bool:
75
75
  try:
76
76
  orig_module = importlib.import_module(orig_module_path)
77
77
  custom_module = importlib.import_module(custom_module_path)
78
- print("orig_module", orig_module, orig_class_name)
79
78
  # Validate class existence before patching
80
79
  if not hasattr(orig_module, orig_class_name):
81
80
  raise PatchError(f"Original class[{orig_class_name}] not found: {orig_module}")
@@ -25,6 +25,9 @@ MODEL_CONFIG = {
25
25
  "deepseek_v3": {
26
26
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
27
27
  },
28
+ "deepseek_v4": {
29
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
30
+ },
28
31
  "dia": {
29
32
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
30
33
  },
@@ -195,6 +198,9 @@ MODEL_CONFIG = {
195
198
  "jetmoe": {
196
199
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
197
200
  },
201
+ "laguna": {
202
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
203
+ },
198
204
  "llama4": {
199
205
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
200
206
  PATCH.EXPERTS_DEFUSE: [
@@ -325,7 +325,6 @@ def _apply_custom_replacements(
325
325
  module,
326
326
  model.config,
327
327
  ).to(orig_dtype)
328
- print("replacement", replacement)
329
328
  model.set_submodule(name, replacement)
330
329
  replaced.append((name, replacement_cls))
331
330
  else:
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
9
9
 
10
10
  [project]
11
11
  name = "Defuser"
12
- version = "0.0.20"
12
+ version = "0.0.22"
13
13
  description = "Model defuser helper for HF Transformers."
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.9"
@@ -17,6 +17,7 @@ from transformers.models.glm4_moe_lite.modeling_glm4_moe_lite import (
17
17
  )
18
18
  from transformers.models.glm4v.configuration_glm4v import Glm4vConfig
19
19
  from transformers.models.glm4v.modeling_glm4v import Glm4vForConditionalGeneration
20
+ from transformers.models.laguna.modeling_laguna import LagunaConfig, LagunaForCausalLM
20
21
  from transformers.models.mixtral.configuration_mixtral import MixtralConfig
21
22
  from transformers.models.mixtral.modeling_mixtral import MixtralForCausalLM, MixtralSparseMoeBlock
22
23
  from transformers.models.qwen2_moe.modeling_qwen2_moe import (
@@ -255,6 +256,29 @@ def _tiny_gpt_oss_config():
255
256
  )
256
257
 
257
258
 
259
+ def _tiny_laguna_config():
260
+ return LagunaConfig(
261
+ vocab_size=128,
262
+ hidden_size=64,
263
+ intermediate_size=128,
264
+ num_hidden_layers=2,
265
+ num_attention_heads=4,
266
+ num_attention_heads_per_layer=[4, 4],
267
+ num_key_value_heads=1,
268
+ head_dim=16,
269
+ num_experts=4,
270
+ num_experts_per_tok=2,
271
+ moe_intermediate_size=32,
272
+ shared_expert_intermediate_size=32,
273
+ mlp_layer_types=["dense", "sparse"],
274
+ layer_types=["full_attention", "full_attention"],
275
+ hidden_act="silu",
276
+ pad_token_id=0,
277
+ bos_token_id=1,
278
+ eos_token_id=2,
279
+ )
280
+
281
+
258
282
  def _tiny_llama4_config():
259
283
  return Llama4Config(
260
284
  text_config={
@@ -1173,6 +1197,61 @@ def test_gpt_oss_split_forward_matches_fused_math():
1173
1197
  # The split experts path should exactly reproduce the original fused experts math.
1174
1198
  torch.testing.assert_close(actual, expected)
1175
1199
 
1200
+
1201
+ def test_laguna():
1202
+ from transformers.models.laguna.modeling_laguna import LagunaExperts
1203
+
1204
+ model = LagunaForCausalLM(_tiny_laguna_config())
1205
+ assert model.config.model_type == "laguna"
1206
+
1207
+ original_moe_block = model.model.layers[1].mlp
1208
+ experts = original_moe_block.experts
1209
+ assert isinstance(experts, LagunaExperts)
1210
+
1211
+ hidden_dim = experts.gate_up_proj.shape[-1]
1212
+ intermediate_dim = experts.gate_up_proj.shape[1] // 2
1213
+
1214
+ expected_gate = experts.gate_up_proj[0, :intermediate_dim, :hidden_dim].contiguous().clone()
1215
+ expected_up = experts.gate_up_proj[0, intermediate_dim:, :hidden_dim].contiguous().clone()
1216
+ expected_down = experts.down_proj[0, :hidden_dim, :intermediate_dim].contiguous().clone()
1217
+
1218
+ converted = convert_model(model, cleanup_original=False, max_layers=2)
1219
+ assert converted
1220
+
1221
+ experts = model.model.layers[1].mlp.experts
1222
+ _assert_unfused_expert_module(experts)
1223
+ expert0 = getattr(experts, "0")
1224
+
1225
+ materialize_model(model.model.layers[1])
1226
+
1227
+ torch.testing.assert_close(expert0.gate_proj.weight, expected_gate)
1228
+ torch.testing.assert_close(expert0.up_proj.weight, expected_up)
1229
+ torch.testing.assert_close(expert0.down_proj.weight, expected_down)
1230
+
1231
+
1232
+ def test_laguna_split_forward_matches_fused_math():
1233
+ model = LagunaForCausalLM(_tiny_laguna_config())
1234
+ fused_experts = model.model.layers[1].mlp.experts
1235
+
1236
+ hidden_states = torch.randn(5, model.config.hidden_size, dtype=torch.float32)
1237
+ top_k_index = torch.zeros((hidden_states.size(0), 1), dtype=torch.long)
1238
+ top_k_weights = torch.ones((hidden_states.size(0), 1), dtype=hidden_states.dtype)
1239
+
1240
+ with torch.no_grad():
1241
+ expected = fused_experts(hidden_states, top_k_index, top_k_weights)
1242
+
1243
+ converted = convert_model(model, cleanup_original=False, max_layers=2)
1244
+ assert converted
1245
+
1246
+ split_experts = model.model.layers[1].mlp.experts
1247
+ _assert_unfused_expert_module(split_experts)
1248
+ materialize_model(model.model.layers[1])
1249
+ with torch.no_grad():
1250
+ actual = split_experts(hidden_states, top_k_index, top_k_weights)
1251
+
1252
+ torch.testing.assert_close(actual, expected)
1253
+
1254
+
1176
1255
  def test_llama4():
1177
1256
  from transformers.models.llama4.modeling_llama4 import Llama4TextMoe
1178
1257
 
@@ -167,12 +167,32 @@ def _build_model_config(case: dict):
167
167
  elif model_type == "glm_moe_dsa":
168
168
  config.mlp_layer_types = ["sparse"] * config.num_hidden_layers
169
169
  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"]
171
174
  config.shared_intermediate_size = 64
172
175
  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
173
184
  elif model_type == "lfm2_moe":
174
185
  config.layer_types = ["full_attention", "short_conv"]
175
186
  config.num_dense_layers = 0
187
+ elif model_type == "laguna":
188
+ config.layer_types = ["full_attention"] * config.num_hidden_layers
189
+ config.mlp_layer_types = ["dense"] + ["sparse"] * (config.num_hidden_layers - 1)
190
+ 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
176
196
  elif model_type == "qwen3_omni_moe":
177
197
  config.enable_audio_output = True
178
198
  config.talker_config.spatial_merge_size = 2
@@ -188,6 +208,12 @@ def _build_model_config(case: dict):
188
208
  num_key_value_heads=1,
189
209
  head_dim=16,
190
210
  )
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))
191
217
 
192
218
  return config
193
219
 
@@ -305,6 +331,17 @@ META_MODEL_CASES = [
305
331
  "validator": "experts",
306
332
  "min_targets": 2,
307
333
  },
334
+ {
335
+ "model_type": "deepseek_v4",
336
+ "mode": "convert",
337
+ "model_module": "transformers.models.deepseek_v4.modeling_deepseek_v4",
338
+ "model_class": "DeepseekV4ForCausalLM",
339
+ "config_module": "transformers.models.deepseek_v4.configuration_deepseek_v4",
340
+ "config_class": "DeepseekV4Config",
341
+ "target_class_paths": ("transformers.models.deepseek_v4.modeling_deepseek_v4.DeepseekV4Experts",),
342
+ "validator": "experts",
343
+ "min_targets": 2,
344
+ },
308
345
  {
309
346
  "model_type": "dia",
310
347
  "mode": "convert",
@@ -537,6 +574,16 @@ META_MODEL_CASES = [
537
574
  "validator": "parallel",
538
575
  "min_targets": 4,
539
576
  },
577
+ {
578
+ "model_type": "laguna",
579
+ "mode": "convert",
580
+ "model_module": "transformers.models.laguna.modeling_laguna",
581
+ "model_class": "LagunaForCausalLM",
582
+ "config_module": "transformers.models.laguna.configuration_laguna",
583
+ "config_class": "LagunaConfig",
584
+ "target_class_paths": ("transformers.models.laguna.modeling_laguna.LagunaExperts",),
585
+ "validator": "experts",
586
+ },
540
587
  {
541
588
  "model_type": "lfm2_moe",
542
589
  "mode": "convert",
File without changes
File without changes
File without changes
File without changes
File without changes