interp-engine 1.6.0__tar.gz → 1.7.0__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 (177) hide show
  1. {interp_engine-1.6.0 → interp_engine-1.7.0}/PKG-INFO +1 -1
  2. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/facts.py +4 -1
  3. {interp_engine-1.6.0 → interp_engine-1.7.0}/pyproject.toml +1 -1
  4. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/synthetic_families.py +38 -0
  5. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_mlp_internals.py +7 -5
  6. {interp_engine-1.6.0 → interp_engine-1.7.0}/.gitignore +0 -0
  7. {interp_engine-1.6.0 → interp_engine-1.7.0}/LICENSE +0 -0
  8. {interp_engine-1.6.0 → interp_engine-1.7.0}/README.md +0 -0
  9. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/README.md +0 -0
  10. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/__init__.py +0 -0
  11. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/bench_spec.py +0 -0
  12. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/cells.py +0 -0
  13. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/probe.py +0 -0
  14. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/probe_lens_stream.py +0 -0
  15. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/publish.py +0 -0
  16. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/report_bench.py +0 -0
  17. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/deepseek-v4-flash-0731__eager.json +0 -0
  18. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/deepseek-v4-flash-0731__vllm-cudagraph.json +0 -0
  19. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/deepseek-v4-flash-0731__vllm-static.json +0 -0
  20. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/deepseek-v4-flash-0731__vllm.json +0 -0
  21. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/gemma-2-2b__eager.json +0 -0
  22. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/gemma-2-2b__vllm-cudagraph.json +0 -0
  23. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/gemma-2-2b__vllm-static.json +0 -0
  24. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/gemma-2-2b__vllm.json +0 -0
  25. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/llama-3.1-8b__eager.json +0 -0
  26. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/llama-3.1-8b__vllm-cudagraph.json +0 -0
  27. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/llama-3.1-8b__vllm-static.json +0 -0
  28. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/llama-3.1-8b__vllm.json +0 -0
  29. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3-4b__eager.json +0 -0
  30. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3-4b__vllm-cudagraph.json +0 -0
  31. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3-4b__vllm-static.json +0 -0
  32. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3-4b__vllm.json +0 -0
  33. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3.8-27b__eager.json +0 -0
  34. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3.8-27b__vllm-cudagraph.json +0 -0
  35. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3.8-27b__vllm-static.json +0 -0
  36. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results/qwen3.8-27b__vllm.json +0 -0
  37. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/results-latest.md +0 -0
  38. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/run_all.sh +0 -0
  39. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/run_bench.py +0 -0
  40. {interp_engine-1.6.0 → interp_engine-1.7.0}/benchmarks/workloads.py +0 -0
  41. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/AGENT_INTEGRATION.md +0 -0
  42. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/ARCHITECTURE_QUIRKS.md +0 -0
  43. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/COMPATIBILITY.md +0 -0
  44. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/ENGINE_HOOK_MAPPINGS.md +0 -0
  45. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/GRADIENTS.md +0 -0
  46. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/INTERNALS.md +0 -0
  47. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/PERFORMANCE.md +0 -0
  48. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/PORTING.md +0 -0
  49. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/README.md +0 -0
  50. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/SUPPORTED_POINTS.md +0 -0
  51. {interp_engine-1.6.0 → interp_engine-1.7.0}/docs/USAGE.md +0 -0
  52. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/__init__.py +0 -0
  53. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/_loop.py +0 -0
  54. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/address.py +0 -0
  55. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/arch.py +0 -0
  56. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/attn_config.py +0 -0
  57. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/attn_scores.py +0 -0
  58. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/autograd_support.py +0 -0
  59. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/capture.py +0 -0
  60. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/chat_compose.py +0 -0
  61. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/chat_conventions.py +0 -0
  62. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/chat_formatters.py +0 -0
  63. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/cuda_preflight.py +0 -0
  64. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/dispatch.py +0 -0
  65. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/hooks.py +0 -0
  66. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/lens.py +0 -0
  67. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/load.py +0 -0
  68. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/mappers.py +0 -0
  69. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/memory.py +0 -0
  70. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/model.py +0 -0
  71. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/moe_routing.py +0 -0
  72. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/notebook_stdout.py +0 -0
  73. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/points.py +0 -0
  74. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/protocol.py +0 -0
  75. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/residual_basis.py +0 -0
  76. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/select.py +0 -0
  77. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/steer.py +0 -0
  78. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/steer_specs.py +0 -0
  79. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/sync.py +0 -0
  80. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/tokenize.py +0 -0
  81. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_backend.py +0 -0
  82. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/__init__.py +0 -0
  83. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/_demux.py +0 -0
  84. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/_hooks.py +0 -0
  85. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/_payload.py +0 -0
  86. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/_tree.py +0 -0
  87. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/attn.py +0 -0
  88. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/capture.py +0 -0
  89. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/graphs.py +0 -0
  90. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/lens/__init__.py +0 -0
  91. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/lens/intervene.py +0 -0
  92. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/lens/readout.py +0 -0
  93. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/lens/unembed.py +0 -0
  94. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/mhc.py +0 -0
  95. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/native.py +0 -0
  96. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/requests.py +0 -0
  97. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/static.py +0 -0
  98. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_capture/steering.py +0 -0
  99. {interp_engine-1.6.0 → interp_engine-1.7.0}/interp_engine/vllm_plugin.py +0 -0
  100. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/conftest.py +0 -0
  101. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/harness.py +0 -0
  102. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/model_expectations.yaml +0 -0
  103. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_address.py +0 -0
  104. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_attn_config_tripwire.py +0 -0
  105. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_attn_probs_indexing.py +0 -0
  106. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_attn_scores.py +0 -0
  107. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_attn_z_gqa.py +0 -0
  108. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_autograd_support.py +0 -0
  109. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_bench_workloads.py +0 -0
  110. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_capability_refusals.py +0 -0
  111. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_capture_addressing.py +0 -0
  112. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_chat_compose.py +0 -0
  113. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_chat_formatters.py +0 -0
  114. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_chat_templates.py +0 -0
  115. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_core.py +0 -0
  116. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_cuda_preflight.py +0 -0
  117. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_doc_code_fences.py +0 -0
  118. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_eager_autograd.py +0 -0
  119. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_facts.py +0 -0
  120. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_family_points.py +0 -0
  121. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_gated_attn_out.py +0 -0
  122. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_gpu_sizer.py +0 -0
  123. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_head_contributions.py +0 -0
  124. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_hook_call_conventions.py +0 -0
  125. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_layer_kinds.py +0 -0
  126. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_load.py +0 -0
  127. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_logit_transform.py +0 -0
  128. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_mappers.py +0 -0
  129. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_memory.py +0 -0
  130. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_model_expectations.py +0 -0
  131. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_moe.py +0 -0
  132. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_multimodal_arch.py +0 -0
  133. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_new_models_gpu.py +0 -0
  134. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_no_chat_template.py +0 -0
  135. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_normalized_hook.py +0 -0
  136. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_notebook_stdout.py +0 -0
  137. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_packaging.py +0 -0
  138. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_parity_gpt2.py +0 -0
  139. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_per_layer_attn_dims.py +0 -0
  140. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_points_registry.py +0 -0
  141. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_protocol.py +0 -0
  142. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_published_benchmarks.py +0 -0
  143. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_qk_norm.py +0 -0
  144. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_qkv_layout.py +0 -0
  145. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_reasoning_spans.py +0 -0
  146. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_release.py +0 -0
  147. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_resid_mid.py +0 -0
  148. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_residual_basis.py +0 -0
  149. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_sandwich_norms.py +0 -0
  150. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_select.py +0 -0
  151. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_sliding_window_attn.py +0 -0
  152. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_small_models_gpu.py +0 -0
  153. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_static_dsv4_gpu.py +0 -0
  154. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_static_parity_gpu.py +0 -0
  155. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_static_set.py +0 -0
  156. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_static_warmup.py +0 -0
  157. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_steer_context.py +0 -0
  158. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_steer_math_parity.py +0 -0
  159. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_sync_loop.py +0 -0
  160. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_sync_parity.py +0 -0
  161. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_unified_free_functions.py +0 -0
  162. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_unresolved_families.py +0 -0
  163. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_capture_gpu.py +0 -0
  164. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_capture_scales.py +0 -0
  165. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_engine_loop.py +0 -0
  166. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_graph_path.py +0 -0
  167. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_graphs_on_gpu.py +0 -0
  168. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_hook_availability.py +0 -0
  169. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_hyper_connections.py +0 -0
  170. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_kv_isolation.py +0 -0
  171. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_new_points.py +0 -0
  172. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_only_families.py +0 -0
  173. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_plugin.py +0 -0
  174. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vllm_wire_grammar.py +0 -0
  175. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_vocabulary_boundary.py +0 -0
  176. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_worker_lens_capture_readout.py +0 -0
  177. {interp_engine-1.6.0 → interp_engine-1.7.0}/tests/test_worker_lens_readout.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: interp-engine
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: A fast, standardized, and easy to use interpretability engine.
5
5
  Project-URL: Homepage, https://github.com/decoderesearch/interp-engine
