Defuser 0.0.16__tar.gz → 0.0.18__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 (45) hide show
  1. {defuser-0.0.16 → defuser-0.0.18}/Defuser.egg-info/PKG-INFO +52 -14
  2. {defuser-0.0.16 → defuser-0.0.18}/Defuser.egg-info/SOURCES.txt +5 -0
  3. defuser-0.0.18/Defuser.egg-info/requires.txt +2 -0
  4. {defuser-0.0.16 → defuser-0.0.18}/PKG-INFO +52 -14
  5. {defuser-0.0.16 → defuser-0.0.18}/README.md +50 -13
  6. {defuser-0.0.16 → defuser-0.0.18}/defuser/__init__.py +13 -3
  7. {defuser-0.0.16 → defuser-0.0.18}/defuser/defuser.py +6 -5
  8. {defuser-0.0.16 → defuser-0.0.18}/defuser/model_registry.py +109 -0
  9. defuser-0.0.18/defuser/modeling/model_patches.py +355 -0
  10. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/moe_experts_interface.py +36 -7
  11. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/replace_modules.py +21 -5
  12. defuser-0.0.18/defuser/modeling/runtime_defusion.py +313 -0
  13. defuser-0.0.18/defuser/modeling/unfused_moe/glm4_moe_lite.py +100 -0
  14. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/qwen3_omni_moe.py +44 -0
  15. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/update_module.py +2 -1
  16. defuser-0.0.18/defuser/utils/common.py +177 -0
  17. {defuser-0.0.16 → defuser-0.0.18}/defuser/utils/hf.py +2 -4
  18. {defuser-0.0.16 → defuser-0.0.18}/pyproject.toml +2 -1
  19. defuser-0.0.18/tests/test_candidate_coverage.py +663 -0
  20. {defuser-0.0.16 → defuser-0.0.18}/tests/test_convert_model.py +67 -3
  21. defuser-0.0.18/tests/test_filter_rules.py +198 -0
  22. defuser-0.0.18/tests/test_meta_model_defusion.py +763 -0
  23. defuser-0.0.16/Defuser.egg-info/requires.txt +0 -1
  24. defuser-0.0.16/defuser/modeling/model_patches.py +0 -124
  25. defuser-0.0.16/defuser/utils/common.py +0 -73
  26. {defuser-0.0.16 → defuser-0.0.18}/Defuser.egg-info/dependency_links.txt +0 -0
  27. {defuser-0.0.16 → defuser-0.0.18}/Defuser.egg-info/top_level.txt +0 -0
  28. {defuser-0.0.16 → defuser-0.0.18}/LICENSE +0 -0
  29. {defuser-0.0.16 → defuser-0.0.18}/defuser/checkpoint_ops.py +0 -0
  30. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/__init__.py +0 -0
  31. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/glm4v.py +0 -0
  32. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/__init__.py +0 -0
  33. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/common.py +0 -0
  34. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/glm4_moe.py +0 -0
  35. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/mixtral.py +0 -0
  36. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/qwen2_moe.py +0 -0
  37. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/qwen3_moe.py +0 -0
  38. {defuser-0.0.16 → defuser-0.0.18}/defuser/modeling/unfused_moe/qwen3_next.py +0 -0
  39. {defuser-0.0.16 → defuser-0.0.18}/defuser/utils/__init__.py +0 -0
  40. {defuser-0.0.16 → defuser-0.0.18}/defuser/utils/device.py +0 -0
  41. {defuser-0.0.16 → defuser-0.0.18}/defuser/utils/model.py +0 -0
  42. {defuser-0.0.16 → defuser-0.0.18}/setup.cfg +0 -0
  43. {defuser-0.0.16 → defuser-0.0.18}/setup.py +0 -0
  44. {defuser-0.0.16 → defuser-0.0.18}/tests/test_device_utils.py +0 -0
  45. {defuser-0.0.16 → defuser-0.0.18}/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.16
