@tensor-cad/engine 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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @tensor-cad/engine
2
+
3
+ The TensorCAD analysis engine: one WebAssembly module with a JSON interface, and
4
+ the TypeScript that loads it.
5
+
6
+ Give it a design and it tells you what the design costs — parameters, FLOPs, KV
7
+ cache, memory at an operating point, throughput, dollars — checks it against
8
+ eighteen design rules, writes the PyTorch, and searches for a way to train it on
9
+ a cluster. The same module answers in a browser, in a Node process and inside
10
+ the desktop shell, so an answer cannot depend on where it was asked.
11
+
12
+ ## Using it
13
+
14
+ In a browser or anything with `fetch`:
15
+
16
+ ```ts
17
+ import { createEngine } from "@tensor-cad/engine";
18
+ import "@tensor-cad/engine/wasm_exec";
19
+
20
+ const engine = await createEngine({ wasm: "/tensorcad.wasm" });
21
+ const doc = engine.preset("llama-3-8b");
22
+ console.log(engine.analyze(doc).params.total); // 8030261248
23
+ ```
24
+
25
+ From a Node process, where the module is read rather than fetched and held as a
26
+ singleton:
27
+
28
+ ```ts
29
+ import { analyze, getPreset, loadEngine } from "@tensor-cad/engine/node";
30
+
31
+ await loadEngine();
32
+ const report = analyze(getPreset("mixtral-8x7b"), { T: 4096 });
33
+ console.log(report.params.total, report.params.active);
34
+ ```
35
+
36
+ `wasm_exec.js` is Go's own loader, vendored from the toolchain that built the
37
+ module. The two travel together or neither works.
38
+
39
+ ## What it answers
40
+
41
+ | call | what it gives |
42
+ | --- | --- |
43
+ | `analyze(doc, options)` | every number at once |
44
+ | `validate(doc, options)` | the design rules, and the analysis they ran against |
45
+ | `derive(doc, options)` | the findings and every shape from one walk of the graph |
46
+ | `infer(doc, mode)` | the shapes alone, for the wire the pointer is over |
47
+ | `explain(doc, path)` | one block: its parameters as written and as evaluated, its shapes, its share |
48
+ | `generateTorch(doc, options)` | a `model.py` and the design that produced it |
49
+ | `scale(doc, {targetParams})` | the design shrunk to a budget, proportions kept |
50
+ | `mup(doc, options)` | the same design at several widths, and what to scale by at each |
51
+ | `plan(doc, options, {gpus})` | every way to split the training across a cluster, and which fit |
52
+ | `importHuggingFace(text)` | a `config.json` read into a design |
53
+ | `preset(name)`, `presets()` | the twenty designs it ships with |
54
+ | `catalog()`, `rules()`, `hardware()` | what it knows about blocks, rules and devices |
55
+
56
+ Everything crosses as JSON text. A design *is* JSON and so is every report, so
57
+ serializing costs a copy and buys a boundary with nothing clever in it. A call
58
+ that cannot answer throws an `EngineError` naming what was wrong rather than
59
+ returning a default.
60
+
61
+ ## What it is held to
62
+
63
+ `packages/core-go/testdata` in the repository: the symbol table and inferred
64
+ shapes for twenty published architectures, the full analysis and the design-rule
65
+ check at three operating points each, and every byte of three generated
66
+ `model.py` variants. Seventeen of the twenty reproduce their published parameter
67
+ count exactly and the other three are within a stated tolerance, and every one
68
+ of them has been instantiated in PyTorch to confirm the count is real — up to
69
+ DeepSeek-V3 at 671,026,419,200.
70
+
71
+ ## Building it
72
+
73
+ ```bash
74
+ bun run build:wasm
75
+ ```
76
+
77
+ from the repository root. The output lands in `wasm/` and is not committed, so a
78
+ fresh clone builds it before anything works.
79
+
80
+ MIT. See LICENSE.md.
package/catalog.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Asking the catalog about a block.
3
+ *
4
+ * The engine ships the blocks it knows; a design can define more. Every client
5
+ * needs the same two things from that — one block by name, and all of them
6
+ * grouped — and needs them to agree with what the analysis counted. So the
7
+ * folding happens here rather than three times over.
8
+ */
9
+ import type { CatalogEntry, Doc, ParamSpec } from "./types.js";
10
+ /** The blocks an engine knows, and the ones a document adds to them. */
11
+ export declare class Catalog {
12
+ private readonly builtIn;
13
+ constructor(entries: readonly CatalogEntry[]);
14
+ /** Every built-in block, in the order the engine declares them. */
15
+ get builtInEntries(): CatalogEntry[];
16
+ /** True when the engine itself knows this type. */
17
+ isBuiltIn(type: string): boolean;
18
+ /**
19
+ * One block's definition.
20
+ *
21
+ * A design never shadows a built-in, which is what the engine's own resolver
22
+ * does; agreeing here keeps a palette showing what the analysis counted.
23
+ */
24
+ get(type: string, doc?: Doc): CatalogEntry | undefined;
25
+ /** Every block a document can use, built-ins first. */
26
+ entries(doc?: Doc): CatalogEntry[];
27
+ byCategory(doc?: Doc): Record<string, CatalogEntry[]>;
28
+ /** True when this type came from the design rather than from the engine. */
29
+ isUserBlock(doc: Doc | undefined, type: string): boolean;
30
+ /** A block's parameter spec, by name. */
31
+ param(type: string, name: string, doc?: Doc): ParamSpec | undefined;
32
+ }
33
+ export declare function isPrimitive(def: CatalogEntry | undefined): boolean;
34
+ export declare function isComposite(def: CatalogEntry | undefined): boolean;
35
+ export declare function isContainer(def: CatalogEntry | undefined): boolean;
package/format.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Display helpers.
3
+ *
4
+ * TypeScript rather than a call into the engine: these run on every repaint,
5
+ * beside a number the panel already has, and a boundary crossing to turn 4096
6
+ * into "4.1K" would be the slowest thing in the frame. The Go engine has the
7
+ * same set for the command line, and a test pins the two against each other.
8
+ */
9
+ /** A parameter count as a person reads it: 8.03B, 124.4M, 12.9K. */
10
+ export declare function formatCount(n: number): string;
11
+ /** A FLOP count: 312.00 TFLOP, 1.20 PFLOP. */
12
+ export declare function formatFlops(n: number): string;
13
+ /** A byte count, in the binary units the hardware is sold in. */
14
+ export declare function formatBytes(n: number): string;
15
+ /** A duration: 45.0 min, 12.5 h, 3.2 days. */
16
+ export declare function formatHours(h: number): string;
17
+ /** A price: $1.20M, $4.5k, $12.34. */
18
+ export declare function formatDollars(d: number): string;
package/index.d.ts ADDED
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The engine, loaded into whatever is running the editor.
3
+ *
4
+ * TensorCAD's analysis is Go, compiled to WebAssembly. That is not an
5
+ * optimisation: it is what keeps there from being two of it. The desktop shell,
6
+ * a browser tab and the command line all ask the same code the same questions,
7
+ * so an answer cannot depend on where it was asked.
8
+ *
9
+ * Everything crosses the boundary as JSON text. A design *is* JSON and so is
10
+ * every report, so serialising costs a copy and buys a boundary with nothing
11
+ * clever in it — no object graph to keep in step, no handles to leak.
12
+ */
13
+ import { Catalog } from "./catalog.js";
14
+ import type { AnalysisOptions, AnalysisResult, CatalogEntry, Derived, Doc, Explanation, GeneratedCode, HardwareProfile, ImportResult, Inference, MupLadder, MupOptions, ScaleOptions, ScaleResult, ClusterRequest, ClusterResult, DesignDiff, TorchOptions, UserBlockDef, ValidationReport } from "./types.js";
15
+ export * from "./types.js";
16
+ export * from "./format.js";
17
+ export * from "./ir.js";
18
+ export * from "./catalog.js";
19
+ /** What the engine says about itself. */
20
+ export interface EngineVersion {
21
+ engine: string;
22
+ target: string;
23
+ }
24
+ /**
25
+ * The engine's whole surface.
26
+ *
27
+ * Synchronous, because the calls run on the thread that asked and the largest
28
+ * design in the library analyses in a couple of milliseconds. Loading is the
29
+ * asynchronous part, and it happens once.
30
+ */
31
+ export interface Engine {
32
+ version(): EngineVersion;
33
+ analyze(doc: Doc, options?: AnalysisOptions): AnalysisResult;
34
+ /** Runs the design rules and returns the analysis they were drawn from. */
35
+ validate(doc: Doc, options?: AnalysisOptions): ValidationReport;
36
+ /**
37
+ * The editor's entry point: the findings and the shapes together.
38
+ *
39
+ * One call rather than two, because both come from the same walk of the same
40
+ * graph and asking separately would walk it twice on every keystroke.
41
+ */
42
+ derive(doc: Doc, options?: AnalysisOptions): Derived;
43
+ /**
44
+ * Shape inference alone, which is what answers "would this wire type-check"
45
+ * for every handle the pointer passes over.
46
+ */
47
+ infer(doc: Doc, mode?: "flat" | "expanded"): Inference;
48
+ explain(doc: Doc, path: string, options?: AnalysisOptions): Explanation;
49
+ /** Every block, largest contribution first. */
50
+ explainAll(doc: Doc, options?: AnalysisOptions): Explanation[];
51
+ generateTorch(doc: Doc, options?: TorchOptions): GeneratedCode;
52
+ scale(doc: Doc, options: ScaleOptions): ScaleResult;
53
+ /**
54
+ * The same design at several widths, with what to multiply the
55
+ * initialization and the learning rate by at each one.
56
+ *
57
+ * It is what makes a sweep affordable: tune at a width that fits on one
58
+ * device, and carry the answer up the ladder. What it does not do is decide
59
+ * the base learning rate, which is what the sweep is for.
60
+ */
61
+ mup(doc: Doc, options?: MupOptions): MupLadder;
62
+ /**
63
+ * Every way of splitting the work across a cluster, and which of them fit.
64
+ *
65
+ * Memory is the claim, and it is arithmetic. Which plan is fastest is not:
66
+ * that turns on the interconnect and the kernels, so each plan carries a
67
+ * note about what it costs to run rather than a number pretending to.
68
+ */
69
+ plan(doc: Doc, options: AnalysisOptions, cluster: ClusterRequest): ClusterResult;
70
+ /** What changed between two designs, structurally and numerically. */
71
+ diff(a: Doc, b: Doc, options?: AnalysisOptions): DesignDiff;
72
+ /** The design library this engine ships with. */
73
+ presets(): string[];
74
+ preset(name: string): Doc;
75
+ /** Reads a Hugging Face `config.json`. */
76
+ importHuggingFace(configText: string, name?: string): ImportResult;
77
+ /** Every block the engine knows, for the palette. */
78
+ catalog(): CatalogEntry[];
79
+ /**
80
+ * The same blocks, ready to be asked about one at a time and to have a
81
+ * design's own definitions folded in. Fetched once when the engine loads.
82
+ */
83
+ readonly blocks: Catalog;
84
+ /** The design rules, for the panel that lists what is being checked. */
85
+ rules(): RuleInfo[];
86
+ /**
87
+ * What is wrong with a block a design defines for itself, so the block editor
88
+ * can say so while it is being written rather than after it is saved.
89
+ */
90
+ checkUserBlock(def: UserBlockDef, name: string): string[];
91
+ hardware(): HardwareProfile[];
92
+ }
93
+ /** The shape the WebAssembly module publishes on `globalThis`. */
94
+ interface Exports {
95
+ version(): string;
96
+ analyze(doc: string, options: string): string;
97
+ validate(doc: string, options: string): string;
98
+ derive(doc: string, options: string): string;
99
+ infer(doc: string, mode: string): string;
100
+ explain(doc: string, path: string, options: string): string;
101
+ explainAll(doc: string, options: string): string;
102
+ generateTorch(doc: string, options: string): string;
103
+ scale(doc: string, options: string): string;
104
+ mup(doc: string, options: string): string;
105
+ plan(doc: string, options: string, cluster: string): string;
106
+ diff(a: string, b: string, options: string): string;
107
+ presets(): string;
108
+ preset(name: string): string;
109
+ importHf(configText: string, name: string): string;
110
+ catalog(): string;
111
+ rules(): string;
112
+ checkUserBlock(def: string, name: string): string;
113
+ hardware(): string;
114
+ }
115
+ /** One design rule, as the rules panel lists it. */
116
+ export interface RuleInfo {
117
+ id: string;
118
+ title: string;
119
+ /** One line on what the rule protects against. */
120
+ description: string;
121
+ }
122
+ /**
123
+ * Raised when the engine refuses a call.
124
+ *
125
+ * A distinct type because a refusal is not a bug: an unknown hardware profile
126
+ * or a design with no version is something a person can fix, and the editor
127
+ * shows it rather than logging it.
128
+ */
129
+ export declare class EngineError extends Error {
130
+ constructor(message: string);
131
+ }
132
+ /** Where to find the compiled engine, and what to run it with. */
133
+ export interface LoadOptions {
134
+ /**
135
+ * The `.wasm` file. A URL is fetched; bytes are used as they are. Omitted,
136
+ * it is resolved next to this module, which is what a bundler produces.
137
+ */
138
+ wasm?: string | URL | BufferSource;
139
+ }
140
+ declare global {
141
+ var Go: (new () => {
142
+ importObject: WebAssembly.Imports;
143
+ run(instance: WebAssembly.Instance): Promise<void>;
144
+ }) | undefined;
145
+ var __tensorcad: Exports | undefined;
146
+ }
147
+ /**
148
+ * Loads the engine.
149
+ *
150
+ * `globalThis.Go` has to exist first: it comes from the Go toolchain's
151
+ * `wasm_exec.js`, which is vendored beside this file. A bundler wants
152
+ * `import "@tensor-cad/engine/wasm_exec"` before the first call.
153
+ */
154
+ export declare function createEngine(options?: LoadOptions): Promise<Engine>;
package/index.js ADDED
@@ -0,0 +1,249 @@
1
+ // packages/engine/src/catalog.ts
2
+ function fromUserBlock(type, def) {
3
+ const params = { ...def.params ?? {} };
4
+ const shapes = (side) => {
5
+ const out = {};
6
+ for (const [name, port] of Object.entries(side ?? {})) {
7
+ out[name] = typeof port === "string" ? port : port.shape;
8
+ }
9
+ return out;
10
+ };
11
+ return {
12
+ type,
13
+ kind: "composite",
14
+ category: def.category ?? "custom",
15
+ docs: {
16
+ summary: def.docs?.summary ?? "A block this design defines for itself.",
17
+ formula: def.docs?.formula,
18
+ refs: def.docs?.refs
19
+ },
20
+ params,
21
+ paramOrder: Object.keys(params),
22
+ ports: { in: shapes(def.ports?.in), out: shapes(def.ports?.out) }
23
+ };
24
+ }
25
+
26
+ class Catalog {
27
+ builtIn;
28
+ constructor(entries) {
29
+ this.builtIn = new Map(entries.map((entry) => [entry.type, entry]));
30
+ }
31
+ get builtInEntries() {
32
+ return [...this.builtIn.values()];
33
+ }
34
+ isBuiltIn(type) {
35
+ return this.builtIn.has(type);
36
+ }
37
+ get(type, doc) {
38
+ if (this.builtIn.has(type))
39
+ return this.builtIn.get(type);
40
+ const own = doc?.defs?.[type];
41
+ return own ? fromUserBlock(type, own) : undefined;
42
+ }
43
+ entries(doc) {
44
+ const out = this.builtInEntries;
45
+ for (const [type, def] of Object.entries(doc?.defs ?? {})) {
46
+ if (!this.builtIn.has(type))
47
+ out.push(fromUserBlock(type, def));
48
+ }
49
+ return out;
50
+ }
51
+ byCategory(doc) {
52
+ const out = {};
53
+ for (const entry of this.entries(doc)) {
54
+ (out[entry.category] ??= []).push(entry);
55
+ }
56
+ return out;
57
+ }
58
+ isUserBlock(doc, type) {
59
+ return Boolean(doc?.defs && type in doc.defs && !this.builtIn.has(type));
60
+ }
61
+ param(type, name, doc) {
62
+ return this.get(type, doc)?.params[name];
63
+ }
64
+ }
65
+ function isPrimitive(def) {
66
+ return def?.kind === "primitive";
67
+ }
68
+ function isComposite(def) {
69
+ return def?.kind === "composite";
70
+ }
71
+ function isContainer(def) {
72
+ return def?.kind === "container";
73
+ }
74
+
75
+ // packages/engine/src/types.ts
76
+ var DOC_VERSION = 1;
77
+ var RUNTIME_SYMBOLS = ["B", "T"];
78
+ var BOUNDARY_IN = "_in";
79
+ var BOUNDARY_OUT = "_out";
80
+ var DEFAULT_PARALLEL = {
81
+ dp: 1,
82
+ tp: 1,
83
+ pp: 1,
84
+ ep: 1,
85
+ zero: 0,
86
+ sequenceParallel: false
87
+ };
88
+ var DEFAULT_HARDWARE = "h100-sxm";
89
+ var DTYPE_BYTES = { fp32: 4, bf16: 2, fp16: 2, fp8: 1 };
90
+ // packages/engine/src/format.ts
91
+ function formatCount(n) {
92
+ const abs = Math.abs(n);
93
+ if (abs >= 1000000000000)
94
+ return `${(n / 1000000000000).toFixed(2)}T`;
95
+ if (abs >= 1e9)
96
+ return `${(n / 1e9).toFixed(2)}B`;
97
+ if (abs >= 1e6)
98
+ return `${(n / 1e6).toFixed(1)}M`;
99
+ if (abs >= 1000)
100
+ return `${(n / 1000).toFixed(1)}K`;
101
+ return String(n);
102
+ }
103
+ function formatFlops(n) {
104
+ const units = [
105
+ [1000000000000000000, "EFLOP"],
106
+ [1000000000000000, "PFLOP"],
107
+ [1000000000000, "TFLOP"],
108
+ [1e9, "GFLOP"],
109
+ [1e6, "MFLOP"],
110
+ [1000, "kFLOP"]
111
+ ];
112
+ for (const [scale, unit] of units) {
113
+ if (Math.abs(n) >= scale)
114
+ return `${(n / scale).toFixed(2)} ${unit}`;
115
+ }
116
+ return `${n.toFixed(0)} FLOP`;
117
+ }
118
+ function formatBytes(n) {
119
+ const units = [
120
+ [1024 ** 5, "PiB"],
121
+ [1024 ** 4, "TiB"],
122
+ [1024 ** 3, "GiB"],
123
+ [1024 ** 2, "MiB"],
124
+ [1024, "KiB"]
125
+ ];
126
+ for (const [scale, unit] of units) {
127
+ if (Math.abs(n) >= scale)
128
+ return `${(n / scale).toFixed(2)} ${unit}`;
129
+ }
130
+ return `${Math.round(n)} B`;
131
+ }
132
+ function formatHours(h) {
133
+ if (h < 1)
134
+ return `${(h * 60).toFixed(1)} min`;
135
+ if (h < 48)
136
+ return `${h.toFixed(1)} h`;
137
+ return `${(h / 24).toFixed(1)} days`;
138
+ }
139
+ function formatDollars(d) {
140
+ if (d >= 1e6)
141
+ return `$${(d / 1e6).toFixed(2)}M`;
142
+ if (d >= 1000)
143
+ return `$${(d / 1000).toFixed(1)}k`;
144
+ return `$${d.toFixed(2)}`;
145
+ }
146
+ // packages/engine/src/ir.ts
147
+ function splitEndpoint(endpoint) {
148
+ const i = endpoint.lastIndexOf(":");
149
+ if (i < 0)
150
+ throw new Error(`Malformed endpoint "${endpoint}", expected "node:port"`);
151
+ return { node: endpoint.slice(0, i), port: endpoint.slice(i + 1) };
152
+ }
153
+ function joinPath(prefix, id) {
154
+ return prefix ? `${prefix}/${id}` : id;
155
+ }
156
+
157
+ // packages/engine/src/index.ts
158
+ class EngineError extends Error {
159
+ constructor(message) {
160
+ super(message);
161
+ this.name = "EngineError";
162
+ }
163
+ }
164
+ function unwrap(text) {
165
+ let parsed;
166
+ try {
167
+ parsed = JSON.parse(text);
168
+ } catch {
169
+ throw new EngineError(`The engine returned something that is not JSON: ${text.slice(0, 200)}`);
170
+ }
171
+ if (parsed && typeof parsed === "object" && "error" in parsed && typeof parsed.error === "string") {
172
+ throw new EngineError(parsed.error);
173
+ }
174
+ return parsed;
175
+ }
176
+ function point(options) {
177
+ return options ? JSON.stringify(options) : "";
178
+ }
179
+ async function bytesOf(wasm) {
180
+ if (wasm && typeof wasm !== "string" && !(wasm instanceof URL))
181
+ return wasm;
182
+ const url = wasm ?? new URL("../wasm/tensorcad.wasm", import.meta.url);
183
+ const response = await fetch(url);
184
+ if (!response.ok) {
185
+ throw new EngineError(`Could not load the engine from ${String(url)}: ${response.status}`);
186
+ }
187
+ return await response.arrayBuffer();
188
+ }
189
+ async function createEngine(options = {}) {
190
+ if (typeof globalThis.Go !== "function") {
191
+ throw new EngineError('The Go WebAssembly runtime is missing. Import "@tensor-cad/engine/wasm_exec" before creating the engine.');
192
+ }
193
+ const go = new globalThis.Go;
194
+ const compiled = await WebAssembly.instantiate(await bytesOf(options.wasm), go.importObject);
195
+ const instance = "instance" in compiled ? compiled.instance : compiled;
196
+ go.run(instance);
197
+ const api = globalThis.__tensorcad;
198
+ if (!api) {
199
+ throw new EngineError("The engine started but published no API.");
200
+ }
201
+ return wrap(api);
202
+ }
203
+ function wrap(api) {
204
+ const entries = JSON.parse(api.catalog());
205
+ return {
206
+ blocks: new Catalog(entries),
207
+ version: () => unwrap(api.version()),
208
+ analyze: (doc, options) => unwrap(api.analyze(JSON.stringify(doc), point(options))),
209
+ validate: (doc, options) => unwrap(api.validate(JSON.stringify(doc), point(options))),
210
+ derive: (doc, options) => unwrap(api.derive(JSON.stringify(doc), point(options))),
211
+ infer: (doc, mode) => unwrap(api.infer(JSON.stringify(doc), mode ?? "flat")),
212
+ explain: (doc, path, options) => unwrap(api.explain(JSON.stringify(doc), path, point(options))),
213
+ explainAll: (doc, options) => unwrap(api.explainAll(JSON.stringify(doc), point(options))),
214
+ generateTorch: (doc, options) => unwrap(api.generateTorch(JSON.stringify(doc), options ? JSON.stringify(options) : "")),
215
+ scale: (doc, options) => unwrap(api.scale(JSON.stringify(doc), JSON.stringify(options))),
216
+ mup: (doc, options) => unwrap(api.mup(JSON.stringify(doc), options ? JSON.stringify(options) : "")),
217
+ plan: (doc, options, cluster) => unwrap(api.plan(JSON.stringify(doc), JSON.stringify(options ?? {}), JSON.stringify(cluster))),
218
+ diff: (a, b, options) => unwrap(api.diff(JSON.stringify(a), JSON.stringify(b), JSON.stringify(options ?? {}))),
219
+ presets: () => unwrap(api.presets()),
220
+ preset: (name) => unwrap(api.preset(name)),
221
+ importHuggingFace: (configText, name) => unwrap(api.importHf(configText, name ?? "")),
222
+ catalog: () => unwrap(api.catalog()),
223
+ rules: () => unwrap(api.rules()),
224
+ checkUserBlock: (def, name) => unwrap(api.checkUserBlock(JSON.stringify(def), name)),
225
+ hardware: () => unwrap(api.hardware())
226
+ };
227
+ }
228
+ export {
229
+ BOUNDARY_IN,
230
+ BOUNDARY_OUT,
231
+ Catalog,
232
+ DEFAULT_HARDWARE,
233
+ DEFAULT_PARALLEL,
234
+ DOC_VERSION,
235
+ DTYPE_BYTES,
236
+ EngineError,
237
+ RUNTIME_SYMBOLS,
238
+ createEngine,
239
+ formatBytes,
240
+ formatCount,
241
+ formatDollars,
242
+ formatFlops,
243
+ formatHours,
244
+ isComposite,
245
+ isContainer,
246
+ isPrimitive,
247
+ joinPath,
248
+ splitEndpoint
249
+ };
package/ir.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Reading the IR's own spellings.
3
+ *
4
+ * An endpoint is `"nodeId:portName"` and a path is `"a/b/c"`. These are the
5
+ * two conventions the whole document rests on, and they are string handling
6
+ * rather than analysis — the editor splits a hundred of them per repaint, and
7
+ * crossing into the engine for that would be absurd. The Go engine has the same
8
+ * two functions for the same reason.
9
+ */
10
+ /** One end of an edge. */
11
+ export interface Endpoint {
12
+ node: string;
13
+ port: string;
14
+ }
15
+ /**
16
+ * Splits `"node:port"`.
17
+ *
18
+ * At the last colon, because a node id inside a container carries slashes and
19
+ * may itself have come from a path.
20
+ */
21
+ export declare function splitEndpoint(endpoint: string): Endpoint;
22
+ /** Builds the dotted path identifying a node inside nested graphs. */
23
+ export declare function joinPath(prefix: string, id: string): string;
package/node.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Loading the engine outside a browser.
3
+ *
4
+ * The command line and the MCP server are Node processes with a filesystem, so
5
+ * they read the module rather than fetching it, and they hold it in a module
6
+ * singleton the way the editor does: one engine per process, loaded before the
7
+ * first command runs.
8
+ */
9
+ import { type Engine } from "./index.js";
10
+ import "../vendor/wasm_exec.js";
11
+ /**
12
+ * Loads the engine, once per process.
13
+ *
14
+ * A missing module is the one failure worth naming precisely: it means the
15
+ * repository was cloned but not built, and the fix is one command.
16
+ */
17
+ export declare function loadEngine(): Promise<Engine>;
18
+ /** The engine. Throws if something asked before the load finished. */
19
+ export declare function engine(): Engine;
20
+ export * from "./index.js";
21
+ import type { AnalysisOptions, AnalysisResult, CatalogEntry, ClusterRequest, ClusterResult, Derived, DesignDiff, Doc, Dtype, Explanation, GeneratedCode, HardwareProfile, ImportResult, Inference, ParamsResult, MupLadder, MupOptions, ScaleOptions, ScaleResult, SymbolTable, TorchOptions, ValidationReport } from "./index.js";
22
+ /** Filled in by the load, so a module can hold a reference at import time. */
23
+ export declare const PRESET_NAMES: string[];
24
+ export declare const HARDWARE: HardwareProfile[];
25
+ export declare const CATALOG: Record<string, CatalogEntry>;
26
+ export declare function analyze(doc: Doc, options?: AnalysisOptions): AnalysisResult;
27
+ export declare function validate(doc: Doc, options?: AnalysisOptions): ValidationReport;
28
+ /** The findings and every shape, from one walk of the graph. */
29
+ export declare function derive(doc: Doc, options?: AnalysisOptions): Derived;
30
+ export declare function inferShapes(doc: Doc, mode?: "flat" | "expanded"): Inference;
31
+ export declare function explain(doc: Doc, path: string, options?: AnalysisOptions): Explanation;
32
+ export declare function explainAll(doc: Doc, options?: AnalysisOptions): Explanation[];
33
+ export declare function generateTorch(doc: Doc, options?: TorchOptions): GeneratedCode;
34
+ export declare function scaleDesign(doc: Doc, options: ScaleOptions): ScaleResult;
35
+ export declare function mupLadder(doc: Doc, options?: MupOptions): MupLadder;
36
+ export declare function planCluster(doc: Doc, options: AnalysisOptions, cluster: ClusterRequest): ClusterResult;
37
+ export declare function diffDesigns(a: Doc, b: Doc, options?: AnalysisOptions): DesignDiff;
38
+ export declare function importHfConfig(configText: string, name?: string): ImportResult;
39
+ export declare function getPreset(name: string): Doc;
40
+ /** The parameter counts alone. */
41
+ export declare function countParams(doc: Doc): ParamsResult;
42
+ /** The symbol table alone. */
43
+ export declare function resolveSymbols(doc: Doc): SymbolTable;
44
+ /** One block's definition, built-in or the design's own. */
45
+ export declare function getBlock(type: string, doc?: Doc): CatalogEntry | undefined;
46
+ export declare function catalogByCategory(doc?: Doc): Record<string, CatalogEntry[]>;
47
+ export declare function hardwareById(id: string): HardwareProfile | undefined;
48
+ /**
49
+ * A device's throughput for a dtype, falling back to BF16 where FP8 is
50
+ * unsupported. Arithmetic on a profile the caller already has, so it stays on
51
+ * this side of the boundary.
52
+ */
53
+ export declare function peakFlops(hw: HardwareProfile, dtype: Dtype): number;