6
6
  Project-URL: Repository, https://github.com/decoderesearch/interp-engine
@@ -478,6 +478,7 @@ class GateUpLayout(StrEnum):
478
478
 
479
479
  #: ``[all_gate | all_up]``, two contiguous halves, gate first. Phi-3's ``gate_up_proj``, whose
480
480
  #: forward is ``gate, up = self.gate_up_proj(x).chunk(2, dim=-1); down(up * act(gate))``.
481
+ #: GLM-4's ``Glm4MLP`` performs the identical chunk.
481
482
  GATE_FIRST = "gate_first"
482
483
  #: ``[gate_0 up_0 | gate_1 up_1 | ...]``, the two branches interleaved per neuron. gpt-oss's
483
484
  #: MXFP4 expert weights, where the kernel reads ``[..., ::2]`` and ``[..., 1::2]``.
@@ -488,8 +489,10 @@ class GateUpLayout(StrEnum):
488
489
  #: packed. Absent means the refusal in ``ArchSpec.mlp_projection`` stands: a family may fuse and pack
489
490
  #: either way, and "probably gate first" is exactly the guess that produces silent garbage.
490
491
  #: Verified by the identity the branches exist to satisfy -- ``act(mlp_pre) * mlp_pre_linear`` is the
491
- #: down projection's input, which is captured independently as ``mlp_act`` (tests/test_fused_mlp.py).
492
+ #: down projection's input, which is captured independently as ``mlp_act``
493
+ #: (tests/test_mlp_internals.py).
492
494
  FUSED_GATE_UP_LAYOUTS: dict[str, GateUpLayout] = {
495
+ "Glm4ForCausalLM": GateUpLayout.GATE_FIRST,
493
496
  "Phi3ForCausalLM": GateUpLayout.GATE_FIRST,
494
497
  }