3
+ Version: 0.0.18
4
4
  Summary: Model defuser helper for HF Transformers.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -19,6 +19,7 @@ Classifier: Topic :: Text Processing :: Linguistic
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
+ Requires-Dist: pypcre>=0.2.13
22
23
  Requires-Dist: transformers
23
24
  Dynamic: license-file
24
25
 
@@ -57,24 +58,47 @@ from defuser import convert_model, replace_fused_blocks
57
58
  ```
58
59
 
59
60
  - `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
60
- - `convert_model(model, cleanup_original=True, max_layers=None)` converts an already loaded model in place. This is the runtime defusion path used for `qwen3_5_moe` style checkpoints.
61
+ - `convert_model(model, cleanup_original=True, 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.
61
62
  - 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.
62
63
 
64
+ `filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
65
+
66
+ - `+:regex` explicitly includes matching candidate module paths
67
+ - `-:regex` explicitly excludes matching candidate module paths
68
+ - `regex` is shorthand for `+:regex`
69
+ - negative rules take priority over positive rules
70
+ - when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
71
+
63
72
  ## Supported Models
64
73
 
65
- | Model type | Recommended entrypoint | Defused op performed |
74
+ Defuser currently supports the following `transformers==5.3.0` `model_type` values.
75
+
76
+ ### `replace_fused_blocks(model_type)` before load
77
+
78
+ | Model type | Defused op performed |
79
+ | --- | --- |
80
+ | `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
81
+ | `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
82
+ | `glm4v` | Replaces the fused text MLP with split `gate_proj`, `up_proj`, and `down_proj` layers. Also splits fused checkpoint `mlp.gate_up_proj.weight` into `mlp.gate_proj.weight` + `mlp.up_proj.weight`. |
83
+ | `mixtral` | Replaces `MixtralSparseMoeBlock` with `LinearMixtralSparseMoeBlock`. Also remaps legacy Mixtral checkpoint keys and splits fused expert `gate_up_proj` tensors into per-expert `gate_proj` and `up_proj`, plus per-expert `down_proj`. |
84
+ | `qwen2_moe` | Replaces `Qwen2MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
85
+ | `qwen3_moe` | Replaces `Qwen3MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
86
+ | `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
87
+ | `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()`. |
88
+
89
+ ### `convert_model(model)` after load
90
+
91
+ | Pattern | Supported model types | Defused op performed |
66
92
  | --- | --- | --- |
67
- | `mixtral` | `replace_fused_blocks("mixtral")` before load | Replaces `MixtralSparseMoeBlock` with `LinearMixtralSparseMoeBlock`. Also remaps legacy Mixtral checkpoint keys and splits fused expert `gate_up_proj` tensors into per-expert `gate_proj` and `up_proj`, plus per-expert `down_proj`. |
68
- | `qwen2_moe` | `replace_fused_blocks("qwen2_moe")` before load | Replaces `Qwen2MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
69
- | `qwen3_moe` | `replace_fused_blocks("qwen3_moe")` before load | Replaces `Qwen3MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
70
- | `qwen3_5_moe` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused `gate_up_proj` into `gate_proj` + `up_proj` and converts 3D expert tensors into numbered expert `nn.Linear` modules. |
71
- | `qwen3_5_moe_text` | `convert_model(model)` after load | Same runtime expert tensor defusion path as `qwen3_5_moe`, applied to the text-only backbone. |
72
- | `qwen3_next` | `replace_fused_blocks("qwen3_next")` before load | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
73
- | `qwen3_omni_moe` | `replace_fused_blocks("qwen3_omni_moe")` before load | Replaces the thinker text sparse MoE block with a defused per-expert linear block and applies small runtime compatibility patches for text `forward()` and `generate()`. |
74
- | `glm4_moe` | `replace_fused_blocks("glm4_moe")` before load | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
75
- | `glm4v` | `replace_fused_blocks("glm4v")` before load | Replaces the fused text MLP with split `gate_proj`, `up_proj`, and `down_proj` layers. Also splits fused checkpoint `mlp.gate_up_proj.weight` into `mlp.gate_proj.weight` + `mlp.up_proj.weight`. |
76
- | `gpt_oss` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused transposed expert `gate_up_proj` into per-expert `gate_proj` + `up_proj`, carries over expert biases, and converts fused expert tensors into numbered expert `nn.Linear` modules. |
77
- | `llama4` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused transposed expert `gate_up_proj` into per-expert `gate_proj` + `up_proj`, converts fused expert tensors into numbered expert `nn.Linear` modules, and preserves the llama4 batched expert-input execution contract. |
93
+ | 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 into numbered expert `nn.Linear` modules with per-expert `gate_proj`, `up_proj`, and `down_proj`. |
94
+ | 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. |
95
+ | 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. |
96
+ | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
97
+ | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
98
+ | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
99
+ | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
100
+ | 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. |
101
+ | 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
102
 
79
103
  ## Workflow Summary
80
104
 
@@ -101,6 +125,20 @@ converted = convert_model(model)
101
125
  print(converted) # True when runtime defusion happened
102
126
  ```
