neurarch 0.0.0-stage → 0.1.0
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.
- package/LICENSE +96 -0
- package/README.md +247 -2
- package/bin/neurarch.mjs +586 -0
- package/engine.bundle.mjs +44602 -0
- package/examples/jobs.sample.csv +7 -0
- package/package.json +38 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Sustainable Use License
|
|
2
|
+
|
|
3
|
+
Version 1.0
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
12
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
13
|
+
available, and prepare derivative works of the software, in each case subject
|
|
14
|
+
to the limitations below.
|
|
15
|
+
|
|
16
|
+
## Limitations
|
|
17
|
+
|
|
18
|
+
You may use or modify the software only for your own internal business purposes
|
|
19
|
+
or for non-commercial or personal use. You may distribute the software or
|
|
20
|
+
provide it to others only if you do so free of charge for non-commercial
|
|
21
|
+
purposes. You may not alter, remove, or obscure any licensing, copyright, or
|
|
22
|
+
other notices of the licensor in the software. Any use of the licensor's
|
|
23
|
+
trademarks is subject to applicable law.
|
|
24
|
+
|
|
25
|
+
## Patents
|
|
26
|
+
|
|
27
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
28
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
29
|
+
sale, import and have imported the software, in each case subject to the
|
|
30
|
+
limitations and conditions in this license. This license does not cover any
|
|
31
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
32
|
+
the software. If you or your company make any written claim that the software
|
|
33
|
+
infringes or contributes to infringement of any patent, your patent license for
|
|
34
|
+
the software granted under these terms ends immediately. If your company makes
|
|
35
|
+
such a claim, your patent license ends immediately for work on behalf of your
|
|
36
|
+
company.
|
|
37
|
+
|
|
38
|
+
## Notices
|
|
39
|
+
|
|
40
|
+
You must ensure that anyone who gets a copy of any part of the software from you
|
|
41
|
+
also gets a copy of these terms.
|
|
42
|
+
|
|
43
|
+
If you modify the software, you must include in any modified copies of the
|
|
44
|
+
software prominent notices stating that you have modified the software.
|
|
45
|
+
|
|
46
|
+
## No Other Rights
|
|
47
|
+
|
|
48
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
49
|
+
these terms.
|
|
50
|
+
|
|
51
|
+
## Termination
|
|
52
|
+
|
|
53
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
54
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
55
|
+
with a notice of your violation, and you cease all violation of this license no
|
|
56
|
+
later than 30 days after you receive that notice, your licenses will be
|
|
57
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
58
|
+
reinstatement, any additional violation of these terms will cause your licenses
|
|
59
|
+
to terminate automatically and permanently.
|
|
60
|
+
|
|
61
|
+
## No Liability
|
|
62
|
+
|
|
63
|
+
As far as the law allows, the software comes as is, without any warranty or
|
|
64
|
+
condition, and the licensor will not be liable to you for any damages arising
|
|
65
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
66
|
+
legal claim.
|
|
67
|
+
|
|
68
|
+
## Definitions
|
|
69
|
+
|
|
70
|
+
The "licensor" is the entity offering these terms.
|
|
71
|
+
|
|
72
|
+
The "software" is the software the licensor makes available under these terms,
|
|
73
|
+
including any portion of it.
|
|
74
|
+
|
|
75
|
+
"You" refers to the individual or entity agreeing to these terms.
|
|
76
|
+
|
|
77
|
+
"Your company" is any legal entity, sole proprietorship, or other kind of
|
|
78
|
+
organization that you work for, plus all organizations that have control over,
|
|
79
|
+
are under the control of, or are under common control with that organization.
|
|
80
|
+
"Control" means ownership of substantially all the assets of an entity, or the
|
|
81
|
+
power to direct its management and policies by vote, contract, or otherwise.
|
|
82
|
+
Control can be direct or indirect.
|
|
83
|
+
|
|
84
|
+
"Your licenses" are all the licenses granted to you for the software under these
|
|
85
|
+
terms.
|
|
86
|
+
|
|
87
|
+
"Use" means anything you do with the software requiring one of your licenses.
|
|
88
|
+
|
|
89
|
+
"Trademark" means trademarks, service marks, and similar rights.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
Copyright (c) 2026 neurarch-ai
|
|
94
|
+
|
|
95
|
+
This license is adapted from the Sustainable Use License, which is published by
|
|
96
|
+
n8n GmbH under CC BY-SA 4.0 and is intended to be reused.
|
package/README.md
CHANGED
|
@@ -1,3 +1,248 @@
|
|
|
1
|
-
#
|
|
1
|
+
# neurarch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
What a GPU training job needs before it takes a reservation: the peak memory at
|
|
4
|
+
its batch, the smallest card that holds it, and what one run costs, read from a
|
|
5
|
+
Hugging Face `config.json` (or a traced graph). No account, no download, no GPU.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npx neurarch rightsize jobs.csv # each past job's request next to what it needed
|
|
9
|
+
npx neurarch Qwen/Qwen3-8B --batch 8 # the training peak for one model
|
|
10
|
+
npx neurarch oom error.txt --model Qwen/Qwen3-8B --batch 8 # why it ran out of memory, and the least invasive fix
|
|
11
|
+
npx neurarch Qwen/Qwen3-8B --inference --batch 4 --seq-len 4096 --gen 512 # weights + KV cache for a generation job
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The memory number is calibrated against measured runs, and every run is public
|
|
15
|
+
next to `accelerate estimate-memory`: <https://neurarch.com/memory-calibration.html>.
|
|
16
|
+
|
|
17
|
+
It also reads any Hugging Face model as an architecture:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
npx neurarch microsoft/Phi-3-mini-4k-instruct
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
microsoft/Phi-3-mini-4k-instruct phi3 · text-generation
|
|
25
|
+
|
|
26
|
+
published (safetensors.total) 3.82B 3,821,079,552
|
|
27
|
+
derived (config.json alone) 3.82B 3,821,111,616
|
|
28
|
+
difference +0.00% within 2%: the reconstruction matches the checkpoint
|
|
29
|
+
|
|
30
|
+
layers (198, showing 12; --layers for all)
|
|
31
|
+
1 Input Input 1 × 4096
|
|
32
|
+
2 Embedding Embedding 1 × 4096 × 3072
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
checks clean
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## The number this exists to print
|
|
39
|
+
|
|
40
|
+
The second one. `derived` is computed from `config.json` alone: the layer graph
|
|
41
|
+
is reconstructed from the config, shapes are propagated through it, and the
|
|
42
|
+
parameters are counted. `published` is `safetensors.total` from the Hub API,
|
|
43
|
+
the scalars actually stored in the weight files.
|
|
44
|
+
|
|
45
|
+
They come from different places, so the gap between them is the only honest
|
|
46
|
+
statement about whether the reconstruction is worth anything. Everything else
|
|
47
|
+
the tool prints, the layer table and the checks, is downstream of that.
|
|
48
|
+
|
|
49
|
+
The tolerance is 2%, chosen before the numbers were looked at because it is
|
|
50
|
+
smaller than one layer in every model in the reference set. Agreement across
|
|
51
|
+
the published set lives at <https://neurarch.com/m/>.
|
|
52
|
+
|
|
53
|
+
## What one training run would cost
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
npx neurarch bert-base-uncased --data 3M --gpu h100-80gb
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
one training run over 3,000,000 samples
|
|
61
|
+
quoted $31 on H100 (80GB), 8.0 h
|
|
62
|
+
fits T4 A100_40 H100_80 (training peak ~4.2 GB at batch 32, 512 tokens, 1.15x headroom)
|
|
63
|
+
tokens 512 assumed (default 2048, or the context if shorter); --seq-len to change
|
|
64
|
+
attention SDPA, scores not stored; eager would add 8.4 GB
|
|
65
|
+
derived, not measured: nothing here was trained
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--data` is the size of YOUR training set (`3M`, `500k`, `2B`). It is required
|
|
69
|
+
for a price, and that is deliberate: with no workload the estimate defaults to
|
|
70
|
+
50,000 rows, which is nobody's training set and would make the largest number
|
|
71
|
+
on the screen the one nobody stated. Same model, same card, three sizes:
|
|
72
|
+
|
|
73
|
+
| `--data` | quoted | wall clock |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| 50k | $0.59 | 0.1 h |
|
|
76
|
+
| 3M | $31 | 8.0 h |
|
|
77
|
+
| 40M | $418 | 4.4 days |
|
|
78
|
+
|
|
79
|
+
`--gpu` is one of `t4-16gb`, `l4-24gb`, `a10g-24gb`, `l40s-48gb`, `a100-40gb`,
|
|
80
|
+
`a100-80gb`, `h100-80gb`; a prefix works when it is unambiguous (`t4`, `h100`),
|
|
81
|
+
and `a100` is refused with both options rather than resolved by array order.
|
|
82
|
+
|
|
83
|
+
`--batch 8` sizes the training peak at that batch (default 32). `--attention` is
|
|
84
|
+
`sdpa` (the default, and Hugging Face's own), `flash` or `eager`. Only eager
|
|
85
|
+
keeps the attention score matrices for backward, so only eager adds them to the
|
|
86
|
+
peak; under the other two the line says how many GB eager would add. On a token
|
|
87
|
+
model at 2048 tokens that term is most of the activation memory, so it is worth
|
|
88
|
+
setting when your training script sets `attn_implementation="eager"`. The
|
|
89
|
+
same option is the `attention` field on `POST /api/v1/plan` and the `attention`
|
|
90
|
+
column of `rightsize`, so all three size one model the same way.
|
|
91
|
+
|
|
92
|
+
`--seq-len 8192` sizes a text model at that many tokens per sample. The default
|
|
93
|
+
is 2048, or the model's context when that is shorter (BERT: 512). It is not
|
|
94
|
+
`max_position_embeddings`: that is the longest input the model accepts, and
|
|
95
|
+
Qwen3-0.6B declares 40,960, which put a ~191 GB peak at batch 8 on a model
|
|
96
|
+
people fine-tune on one card. At 2048 tokens the same line reads ~21 GB. The
|
|
97
|
+
default is the one `rightsize` uses for a blank `seq_len` and the `seq_len`
|
|
98
|
+
field on `POST /api/v1/plan` (which the MCP `plan` tool calls), and the line
|
|
99
|
+
always says the length it used. `--batch`, `--seq-len` or `--attention` without
|
|
100
|
+
`--data` print the training peak with no price.
|
|
101
|
+
|
|
102
|
+
`--inference` sizes a generation job instead: weights at `--precision` (bf16 by
|
|
103
|
+
default; fp16, fp32, int8, int4) plus the KV cache at the last generated token,
|
|
104
|
+
`--seq-len` prompt tokens plus `--gen` generated ones (default 256) at `--batch`
|
|
105
|
+
(default 1), with the same 1.15x headroom. The KV cache is the same per-token
|
|
106
|
+
formula the change record's serving line uses. It has no activation term, and it
|
|
107
|
+
prints what that leaves out (the prefill working set and prompt logits, the CUDA
|
|
108
|
+
context and any framework reserve). `npx neurarch oom --inference` compares an
|
|
109
|
+
error against this peak instead of the training one.
|
|
110
|
+
|
|
111
|
+
A repo with no safetensors index (InternLM-7B, xlnet-large-cased) has no
|
|
112
|
+
published count, so the plain read refuses it. With any sizing flag it is still
|
|
113
|
+
sized from `config.json`, under a line saying there was nothing to compare.
|
|
114
|
+
|
|
115
|
+
Everything in that block is arithmetic over the reconstructed graph at list GPU
|
|
116
|
+
prices. Nothing was trained and nothing was measured, which is why the verb is
|
|
117
|
+
"quoted": what it is good for is deciding whether to start the run, not
|
|
118
|
+
predicting what comes out of it.
|
|
119
|
+
|
|
120
|
+
## Which modules a LoRA can sit on
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
npx neurarch Qwen/Qwen3-0.6B --lora-targets
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
LoRA targets base 596.0M params, 196 linear modules
|
|
128
|
+
attention r=8 +2.3M (0.38%) r=16 +4.6M (0.77%) over 112 modules
|
|
129
|
+
target_modules=["q_proj","k_proj","v_proj","o_proj"]
|
|
130
|
+
all-linear r=8 +5.0M (0.85%) r=16 +10.1M (1.69%) over 196 modules
|
|
131
|
+
target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"]
|
|
132
|
+
by role q_proj 28 · k_proj 28 · v_proj 28 · o_proj 28 · gate 28 · up 28 · down 28
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Every Linear in the reconstructed graph, with attention opened into its q, k,
|
|
136
|
+
v and o projections (GQA's narrower k and v included) and SwiGLU into gate, up
|
|
137
|
+
and down. A rank-r adapter adds r x (in + out) per module; the output head is
|
|
138
|
+
left out of all-linear, as PEFT does. `--json --lora-targets` returns the
|
|
139
|
+
per-module list. It says what each preset costs, not which one trains better.
|
|
140
|
+
|
|
141
|
+
## Requested vs needed, over your past jobs
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
npx neurarch rightsize jobs.csv --out report.md
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
6 jobs, 174 GPU-hours, $507.85 spent; a right-sized request would have saved $404.64 (80% of spend), claiming nothing on 1 row marked under or unknown.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For each job in the CSV: what it requested (card x count), what it needed
|
|
152
|
+
(the training peak the plan predicts at that batch), the smallest card that
|
|
153
|
+
holds it, a verdict, and the dollars a right-sized request would have saved
|
|
154
|
+
over the hours the reservation was held.
|
|
155
|
+
|
|
156
|
+
| verdict | means |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `right-sized` | the request is the smallest capacity that holds the peak with 1.15x headroom, or one size up |
|
|
159
|
+
| `over` | a smaller card or fewer cards would have held it; the saving is priced on the cheapest one that does |
|
|
160
|
+
| `under` | the predicted peak does not fit what was requested: the job OOMed or relied on something the graph does not declare. No saving claimed |
|
|
161
|
+
| `unknown` | no graph (bad id, unreadable config, a card name it cannot resolve); the row says why. No saving claimed |
|
|
162
|
+
|
|
163
|
+
The CSV has a header row; column order is free. A sample is in
|
|
164
|
+
[`examples/jobs.sample.csv`](examples/jobs.sample.csv):
|
|
165
|
+
|
|
166
|
+
```csv
|
|
167
|
+
job_id,model,gpu,batch,hours,seq_len,lora_rank
|
|
168
|
+
ft-qwen-small,Qwen/Qwen3-0.6B,8xH100,8,6,2048,
|
|
169
|
+
lora-qwen-8b,Qwen/Qwen3-8B,A100-80,4,12,2048,16
|
|
170
|
+
bert-classifier,bert-base-uncased,A100-80,32,3,512,
|
|
171
|
+
tinyllama-lora,TinyLlama/TinyLlama-1.1B-Chat-v1.0,H100,8,5,2048,8
|
|
172
|
+
phi3-sft,microsoft/Phi-3-mini-4k-instruct,A100-40,8,10,4096,
|
|
173
|
+
gpt2-pretrain,openai-community/gpt2,4xA100-80,16,24,1024,
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| column | required | |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `job_id` | yes | any label |
|
|
179
|
+
| `model` | yes | a Hugging Face id, or a path to a `.neurarch.json` graph written by `neurarch-trace` |
|
|
180
|
+
| `gpu` | yes | `T4`, `L4`, `A10G`, `A100-40`, `A100-80`, `L40S`, `H100`, `H200`, or a count like `8xH100`. A bare `A100` is refused: the two differ by 2x in memory |
|
|
181
|
+
| `gpus` | no | card count, instead of `8xH100` |
|
|
182
|
+
| `hours` | yes | wall hours the job held the reservation |
|
|
183
|
+
| `batch` | no | per-device batch, default 32 |
|
|
184
|
+
| `seq_len` | no | sequence length for token models; default 2048, or the model's context if shorter |
|
|
185
|
+
| `lora_rank` | no | size the base frozen in bf16 (2 bytes per parameter) and only adapters (on every Linear but the head) as trainable |
|
|
186
|
+
| `attention` | no | `sdpa` (default), `flash` or `eager`. Only eager keeps the attention score matrices for backward |
|
|
187
|
+
|
|
188
|
+
`--prices prices.json` overrides the default list prices per GPU-hour
|
|
189
|
+
(`{"H100": 2.49, "A100-80": 1.79}`); cards it does not name keep theirs, and the
|
|
190
|
+
report says which were overridden. `--json` returns every row with the plan's
|
|
191
|
+
own `peakGb`, `smallestFitGpu`, `maxBatch` and `twoCardSplit`.
|
|
192
|
+
|
|
193
|
+
**The memory model.** The peak is `buildPlan`'s training peak under AdamW with
|
|
194
|
+
bf16 autocast: weights, gradients and optimizer state plus activations (every
|
|
195
|
+
layer's output, the intermediates inside each layer, recurrent gate states, the
|
|
196
|
+
largest tensor's backward copy and, under eager attention, stored scores),
|
|
197
|
+
fitted on 178 measured runs. Held out one architecture at a time, the median
|
|
198
|
+
error is 7% on runs of 1 GB or more and it flagged 113 of the 131 times a run
|
|
199
|
+
did not fit a card; on 25 runs at the edge of a 24 GB card measured after the
|
|
200
|
+
fit, the median miss is 14%. Inference jobs and other optimizers will differ. A multi-card job
|
|
201
|
+
is sized as one model sharded across its cards (FSDP / ZeRO-3): the peak at the
|
|
202
|
+
global batch against the summed capacity. Default prices are Modal's on-demand
|
|
203
|
+
list prices, the same table the Neurarch app prices training with.
|
|
204
|
+
|
|
205
|
+
**What it does not know**: data loader memory, custom kernels beyond the
|
|
206
|
+
attention column, activation checkpointing (a checkpointed job needs less),
|
|
207
|
+
multi-node communication buffers, and throughput: the saving assumes the same
|
|
208
|
+
wall hours, so a request cut from 8 cards to 1 is capacity reserved and not
|
|
209
|
+
needed, not a promise the same job finishes as fast. The report prints all of
|
|
210
|
+
this next to the numbers.
|
|
211
|
+
|
|
212
|
+
## What it refuses to do
|
|
213
|
+
|
|
214
|
+
- **No safetensors index on the Hub** → it prints nothing and says so. A
|
|
215
|
+
derived count with nothing to check it against looks authoritative and means
|
|
216
|
+
nothing.
|
|
217
|
+
- **A quantized checkpoint** → the counts are reported and explicitly marked
|
|
218
|
+
not comparable. Packed storage counts stored elements under a packing scheme,
|
|
219
|
+
not logical parameters, so the delta measures the packing, not us.
|
|
220
|
+
- **A multi-tower config** (CLIP, LLaVA) → same, with the sub-models named. The
|
|
221
|
+
checkpoint carries every tower; the graph carries what the reader
|
|
222
|
+
reconstructs, so a gap is a statement about what we read.
|
|
223
|
+
- **Weight sharing** (`num_hidden_groups`) → the graph counts blocks in the
|
|
224
|
+
forward pass, so a structural count is necessarily larger than the file.
|
|
225
|
+
|
|
226
|
+
## Usage
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
npx neurarch <model-id> # bert-base-uncased, Qwen/Qwen3-8B, ...
|
|
230
|
+
npx neurarch <model-id> --layers # every layer, not the first 12
|
|
231
|
+
npx neurarch <model-id> --json # machine-readable
|
|
232
|
+
npx neurarch rightsize jobs.csv # requested vs needed GPU over past jobs
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Exit code is `1` when the model cannot be read or a check blocks, `0`
|
|
236
|
+
otherwise, so it composes in a script.
|
|
237
|
+
|
|
238
|
+
Set `HF_TOKEN` to read a gated repo. The token is sent only to
|
|
239
|
+
`huggingface.co`. Nothing is uploaded anywhere and nothing is stored.
|
|
240
|
+
|
|
241
|
+
## Related
|
|
242
|
+
|
|
243
|
+
- <https://neurarch.com/compare> — the same comparison for two models, in a
|
|
244
|
+
browser, no account
|
|
245
|
+
- `POST https://www.neurarch.com/api/v1/check` — run the checks on a graph of
|
|
246
|
+
your own
|
|
247
|
+
- [`neurarch-lint`](https://www.npmjs.com/package/neurarch-lint) — the same
|
|
248
|
+
checks against PyTorch source
|