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.
Files changed (43) hide show
  1. {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/PKG-INFO +1 -1
  2. {defuser-0.0.19 → defuser-0.0.20}/PKG-INFO +1 -1
  3. {defuser-0.0.19 → defuser-0.0.20}/pyproject.toml +2 -2
  4. {defuser-0.0.19 → defuser-0.0.20}/tests/test_convert_model.py +92 -0
  5. {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/SOURCES.txt +0 -0
  6. {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/dependency_links.txt +0 -0
  7. {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/requires.txt +0 -0
  8. {defuser-0.0.19 → defuser-0.0.20}/Defuser.egg-info/top_level.txt +0 -0
  9. {defuser-0.0.19 → defuser-0.0.20}/LICENSE +0 -0
  10. {defuser-0.0.19 → defuser-0.0.20}/README.md +0 -0
  11. {defuser-0.0.19 → defuser-0.0.20}/defuser/__init__.py +0 -0
  12. {defuser-0.0.19 → defuser-0.0.20}/defuser/checkpoint_ops.py +0 -0
  13. {defuser-0.0.19 → defuser-0.0.20}/defuser/defuser.py +0 -0
  14. {defuser-0.0.19 → defuser-0.0.20}/defuser/model_registry.py +0 -0
  15. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/__init__.py +0 -0
  16. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/glm4v.py +0 -0
  17. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/model_patches.py +0 -0
  18. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/moe_experts_interface.py +0 -0
  19. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/replace_modules.py +0 -0
  20. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/runtime_defusion.py +0 -0
  21. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/__init__.py +0 -0
  22. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/common.py +0 -0
  23. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/glm4_moe.py +0 -0
  24. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/glm4_moe_lite.py +0 -0
  25. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/mixtral.py +0 -0
  26. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen2_moe.py +1 -1
  27. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen3_moe.py +0 -0
  28. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen3_next.py +1 -1
  29. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/unfused_moe/qwen3_omni_moe.py +0 -0
  30. {defuser-0.0.19 → defuser-0.0.20}/defuser/modeling/update_module.py +0 -0
  31. {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/__init__.py +0 -0
  32. {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/common.py +0 -0
  33. {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/device.py +0 -0
  34. {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/hf.py +0 -0
  35. {defuser-0.0.19 → defuser-0.0.20}/defuser/utils/model.py +0 -0
  36. {defuser-0.0.19 → defuser-0.0.20}/setup.cfg +0 -0
  37. {defuser-0.0.19 → defuser-0.0.20}/setup.py +0 -0
  38. {defuser-0.0.19 → defuser-0.0.20}/tests/test_candidate_coverage.py +0 -0
  39. {defuser-0.0.19 → defuser-0.0.20}/tests/test_device_utils.py +0 -0
  40. {defuser-0.0.19 → defuser-0.0.20}/tests/test_filter_rules.py +0 -0
  41. {defuser-0.0.19 → defuser-0.0.20}/tests/test_meta_model_defusion.py +0 -0
  42. {defuser-0.0.19 → defuser-0.0.20}/tests/test_moe_experts_interface.py +0 -0
  43. {defuser-0.0.19 → defuser-0.0.20}/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.19
3
+ Version: 0.0.20
4
4
  Summary: Model defuser helper for HF Transformers.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Defuser
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: Model defuser helper for HF Transformers.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -4,12 +4,12 @@
4
4
  # Contact: qubitium@modelcloud.ai, x.com/qubitium
5
5
 
6
6
  [build-system]
7
- requires = ["setuptools>=80"]
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.19"
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
@@ -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
@@ -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