103
127
 
128
+ Use `filter` when only specific blocks should be defused:
129
+
130
+ ```python
131
+ from defuser import convert_model
132
+
133
+ convert_model(
134
+ model,
135
+ filter=[
136
+ r"+:^model\.layers\.0\.mlp\.experts$",
137
+ r"-:^model\.layers\.0\.mlp\.experts\.shared_",
138
+ ],
139
+ )
140
+ ```
141
+
104
142
  ## Real Qwen3.5 MoE Example
105
143
 
106
144
  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`.
@@ -16,10 +16,12 @@ defuser/modeling/glm4v.py
16
16
  defuser/modeling/model_patches.py
17
17
  defuser/modeling/moe_experts_interface.py
18
18
  defuser/modeling/replace_modules.py
19
+ defuser/modeling/runtime_defusion.py
19
20
  defuser/modeling/update_module.py
20
21
  defuser/modeling/unfused_moe/__init__.py
21
22
  defuser/modeling/unfused_moe/common.py
22
23
  defuser/modeling/unfused_moe/glm4_moe.py
24
+ defuser/modeling/unfused_moe/glm4_moe_lite.py
23
25
  defuser/modeling/unfused_moe/mixtral.py
24
26
  defuser/modeling/unfused_moe/qwen2_moe.py
25
27
  defuser/modeling/unfused_moe/qwen3_moe.py
@@ -30,6 +32,9 @@ defuser/utils/common.py
30
32
  defuser/utils/device.py
31
33
  defuser/utils/hf.py
32
34
  defuser/utils/model.py
35
+ tests/test_candidate_coverage.py
33
36
  tests/test_convert_model.py
34
37
  tests/test_device_utils.py
38
+ tests/test_filter_rules.py
39
+ tests/test_meta_model_defusion.py
35
40
  tests/test_replace_modules_tracker.py
@@ -0,0 +1,2 @@
1
+ pypcre>=0.2.13
2
+ transformers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Defuser
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: Model defuser helper for HF Transformers.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -19,6 +19,7 @@ Classifier: Topic :: Text Processing :: Linguistic
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
+ Requires-Dist: pypcre>=0.2.13
22
23
  Requires-Dist: transformers
23
24
  Dynamic: license-file
24
25
 
@@ -57,24 +58,47 @@ from defuser import convert_model, replace_fused_blocks
57
58
  ```
58
59
 