495
498
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "interp-engine"
3
- version = "1.6.0"
3
+ version = "1.7.0"
4
4
  description = "A fast, standardized, and easy to use interpretability engine."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -437,6 +437,44 @@ def shrunk_phi3(*, seed: int = 0) -> Any:
437
437
  return EagerModel("Phi3ForCausalLM", hf_model=model, tokenizer=NoTokenizer(), device=None, dtype="float32")
438
438
 
439
439
 
440
+ #: Dims for :func:`shrunk_glm4`. Same subject as :data:`_PHI3_SHRUNK` -- the MLP's single
441
+ #: ``gate_up_proj`` -- so the only constraint is an even ``intermediate_size``. GLM-4 rotates half
442
+ #: its head dimension, so ``head_dim`` stays a multiple of four.
443
+ _GLM4_SHRUNK: dict[str, Any] = {
444
+ "hidden_size": 32,
445
+ "intermediate_size": 64,
446
+ "num_hidden_layers": 2,
447
+ "num_attention_heads": 4,
448
+ "num_key_value_heads": 2,
449
+ "head_dim": 8,
450
+ "vocab_size": 128,
451
+ "max_position_embeddings": 64,
452
+ "pad_token_id": 0,
453
+ "eos_token_id": 1,
454
+ }
455
+
456
+
457
+ def shrunk_glm4(*, seed: int = 0) -> Any:
458
+ """A tiny real GLM-4 with float32 weights on CPU, as an ``EagerModel``.
459
+
460
+ The second family to fuse its two pre-activation projections into one ``gate_up_proj``, and the
461
+ reason the packing is a table rather than a default: ``Glm4MLP`` chunks gate-first exactly as
462
+ Phi-3 does, but that is a fact about the family, established by running it, not one the shared
463
+ attribute name implies.
464
+ """
465
+ from transformers import Glm4Config, Glm4ForCausalLM
466
+
467
+ from interp_engine import EagerModel
468
+
469
+ config = Glm4Config(**_GLM4_SHRUNK)
470
+ config.architectures = ["Glm4ForCausalLM"]
471
+ torch.manual_seed(seed)
472
+ with warnings.catch_warnings():
473
+ warnings.simplefilter("ignore")
474
+ model = Glm4ForCausalLM(config).to(torch.float32).eval()
475
+ return EagerModel("Glm4ForCausalLM", hf_model=model, tokenizer=NoTokenizer(), device=None, dtype="float32")
476
+
477
+
440
478
  def shrunk_lfm2_moe(*, seed: int = 0) -> Any:
