nnschemaparser 0.1.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.
- nnschemaparser-0.1.0/LICENSE +21 -0
- nnschemaparser-0.1.0/PKG-INFO +200 -0
- nnschemaparser-0.1.0/README.md +145 -0
- nnschemaparser-0.1.0/nnschemaparser/__init__.py +27 -0
- nnschemaparser-0.1.0/nnschemaparser/core/__init__.py +16 -0
- nnschemaparser-0.1.0/nnschemaparser/core/container_keys.py +69 -0
- nnschemaparser-0.1.0/nnschemaparser/core/instructions.py +399 -0
- nnschemaparser-0.1.0/nnschemaparser/core/model_schema.py +94 -0
- nnschemaparser-0.1.0/nnschemaparser/core/tensor_schema.py +55 -0
- nnschemaparser-0.1.0/nnschemaparser/dump.py +487 -0
- nnschemaparser-0.1.0/nnschemaparser/forward.py +29 -0
- nnschemaparser-0.1.0/nnschemaparser/interpreter/__init__.py +19 -0
- nnschemaparser-0.1.0/nnschemaparser/interpreter/control_flow.py +570 -0
- nnschemaparser-0.1.0/nnschemaparser/interpreter/execution.py +767 -0
- nnschemaparser-0.1.0/nnschemaparser/interpreter/interpreter.py +338 -0
- nnschemaparser-0.1.0/nnschemaparser/interpreter/scope.py +121 -0
- nnschemaparser-0.1.0/nnschemaparser/interpreter/tracer.py +346 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/__init__.py +14 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/children.py +171 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/__init__.py +16 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/base.py +354 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/block_calls.py +350 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/body.py +37 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/calls.py +689 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/control_flow.py +519 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/effects.py +164 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/errors.py +24 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/expressions.py +805 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/naming.py +179 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/operators.py +333 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/runtime_ops.py +260 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/statements.py +600 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/lowering/values.py +97 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/parser.py +599 -0
- nnschemaparser-0.1.0/nnschemaparser/parser/resolution.py +280 -0
- nnschemaparser-0.1.0/nnschemaparser/py.typed +0 -0
- nnschemaparser-0.1.0/nnschemaparser.egg-info/PKG-INFO +200 -0
- nnschemaparser-0.1.0/nnschemaparser.egg-info/SOURCES.txt +45 -0
- nnschemaparser-0.1.0/nnschemaparser.egg-info/dependency_links.txt +1 -0
- nnschemaparser-0.1.0/nnschemaparser.egg-info/requires.txt +8 -0
- nnschemaparser-0.1.0/nnschemaparser.egg-info/top_level.txt +1 -0
- nnschemaparser-0.1.0/pyproject.toml +51 -0
- nnschemaparser-0.1.0/setup.cfg +4 -0
- nnschemaparser-0.1.0/tests/test_methods_and_scalars.py +152 -0
- nnschemaparser-0.1.0/tests/test_new_syntax.py +221 -0
- nnschemaparser-0.1.0/tests/test_parser.py +193 -0
- nnschemaparser-0.1.0/tests/test_speech_models.py +243 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cerisara Nathan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nnschemaparser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parse torch nn.Module models into an inspectable ModelBlockSchema graph and execute it with a faithful schema interpreter
|
|
5
|
+
Author: Cerisara Nathan
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Cerisara Nathan
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/nath54/NNViewer
|
|
29
|
+
Project-URL: Documentation, https://github.com/nath54/NNViewer/blob/main/docs/DOCUMENTATION.md
|
|
30
|
+
Project-URL: Changelog, https://github.com/nath54/NNViewer/blob/main/CHANGELOG.md
|
|
31
|
+
Project-URL: Issues, https://github.com/nath54/NNViewer/issues
|
|
32
|
+
Keywords: pytorch,neural-network,graph,schema,interpreter,model-inspection
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
44
|
+
Classifier: Typing :: Typed
|
|
45
|
+
Requires-Python: >=3.10
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
License-File: LICENSE
|
|
48
|
+
Requires-Dist: torch>=2.0
|
|
49
|
+
Provides-Extra: test
|
|
50
|
+
Requires-Dist: pytest; extra == "test"
|
|
51
|
+
Provides-Extra: research
|
|
52
|
+
Requires-Dist: transformers>=4.40; extra == "research"
|
|
53
|
+
Requires-Dist: espnet; extra == "research"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# NNSchemaParser
|
|
57
|
+
|
|
58
|
+
**NNSchemaParser** converts a PyTorch `nn.Module` into a serializable, inspectable
|
|
59
|
+
graph representation — the **`ModelBlockSchema`** — that keeps the *original variable
|
|
60
|
+
names of the forward source* and *every sub-module* (including MoE experts that a
|
|
61
|
+
single trace would never visit). The schema can then be executed by a built-in
|
|
62
|
+
**schema interpreter** that re-dispatches every recorded call to the real torch
|
|
63
|
+
callables, so the graph runs exactly like the model — and can optionally capture
|
|
64
|
+
the value of every intermediate tensor.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
nn.Module ──parse──► ModelBlockSchema ──interpret──► outputs / captured values
|
|
68
|
+
(your code) (names + graph) (identical to model(x))
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Why not `torch.fx`?
|
|
72
|
+
|
|
73
|
+
| | `torch.fx.symbolic_trace` | **NNSchemaParser** |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Variable names | lost (`_0`, `_1`, …) | **kept** (`hidden_states`, `query`, …) |
|
|
76
|
+
| Untaken branches (MoE experts) | missing | **all present** |
|
|
77
|
+
| Control flow | `graph_module` nodes only | `if` / `for` / `try` as first-class instructions |
|
|
78
|
+
| `*args` / `**kwargs` | limited | supported in signatures and call sites |
|
|
79
|
+
| Comprehensions | unsupported | compiled to helper functions |
|
|
80
|
+
| Re-executable | via the FX machinery | via a small self-contained interpreter |
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import torch
|
|
86
|
+
from torch import nn
|
|
87
|
+
|
|
88
|
+
from nnschemaparser import NNParser, SchemaInterpreter, TraceRecorder, dump_schema
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MLP(nn.Module):
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
super().__init__()
|
|
94
|
+
self.fc1 = nn.Linear(784, 256)
|
|
95
|
+
self.fc2 = nn.Linear(256, 10)
|
|
96
|
+
|
|
97
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
98
|
+
hidden = self.fc1(x)
|
|
99
|
+
activated = hidden.relu()
|
|
100
|
+
return self.fc2(activated)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
model = MLP().eval()
|
|
104
|
+
x = torch.randn(2, 784)
|
|
105
|
+
|
|
106
|
+
# 1. Parse the model into a schema (resolves shapes and dtypes)
|
|
107
|
+
schema = NNParser.parse_model(
|
|
108
|
+
model=model,
|
|
109
|
+
inputs_tensors_shapes={"x": (2, 784)},
|
|
110
|
+
inputs_tensors_dtypes={"x": "torch.float32"},
|
|
111
|
+
inputs_tensors_values={"x": x},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# 2. Dump a readable view of the whole block tree
|
|
115
|
+
dump_schema(schema=schema, path="schema.txt")
|
|
116
|
+
|
|
117
|
+
# 3. Execute it with a tracer and compare with the real forward
|
|
118
|
+
tracer = TraceRecorder()
|
|
119
|
+
outputs = SchemaInterpreter(capture_values=True, listeners=[tracer]).run(
|
|
120
|
+
block=schema,
|
|
121
|
+
scope={"x": x.clone()},
|
|
122
|
+
)
|
|
123
|
+
tracer.save("trace.txt")
|
|
124
|
+
reference = model(x)
|
|
125
|
+
assert torch.equal(outputs[0], reference)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The schema dump of `MLP` reads exactly like the source — original variable
|
|
129
|
+
names, every sub-module, and the resolved shape of every step:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
=== BLOCK MLP ===
|
|
133
|
+
inputs:
|
|
134
|
+
- x = torch.float32[2, 784]
|
|
135
|
+
outputs:
|
|
136
|
+
- torch.float32[2, 10]
|
|
137
|
+
weights:
|
|
138
|
+
(none)
|
|
139
|
+
sub-blocks:
|
|
140
|
+
- fc1(Linear)
|
|
141
|
+
- fc2(Linear)
|
|
142
|
+
body:
|
|
143
|
+
hidden = CALL fc1(x) -> torch.float32[2, 256]
|
|
144
|
+
$t1 = getattr(hidden, 'relu') -> builtin_function_or_method
|
|
145
|
+
activated = hidden.relu() -> torch.float32[2, 256]
|
|
146
|
+
$t2 = CALL fc2(activated) -> torch.float32[2, 10]
|
|
147
|
+
RETURN $t2
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The tracer records the same execution chronologically, with the value of
|
|
151
|
+
every intermediate tensor (one line per event, indented by the nesting
|
|
152
|
+
depth) — this is what a WebUI consumes to display every intermediate value:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
[00001] ENTER BLOCK MLP
|
|
156
|
+
[00002] in x = torch.float32[2, 784] abs_sum=1241.4496 min=-3.0303 max=3.3046
|
|
157
|
+
[00003] ENTER BLOCK Linear
|
|
158
|
+
[00004] in input = torch.float32[2, 784] abs_sum=1241.4496 min=-3.0303 max=3.3046
|
|
159
|
+
[00005] $t1 = F.linear(input, weight, bias) [torch.float32[2, 256] abs_sum=215.3293 ...]
|
|
160
|
+
[00006] EXIT BLOCK Linear -> (torch.float32[2, 256] abs_sum=215.3293 ...)
|
|
161
|
+
[00007] hidden = CALL fc1 [Linear] [torch.float32[2, 256] abs_sum=215.3293 ...]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Installation
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pip install . # or: pip install -e .[research] for the harness extras
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Requires Python ≥ 3.12 and PyTorch ≥ 2.0. The model classes must be importable
|
|
171
|
+
from a **real file** (the parser reads the forward source with
|
|
172
|
+
`inspect.getsource`, which fails for classes defined in a REPL or stdin).
|
|
173
|
+
|
|
174
|
+
## Validation
|
|
175
|
+
|
|
176
|
+
A robustness sweep of **58 models** — 48 Hugging Face hub checkpoints (text,
|
|
177
|
+
vision, audio, multimodal) plus 10 locally built architectures (MLP, RNN, CNN,
|
|
178
|
+
autoencoder, GAN, GNN) — runs each model through *parse → interpret → compare
|
|
179
|
+
against the real forward*. **All 58 pass with `maxdiff = 0.0`** against the real
|
|
180
|
+
forward. See [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md) for the full model
|
|
181
|
+
matrix and the sweep CLI.
|
|
182
|
+
|
|
183
|
+
## Documentation
|
|
184
|
+
|
|
185
|
+
- **[docs/API.md](docs/API.md)** — the public API: parsing, executing,
|
|
186
|
+
tracing, dumping and walking schemas.
|
|
187
|
+
- **[docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)** — full documentation:
|
|
188
|
+
how the parser works, the schema and instruction reference, the
|
|
189
|
+
interpreter, usage recipes, supported syntax, supported/unsupported
|
|
190
|
+
models, and limitations.
|
|
191
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — the release history.
|
|
192
|
+
- `tests/hf_sweep.py` — the validation harness CLI (58 HuggingFace models).
|
|
193
|
+
- `tests/test_new_syntax.py`, `tests/test_parser.py`,
|
|
194
|
+
`tests/test_methods_and_scalars.py` — the fixture test suite (pytest,
|
|
195
|
+
no network needed), also runnable with `python -m tests.test_new_syntax`.
|
|
196
|
+
- `tests/test_speech_models.py` — the espnet/torchaudio regression tests.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# NNSchemaParser
|
|
2
|
+
|
|
3
|
+
**NNSchemaParser** converts a PyTorch `nn.Module` into a serializable, inspectable
|
|
4
|
+
graph representation — the **`ModelBlockSchema`** — that keeps the *original variable
|
|
5
|
+
names of the forward source* and *every sub-module* (including MoE experts that a
|
|
6
|
+
single trace would never visit). The schema can then be executed by a built-in
|
|
7
|
+
**schema interpreter** that re-dispatches every recorded call to the real torch
|
|
8
|
+
callables, so the graph runs exactly like the model — and can optionally capture
|
|
9
|
+
the value of every intermediate tensor.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
nn.Module ──parse──► ModelBlockSchema ──interpret──► outputs / captured values
|
|
13
|
+
(your code) (names + graph) (identical to model(x))
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Why not `torch.fx`?
|
|
17
|
+
|
|
18
|
+
| | `torch.fx.symbolic_trace` | **NNSchemaParser** |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Variable names | lost (`_0`, `_1`, …) | **kept** (`hidden_states`, `query`, …) |
|
|
21
|
+
| Untaken branches (MoE experts) | missing | **all present** |
|
|
22
|
+
| Control flow | `graph_module` nodes only | `if` / `for` / `try` as first-class instructions |
|
|
23
|
+
| `*args` / `**kwargs` | limited | supported in signatures and call sites |
|
|
24
|
+
| Comprehensions | unsupported | compiled to helper functions |
|
|
25
|
+
| Re-executable | via the FX machinery | via a small self-contained interpreter |
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import torch
|
|
31
|
+
from torch import nn
|
|
32
|
+
|
|
33
|
+
from nnschemaparser import NNParser, SchemaInterpreter, TraceRecorder, dump_schema
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MLP(nn.Module):
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.fc1 = nn.Linear(784, 256)
|
|
40
|
+
self.fc2 = nn.Linear(256, 10)
|
|
41
|
+
|
|
42
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
43
|
+
hidden = self.fc1(x)
|
|
44
|
+
activated = hidden.relu()
|
|
45
|
+
return self.fc2(activated)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
model = MLP().eval()
|
|
49
|
+
x = torch.randn(2, 784)
|
|
50
|
+
|
|
51
|
+
# 1. Parse the model into a schema (resolves shapes and dtypes)
|
|
52
|
+
schema = NNParser.parse_model(
|
|
53
|
+
model=model,
|
|
54
|
+
inputs_tensors_shapes={"x": (2, 784)},
|
|
55
|
+
inputs_tensors_dtypes={"x": "torch.float32"},
|
|
56
|
+
inputs_tensors_values={"x": x},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# 2. Dump a readable view of the whole block tree
|
|
60
|
+
dump_schema(schema=schema, path="schema.txt")
|
|
61
|
+
|
|
62
|
+
# 3. Execute it with a tracer and compare with the real forward
|
|
63
|
+
tracer = TraceRecorder()
|
|
64
|
+
outputs = SchemaInterpreter(capture_values=True, listeners=[tracer]).run(
|
|
65
|
+
block=schema,
|
|
66
|
+
scope={"x": x.clone()},
|
|
67
|
+
)
|
|
68
|
+
tracer.save("trace.txt")
|
|
69
|
+
reference = model(x)
|
|
70
|
+
assert torch.equal(outputs[0], reference)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The schema dump of `MLP` reads exactly like the source — original variable
|
|
74
|
+
names, every sub-module, and the resolved shape of every step:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
=== BLOCK MLP ===
|
|
78
|
+
inputs:
|
|
79
|
+
- x = torch.float32[2, 784]
|
|
80
|
+
outputs:
|
|
81
|
+
- torch.float32[2, 10]
|
|
82
|
+
weights:
|
|
83
|
+
(none)
|
|
84
|
+
sub-blocks:
|
|
85
|
+
- fc1(Linear)
|
|
86
|
+
- fc2(Linear)
|
|
87
|
+
body:
|
|
88
|
+
hidden = CALL fc1(x) -> torch.float32[2, 256]
|
|
89
|
+
$t1 = getattr(hidden, 'relu') -> builtin_function_or_method
|
|
90
|
+
activated = hidden.relu() -> torch.float32[2, 256]
|
|
91
|
+
$t2 = CALL fc2(activated) -> torch.float32[2, 10]
|
|
92
|
+
RETURN $t2
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The tracer records the same execution chronologically, with the value of
|
|
96
|
+
every intermediate tensor (one line per event, indented by the nesting
|
|
97
|
+
depth) — this is what a WebUI consumes to display every intermediate value:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
[00001] ENTER BLOCK MLP
|
|
101
|
+
[00002] in x = torch.float32[2, 784] abs_sum=1241.4496 min=-3.0303 max=3.3046
|
|
102
|
+
[00003] ENTER BLOCK Linear
|
|
103
|
+
[00004] in input = torch.float32[2, 784] abs_sum=1241.4496 min=-3.0303 max=3.3046
|
|
104
|
+
[00005] $t1 = F.linear(input, weight, bias) [torch.float32[2, 256] abs_sum=215.3293 ...]
|
|
105
|
+
[00006] EXIT BLOCK Linear -> (torch.float32[2, 256] abs_sum=215.3293 ...)
|
|
106
|
+
[00007] hidden = CALL fc1 [Linear] [torch.float32[2, 256] abs_sum=215.3293 ...]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Installation
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install . # or: pip install -e .[research] for the harness extras
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Requires Python ≥ 3.12 and PyTorch ≥ 2.0. The model classes must be importable
|
|
116
|
+
from a **real file** (the parser reads the forward source with
|
|
117
|
+
`inspect.getsource`, which fails for classes defined in a REPL or stdin).
|
|
118
|
+
|
|
119
|
+
## Validation
|
|
120
|
+
|
|
121
|
+
A robustness sweep of **58 models** — 48 Hugging Face hub checkpoints (text,
|
|
122
|
+
vision, audio, multimodal) plus 10 locally built architectures (MLP, RNN, CNN,
|
|
123
|
+
autoencoder, GAN, GNN) — runs each model through *parse → interpret → compare
|
|
124
|
+
against the real forward*. **All 58 pass with `maxdiff = 0.0`** against the real
|
|
125
|
+
forward. See [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md) for the full model
|
|
126
|
+
matrix and the sweep CLI.
|
|
127
|
+
|
|
128
|
+
## Documentation
|
|
129
|
+
|
|
130
|
+
- **[docs/API.md](docs/API.md)** — the public API: parsing, executing,
|
|
131
|
+
tracing, dumping and walking schemas.
|
|
132
|
+
- **[docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)** — full documentation:
|
|
133
|
+
how the parser works, the schema and instruction reference, the
|
|
134
|
+
interpreter, usage recipes, supported syntax, supported/unsupported
|
|
135
|
+
models, and limitations.
|
|
136
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — the release history.
|
|
137
|
+
- `tests/hf_sweep.py` — the validation harness CLI (58 HuggingFace models).
|
|
138
|
+
- `tests/test_new_syntax.py`, `tests/test_parser.py`,
|
|
139
|
+
`tests/test_methods_and_scalars.py` — the fixture test suite (pytest,
|
|
140
|
+
no network needed), also runnable with `python -m tests.test_new_syntax`.
|
|
141
|
+
- `tests/test_speech_models.py` — the espnet/torchaudio regression tests.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NNSchemaParser.
|
|
3
|
+
|
|
4
|
+
Converts a torch nn.Module into an inspectable ModelBlockSchema graph that
|
|
5
|
+
keeps the original variable names of the forward source and every sub-module,
|
|
6
|
+
and executes it with a schema interpreter re-dispatching to the real torch
|
|
7
|
+
callables.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Import Modules
|
|
11
|
+
from nnschemaparser.core import ModelBlockSchema, TensorRepr, TensorReprWithValue
|
|
12
|
+
from nnschemaparser.dump import dump_schema
|
|
13
|
+
from nnschemaparser.interpreter import SchemaInterpreter, TraceListener, TraceRecorder
|
|
14
|
+
from nnschemaparser.parser import NNParser
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ModelBlockSchema",
|
|
18
|
+
"TraceListener",
|
|
19
|
+
"TraceRecorder",
|
|
20
|
+
"dump_schema",
|
|
21
|
+
"NNParser",
|
|
22
|
+
"SchemaInterpreter",
|
|
23
|
+
"TensorRepr",
|
|
24
|
+
"TensorReprWithValue"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
__version__: str = "0.1.0"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core data model of NNSchemaParser.
|
|
3
|
+
|
|
4
|
+
The schema types describing a parsed model: the block schema tree, the tensor
|
|
5
|
+
representations and the instruction types of the block bodies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import Modules
|
|
9
|
+
from nnschemaparser.core.model_schema import ModelBlockSchema
|
|
10
|
+
from nnschemaparser.core.tensor_schema import TensorRepr, TensorReprWithValue
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"ModelBlockSchema",
|
|
14
|
+
"TensorRepr",
|
|
15
|
+
"TensorReprWithValue"
|
|
16
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Container entry key utilities shared by the lowering and the interpreter.
|
|
3
|
+
|
|
4
|
+
The entries of a sub-block container are keyed by "container.index" in the
|
|
5
|
+
sub-block dictionaries, and must be iterated in their declaration order.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import Modules
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def container_entry_sort_key(entry_key: str) -> int:
|
|
12
|
+
"""
|
|
13
|
+
Get the numeric sort key of a container entry key.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
entry_key (str): The entry key (e.g. "encoders.2").
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
int: The numeric suffix of the key, or -1 when not numeric.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Split the key into its container prefix and its suffix
|
|
23
|
+
parts: list[str] = entry_key.rsplit(".", 1)
|
|
24
|
+
|
|
25
|
+
# A numeric suffix is sorted by its value
|
|
26
|
+
if len(parts) == 2 and parts[1].isdigit():
|
|
27
|
+
return int(parts[1])
|
|
28
|
+
|
|
29
|
+
# A non numeric suffix is sorted last
|
|
30
|
+
return -1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def container_entry_keys(
|
|
34
|
+
sub_block_names: set[str] | dict[str, str],
|
|
35
|
+
container_name: str
|
|
36
|
+
) -> list[str]:
|
|
37
|
+
"""
|
|
38
|
+
Get the ordered entry keys of a container in the parsed sub-blocks.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
sub_block_names (set[str] | dict[str, str]): The names of the parsed
|
|
42
|
+
sub-blocks, either a set of names or the sub-block dictionary.
|
|
43
|
+
container_name (str): The name of the container.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
list[str]: The entry keys sorted by their numeric suffix.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# The special self path holds the direct numeric entries of the module
|
|
50
|
+
if container_name == "$self":
|
|
51
|
+
return sorted(
|
|
52
|
+
[
|
|
53
|
+
sub_block_name for sub_block_name in sub_block_names
|
|
54
|
+
if sub_block_name.isdigit()
|
|
55
|
+
],
|
|
56
|
+
key=container_entry_sort_key
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Collect the sub-block keys of the container entries
|
|
60
|
+
keys: list[str] = [
|
|
61
|
+
sub_block_name for sub_block_name in sub_block_names
|
|
62
|
+
if sub_block_name.startswith(f"{container_name}.")
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Sort the entry keys by their numeric suffix
|
|
66
|
+
keys.sort(key=container_entry_sort_key)
|
|
67
|
+
|
|
68
|
+
# Return the ordered entry keys of the container
|
|
69
|
+
return keys
|