deepbom 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 +15 -0
- package/README.md +111 -0
- package/bin/deepbom.js +188 -0
- package/package.json +33 -0
- package/vendor/tflite_wasm_audit.js +1222 -0
- package/vendor/tflite_wasm_audit_bg.js +333 -0
- package/vendor/tflite_wasm_audit_bg.wasm +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (C) 2026 Jun-Hwan Kwon. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This repository and its generated software artifacts are currently provided as
|
|
4
|
+
private research software. No license is granted to copy, modify, distribute,
|
|
5
|
+
sublicense, reverse engineer, or create derivative works from the source code,
|
|
6
|
+
WebAssembly modules, generated JavaScript bindings, or packaged executables,
|
|
7
|
+
except where a separate file or component carries an explicit license.
|
|
8
|
+
|
|
9
|
+
Access to the hosted service does not grant a software or implementation
|
|
10
|
+
license.
|
|
11
|
+
|
|
12
|
+
Future public releases may license selected contracts, conformance fixtures,
|
|
13
|
+
validation data, or automation clients separately. A license applies only to
|
|
14
|
+
the files and versions that explicitly carry it. Third-party dependencies and
|
|
15
|
+
model artifacts remain subject to their respective licenses.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# deepbom
|
|
2
|
+
|
|
3
|
+
Deployment-artifact analysis for on-device neural network models.
|
|
4
|
+
|
|
5
|
+
Answers questions you otherwise have to guess at before shipping a model:
|
|
6
|
+
which operators the XNNPACK delegate will accept, where the graph falls back
|
|
7
|
+
to CPU and how much traffic crosses that boundary, how much of the quantized
|
|
8
|
+
range the weights actually use, and which target profile the numbers apply to.
|
|
9
|
+
|
|
10
|
+
```console
|
|
11
|
+
$ npx deepbom audit mobilenet_v2_1.0_224_quant.tflite
|
|
12
|
+
|
|
13
|
+
mobilenet_v2_1.0_224_quant.tflite
|
|
14
|
+
sha256 f08d447cde49b4e0446428aa921aff0a14ea589fa9c5817b31f83128e9a43c1d
|
|
15
|
+
format tflite size 3.4 MB
|
|
16
|
+
target android_mid_a55
|
|
17
|
+
|
|
18
|
+
graph
|
|
19
|
+
operators 65
|
|
20
|
+
tensors 173
|
|
21
|
+
total MACs 300,775,552
|
|
22
|
+
|
|
23
|
+
quantization
|
|
24
|
+
quantized tensors 172 / 173 (99%)
|
|
25
|
+
per-axis tensors 0
|
|
26
|
+
|
|
27
|
+
XNNPACK delegate (predicted)
|
|
28
|
+
delegated ops 64 / 65 (98%)
|
|
29
|
+
chain breaks 1 effective / 1 total
|
|
30
|
+
fallback traffic 62.5 KB
|
|
31
|
+
|
|
32
|
+
findings (7)
|
|
33
|
+
MEDIUM 1 XNNPACK predicted partition breaks total
|
|
34
|
+
MEDIUM 17 quantized DEPTHWISE_CONV_2D op(s) use per-tensor weights
|
|
35
|
+
MEDIUM 53 quantized kernel op(s) use asymmetric UINT8 weights
|
|
36
|
+
LOW Input preprocessing contract not encoded in model
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Nothing to install:
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
npx deepbom audit model.tflite
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or add it to a project:
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
npm install --save-dev deepbom
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Requires Node.js 20 or newer.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
deepbom audit <file> [--target <id>] [--json]
|
|
60
|
+
deepbom targets
|
|
61
|
+
deepbom --version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Option | |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `-t, --target <id>` | Target profile, default `android_mid_a55` |
|
|
67
|
+
| `--json` | Emit the complete analysis ledger as JSON |
|
|
68
|
+
| `-q, --quiet` | Suppress the summary header |
|
|
69
|
+
|
|
70
|
+
### Target profiles
|
|
71
|
+
|
|
72
|
+
Cache pressure and roofline results are profile-bound, not global. Run
|
|
73
|
+
`deepbom targets` for the current list; it includes Cortex-A72/A76/A55/A53
|
|
74
|
+
profiles, an illustrative X3/A715 planning profile, x86 AVX2/SSE4 and browser
|
|
75
|
+
WASM SIMD.
|
|
76
|
+
|
|
77
|
+
```console
|
|
78
|
+
deepbom audit model.tflite --target rpi5_a76
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### JSON output
|
|
82
|
+
|
|
83
|
+
`--json` emits the full ledger: per-operator MACs, cache payload
|
|
84
|
+
decomposition, predicted delegate placement with reasons, per-tensor
|
|
85
|
+
quantization metadata, and the finding queue. Suitable for CI:
|
|
86
|
+
|
|
87
|
+
```console
|
|
88
|
+
deepbom audit model.tflite --json > audit.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Privacy
|
|
92
|
+
|
|
93
|
+
Analysis runs locally in WebAssembly. The command performs **no network
|
|
94
|
+
access**. Model bytes, filenames and results are never uploaded.
|
|
95
|
+
|
|
96
|
+
## Scope and limits
|
|
97
|
+
|
|
98
|
+
This release analyses **TFLite FlatBuffer** artifacts.
|
|
99
|
+
|
|
100
|
+
Delegate placement is a **static projection** derived from the serialized
|
|
101
|
+
graph. It is not observed runtime behaviour: build configuration, device
|
|
102
|
+
capability and runtime partitioning can differ. Roofline and cache figures are
|
|
103
|
+
target-profile projections, not measurements.
|
|
104
|
+
|
|
105
|
+
ONNX, GGUF, SafeTensors and Core ML analysis, runtime benchmarks, CycloneDX
|
|
106
|
+
ML-BOM export and the source-pinned execution-provider evidence packs are
|
|
107
|
+
available in the browser version at <https://deepbom.org>.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
ISC — see [LICENSE](./LICENSE).
|
package/bin/deepbom.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// deepbom — deployment-artifact analysis for on-device neural network models.
|
|
3
|
+
//
|
|
4
|
+
// Runs the browser analyzer's WebAssembly module locally. Model bytes never
|
|
5
|
+
// leave the machine; this command performs no network access.
|
|
6
|
+
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { dirname, join, basename } from "node:path";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const ROOT = join(HERE, "..");
|
|
14
|
+
const pkg = JSON.parse(await readFile(join(ROOT, "package.json"), "utf8"));
|
|
15
|
+
|
|
16
|
+
const argv = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
function parseArgs(list) {
|
|
19
|
+
const opts = { _: [] };
|
|
20
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
21
|
+
const a = list[i];
|
|
22
|
+
if (a === "--json") opts.json = true;
|
|
23
|
+
else if (a === "--quiet" || a === "-q") opts.quiet = true;
|
|
24
|
+
else if (a === "--target" || a === "-t") opts.target = list[++i];
|
|
25
|
+
else if (a === "--help" || a === "-h") opts.help = true;
|
|
26
|
+
else if (a === "--version" || a === "-v") opts.version = true;
|
|
27
|
+
else if (a.startsWith("--target=")) opts.target = a.slice(9);
|
|
28
|
+
else if (a.startsWith("-")) { fail(`unknown option: ${a}`); }
|
|
29
|
+
else opts._.push(a);
|
|
30
|
+
}
|
|
31
|
+
return opts;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fail(message, code = 2) {
|
|
35
|
+
process.stderr.write(`deepbom: ${message}\n`);
|
|
36
|
+
process.exit(code);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const USAGE = `deepbom ${pkg.version} — deployment-artifact analysis for on-device models
|
|
40
|
+
|
|
41
|
+
USAGE
|
|
42
|
+
deepbom audit <file> [--target <id>] [--json]
|
|
43
|
+
deepbom targets
|
|
44
|
+
deepbom --version
|
|
45
|
+
|
|
46
|
+
COMMANDS
|
|
47
|
+
audit Analyse a model artifact and print a summary
|
|
48
|
+
targets List available target profiles
|
|
49
|
+
|
|
50
|
+
OPTIONS
|
|
51
|
+
-t, --target <id> Target profile (default: android_mid_a55)
|
|
52
|
+
--json Emit the full analysis ledger as JSON
|
|
53
|
+
-q, --quiet Suppress the summary header
|
|
54
|
+
-h, --help Show this help
|
|
55
|
+
|
|
56
|
+
FORMATS
|
|
57
|
+
TFLite (.tflite) is supported in this release. ONNX, GGUF, SafeTensors and
|
|
58
|
+
Core ML are available in the browser version at https://deepbom.org
|
|
59
|
+
|
|
60
|
+
PRIVACY
|
|
61
|
+
Analysis runs locally. No model bytes, filenames or results are uploaded.
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
async function loadEngine() {
|
|
65
|
+
const mod = await import(new URL("../vendor/tflite_wasm_audit.js", import.meta.url));
|
|
66
|
+
const wasm = await readFile(new URL("../vendor/tflite_wasm_audit_bg.wasm", import.meta.url));
|
|
67
|
+
mod.initSync({ module: wasm });
|
|
68
|
+
return mod;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const fmtInt = (n) => Number(n ?? 0).toLocaleString("en-US");
|
|
72
|
+
function fmtBytes(n) {
|
|
73
|
+
const v = Number(n ?? 0);
|
|
74
|
+
if (!Number.isFinite(v) || v <= 0) return "0 B";
|
|
75
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
76
|
+
const i = Math.min(units.length - 1, Math.floor(Math.log(v) / Math.log(1024)));
|
|
77
|
+
return `${(v / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
78
|
+
}
|
|
79
|
+
const pct = (n, d) => (d > 0 ? `${((n / d) * 100).toFixed(0)}%` : "n/a");
|
|
80
|
+
|
|
81
|
+
async function cmdTargets() {
|
|
82
|
+
const engine = await loadEngine();
|
|
83
|
+
const profiles = engine.target_profiles() || [];
|
|
84
|
+
for (const p of profiles) {
|
|
85
|
+
const id = p?.id ?? p;
|
|
86
|
+
const label = p?.label ? ` — ${p.label}` : "";
|
|
87
|
+
process.stdout.write(`${id}${label}\n`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function cmdAudit(opts) {
|
|
92
|
+
const file = opts._[0];
|
|
93
|
+
if (!file) fail("audit requires a file path\n\n" + USAGE);
|
|
94
|
+
|
|
95
|
+
let bytes;
|
|
96
|
+
try {
|
|
97
|
+
bytes = new Uint8Array(await readFile(file));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
fail(`cannot read ${file}: ${error.code ?? error.message}`);
|
|
100
|
+
}
|
|
101
|
+
if (bytes.length === 0) fail(`${file} is empty`);
|
|
102
|
+
|
|
103
|
+
const engine = await loadEngine();
|
|
104
|
+
const target = opts.target || "android_mid_a55";
|
|
105
|
+
const known = (engine.target_profiles() || []).map((p) => p?.id ?? p);
|
|
106
|
+
if (known.length && !known.includes(target)) {
|
|
107
|
+
fail(`unknown target "${target}". Run: deepbom targets`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let analysis;
|
|
111
|
+
try {
|
|
112
|
+
analysis = engine.analyze_tflite_for_target(bytes, basename(file), target);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
fail(`analysis failed: ${error?.message ?? error}\n` +
|
|
115
|
+
`This release supports TFLite FlatBuffer artifacts. ` +
|
|
116
|
+
`For ONNX, GGUF, SafeTensors and Core ML see https://deepbom.org`, 1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (opts.json) {
|
|
120
|
+
process.stdout.write(`${JSON.stringify(analysis, null, 2)}\n`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const sha = analysis.model_sha256 || createHash("sha256").update(bytes).digest("hex");
|
|
125
|
+
const ops = Array.isArray(analysis.ops) ? analysis.ops : [];
|
|
126
|
+
const delegated = ops.filter((o) => o?.xnnpack_supported === true).length;
|
|
127
|
+
const findings = Array.isArray(analysis.findings) ? analysis.findings : [];
|
|
128
|
+
const out = [];
|
|
129
|
+
|
|
130
|
+
if (!opts.quiet) {
|
|
131
|
+
out.push(`${analysis.filename || basename(file)}`);
|
|
132
|
+
out.push(` sha256 ${sha}`);
|
|
133
|
+
out.push(` format ${analysis.format ?? "unknown"} size ${fmtBytes(analysis.file_size ?? bytes.length)}`);
|
|
134
|
+
out.push(` target ${target}`);
|
|
135
|
+
out.push("");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
out.push("graph");
|
|
139
|
+
out.push(` operators ${fmtInt(analysis.operator_count)}`);
|
|
140
|
+
out.push(` tensors ${fmtInt(analysis.tensor_count)}`);
|
|
141
|
+
if (analysis.total_macs != null) out.push(` total MACs ${fmtInt(analysis.total_macs)}`);
|
|
142
|
+
out.push("");
|
|
143
|
+
|
|
144
|
+
out.push("quantization");
|
|
145
|
+
out.push(` quantized tensors ${fmtInt(analysis.quantized_tensors)} / ${fmtInt(analysis.tensor_count)} (${pct(analysis.quantized_tensors, analysis.tensor_count)})`);
|
|
146
|
+
out.push(` per-axis tensors ${fmtInt(analysis.per_channel_tensors)}`);
|
|
147
|
+
out.push("");
|
|
148
|
+
|
|
149
|
+
if (ops.length) {
|
|
150
|
+
out.push("XNNPACK delegate (predicted)");
|
|
151
|
+
out.push(` delegated ops ${fmtInt(delegated)} / ${fmtInt(ops.length)} (${pct(delegated, ops.length)})`);
|
|
152
|
+
if (analysis.xnnpack_effective_chain_breaks != null) {
|
|
153
|
+
out.push(` chain breaks ${fmtInt(analysis.xnnpack_effective_chain_breaks)} effective / ${fmtInt(analysis.xnnpack_chain_breaks)} total`);
|
|
154
|
+
}
|
|
155
|
+
if (analysis.fallback_estimated_bytes) {
|
|
156
|
+
out.push(` fallback traffic ${fmtBytes(analysis.fallback_estimated_bytes)}`);
|
|
157
|
+
}
|
|
158
|
+
out.push("");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (findings.length) {
|
|
162
|
+
out.push(`findings (${findings.length})`);
|
|
163
|
+
for (const f of findings.slice(0, 10)) {
|
|
164
|
+
const sev = String(f?.severity ?? f?.priority ?? "info").toUpperCase().padEnd(6);
|
|
165
|
+
const title = f?.title ?? f?.message ?? f?.id ?? "";
|
|
166
|
+
out.push(` ${sev} ${title}`);
|
|
167
|
+
}
|
|
168
|
+
if (findings.length > 10) out.push(` ... ${findings.length - 10} more (use --json)`);
|
|
169
|
+
out.push("");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
out.push("Predicted delegate placement is a static projection, not observed runtime behaviour.");
|
|
173
|
+
out.push("Full ledger: deepbom audit <file> --json");
|
|
174
|
+
process.stdout.write(`${out.join("\n")}\n`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const opts = parseArgs(argv);
|
|
178
|
+
if (opts.version) { process.stdout.write(`${pkg.version}\n`); process.exit(0); }
|
|
179
|
+
if (opts.help || argv.length === 0) { process.stdout.write(USAGE); process.exit(argv.length === 0 ? 1 : 0); }
|
|
180
|
+
|
|
181
|
+
const command = opts._.shift();
|
|
182
|
+
try {
|
|
183
|
+
if (command === "audit") await cmdAudit(opts);
|
|
184
|
+
else if (command === "targets") await cmdTargets();
|
|
185
|
+
else fail(`unknown command "${command}"\n\n${USAGE}`);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
fail(error?.stack ?? String(error), 1);
|
|
188
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deepbom",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deployment-artifact analysis for on-device neural network models — local static audit of TFLite graphs, quantization contracts and predicted XNNPACK delegate placement",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"deepbom": "bin/deepbom.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"vendor/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tflite",
|
|
21
|
+
"litert",
|
|
22
|
+
"onnx",
|
|
23
|
+
"quantization",
|
|
24
|
+
"on-device",
|
|
25
|
+
"edge-ai",
|
|
26
|
+
"xnnpack",
|
|
27
|
+
"ml-bom",
|
|
28
|
+
"cyclonedx",
|
|
29
|
+
"static-analysis"
|
|
30
|
+
],
|
|
31
|
+
"homepage": "https://deepbom.org",
|
|
32
|
+
"author": "Jun-Hwan Kwon"
|
|
33
|
+
}
|