Defuser 0.0.19__tar.gz → 0.0.20__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.
- {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/PKG-INFO +1 -1
- {defuser-0.0.19 → defuser-0.0.20}/PKG-INFO +1 -1
- {defuser-0.0.19 → defuser-0.0.20}/pyproject.toml +2 -2
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_convert_model.py +92 -0
- {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/SOURCES.txt +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/dependency_links.txt +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/requires.txt +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/top_level.txt +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/LICENSE +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/README.md +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/__init__.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/checkpoint_ops.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/defuser.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/model_registry.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/__init__.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/glm4v.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/model_patches.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/moe_experts_interface.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/replace_modules.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/runtime_defusion.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/__init__.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/common.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/glm4_moe.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/glm4_moe_lite.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/mixtral.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen2_moe.py +1 -1
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen3_moe.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen3_next.py +1 -1
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen3_omni_moe.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/update_module.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/__init__.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/common.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/device.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/hf.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/model.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/setup.cfg +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/setup.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_candidate_coverage.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_device_utils.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_filter_rules.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_meta_model_defusion.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_moe_experts_interface.py +0 -0
- {defuser-0.0.19 → defuser-0.0.20}/tests/test_replace_modules_tracker.py +0 -0
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
# Contact: qubitium@modelcloud.ai, x.com/qubitium
|
|
5
5
|
|
|
6
6
|
[build-system]
|
|
7
|
-
requires = ["setuptools>=
|
|
7
|
+
requires = ["setuptools>=77.0.1,<83"]
|
|
8
8
|
build-backend = "setuptools.build_meta"
|
|
9
9
|
|
|
10
10
|
[project]
|
|
11
11
|
name = "Defuser"
|
|
12
|
-
version = "0.0.
|
|
12
|
+
version = "0.0.20"
|
|
13
13
|
description = "Model defuser helper for HF Transformers."
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.9"
|
|
@@ -402,6 +402,76 @@ def _assert_sparse_moe_defused_matches_fused_math(
|
|
|
402
402
|
torch.testing.assert_close(actual, expected, **assert_close_kwargs)
|
|
403
403
|
|
|
404
404
|
|
|
405
|
+
def _force_route_all_experts(block: nn.Module) -> None:
|
|
406
|
+
"""Set MoE routers to select all experts so execution-order hooks always fire."""
|
|
407
|
+
|
|
408
|
+
router = getattr(block, "gate", None)
|
|
409
|
+
num_experts = getattr(block, "num_experts", None)
|
|
410
|
+
if router is None or num_experts is None:
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
for name in ("top_k", "num_experts_per_tok"):
|
|
414
|
+
if hasattr(router, name):
|
|
415
|
+
setattr(router, name, num_experts)
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _semantic_sparse_moe_execution_order(block: nn.Module, hidden_states: torch.Tensor) -> list[str]:
|
|
420
|
+
"""Record semantic MoE execution order for shared expert, router, routed experts, and shared gate."""
|
|
421
|
+
|
|
422
|
+
_force_route_all_experts(block)
|
|
423
|
+
raw_events: list[str] = []
|
|
424
|
+
handles = []
|
|
425
|
+
|
|
426
|
+
def _record(event_name: str):
|
|
427
|
+
def _hook(_module, _inputs):
|
|
428
|
+
raw_events.append(event_name)
|
|
429
|
+
return _hook
|
|
430
|
+
|
|
431
|
+
if hasattr(block, "shared_expert"):
|
|
432
|
+
handles.append(block.shared_expert.register_forward_pre_hook(_record("shared_expert")))
|
|
433
|
+
if hasattr(block, "gate"):
|
|
434
|
+
handles.append(block.gate.register_forward_pre_hook(_record("gate")))
|
|
435
|
+
if hasattr(block, "shared_expert_gate"):
|
|
436
|
+
handles.append(block.shared_expert_gate.register_forward_pre_hook(_record("shared_expert_gate")))
|
|
437
|
+
|
|
438
|
+
experts = getattr(block, "experts", None)
|
|
439
|
+
if isinstance(experts, nn.ModuleList):
|
|
440
|
+
for idx, expert in enumerate(experts):
|
|
441
|
+
handles.append(expert.register_forward_pre_hook(_record(f"expert_{idx}")))
|
|
442
|
+
elif isinstance(experts, nn.Module):
|
|
443
|
+
handles.append(experts.register_forward_pre_hook(_record("experts")))
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
with torch.inference_mode():
|
|
447
|
+
block.eval()(hidden_states)
|
|
448
|
+
finally:
|
|
449
|
+
for handle in handles:
|
|
450
|
+
handle.remove()
|
|
451
|
+
|
|
452
|
+
semantic_events: list[str] = []
|
|
453
|
+
for event in raw_events:
|
|
454
|
+
normalized = "routed_experts" if event.startswith("expert_") or event == "experts" else event
|
|
455
|
+
if not semantic_events or semantic_events[-1] != normalized:
|
|
456
|
+
semantic_events.append(normalized)
|
|
457
|
+
return semantic_events
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _assert_sparse_moe_defused_matches_fused_execution_order(
|
|
461
|
+
original_block: nn.Module,
|
|
462
|
+
defused_block: nn.Module,
|
|
463
|
+
hidden_states: torch.Tensor,
|
|
464
|
+
) -> None:
|
|
465
|
+
"""Defused blocks must preserve the same semantic execution order as fused HF blocks."""
|
|
466
|
+
|
|
467
|
+
_seed_floating_tensors(original_block)
|
|
468
|
+
_copy_sparse_moe_weights(original_block, defused_block)
|
|
469
|
+
|
|
470
|
+
expected = _semantic_sparse_moe_execution_order(original_block, hidden_states)
|
|
471
|
+
actual = _semantic_sparse_moe_execution_order(defused_block, hidden_states)
|
|
472
|
+
assert actual == expected
|
|
473
|
+
|
|
474
|
+
|
|
405
475
|
def test_qwen2_moe():
|
|
406
476
|
model_type = "qwen2_moe"
|
|
407
477
|
replace_fused_blocks(model_type)
|
|
@@ -858,6 +928,17 @@ def test_qwen2_moe_defused_forward_matches_fused_math():
|
|
|
858
928
|
)
|
|
859
929
|
|
|
860
930
|
|
|
931
|
+
def test_qwen2_moe_defused_forward_matches_fused_execution_order():
|
|
932
|
+
config = _tiny_moe_config(Qwen2MoeConfig)
|
|
933
|
+
hidden_states = torch.randn(2, 3, config.hidden_size, dtype=torch.float32)
|
|
934
|
+
|
|
935
|
+
_assert_sparse_moe_defused_matches_fused_execution_order(
|
|
936
|
+
Qwen2MoeSparseMoeBlock(config),
|
|
937
|
+
LinearQwen2MoeSparseMoeBlock(config),
|
|
938
|
+
hidden_states,
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
|
|
861
942
|
def test_qwen3_moe_defused_forward_matches_fused_math():
|
|
862
943
|
config = _tiny_moe_config(Qwen3MoeConfig)
|
|
863
944
|
hidden_states = torch.randn(2, 3, config.hidden_size, dtype=torch.float32)
|
|
@@ -880,6 +961,17 @@ def test_qwen3_next_defused_forward_matches_fused_math():
|
|
|
880
961
|
)
|
|
881
962
|
|
|
882
963
|
|
|
964
|
+
def test_qwen3_next_defused_forward_matches_fused_execution_order():
|
|
965
|
+
config = _tiny_moe_config(Qwen3NextConfig)
|
|
966
|
+
hidden_states = torch.randn(2, 3, config.hidden_size, dtype=torch.float32)
|
|
967
|
+
|
|
968
|
+
_assert_sparse_moe_defused_matches_fused_execution_order(
|
|
969
|
+
Qwen3NextSparseMoeBlock(config),
|
|
970
|
+
LinearQwen3NextSparseMoeBlock(config),
|
|
971
|
+
hidden_states,
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
|
|
883
975
|
def test_qwen3_omni_defused_forward_matches_fused_math():
|
|
884
976
|
config = _tiny_qwen3_omni_config().thinker_config.text_config
|
|
885
977
|
hidden_states = torch.randn(2, 3, config.hidden_size, dtype=torch.float32)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -34,6 +34,7 @@ class LinearQwen2MoeSparseMoeBlock(nn.Module):
|
|
|
34
34
|
"""Route tokens exactly like HF Qwen2 MoE, then run explicit expert modules."""
|
|
35
35
|
batch_size, sequence_length, hidden_dim = hidden_states.shape
|
|
36
36
|
hidden_states = hidden_states.view(-1, hidden_dim)
|
|
37
|
+
shared_expert_output = self.shared_expert(hidden_states)
|
|
37
38
|
_, routing_weights, selected_experts = self.gate(hidden_states)
|
|
38
39
|
routing_weights = routing_weights.to(hidden_states.dtype)
|
|
39
40
|
final_hidden_states = run_routed_experts(
|
|
@@ -44,7 +45,6 @@ class LinearQwen2MoeSparseMoeBlock(nn.Module):
|
|
|
44
45
|
self.num_experts,
|
|
45
46
|
)
|
|
46
47
|
|
|
47
|
-
shared_expert_output = self.shared_expert(hidden_states)
|
|
48
48
|
shared_expert_output = F.sigmoid(self.shared_expert_gate(hidden_states)) * shared_expert_output
|
|
49
49
|
|
|
50
50
|
final_hidden_states = final_hidden_states + shared_expert_output
|
|
File without changes
|
|
@@ -33,6 +33,7 @@ class LinearQwen3NextSparseMoeBlock(nn.Module):
|
|
|
33
33
|
"""Route tokens exactly like HF Qwen3-Next MoE, then run explicit experts."""
|
|
34
34
|
batch_size, sequence_length, hidden_dim = hidden_states.shape
|
|
35
35
|
hidden_states = hidden_states.view(-1, hidden_dim)
|
|
36
|
+
shared_expert_output = self.shared_expert(hidden_states)
|
|
36
37
|
_, routing_weights, selected_experts = self.gate(hidden_states)
|
|
37
38
|
routing_weights = routing_weights.to(hidden_states.dtype)
|
|
38
39
|
final_hidden_states = run_routed_experts(
|
|
@@ -43,7 +44,6 @@ class LinearQwen3NextSparseMoeBlock(nn.Module):
|
|
|
43
44
|
self.num_experts,
|
|
44
45
|
)
|
|
45
46
|
|
|
46
|
-
shared_expert_output = self.shared_expert(hidden_states)
|
|
47
47
|
shared_expert_output = F.sigmoid(self.shared_expert_gate(hidden_states)) * shared_expert_output
|
|
48
48
|
|
|
49
49
|
final_hidden_states = final_hidden_states + shared_expert_output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|