innards 0.0.1__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 (75) hide show
  1. innards-0.0.1/LICENSE +201 -0
  2. innards-0.0.1/PKG-INFO +401 -0
  3. innards-0.0.1/README.md +358 -0
  4. innards-0.0.1/pyproject.toml +147 -0
  5. innards-0.0.1/pyproject.toml.orig +116 -0
  6. innards-0.0.1/src/innards/__init__.py +79 -0
  7. innards-0.0.1/src/innards/__main__.py +7 -0
  8. innards-0.0.1/src/innards/_env.py +78 -0
  9. innards-0.0.1/src/innards/_text.py +76 -0
  10. innards-0.0.1/src/innards/cli.py +308 -0
  11. innards-0.0.1/src/innards/collectors/__init__.py +6 -0
  12. innards-0.0.1/src/innards/collectors/memory.py +85 -0
  13. innards-0.0.1/src/innards/collectors/timing.py +32 -0
  14. innards-0.0.1/src/innards/experts/__init__.py +43 -0
  15. innards-0.0.1/src/innards/experts/analysis.py +513 -0
  16. innards-0.0.1/src/innards/experts/routing.py +446 -0
  17. innards-0.0.1/src/innards/experts/trace.py +679 -0
  18. innards-0.0.1/src/innards/features/__init__.py +72 -0
  19. innards-0.0.1/src/innards/features/base.py +359 -0
  20. innards-0.0.1/src/innards/features/deltanet.py +321 -0
  21. innards-0.0.1/src/innards/features/gates.py +157 -0
  22. innards-0.0.1/src/innards/features/kv.py +238 -0
  23. innards-0.0.1/src/innards/features/layout.py +371 -0
  24. innards-0.0.1/src/innards/features/mhc.py +240 -0
  25. innards-0.0.1/src/innards/features/moe.py +231 -0
  26. innards-0.0.1/src/innards/features/registry.py +199 -0
  27. innards-0.0.1/src/innards/kv/__init__.py +26 -0
  28. innards-0.0.1/src/innards/kv/calculator.py +287 -0
  29. innards-0.0.1/src/innards/kv/config.py +649 -0
  30. innards-0.0.1/src/innards/kv/dtypes.py +56 -0
  31. innards-0.0.1/src/innards/lineage/__init__.py +200 -0
  32. innards-0.0.1/src/innards/lineage/configs/Qwen__Qwen3-0.6B.json +30 -0
  33. innards-0.0.1/src/innards/lineage/configs/Qwen__Qwen3-1.7B.json +30 -0
  34. innards-0.0.1/src/innards/lineage/configs/Qwen__Qwen3.5-0.8B.json +96 -0
  35. innards-0.0.1/src/innards/lineage/configs/Qwen__Qwen3.5-2B.json +96 -0
  36. innards-0.0.1/src/innards/lineage/configs/ibm-granite__granite-3.1-3b-a800m-instruct.json +35 -0
  37. innards-0.0.1/src/innards/lineage/configs/ibm-granite__granite-swash-3b-a600m.json +70 -0
  38. innards-0.0.1/src/innards/lineage/configs/sources.json +56 -0
  39. innards-0.0.1/src/innards/lineage/data/compressed.json +18 -0
  40. innards-0.0.1/src/innards/lineage/data/dsa.json +18 -0
  41. innards-0.0.1/src/innards/lineage/data/fp8_kv.json +16 -0
  42. innards-0.0.1/src/innards/lineage/data/gqa.json +19 -0
  43. innards-0.0.1/src/innards/lineage/data/hybrid.json +20 -0
  44. innards-0.0.1/src/innards/lineage/data/mha.json +19 -0
  45. innards-0.0.1/src/innards/lineage/data/mla.json +18 -0
  46. innards-0.0.1/src/innards/lineage/data/mqa.json +19 -0
  47. innards-0.0.1/src/innards/lineage/data/sliding_window.json +19 -0
  48. innards-0.0.1/src/innards/lineage/data/ssm.json +18 -0
  49. innards-0.0.1/src/innards/lineage/lineup.json +79 -0
  50. innards-0.0.1/src/innards/lineage/lineup.py +290 -0
  51. innards-0.0.1/src/innards/observer/__init__.py +30 -0
  52. innards-0.0.1/src/innards/observer/cache.py +304 -0
  53. innards-0.0.1/src/innards/observer/events.py +74 -0
  54. innards-0.0.1/src/innards/observer/observer.py +291 -0
  55. innards-0.0.1/src/innards/observer/probes.py +204 -0
  56. innards-0.0.1/src/innards/observer/queue.py +62 -0
  57. innards-0.0.1/src/innards/observer/watch.py +904 -0
  58. innards-0.0.1/src/innards/py.typed +0 -0
  59. innards-0.0.1/src/innards/schema.py +315 -0
  60. innards-0.0.1/src/innards/sets.py +289 -0
  61. innards-0.0.1/src/innards/sinks/__init__.py +85 -0
  62. innards-0.0.1/src/innards/sinks/console.py +95 -0
  63. innards-0.0.1/src/innards/store.py +241 -0
  64. innards-0.0.1/src/innards/sync.py +126 -0
  65. innards-0.0.1/src/innards/validate.py +562 -0
  66. innards-0.0.1/src/innards/view/__init__.py +111 -0
  67. innards-0.0.1/src/innards/view/analysis.py +235 -0
  68. innards-0.0.1/src/innards/view/app.py +497 -0
  69. innards-0.0.1/src/innards/view/experts.py +292 -0
  70. innards-0.0.1/src/innards/view/runner.py +152 -0
  71. innards-0.0.1/src/innards/view/static/app.js +1031 -0
  72. innards-0.0.1/src/innards/view/static/index.html +138 -0
  73. innards-0.0.1/src/innards/view/static/style.css +219 -0
  74. innards-0.0.1/src/innards/view/static/vendor/PLOTLY-LICENSE +21 -0
  75. innards-0.0.1/src/innards/view/static/vendor/plotly-4.1.1.min.js +3620 -0