59
60
  - `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
60
- - `convert_model(model, cleanup_original=True, max_layers=None)` converts an already loaded model in place. This is the runtime defusion path used for `qwen3_5_moe` style checkpoints.
61
+ - `convert_model(model, cleanup_original=True, 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.
61
62
  - 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.
62
63
 
64
+ `filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
65
+
66
+ - `+:regex` explicitly includes matching candidate module paths
67
+ - `-:regex` explicitly excludes matching candidate module paths
68
+ - `regex` is shorthand for `+:regex`
69
+ - negative rules take priority over positive rules
70
+ - when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
71
+
63
72
  ## Supported Models
64
73
 
65
- | Model type | Recommended entrypoint | Defused op performed |
74
+ Defuser currently supports the following `transformers==5.3.0` `model_type` values.
75
+
76
+ ### `replace_fused_blocks(model_type)` before load
77
+
78
+ | Model type | Defused op performed |
79
+ | --- | --- |
80
+ | `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
81
+ | `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
82
+ | `glm4v` | Replaces the fused text MLP with split `gate_proj`, `up_proj`, and `down_proj` layers. Also splits fused checkpoint `mlp.gate_up_proj.weight` into `mlp.gate_proj.weight` + `mlp.up_proj.weight`. |
83
+ | `mixtral` | Replaces `MixtralSparseMoeBlock` with `LinearMixtralSparseMoeBlock`. Also remaps legacy Mixtral checkpoint keys and splits fused expert `gate_up_proj` tensors into per-expert `gate_proj` and `up_proj`, plus per-expert `down_proj`. |
84
+ | `qwen2_moe` | Replaces `Qwen2MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
85
+ | `qwen3_moe` | Replaces `Qwen3MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
86
+ | `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
87
+ | `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()`. |
88
+
89
+ ### `convert_model(model)` after load
90
+
91
+ | Pattern | Supported model types | Defused op performed |
66
92
  | --- | --- | --- |
67
- | `mixtral` | `replace_fused_blocks("mixtral")` before load | Replaces `MixtralSparseMoeBlock` with `LinearMixtralSparseMoeBlock`. Also remaps legacy Mixtral checkpoint keys and splits fused expert `gate_up_proj` tensors into per-expert `gate_proj` and `up_proj`, plus per-expert `down_proj`. |
68
- | `qwen2_moe` | `replace_fused_blocks("qwen2_moe")` before load | Replaces `Qwen2MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
69
- | `qwen3_moe` | `replace_fused_blocks("qwen3_moe")` before load | Replaces `Qwen3MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
70
- | `qwen3_5_moe` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused `gate_up_proj` into `gate_proj` + `up_proj` and converts 3D expert tensors into numbered expert `nn.Linear` modules. |
71
- | `qwen3_5_moe_text` | `convert_model(model)` after load | Same runtime expert tensor defusion path as `qwen3_5_moe`, applied to the text-only backbone. |
72
- | `qwen3_next` | `replace_fused_blocks("qwen3_next")` before load | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
73
- | `qwen3_omni_moe` | `replace_fused_blocks("qwen3_omni_moe")` before load | Replaces the thinker text sparse MoE block with a defused per-expert linear block and applies small runtime compatibility patches for text `forward()` and `generate()`. |
74
- | `glm4_moe` | `replace_fused_blocks("glm4_moe")` before load | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
75
- | `glm4v` | `replace_fused_blocks("glm4v")` before load | Replaces the fused text MLP with split `gate_proj`, `up_proj`, and `down_proj` layers. Also splits fused checkpoint `mlp.gate_up_proj.weight` into `mlp.gate_proj.weight` + `mlp.up_proj.weight`. |
76
- | `gpt_oss` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused transposed expert `gate_up_proj` into per-expert `gate_proj` + `up_proj`, carries over expert biases, and converts fused expert tensors into numbered expert `nn.Linear` modules. |
77
- | `llama4` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused transposed expert `gate_up_proj` into per-expert `gate_proj` + `up_proj`, converts fused expert tensors into numbered expert `nn.Linear` modules, and preserves the llama4 batched expert-input execution contract. |
93
+ | 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 into numbered expert `nn.Linear` modules with per-expert `gate_proj`, `up_proj`, and `down_proj`. |
94
+ | 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. |
95
+ | 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. |
96
+ | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
97
+ | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
98
+ | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
99
+ | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
100
+ | 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. |
101
+ | 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
102
 
79
103
  ## Workflow Summary
80
104
 
@@ -101,6 +125,20 @@ converted = convert_model(model)
101
125
  print(converted) # True when runtime defusion happened
102
126
  ```
103
127
 
128
+ Use `filter` when only specific blocks should be defused:
129
+
130
+ ```python
131
+ from defuser import convert_model
132
+
133
+ convert_model(
134
+ model,
135
+ filter=[
136
+ r"+:^model\.layers\.0\.mlp\.experts$",
137
+ r"-:^model\.layers\.0\.mlp\.experts\.shared_",
138
+ ],
139
+ )
140
+ ```
141
+
104
142
  ## Real Qwen3.5 MoE Example
105
143
 
106
144
  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`.
@@ -33,24 +33,47 @@ from defuser import convert_model, replace_fused_blocks
33
33
  ```
34
34
 
35
35
  - `replace_fused_blocks(model_type)` patches supported HF model classes before `from_pretrained()` or direct model construction.
36
- - `convert_model(model, cleanup_original=True, max_layers=None)` converts an already loaded model in place. This is the runtime defusion path used for `qwen3_5_moe` style checkpoints.
36
+ - `convert_model(model, cleanup_original=True, 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
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
38
 
39
+ `filter` is an optional list of PCRE regex rules evaluated against full module paths such as `model.layers.0.mlp.experts`:
40
+
41
+ - `+:regex` explicitly includes matching candidate module paths
42
+ - `-:regex` explicitly excludes matching candidate module paths
43
+ - `regex` is shorthand for `+:regex`
44
+ - negative rules take priority over positive rules
45
+ - when `filter` is provided, a candidate module is defused only if it matches at least one positive rule and no negative rules
46
+
39
47
  ## Supported Models
40
48
 
41
- | Model type | Recommended entrypoint | Defused op performed |
49
+ Defuser currently supports the following `transformers==5.3.0` `model_type` values.
50
+
51
+ ### `replace_fused_blocks(model_type)` before load
52
+
53
+ | Model type | Defused op performed |
54
+ | --- | --- |
55
+ | `glm4_moe` | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
56
+ | `glm4_moe_lite` | Replaces `Glm4MoeLiteMoE` with a defused per-expert linear MoE block.|
57
+ | `glm4v` | Replaces the fused text MLP with split `gate_proj`, `up_proj`, and `down_proj` layers. Also splits fused checkpoint `mlp.gate_up_proj.weight` into `mlp.gate_proj.weight` + `mlp.up_proj.weight`. |
58
+ | `mixtral` | Replaces `MixtralSparseMoeBlock` with `LinearMixtralSparseMoeBlock`. Also remaps legacy Mixtral checkpoint keys and splits fused expert `gate_up_proj` tensors into per-expert `gate_proj` and `up_proj`, plus per-expert `down_proj`. |
59
+ | `qwen2_moe` | Replaces `Qwen2MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
60
+ | `qwen3_moe` | Replaces `Qwen3MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
61
+ | `qwen3_next` | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
62
+ | `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()`. |
63
+
64
+ ### `convert_model(model)` after load
65
+
66
+ | Pattern | Supported model types | Defused op performed |
42
67
  | --- | --- | --- |
43
- | `mixtral` | `replace_fused_blocks("mixtral")` before load | Replaces `MixtralSparseMoeBlock` with `LinearMixtralSparseMoeBlock`. Also remaps legacy Mixtral checkpoint keys and splits fused expert `gate_up_proj` tensors into per-expert `gate_proj` and `up_proj`, plus per-expert `down_proj`. |
44
- | `qwen2_moe` | `replace_fused_blocks("qwen2_moe")` before load | Replaces `Qwen2MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
45
- | `qwen3_moe` | `replace_fused_blocks("qwen3_moe")` before load | Replaces `Qwen3MoeSparseMoeBlock` with a defused per-expert linear MoE block. |
46
- | `qwen3_5_moe` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused `gate_up_proj` into `gate_proj` + `up_proj` and converts 3D expert tensors into numbered expert `nn.Linear` modules. |
47
- | `qwen3_5_moe_text` | `convert_model(model)` after load | Same runtime expert tensor defusion path as `qwen3_5_moe`, applied to the text-only backbone. |
48
- | `qwen3_next` | `replace_fused_blocks("qwen3_next")` before load | Replaces `Qwen3NextSparseMoeBlock` with a defused per-expert linear MoE block. |
49
- | `qwen3_omni_moe` | `replace_fused_blocks("qwen3_omni_moe")` before load | Replaces the thinker text sparse MoE block with a defused per-expert linear block and applies small runtime compatibility patches for text `forward()` and `generate()`. |
50
- | `glm4_moe` | `replace_fused_blocks("glm4_moe")` before load | Replaces `Glm4MoeMoE` with a defused per-expert linear MoE block. |
51
- | `glm4v` | `replace_fused_blocks("glm4v")` before load | Replaces the fused text MLP with split `gate_proj`, `up_proj`, and `down_proj` layers. Also splits fused checkpoint `mlp.gate_up_proj.weight` into `mlp.gate_proj.weight` + `mlp.up_proj.weight`. |
52
- | `gpt_oss` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused transposed expert `gate_up_proj` into per-expert `gate_proj` + `up_proj`, carries over expert biases, and converts fused expert tensors into numbered expert `nn.Linear` modules. |
53
- | `llama4` | `convert_model(model)` after load | Runtime expert tensor defusion. Splits fused transposed expert `gate_up_proj` into per-expert `gate_proj` + `up_proj`, converts fused expert tensors into numbered expert `nn.Linear` modules, and preserves the llama4 batched expert-input execution contract. |
68
+ | 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 into numbered expert `nn.Linear` modules with per-expert `gate_proj`, `up_proj`, and `down_proj`. |
69
+ | 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. |
70
+ | 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. |
71
+ | Flattened expert layout | `dbrx` | Rebuilds the flattened DBRX expert FFN weights into numbered expert `gate_proj`, `up_proj`, and `down_proj` `nn.Linear` modules. |
72
+ | Batched expert-input execution | `llama4` | Runtime expert tensor defusion plus preservation of the llama4 batched expert-input execution contract. |
73
+ | Non-gated expert MLPs | `nemotron_h` | Converts routed expert tensors into numbered `up_proj` and `down_proj` `nn.Linear` modules for non-gated experts. |
74
+ | Parallel expert blocks | `granitemoe`, `granitemoehybrid`, `granitemoeshared`, `jetmoe` | Converts packed expert weight tensors into numbered expert `linear` modules while keeping grouped expert execution intact. |
75
+ | 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. |
76
+ | 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. |
54
77
 
55
78
  ## Workflow Summary
56
79
 
@@ -77,6 +100,20 @@ converted = convert_model(model)
77
100
  print(converted) # True when runtime defusion happened
78
101
  ```
79
102
 
103
+ Use `filter` when only specific blocks should be defused:
104
+
105
+ ```python
106
+ from defuser import convert_model
107
+
108
+ convert_model(
109
+ model,
110
+ filter=[
111
+ r"+:^model\.layers\.0\.mlp\.experts$",
112
+ r"-:^model\.layers\.0\.mlp\.experts\.shared_",
113
+ ],
114
+ )
115
+ ```
116
+
80
117
  ## Real Qwen3.5 MoE Example
81
118
 
82
119
  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`.
@@ -3,19 +3,29 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
  # Contact: qubitium@modelcloud.ai, x.com/qubitium
5
5
 
6
- from defuser.utils.common import env_flag
6
+ from defuser.utils.common import env_flag, warn_if_public_api_transformers_unsupported
7
+ from logbar import LogBar
7
8
 
8
9
  DEBUG_ON = env_flag("DEBUG")
9
10
 
10
- def convert_model(*args, **kwargs):
11
+ logger = LogBar(__name__)
12
+
13
+
14
+ def convert_model(*args, **kwargs) -> bool:
11
15
  """Lazily import conversion entrypoint to avoid import-time cycles."""
16
+ if warn_if_public_api_transformers_unsupported("convert_model()", logger):
17
+ return False
18
+
12
19
  from .defuser import convert_model as _convert_model
13
20
 
14
21
  return _convert_model(*args, **kwargs)
15
22
 
16
23
 
17
- def replace_fused_blocks(*args, **kwargs):
24
+ def replace_fused_blocks(*args, **kwargs) -> bool:
18
25
  """Lazily import conversion entrypoint to avoid import-time cycles."""
26
+ if warn_if_public_api_transformers_unsupported("replace_fused_blocks()", logger):
27
+ return False
28
+
19
29
  from .defuser import replace_fused_blocks as _replace_fused_blocks
20
30
 
21
31
  return _replace_fused_blocks(*args, **kwargs)
@@ -12,10 +12,10 @@ from defuser.modeling.model_patches import apply_model_class_patches, apply_mode
12
12
  from defuser.modeling.update_module import update_module
13
13
  from defuser.utils.common import (
14
14
  MIN_SUPPORTED_TRANSFORMERS_VERSION,
15
+ is_version_at_least,
15
16
  is_supported_transformers_version,
16
17
  warn_if_public_api_transformers_unsupported,
17
18
  )
18
- from packaging import version
19
19
  import transformers
20
20
  from logbar import LogBar
21
21
 
@@ -69,7 +69,7 @@ def replace_fused_blocks(model_type: str) -> bool:
69
69
  custom_class = getattr(custom_module, custom_class_name)
70
70
  setattr(orig_module, orig_class_name, custom_class)
71
71
 
72
- if version.parse(transformers.__version__) >= version.parse(MIN_SUPPORTED_TRANSFORMERS_VERSION):
72
+ if is_version_at_least(transformers.__version__, MIN_SUPPORTED_TRANSFORMERS_VERSION):
73
73
  from transformers import conversion_mapping
74
74
 
75
75
  if not hasattr(conversion_mapping, "orig_get_checkpoint_conversion_mapping"):
@@ -102,8 +102,7 @@ def check_model_compatibility(model: nn.Module) -> bool:
102
102
  return False
103
103
 
104
104
  min_ver = MODEL_CONFIG[model_type].get("min_transformers_version")
105
- current_ver = version.parse(transformers.__version__)
106
- if min_ver and current_ver < version.parse(min_ver):
105
+ if min_ver and not is_version_at_least(transformers.__version__, min_ver):
107
106
  logger.warn(
108
107
  f"Skip conversion for model_type={model_type}: "
109
108
  f"requires transformers>={min_ver}, current version is {transformers.__version__}."
@@ -117,6 +116,7 @@ def convert_model(
117
116
  model: nn.Module,
118
117
  cleanup_original: bool = False,
119
118
  max_layers: int | None = None,
119
+ filter: list[str] | None = None,
120
120
  ) -> bool:
121
121
  """Convert one loaded model in place from fused experts to defused modules."""
122
122
  if warn_if_public_api_transformers_unsupported("convert_model()", logger):
@@ -200,7 +200,7 @@ def convert_model(
200
200
  if not check_model_compatibility(model):
201
201
  return False
202
202
 
203
- apply_model_patches(model)
203
+ apply_model_patches(model, max_layers=max_layers, filter_rules=filter)
204
204
 
205
205
  # If fused blocks have already been structurally replaced at load model before,
206
206
  # there is no need to perform runtime defusing again
@@ -214,6 +214,7 @@ def convert_model(
214
214
  model,
215
215
  cleanup_original=cleanup_original,
216
216
  max_layers=max_layers,
217
+ filter_rules=filter,
217
218
  )
218
219
 
219
220
  return True
@@ -16,6 +16,39 @@ class PATCH(str, Enum):
16
16
 
17
17
 
18
18
  MODEL_CONFIG = {
19
+ "dbrx": {
20
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
21
+ },
22
+ "deepseek_v2": {
23
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
24
+ },
25
+ "deepseek_v3": {
26
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
27
+ },
28
+ "dia": {
29
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
30
+ },
31
+ "dots1": {
32
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
33
+ },
34
+ "ernie4_5_moe": {
35
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
36
+ },
37
+ "ernie4_5_vl_moe": {
38
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
39
+ },
40
+ "exaone_moe": {
41
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
42
+ },
43
+ "flex_olmo": {
44
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
45
+ },
46
+ "glm": {
47
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
48
+ },
49
+ "glm4": {
50
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
51
+ },
19
52
  "mixtral": {
20
53
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
21
54
  PATCH.REPLACE_MODULE: [
@@ -84,6 +117,10 @@ MODEL_CONFIG = {
84
117
  (
85
118
  "transformers.models.qwen3_omni_moe.modeling_qwen3_omni_moe.Qwen3OmniMoeThinkerTextSparseMoeBlock",
86
119
  "defuser.modeling.unfused_moe.qwen3_omni_moe.LinearQwen3OmniMoeThinkerTextSparseMoeBlock",
120
+ ),
121
+ (
122
+ "transformers.models.qwen3_omni_moe.modeling_qwen3_omni_moe.Qwen3OmniMoeTalkerTextSparseMoeBlock",
123
+ "defuser.modeling.unfused_moe.qwen3_omni_moe.LinearQwen3OmniMoeTalkerTextSparseMoeBlock",
87
124
  )
88
125
  ],
89
126
  },
@@ -96,6 +133,15 @@ MODEL_CONFIG = {
96
133
  )
97
134
  ],
98
135
  },
136
+ "glm4_moe_lite": {
137
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
138
+ PATCH.REPLACE_MODULE: [
139
+ (
140
+ "transformers.models.glm4_moe_lite.modeling_glm4_moe_lite.Glm4MoeLiteMoE",
141
+ "defuser.modeling.unfused_moe.glm4_moe_lite.LinearGlm4MoeLiteMoE",
142
+ )
143
+ ],
144
+ },
99
145
  "glm4v": {
100
146
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
101
147
  PATCH.REPLACE_MODULE: [
@@ -116,9 +162,39 @@ MODEL_CONFIG = {
116
162
  ),
117
163
  ],
118
164
  },
165
+ "glm4v_moe": {
166
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
167
+ },
168
+ "glm_image": {
169
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
170
+ },
171
+ "glm_moe_dsa": {
172
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
173
+ },
174
+ "glm_ocr": {
175
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
176
+ },
119
177
  "gpt_oss": {
120
178
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
121
179
  },
180
+ "granitemoe": {
181
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
182
+ },
183
+ "granitemoehybrid": {
184
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
185
+ },
186
+ "granitemoeshared": {
187
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
188
+ },
189
+ "hunyuan_v1_moe": {
190
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
191
+ },
192
+ "jamba": {
193
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
194
+ },
195
+ "jetmoe": {
196
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
197
+ },
122
198
  "llama4": {
123
199
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
124
200
  PATCH.EXPERTS_DEFUSE: [
@@ -128,7 +204,40 @@ MODEL_CONFIG = {
128
204
  }
129
205
  ],
130
206
  },
207
+ "lfm2_moe": {
208
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
209
+ },
210
+ "longcat_flash": {
211
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
212
+ },
213
+ "minimax": {
214
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
215
+ },
216
+ "minimax_m2": {
217
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
218
+ },
219
+ "nemotron_h": {
220
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
221
+ },
222
+ "olmoe": {
223
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
224
+ },
225
+ "phi3": {
226
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
227
+ },
228
+ "phi4_multimodal": {
229
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
230
+ },
131
231
  "phimoe": {
132
232
  "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
133
233
  },
234
+ "qwen3_vl_moe": {
235
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
236
+ },
237
+ "solar_open": {
238
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
239
+ },
240
+ "zamba2": {
241
+ "min_transformers_version": MIN_SUPPORTED_TRANSFORMERS_VERSION,
242
+ },
134
243
  }