441
479
  """A tiny LFM2-MoE with real float32 weights on CPU, as an ``EagerModel``.
442
480
 
@@ -29,7 +29,7 @@ from types import SimpleNamespace
29
29
  import pytest
30
30
  import torch
31
31
  from harness import GEMMA_IT, GPT2, QWEN_THINKING, ModelSpec, load_model, require_hf_token
32
- from synthetic_families import shrunk_opt, shrunk_phi3
32
+ from synthetic_families import shrunk_glm4, shrunk_opt, shrunk_phi3
33
33
 
34
34
  from interp_engine import run_with_cache
35
35
  from interp_engine.capture import AddressLike
@@ -176,8 +176,9 @@ def test_a_plain_mlp_refuses_the_multiplied_branch():
176
176
  model.resolve_point("mlp_pre_linear", 0)
177
177
 
178
178
 
179
- def test_a_fused_gate_up_is_sliced_into_its_two_branches():
180
- """Phi-3's shape: one projection holding both branches, so neither is a module output.
179
+ @pytest.mark.parametrize("build", [shrunk_phi3, shrunk_glm4], ids=["phi3", "glm4"])
180
+ def test_a_fused_gate_up_is_sliced_into_its_two_branches(build):
181
+ """Phi-3's and GLM-4's shape: one projection holding both branches, so neither is a module output.
181
182
 
182
183
  Served rather than refused, because a dense MLP's neuron basis exists whether or not the
183
184
  checkpoint stores the two matrices concatenated -- and this is a read plus a last-axis slice, the
@@ -186,9 +187,10 @@ def test_a_fused_gate_up_is_sliced_into_its_two_branches():
186
187
  Checked by the identity the branches exist to satisfy: `act(mlp_pre) * mlp_pre_linear` is the
187
188
  down projection's input, which arrives independently as `mlp_act`. That is what says the halves
188
189
  were cut the right way round, and it is exact -- swapping them changes the answer on any
189
- non-symmetric activation, which SiLU is.
190
+ non-symmetric activation, which SiLU is. Run per family rather than once, because the packing is
191
+ a property of the family: both of these chunk gate-first, and nothing but running them says so.
190
192
  """
191
- model = shrunk_phi3()
193
+ model = build()
192
194
  ids = torch.arange(7).unsqueeze(0) % 128
193
195
  cache = run_with_cache(model, ids, [("mlp_pre", 1), ("mlp_pre_linear", 1), ("mlp_act", 1)])
194
196
  pre, linear, act = (cache.get(name, 1) for name in ("mlp_pre", "mlp_pre_linear", "mlp_act"))
File without changes
File without changes
File without changes