innards-0.0.1/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
innards-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,401 @@
1
+ Metadata-Version: 2.4
2
+ Name: innards
3
+ Version: 0.0.1
4
+ Summary: See inside open-weight models while they run on your machine: read-only hooks on the model you loaded; for MoE models, which experts each token and topic activates during inference and training, and the expert set a topic needs; one analyzer per architectural feature; KV and memory predicted vs measured; a live web view.
5
+ Keywords: llm,kv-cache,transformers,attention,gqa,mla,deltanet,mixture-of-experts,moe,expert-routing,interpretability,observability,profiling,inference
6
+ Author: Jai
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: System :: Monitoring
23
+ Classifier: Typing :: Typed
24
+ Requires-Dist: httpx>=0.28.1
25
+ Requires-Dist: huggingface-hub>=1.5
26
+ Requires-Dist: innards[hf,view,aws,docs] ; extra == 'all'
27
+ Requires-Dist: boto3>=1.34 ; extra == 'aws'
28
+ Requires-Dist: pypdf>=5.0 ; extra == 'docs'
29
+ Requires-Dist: psutil>=5.9 ; extra == 'hf'
30
+ Requires-Dist: torch>=2.4 ; extra == 'hf'
31
+ Requires-Dist: transformers>=5.0 ; extra == 'hf'
32
+ Requires-Dist: fastapi>=0.115 ; extra == 'view'
33
+ Requires-Dist: uvicorn>=0.30 ; extra == 'view'
34
+ Requires-Python: >=3.10
35
+ Project-URL: Homepage, https://pypi.org/project/innards/
36
+ Project-URL: Documentation, https://pypi.org/project/innards/#description
37
+ Provides-Extra: all
38
+ Provides-Extra: aws
39
+ Provides-Extra: docs
40
+ Provides-Extra: hf
41
+ Provides-Extra: view
42
+ Description-Content-Type: text/markdown
43
+
44
+ <h1 align="center">Innards</h1>
45
+ <h3 align="center">See inside an open-weight model while it runs on your machine: which MoE experts every token and topic activates, and what each architectural feature does</h3>
46
+
47
+ <p align="center">
48
+ <a href="https://medium.com/@jaiganesan"><img alt="Medium: @jaiganesan" src="https://img.shields.io/badge/Medium-@jaiganesan-000000?style=for-the-badge&logo=medium&logoColor=white"></a>
49
+ <a href="https://www.linkedin.com/in/jaiganesan-n/"><img alt="LinkedIn: jaiganesan-n" src="https://img.shields.io/badge/LinkedIn-jaiganesan--n-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white"></a>
50
+ </p>
51
+ <p align="center">
52
+ <img alt="Version 0.0.1" src="https://img.shields.io/badge/version-0.0.1-555555?style=flat-square">
53
+ <img alt="Code license: Apache 2.0" src="https://img.shields.io/badge/code-Apache%202.0-blue?style=flat-square">
54
+ <img alt="Python 3.10 to 3.15" src="https://img.shields.io/badge/Python-3.10%20to%203.15-3776AB?style=flat-square&logo=python&logoColor=white">
55
+ <img alt="PyTorch 2.4 or later" src="https://img.shields.io/badge/PyTorch-2.4%2B-EE4C2C?style=flat-square&logo=pytorch&logoColor=white">
56
+ <img alt="Transformers 5" src="https://img.shields.io/badge/Transformers-5-FFD21E?style=flat-square&logo=huggingface&logoColor=black">
57
+ <img alt="Runs on a MacBook Air M5 (MPS), 16 GB" src="https://img.shields.io/badge/runs%20on-MacBook%20Air%20M5%20(MPS)%2C%2016%20GB-555555?style=flat-square&logo=apple&logoColor=white">
58
+ </p>
59
+
60
+ <p align="center">
61
+ <img alt="Granite 3.1 1B-A400M runs unchanged; Innards reads which experts each token chose in every MoE layer and counts them per topic" src="docs/figures/banner_light.png" width="100%">
62
+ </p>
63
+
64
+ Measuring and analysis code for one question: **which experts of a Mixture-of-Experts model does a token, a topic or a task actually use, measured while the model runs and without changing what it computes?** Your code loads the original checkpoint and runs it, for inference or for training; Innards attaches read-only hooks to that model and analyzes what they see beside it. For every model, MoE or not, it also records tokens, context, KV cache, memory and timing, and serves a live web view from the same machine. It targets models that run on a 16 GB machine at 16-bit precision or higher, through Hugging Face Transformers 5.
65
+
66
+ > [!NOTE]
67
+ > **Objective.** I am researching whether the experts that a specialized topic, task or token activates can be taken out of an MoE model and run on their own, as a smaller MoE with the model's own structure, without the full model. That starts with a measurement: which experts every token chose, in every layer, per topic, during inference and training. Innards does that step and exports the expert set a topic needs; building the smaller model is the job of your own code. Innards never removes, merges or edits experts.
68
+
69
+ | Contents | |
70
+ | --- | --- |
71
+ | [1. What Innards does](#1-what-innards-does) | The parts, install |
72
+ | [2. MoE expert routing](#2-moe-expert-routing) | Trace topics in inference and training, what is measured and derived, any MoE architecture |
73
+ | [3. Observe a model](#3-observe-a-model) | One turn per `generate()`: KV, memory, timing, feature analyzers |
74
+ | [4. The web view](#4-the-web-view) | Conversation, Analysis and Experts tabs |
75
+ | [5. Where results go](#5-where-results-go) | Run folders, schema 1.0 |
76
+ | [6. Checks, lineup and KV](#6-checks-lineup-and-kv) | Before loading, pairs of models, KV from a config |
77
+ | [Run it](#run-it) | From a clone: setup and tests; repository layout |
78
+
79
+ ---
80
+
81
+ ## 1. What Innards does
82
+
83
+ | Part | What it gives you |
84
+ | --- | --- |
85
+ | MoE expert routing | Which experts every token and topic activates, in inference and training, on any MoE architecture (including remote code such as `facebook/MobileMoE`), and the experts a topic needs per layer, ready to export |
86
+ | Every model | Tokens, context, KV cache per layer (predicted from the config and measured), memory and timing |
87
+ | Feature analyzers | One per architectural feature (Gated DeltaNet, gated attention, MoE, mHC, CSA/HCA, DSA, MLA), picked from the model's config |
88
+ | Web view | Served from the machine that runs the model: no central site, no API key |
89
+
90
+ Innards never loads or runs a model itself, and its hooks only read. Missing measurements are `null`, never zero, and every value computed from a measurement is labeled as derived.
91
+
92
+ ### Install
93
+
94
+ <!-- test: install (checked by the fresh-environment wheel install in CI) -->
95
+ ```bash
96
+ pip install innards # core: analysis of stored traces, KV calculator, lineage
97
+ pip install "innards[hf]" # + torch, transformers: observe and trace a model
98
+ pip install "innards[view]" # + the web view
99
+ pip install "innards[all]" # everything, including S3 sync and PDF documents
100
+
101
+ uv add "innards[hf,view]" # inside a uv project
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 2. MoE expert routing
107
+
108
+ ### 2.1 Trace topics during inference
109
+
110
+ `innards.experts.trace` hooks every MoE layer of the model you loaded. Inside `trace.topic(...)`, every forward pass is recorded (the prefill and each decoded token); outside a topic nothing is.
111
+
112
+ <!-- test: requires=hf,network -->
113
+ ```python
114
+ import torch
115
+ from transformers import AutoModelForCausalLM, AutoTokenizer
116
+
117
+ import innards
118
+
119
+ model_id = "ibm-granite/granite-3.1-1b-a400m-instruct" # 32 experts, 8 per token; 2.7 GB in bf16
120
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
121
+ model = AutoModelForCausalLM.from_pretrained(model_id, dtype=torch.bfloat16)
122
+
123
+ trace = innards.experts.trace(model, tokenizer, run="topic-experts") # read-only hooks on every MoE layer
124
+ prompts = {
125
+ "math": ["The derivative of x squared is", "Solve for x: 3x + 5 = 20. First,"],
126
+ "code": ["def fibonacci(n):\n if n <", "import numpy as np\narr = np.zeros("],
127
+ }
128
+ for topic, texts in prompts.items():
129
+ for text in texts:
130
+ with trace.topic(topic):
131
+ model.generate(**tokenizer(text, return_tensors="pt"), max_new_tokens=16, do_sample=False)
132
+
133
+ experts = trace.map() # topic x layer x expert, from the routing the model chose
134
+ print(experts.top("math", layer=12, n=4)) # the experts math tokens chose most in layer 12
135
+ print(experts.specialists("code", n=3)) # chosen far more for code than for math
136
+ keep = experts.select("math", coverage=0.9) # per layer, the fewest experts that take 90% of math's routing
137
+ keep.save("math-experts.json")
138
+ print(keep)
139
+
140
+ routing = trace.tokens() # every recorded token's experts, per layer
141
+ print(routing.token(" x")["counts"][12]) # how often each expert took the token " x" in layer 12
142
+ print(routing.expert(layer=12, expert=keep.keep(12)[0])[:3]) # the tokens one expert takes most
143
+ trace.close() # removes the hooks and writes innards_runs/topic-experts/experts/<model>/
144
+ ```
145
+
146
+ ### 2.2 During training
147
+
148
+ Give one topic per sequence of the batch. Padding is never counted, and the forward that gradient checkpointing recomputes inside `backward()` is not counted twice. The example fine-tunes only the routers, to stay light; the trace works the same when every weight trains.
149
+
150
+ <!-- test: requires=hf,network -->
151
+ ```python
152
+ import torch
153
+ from transformers import AutoModelForCausalLM, AutoTokenizer
154
+
155
+ import innards
156
+
157
+ model_id = "ibm-granite/granite-3.1-1b-a400m-instruct"
158
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
159
+ model = AutoModelForCausalLM.from_pretrained(model_id, dtype=torch.bfloat16).train()
160
+ model.requires_grad_(False)
161
+ routers = [m for name, m in model.named_modules() if name.endswith(".router")]
162
+ for router in routers:
163
+ router.requires_grad_(True)
164
+ optimizer = torch.optim.SGD([p for r in routers for p in r.parameters()], lr=1e-5)
165
+
166
+ trace = innards.experts.trace(model, tokenizer, run="finetune", tokens=False) # counts only: light for long runs
167
+ batch = tokenizer(
168
+ ["The integral of 2x is x squared.", "for i in range(3):\n print(i)"], return_tensors="pt", padding=True
169
+ )
170
+ labels = batch["input_ids"].masked_fill(batch["attention_mask"] == 0, -100)
171
+ with trace.topic(["math", "code"]): # one label per sequence
172
+ loss = model(**batch, labels=labels).loss
173
+ loss.backward()
174
+ optimizer.step()
175
+ print(trace.map()) # <ExpertMap ...: 24 MoE layers; tokens math: ..., code: ...>
176
+ trace.close()
177
+ ```
178
+
179
+ ### 2.3 What is measured, and what is derived
180
+
181
+ | | Value | Meaning |
182
+ | --- | --- | --- |
183
+ | Measured | `tokens` | Tokens of a topic that went through each MoE layer (padding excluded) |
184
+ | Measured | `counts` | How often each expert was in a token's top-k |
185
+ | Measured | per-token routing | Each token's chosen experts and their weights, per layer (`tokens=True`) |
186
+ | Derived | `selection` | Share of the topic's tokens whose top-k included the expert (`counts / tokens`) |
187
+ | Derived | `gate` | The expert's average share of a token's routing weight |
188
+ | Derived | `lift` | Selection for this topic ÷ selection for every other topic together |
189
+ | Derived | `select(topic, coverage)` | Per layer, the fewest experts that together take `coverage` of the topic's routing |
190
+
191
+ `select` saves the expert ids to keep per layer, with the module paths of each layer's router, experts and shared expert and the routing settings from the config. That file is the input for your own code that builds the smaller model; shared experts run for every token and always stay.
192
+
193
+ ### 2.4 Any MoE architecture
194
+
195
+ Each MoE layer is read from the first place that carries the model's own routing decision:
196
+
197
+ | Source | Used when |
198
+ | --- | --- |
199
+ | `experts_input` | The block calls `experts(hidden, top_k_index, top_k_weights)`: transformers 5 MoE models (Qwen3-MoE, Qwen3.5-MoE, Mixtral, OLMoE, Granite MoE, GPT-OSS, DeepSeek-V2/V3, Phi-MoE, LFM2-MoE, Nemotron-H ...) |
200
+ | `router_output` | The router module returns the chosen experts |
201
+ | `topk` | Code that routes inline, such as `facebook/MobileMoE`: the block's own `torch.topk` |
202
+
203
+ MoE checkpoints that fit 16 GB at bf16: `ibm-granite/granite-3.1-1b-a400m-instruct` (2.7 GB), `facebook/MobileMoE-S-Base` (2.4 GB, remote code), `ibm-granite/granite-swash-3b-a600m` (6 GB), `microsoft/Phi-tiny-MoE-instruct` (7.5 GB), `arcee-ai/Trinity-Nano-Preview` (12.2 GB) and `allenai/OLMoE-1B-7B-0125-Instruct` (13.8 GB, tight).
204
+
205
+ From the command line, `innards experts` lists every trace under `./innards_runs`, and the **Experts** tab of the web view shows a trace live:
206
+
207
+ <!-- test: cli -->
208
+ ```bash
209
+ innards experts # every trace under ./innards_runs: model, topics, tokens
210
+ ```
211
+
212
+ <!-- test: text -->
213
+ ```text
214
+ innards experts innards_runs/topic-experts --topic math # top experts per layer
215
+ innards experts innards_runs/topic-experts --topic code --by lift # specialists against the other topics
216
+ innards experts innards_runs/topic-experts --topic math --select 0.9 --out math-experts.json
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 3. Observe a model
222
+
223
+ `innards.observe` records one turn per `generate()`: tokens, context, KV per layer (predicted vs measured), memory and timing, plus an analyzer for each feature the config says the model has.
224
+
225
+ <!-- test: requires=hf,network -->
226
+ ```python
227
+ import innards
228
+ from transformers import AutoModelForCausalLM, AutoTokenizer
229
+
230
+ tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3.5-0.8B")
231
+ model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-0.8B") # your code loads the original checkpoint
232
+
233
+ watch = innards.observe(model, tokenizer, model_id="Qwen/Qwen3.5-0.8B", run="deltanet-study")
234
+
235
+ messages = [{"role": "user", "content": "Remember this: my name is Priya."}]
236
+ inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt", return_dict=True)
237
+ out = model.generate(**inputs, max_new_tokens=32) # your code, unchanged: one turn
238
+
239
+ turn = watch.last_turn().record
240
+ print(turn.kv.measured_bytes == turn.kv.predicted_bytes) # True
241
+ print(sorted(turn.features)) # ['gated_attention', 'gated_deltanet', 'kv']
242
+ watch.close()
243
+ ```
244
+
245
+ | Analyzer | Shows |
246
+ | --- | --- |
247
+ | `kv` (every model) | KV per layer and layer type, growth, context, predicted vs measured |
248
+ | `gated_deltanet` | Write strength, retention, half-life, fixed state next to growing KV |
249
+ | `gated_attention` | Gate openness per head and token |
250
+ | `moe` | Expert load per turn, routing entropy |
251
+ | `mhc` | Stream mixing, doubly-stochastic check, signal gain across depth |
252
+ | `csa_hca`, `dsa`, `mla` | Compressed and indexer caches, latent vs expanded layout |
253
+
254
+ `observe` and `innards.experts.trace` can watch the same model at once.
255
+
256
+ ---
257
+
258
+ ## 4. The web view
259
+
260
+ `innards view` (or `watch.view()`) serves a monitoring page from the machine that runs the model:
261
+
262
+ | Tab | What it shows |
263
+ | --- | --- |
264
+ | Conversation | Run a conversation set turn by turn, or type your own messages; tokens reused, KV change and speed per reply |
265
+ | Analysis | Each feature at work, KV/context/memory per turn predicted vs measured, and the same set compared with the version before |
266
+ | Experts | Experts per layer for a topic, topics side by side, the experts each topic needs, specialists, each sequence as tokens × experts; downloads the expert set |
267
+
268
+ <!-- test: text -->
269
+ ```text
270
+ innards view # ./innards_runs on http://127.0.0.1:8000
271
+ innards view ./innards_runs/deltanet-study # one run folder, e.g. copied from another machine
272
+ ssh -L 8000:127.0.0.1:8000 user@<gpu-host> # a remote machine's view through a tunnel
273
+ ```
274
+
275
+ ---
276
+
277
+ ## 5. Where results go
278
+
279
+ Results stay on the machine that runs the model:
280
+
281
+ <!-- test: text -->
282
+ ```text
283
+ innards_runs/<run>/run.json name, host, environment
284
+ innards_runs/<run>/<conversation>/turns.jsonl one TurnRecord (schema 1.0) per turn
285
+ innards_runs/<run>/experts/<name>/summary.json expert trace: topics, tokens and counts per layer
286
+ innards_runs/<run>/experts/<name>/tokens/shard-*.safetensors per-token experts, weights, positions, topics
287
+ ```
288
+
289
+ <!-- test: check-output -->
290
+ ```python
291
+ from innards.schema import TurnRecord
292
+
293
+ record = TurnRecord.from_dict(
294
+ {
295
+ "schema_version": "1.0",
296
+ "run": "deltanet-study",
297
+ "conversation": "20261103-081500-Qwen3.5-0.8B-a1b2",
298
+ "turn": 7,
299
+ "timestamp": "2026-11-03T08:15:42Z",
300
+ "model": {"id": "Qwen/Qwen3.5-0.8B", "architecture": "hybrid", "features": ["gated_deltanet"]},
301
+ "runtime": {"backend": "observe"},
302
+ "kv": {"predicted_bytes": 2154496, "measured_bytes": 2154496, "measured_source": "hf_cache"},
303
+ }
304
+ )
305
+ print(record.kv.measured_bytes, record.memory.peak_bytes) # 2154496 None
306
+ ```
307
+
308
+ ---
309
+
310
+ ## 6. Checks, lineup and KV
311
+
312
+ Before anything is downloaded, `validate` checks architecture support, the device against the checkpoint's published precision, and whether weights plus KV fit the memory budget (16 GB by default, `INNARDS_GPU_BUDGET_GB`):
313
+
314
+ <!-- test: check-output requires=hf -->
315
+ ```python
316
+ from innards.validate import validate
317
+
318
+ report = validate("ibm-granite/granite-swash-3b-a600m", device="mps", check_access=False)
319
+ print(report.status) # Ready
320
+ small = validate("ibm-granite/granite-swash-3b-a600m", device="mps", budget_gb=4, check_access=False)
321
+ print([check.id for check in small.blocked]) # ['memory']
322
+ ```
323
+
324
+ The lineup pairs a newer model with the version before it, each running on a 16 GB machine at 16-bit precision:
325
+
326
+ | Feature | Newer model | Version before |
327
+ | --- | --- | --- |
328
+ | Gated DeltaNet + gated attention | Qwen3.5-0.8B (or 2B) | Qwen3-0.6B (or 1.7B) |
329
+ | MoE with a shared expert + sliding-window attention | Granite Swash 3B-A600M | Granite 3.1 3B-A800M |
330
+
331
+ `predict` gives KV bytes and a per-layer breakdown from a config alone, and `explain` says what an architecture changed from its predecessor:
332
+
333
+ <!-- test: check-output -->
334
+ ```python
335
+ from innards.kv import predict
336
+ from innards.lineage import explain
337
+ from innards.lineage.lineup import model_config
338
+
339
+ p = predict(model_config("ibm-granite/granite-swash-3b-a600m"), context_tokens=32768, kv_dtype="bf16")
340
+ print(p.architecture, round(p.total_bytes / 2**20)) # sliding_window 258
341
+ print(explain("gqa").predecessor, "->", explain("gqa").family) # mha -> gqa
342
+ ```
343
+
344
+ <!-- test: cli -->
345
+ ```bash
346
+ innards lineup # the pairs and what each newer model adds
347
+ innards predict --model Qwen/Qwen3-0.6B --context 32768 --kv-dtype fp8
348
+ innards explain sliding_window
349
+ innards validate Qwen/Qwen3.5-0.8B --device cpu --set with_context
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Run it
355
+
356
+ From a clone, with [uv](https://docs.astral.sh/uv/):
357
+
358
+ <!-- test: dev -->
359
+ ```bash
360
+ git clone https://github.com/jaiganesan-n/innards.git && cd innards
361
+ uv sync --all-extras # Python 3.13, torch, transformers 5, FastAPI, dev tools
362
+ uv run pytest # unit tests, README examples, small real checkpoints
363
+ uv run ruff check && uv run ruff format --check && uv run mypy src
364
+ INNARDS_REMOTE_CODE_TESTS=1 uv run pytest tests/test_experts_models.py -k mobilemoe # facebook/MobileMoE (remote code)
365
+ uv run --with playwright python docs/figures/src/make_banner.py --measure # re-trace Granite, redraw the banner
366
+ ```
367
+
368
+ ### Repository layout
369
+
370
+ <!-- test: text -->
371
+ ```text
372
+ innards/
373
+ ├── README.md, LICENSE
374
+ ├── pyproject.toml, uv.lock, .python-version # uv; Python 3.10 to 3.15
375
+ ├── src/innards/
376
+ │ ├── experts/ routing.py, trace.py, analysis.py # MoE expert routing
377
+ │ ├── observer/ watch.py, probes.py, observer.py, cache.py
378
+ │ ├── features/ one analyzer per architectural feature
379
+ │ ├── kv/ KV calculator
380
+ │ ├── lineage/ lineup, pinned configs, architecture families
381
+ │ ├── view/ web view (FastAPI, static page)
382
+ │ ├── schema.py, store.py, sync.py, validate.py, sets.py
383
+ │ └── cli.py # innards <command>
384
+ ├── tests/ replicas.py (tiny random-weight models), test_*.py
385
+ ├── scripts/ overhead_bench.py
386
+ ├── docs/figures/ banner_light.png, banner_data.json (what the banner shows), src/make_banner.py
387
+ └── .github/workflows/ ci.yml, publish.yml (PyPI on a v* tag)
388
+ ```
389
+
390
+ ## License
391
+
392
+ Code: Apache 2.0 (`LICENSE`). Models keep their own terms: Innards never downloads model weights itself (`facebook/MobileMoE` is gated, for non-commercial research only).
393
+
394
+ ---
395
+
396
+ ## Author
397
+
398
+ **Jai Ganesan** · [Medium](https://medium.com/@jaiganesan) · [LinkedIn](https://www.linkedin.com/in/jaiganesan-n/)
399
+
400
+ > [!IMPORTANT]
401
+ > **Note.** Innards is developed on one MacBook Air M5 with 16 GB of memory, so it targets models that fit there. If you trace another MoE model, or one whose routing Innards does not read yet, let me know.