neurarch 0.1.1 → 0.1.2
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/README.md +29 -0
- package/bin/neurarch.mjs +43 -4
- package/engine.bundle.mjs +163 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ npx neurarch rightsize jobs.csv # each past job's request next to what
|
|
|
9
9
|
npx neurarch Qwen/Qwen3-8B --batch 8 # the training peak for one model
|
|
10
10
|
npx neurarch oom error.txt --model Qwen/Qwen3-8B --batch 8 # why it ran out of memory, and the least invasive fix
|
|
11
11
|
npx neurarch Qwen/Qwen3-8B --inference --batch 4 --seq-len 4096 --gen 512 # weights + KV cache for a generation job
|
|
12
|
+
npx neurarch bert-base-uncased --seq-len 1024 --tokenizer-size 30524 # does the data fit what the config accepts
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
The memory number is calibrated against measured runs, and every run is public
|
|
@@ -117,6 +118,34 @@ prices. Nothing was trained and nothing was measured, which is why the verb is
|
|
|
117
118
|
"quoted": what it is good for is deciding whether to start the run, not
|
|
118
119
|
predicting what comes out of it.
|
|
119
120
|
|
|
121
|
+
## Does the data fit what the config accepts
|
|
122
|
+
|
|
123
|
+
Some jobs never reach their peak: they are admitted, wait for the card, load the
|
|
124
|
+
weights and then index past a table on the first batch. Each of these is
|
|
125
|
+
readable from the config before anything is reserved.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
$ npx neurarch bert-base-uncased --seq-len 1024 --tokenizer-size 30524
|
|
129
|
+
|
|
130
|
+
data fit checked: sequence length, tokenizer size
|
|
131
|
+
block Sequences of 1024 tokens are longer than the 512-row position table
|
|
132
|
+
fix: Truncate or chunk to 512 tokens, or pick a model whose context covers 1024.
|
|
133
|
+
block The tokenizer has 30524 ids and the embedding has 30522 rows
|
|
134
|
+
fix: Call model.resize_token_embeddings(len(tokenizer)) before training, and budget for the new rows.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| flag | checked against | when it blocks |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `--seq-len N` | the position scheme | past a learned position table (a rotary context only warns: it runs) |
|
|
140
|
+
| `--tokenizer-size N` | rows in the token embedding | more tokenizer ids than rows |
|
|
141
|
+
| `--classes N` | a `*ForSequenceClassification`-style head in the config, or a graph's final Linear | more labels than the head emits (fewer only warns) |
|
|
142
|
+
| `--image-size N` | the patch position table | any other size |
|
|
143
|
+
| `--channels N` | the first layer's input channels | any other count |
|
|
144
|
+
|
|
145
|
+
A field you do not state is not checked, and the output lists what could not be
|
|
146
|
+
compared and why, so a clean result never stands in for one nobody ran. A block
|
|
147
|
+
sets the exit code to 1. The same check is `data` on `POST /api/v1/check`.
|
|
148
|
+
|
|
120
149
|
## Which modules a LoRA can sit on
|
|
121
150
|
|
|
122
151
|
```
|
package/bin/neurarch.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* npx neurarch Qwen/Qwen3-8B --batch 8 --seq-len 8192 # training peak at that length, no price
|
|
11
11
|
* npx neurarch Qwen/Qwen3-8B --inference --batch 4 --seq-len 4096 --gen 512 # weights + KV cache, which cards hold it
|
|
12
12
|
* npx neurarch Qwen/Qwen3-8B --lora-targets # which modules a LoRA can sit on
|
|
13
|
+
* npx neurarch bert-base-uncased --seq-len 1024 --tokenizer-size 30524 # will this data crash it after it gets the GPU
|
|
13
14
|
* npx neurarch rightsize jobs.csv # requested vs needed GPU over past jobs
|
|
14
15
|
*
|
|
15
16
|
* The number this exists to print is the second one: parameters derived from
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
*
|
|
20
21
|
* Zero dependencies; the engine is bundled. Set HF_TOKEN for gated repos.
|
|
21
22
|
*/
|
|
22
|
-
import { inspectHFModel, keyFor, AGREE_TOLERANCE, priceHFModel, parseHumanSize, PREFLIGHT_GPUS, specFromFlags, renderLoraText, rightsize, renderMarkdown, headline, applyPrices, DEFAULT_CARDS, oomReport, renderOomText, readAttention, attentionPhrase, readSeqLen, DEFAULT_SEQ_LEN, inferHFModel, readInferencePrecision, INFERENCE_PRECISIONS, DEFAULT_GEN_TOKENS, DEFAULT_INFERENCE_BATCH, CHANGE_HEADROOM, storedTwiceSentence } from '../engine.bundle.mjs';
|
|
23
|
+
import { inspectHFModel, keyFor, AGREE_TOLERANCE, priceHFModel, parseHumanSize, PREFLIGHT_GPUS, specFromFlags, renderLoraText, rightsize, renderMarkdown, headline, applyPrices, DEFAULT_CARDS, oomReport, renderOomText, readAttention, attentionPhrase, readSeqLen, DEFAULT_SEQ_LEN, inferHFModel, readInferencePrecision, INFERENCE_PRECISIONS, DEFAULT_GEN_TOKENS, DEFAULT_INFERENCE_BATCH, CHANGE_HEADROOM, storedTwiceSentence, dataFitHFModel } from '../engine.bundle.mjs';
|
|
23
24
|
/** The channel tag on every link this program prints. `docs/DISTRIBUTION.md`
|
|
24
25
|
* has asked for one since the first release: without it a visit from a tool
|
|
25
26
|
* and a visit from a search result are the same row, and "which channel brings
|
|
@@ -197,7 +198,7 @@ for gated repos); nothing is uploaded.
|
|
|
197
198
|
|
|
198
199
|
// `--data 3M` and `--gpu a100` take a value, so their value must not fall
|
|
199
200
|
// through to `positional` and be read as a second model id.
|
|
200
|
-
const VALUED = new Set(['--data', '--gpu', '--lora', '--quant', '--fit', '--serve', '--batch', '--attention', '--seq-len', '--gen', '--precision']);
|
|
201
|
+
const VALUED = new Set(['--data', '--gpu', '--lora', '--quant', '--fit', '--serve', '--batch', '--attention', '--seq-len', '--gen', '--precision', '--tokenizer-size', '--classes', '--image-size', '--channels']);
|
|
201
202
|
const flags = new Set();
|
|
202
203
|
const opts = {};
|
|
203
204
|
const positional = [];
|
|
@@ -234,6 +235,11 @@ if (flags.has('-h') || flags.has('--help') || positional.length === 0) {
|
|
|
234
235
|
the change record: what may move on this base, and whether the gate approves it
|
|
235
236
|
npx neurarch <model-id> --lora-targets every Linear a LoRA can target, and PEFT target_modules
|
|
236
237
|
for the attention and all-linear presets with r=8 / r=16 totals
|
|
238
|
+
npx neurarch <model-id> --seq-len 1024 --tokenizer-size 30524 --classes 5 [--image-size 384 --channels 1]
|
|
239
|
+
does YOUR data fit what the config accepts: sequence vs the
|
|
240
|
+
position table, tokenizer vs the embedding, labels vs the head,
|
|
241
|
+
image size and channels vs the first layer. These crash on the
|
|
242
|
+
first batch, after the reservation; this says so before it.
|
|
237
243
|
|
|
238
244
|
--data takes the size of YOUR training set (3M, 500k, 2B). Without it there is
|
|
239
245
|
no price, because a cost with no workload behind it is a cost for somebody
|
|
@@ -303,7 +309,21 @@ if (opts.gen != null) {
|
|
|
303
309
|
const inferOpts = { batch: batchSize, promptTokens: seqLen, genTokens, precision };
|
|
304
310
|
/** The training-peak block runs on any of these; `--inference` replaces it. */
|
|
305
311
|
const trainingSizing = !inference && (opts.data != null || batchSize != null || seqLen != null || opts.attention != null);
|
|
306
|
-
|
|
312
|
+
// The data the caller describes. `--seq-len` is the same number the peak is
|
|
313
|
+
// sized at, so it is checked against the position scheme whenever it is given.
|
|
314
|
+
const DATA_FLAGS = [['seq-len', 'seqLen'], ['tokenizer-size', 'tokenizerSize'], ['classes', 'numClasses'], ['image-size', 'imageSize'], ['channels', 'channels']];
|
|
315
|
+
const dataSpec = {};
|
|
316
|
+
for (const [flag, key] of DATA_FLAGS) {
|
|
317
|
+
if (opts[flag] == null) continue;
|
|
318
|
+
const n = Number(opts[flag]);
|
|
319
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
320
|
+
process.stderr.write(`\n ${red('cannot read')} --${flag} ${opts[flag]}. Use a positive integer.\n`);
|
|
321
|
+
process.exit(2);
|
|
322
|
+
}
|
|
323
|
+
dataSpec[key] = n;
|
|
324
|
+
}
|
|
325
|
+
const dataAsked = Object.keys(dataSpec).length > 0;
|
|
326
|
+
const sizingAsked = inference || trainingSizing || opts.lora != null || dataAsked;
|
|
307
327
|
|
|
308
328
|
const modelId = positional[0];
|
|
309
329
|
const result = await inspectHFModel(modelId);
|
|
@@ -332,6 +352,7 @@ if (flags.has('--json')) {
|
|
|
332
352
|
const spec = specFromFlags({ lora: opts.lora, quant: opts.quant, fit: opts.fit, serve: opts.serve });
|
|
333
353
|
if (spec) out.change = priceHFModel(f, { samples: 50_000, source: 'assumed' }, runFrom(gpuFromFlag(opts.gpu)), spec, attention, seqLen).plan.change;
|
|
334
354
|
if (flags.has('--lora-targets')) out.lora = priceHFModel(f, { samples: 50_000, source: 'assumed' }).plan.lora;
|
|
355
|
+
if (dataAsked) out.dataFit = dataFitHFModel(f, dataSpec);
|
|
335
356
|
return out;
|
|
336
357
|
};
|
|
337
358
|
const payload = result.ok
|
|
@@ -365,7 +386,7 @@ if (flags.has('--json')) {
|
|
|
365
386
|
}
|
|
366
387
|
: { ok: false, modelId, reason: result.reason };
|
|
367
388
|
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
368
|
-
process.exit(payload.ok ? 0 : 1);
|
|
389
|
+
process.exit(payload.ok && !(payload.dataFit?.findings ?? []).some((f) => f.severity === 'block') ? 0 : 1);
|
|
369
390
|
}
|
|
370
391
|
|
|
371
392
|
if (!result.ok && !sizingOnly) {
|
|
@@ -442,6 +463,24 @@ if (sizingOnly) {
|
|
|
442
463
|
}
|
|
443
464
|
}
|
|
444
465
|
|
|
466
|
+
// ── Does the data fit what the config accepts ───────────────────────────────
|
|
467
|
+
//
|
|
468
|
+
// Before memory, because a job that indexes past a table on its first batch
|
|
469
|
+
// does not get far enough for its peak to matter.
|
|
470
|
+
if (dataAsked) {
|
|
471
|
+
const fit = dataFitHFModel(facts, dataSpec);
|
|
472
|
+
const blocks = fit.findings.filter((f) => f.severity === 'block');
|
|
473
|
+
process.stdout.write(`\n ${bold('data fit')} ${dim(`checked: ${fit.checked.join(', ') || 'nothing'}`)}\n`);
|
|
474
|
+
if (!fit.findings.length && fit.checked.length) process.stdout.write(` ${green('fits')} ${dim('every stated field is within what the config accepts')}\n`);
|
|
475
|
+
for (const f of fit.findings) {
|
|
476
|
+
process.stdout.write(` ${f.severity === 'block' ? red('block') : yellow('warn ')} ${f.title}\n`);
|
|
477
|
+
process.stdout.write(` ${dim(f.detail)}\n`);
|
|
478
|
+
process.stdout.write(` ${dim(`fix: ${f.fix}`)}\n`);
|
|
479
|
+
}
|
|
480
|
+
for (const s of fit.skipped) process.stdout.write(` ${dim(`not checked: ${s.check} (${s.reason})`)}\n`);
|
|
481
|
+
for (const b of blocks) blockers.push({ title: b.title });
|
|
482
|
+
}
|
|
483
|
+
|
|
445
484
|
// ── What an inference job needs ────────────────────────────────────────────
|
|
446
485
|
//
|
|
447
486
|
// Weights at the stated precision plus the KV cache at the last generated
|
package/engine.bundle.mjs
CHANGED
|
@@ -43892,6 +43892,165 @@ function storedTwiceSentence(t) {
|
|
|
43892
43892
|
return `The checkpoint stores the tied embedding twice: +${n2} (\`${t.head}\` and \`${t.embedding}\`, ${t.shape.join(" x ")}). The model has one such matrix and the count above has it once; the delta compares that count plus the second copy against the file.`;
|
|
43893
43893
|
}
|
|
43894
43894
|
|
|
43895
|
+
// src/utils/configDataFit.ts
|
|
43896
|
+
var CLASSIFIER_ARCH = /For(Sequence|Token|Image|Audio|MultipleChoice)Classification$/;
|
|
43897
|
+
var posInt2 = (v) => {
|
|
43898
|
+
const n2 = typeof v === "string" && v.trim() !== "" ? Number(v) : v;
|
|
43899
|
+
return typeof n2 === "number" && Number.isInteger(n2) && n2 > 0 ? n2 : null;
|
|
43900
|
+
};
|
|
43901
|
+
function inputContractOf(model, config) {
|
|
43902
|
+
const comps = model.components ?? [];
|
|
43903
|
+
const first = (t) => comps.find((c) => c.type === t);
|
|
43904
|
+
const source = {};
|
|
43905
|
+
const emb = first("embedding");
|
|
43906
|
+
const vocabSize = posInt2(emb?.params?.vocabSize);
|
|
43907
|
+
if (vocabSize != null) source.vocabSize = "the token embedding";
|
|
43908
|
+
const patch = first("patchEmbed");
|
|
43909
|
+
const learned = first("learnedPositionalEmbedding");
|
|
43910
|
+
let maxPositions = null;
|
|
43911
|
+
let positionKind = null;
|
|
43912
|
+
if (!patch) {
|
|
43913
|
+
if (learned && posInt2(learned.params?.maxLen) != null) {
|
|
43914
|
+
maxPositions = posInt2(learned.params?.maxLen);
|
|
43915
|
+
positionKind = "learned";
|
|
43916
|
+
source.maxPositions = "the learned position table";
|
|
43917
|
+
} else if (posInt2(emb?.params?.maxSeqLen) != null) {
|
|
43918
|
+
maxPositions = posInt2(emb?.params?.maxSeqLen);
|
|
43919
|
+
positionKind = comps.some((c) => c.type === "rope") ? "rotary" : "declared";
|
|
43920
|
+
source.maxPositions = positionKind === "rotary" ? "max_position_embeddings (rotary)" : "the declared maximum length";
|
|
43921
|
+
}
|
|
43922
|
+
}
|
|
43923
|
+
let imageSize = null;
|
|
43924
|
+
let channels = null;
|
|
43925
|
+
if (patch) {
|
|
43926
|
+
imageSize = posInt2(patch.params?.imgSize);
|
|
43927
|
+
channels = posInt2(patch.params?.inChans);
|
|
43928
|
+
if (imageSize != null) source.imageSize = "the patch embedding";
|
|
43929
|
+
if (channels != null) source.channels = "the patch embedding";
|
|
43930
|
+
} else if (!emb) {
|
|
43931
|
+
const shape = first("input")?.params?.shape ?? null;
|
|
43932
|
+
if (Array.isArray(shape) && shape.length === 3 && shape.every((x) => posInt2(x) != null)) {
|
|
43933
|
+
channels = shape[0];
|
|
43934
|
+
source.channels = "the input shape";
|
|
43935
|
+
}
|
|
43936
|
+
}
|
|
43937
|
+
let numLabels = null;
|
|
43938
|
+
const archs = Array.isArray(config?.architectures) ? config.architectures.map(String) : [];
|
|
43939
|
+
if (archs.some((a) => CLASSIFIER_ARCH.test(a))) {
|
|
43940
|
+
const id2label = config?.id2label;
|
|
43941
|
+
numLabels = id2label && typeof id2label === "object" ? Object.keys(id2label).length || null : posInt2(config?.num_labels);
|
|
43942
|
+
if (numLabels != null) source.numLabels = `the ${archs.find((a) => CLASSIFIER_ARCH.test(a))} head`;
|
|
43943
|
+
} else if (!emb) {
|
|
43944
|
+
const tail = [...comps].reverse().find((c) => !["output", "softmax", "dropout", "flatten", "activation", "relu"].includes(c.type));
|
|
43945
|
+
if (tail?.type === "linear" && posInt2(tail.params?.outFeatures) != null) {
|
|
43946
|
+
numLabels = posInt2(tail.params?.outFeatures);
|
|
43947
|
+
source.numLabels = "the final Linear";
|
|
43948
|
+
}
|
|
43949
|
+
}
|
|
43950
|
+
return { vocabSize, maxPositions, positionKind, numLabels, imageSize, channels, source };
|
|
43951
|
+
}
|
|
43952
|
+
function checkDataFit(contract, data) {
|
|
43953
|
+
const checked = [];
|
|
43954
|
+
const skipped = [];
|
|
43955
|
+
const findings = [];
|
|
43956
|
+
const c = contract;
|
|
43957
|
+
if (data.seqLen != null) {
|
|
43958
|
+
if (c.maxPositions == null) {
|
|
43959
|
+
skipped.push({ check: "sequence length", reason: c.imageSize != null ? "a vision model: its position table is checked against the image size" : "the model declares no maximum length" });
|
|
43960
|
+
} else {
|
|
43961
|
+
checked.push("sequence length");
|
|
43962
|
+
if (data.seqLen > c.maxPositions) {
|
|
43963
|
+
if (c.positionKind === "learned") {
|
|
43964
|
+
findings.push({
|
|
43965
|
+
rule: "seq-over-positions",
|
|
43966
|
+
severity: "block",
|
|
43967
|
+
title: `Sequences of ${data.seqLen} tokens are longer than the ${c.maxPositions}-row position table`,
|
|
43968
|
+
detail: `Position ids past ${c.maxPositions - 1} index outside a learned table, so the first batch that long raises an IndexError (a device-side assert on GPU) after the reservation is taken.`,
|
|
43969
|
+
fix: `Truncate or chunk to ${c.maxPositions} tokens, or pick a model whose context covers ${data.seqLen}.`
|
|
43970
|
+
});
|
|
43971
|
+
} else {
|
|
43972
|
+
findings.push({
|
|
43973
|
+
rule: "seq-over-context",
|
|
43974
|
+
severity: "warn",
|
|
43975
|
+
title: `Sequences of ${data.seqLen} tokens are past the model's ${c.maxPositions}-token context`,
|
|
43976
|
+
detail: c.positionKind === "rotary" ? "Rotary positions are computed, so the job runs; quality past the trained length is not something the config can vouch for, and attention memory grows with the length." : "The config declares this as the longest input; past it, behaviour depends on the implementation.",
|
|
43977
|
+
fix: c.positionKind === "rotary" ? "Set rope_scaling for the longer context, or truncate to the trained length." : `Truncate to ${c.maxPositions} tokens.`
|
|
43978
|
+
});
|
|
43979
|
+
}
|
|
43980
|
+
}
|
|
43981
|
+
}
|
|
43982
|
+
}
|
|
43983
|
+
if (data.tokenizerSize != null) {
|
|
43984
|
+
if (c.vocabSize == null) skipped.push({ check: "tokenizer size", reason: "the model has no token embedding" });
|
|
43985
|
+
else {
|
|
43986
|
+
checked.push("tokenizer size");
|
|
43987
|
+
if (data.tokenizerSize > c.vocabSize) {
|
|
43988
|
+
findings.push({
|
|
43989
|
+
rule: "tokenizer-over-vocab",
|
|
43990
|
+
severity: "block",
|
|
43991
|
+
title: `The tokenizer has ${data.tokenizerSize} ids and the embedding has ${c.vocabSize} rows`,
|
|
43992
|
+
detail: `Any of the ${data.tokenizerSize - c.vocabSize} ids past ${c.vocabSize - 1} indexes outside the embedding. The usual cause is special tokens added to the tokenizer without resizing the model.`,
|
|
43993
|
+
fix: "Call model.resize_token_embeddings(len(tokenizer)) before training, and budget for the new rows."
|
|
43994
|
+
});
|
|
43995
|
+
}
|
|
43996
|
+
}
|
|
43997
|
+
}
|
|
43998
|
+
if (data.numClasses != null) {
|
|
43999
|
+
if (c.numLabels == null) skipped.push({ check: "label count", reason: "no classification head is visible in the config or the graph" });
|
|
44000
|
+
else {
|
|
44001
|
+
checked.push("label count");
|
|
44002
|
+
if (data.numClasses > c.numLabels) {
|
|
44003
|
+
findings.push({
|
|
44004
|
+
rule: "labels-over-head",
|
|
44005
|
+
severity: "block",
|
|
44006
|
+
title: `The data has ${data.numClasses} labels and ${c.source.numLabels ?? "the head"} emits ${c.numLabels}`,
|
|
44007
|
+
detail: `A label id of ${c.numLabels} or more is out of range for the loss, which on GPU is a device-side assert with no line number.`,
|
|
44008
|
+
fix: `Load with num_labels=${data.numClasses} (and ignore_mismatched_sizes=True for a pretrained head).`
|
|
44009
|
+
});
|
|
44010
|
+
} else if (data.numClasses < c.numLabels) {
|
|
44011
|
+
findings.push({
|
|
44012
|
+
rule: "labels-under-head",
|
|
44013
|
+
severity: "warn",
|
|
44014
|
+
title: `The data has ${data.numClasses} labels and the head emits ${c.numLabels}`,
|
|
44015
|
+
detail: "It runs; the extra outputs are never a target, which is usually a head left over from a different task.",
|
|
44016
|
+
fix: `Load with num_labels=${data.numClasses} if the head is meant for this data.`
|
|
44017
|
+
});
|
|
44018
|
+
}
|
|
44019
|
+
}
|
|
44020
|
+
}
|
|
44021
|
+
if (data.imageSize != null) {
|
|
44022
|
+
if (c.imageSize == null) skipped.push({ check: "image size", reason: "the model has no fixed image size (no patch position table)" });
|
|
44023
|
+
else {
|
|
44024
|
+
checked.push("image size");
|
|
44025
|
+
if (data.imageSize !== c.imageSize) {
|
|
44026
|
+
findings.push({
|
|
44027
|
+
rule: "image-size-mismatch",
|
|
44028
|
+
severity: "block",
|
|
44029
|
+
title: `Images are ${data.imageSize}px and the position table was built for ${c.imageSize}px`,
|
|
44030
|
+
detail: "The patch count no longer matches the learned position table, so the forward pass raises on the first batch.",
|
|
44031
|
+
fix: `Resize to ${c.imageSize}px, or pass interpolate_pos_encoding=True where the model supports it.`
|
|
44032
|
+
});
|
|
44033
|
+
}
|
|
44034
|
+
}
|
|
44035
|
+
}
|
|
44036
|
+
if (data.channels != null) {
|
|
44037
|
+
if (c.channels == null) skipped.push({ check: "channels", reason: "the model states no input channels" });
|
|
44038
|
+
else {
|
|
44039
|
+
checked.push("channels");
|
|
44040
|
+
if (data.channels !== c.channels) {
|
|
44041
|
+
findings.push({
|
|
44042
|
+
rule: "channels-mismatch",
|
|
44043
|
+
severity: "block",
|
|
44044
|
+
title: `Images have ${data.channels} channel${data.channels === 1 ? "" : "s"} and the first layer expects ${c.channels}`,
|
|
44045
|
+
detail: "The first convolution or patch projection has a fixed input-channel count; a mismatch raises on the first batch.",
|
|
44046
|
+
fix: data.channels === 1 && c.channels === 3 ? "Repeat the grey channel to three in the transform." : `Convert the images to ${c.channels} channels.`
|
|
44047
|
+
});
|
|
44048
|
+
}
|
|
44049
|
+
}
|
|
44050
|
+
}
|
|
44051
|
+
return { checked, skipped, findings, contract };
|
|
44052
|
+
}
|
|
44053
|
+
|
|
43895
44054
|
// src/utils/trainCost.ts
|
|
43896
44055
|
init_paramEstimator();
|
|
43897
44056
|
var GPU_CATALOG = {
|
|
@@ -44614,6 +44773,9 @@ function graphOfFacts(facts) {
|
|
|
44614
44773
|
updatedAt: ""
|
|
44615
44774
|
};
|
|
44616
44775
|
}
|
|
44776
|
+
function dataFitHFModel(facts, data) {
|
|
44777
|
+
return checkDataFit(inputContractOf(graphOfFacts(facts), facts.snap.config), data);
|
|
44778
|
+
}
|
|
44617
44779
|
function inferHFModel(facts, opts) {
|
|
44618
44780
|
const model = graphOfFacts(facts);
|
|
44619
44781
|
const prompt = opts.promptTokens ?? withSeqLen(model, null).seqLen ?? DEFAULT_SEQ_LEN;
|
|
@@ -44700,6 +44862,7 @@ export {
|
|
|
44700
44862
|
PRICE_SOURCE,
|
|
44701
44863
|
applyPrices,
|
|
44702
44864
|
attentionPhrase,
|
|
44865
|
+
dataFitHFModel,
|
|
44703
44866
|
graphOfFacts,
|
|
44704
44867
|
headline,
|
|
44705
44868
|
inferHFModel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neurarch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Know what a GPU job needs before it takes a reservation: training peak memory, the smallest card that holds it and what a run costs, read from a Hugging Face config or a traced graph. `neurarch rightsize jobs.csv` puts each past job's request next to what it needed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|