ncm-benchmarks 2.0.0__py3-none-any.whl
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.
- ncm_bench/cli.py +650 -0
- ncm_bench/inference.py +609 -0
- ncm_bench/longdoc.py +160 -0
- ncm_bench/mbr.py +141 -0
- ncm_bench/metrics.py +153 -0
- ncm_bench/progressive.py +172 -0
- ncm_benchmarks-2.0.0.dist-info/METADATA +275 -0
- ncm_benchmarks-2.0.0.dist-info/RECORD +18 -0
- ncm_benchmarks-2.0.0.dist-info/WHEEL +5 -0
- ncm_benchmarks-2.0.0.dist-info/entry_points.txt +2 -0
- ncm_benchmarks-2.0.0.dist-info/licenses/LICENSE +21 -0
- ncm_benchmarks-2.0.0.dist-info/licenses/NOTICE +43 -0
- ncm_benchmarks-2.0.0.dist-info/top_level.txt +3 -0
- ncm_train/__init__.py +11 -0
- ncm_train/ncm_trainer.py +147 -0
- ncm_train/vqvae_trainer.py +117 -0
- service/client.py +205 -0
- service/summarize_server.py +670 -0
ncm_bench/cli.py
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
"""Unified command-line interface for NCM benchmarks, sovereign inference, training, and inspection."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import pickle
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
ROOT_DIR = Path(__file__).resolve().parent.parent
|
|
12
|
+
if str(ROOT_DIR) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(ROOT_DIR))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_model_bundle(
|
|
17
|
+
task: str = "xsum",
|
|
18
|
+
quantize: str = "none",
|
|
19
|
+
device: str | None = None,
|
|
20
|
+
verify_checksums: bool = True,
|
|
21
|
+
strict_checksums: bool = False,
|
|
22
|
+
dtype: str = "fp32",
|
|
23
|
+
compile_model: bool = False,
|
|
24
|
+
):
|
|
25
|
+
from ncm_bench.inference import load_bundle
|
|
26
|
+
|
|
27
|
+
return load_bundle(
|
|
28
|
+
task=task,
|
|
29
|
+
device=device,
|
|
30
|
+
quantize=quantize,
|
|
31
|
+
dtype=dtype,
|
|
32
|
+
verify_checksums=verify_checksums,
|
|
33
|
+
strict_checksums=strict_checksums,
|
|
34
|
+
compile_model=compile_model,
|
|
35
|
+
root_dir=ROOT_DIR,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _read_text_arg(args) -> str:
|
|
40
|
+
if getattr(args, "input_file", None):
|
|
41
|
+
from ncm_bench.longdoc import extract_text_from_file
|
|
42
|
+
|
|
43
|
+
return extract_text_from_file(Path(args.input_file))
|
|
44
|
+
text = getattr(args, "text", None)
|
|
45
|
+
if not text and not sys.stdin.isatty():
|
|
46
|
+
text = sys.stdin.read().strip()
|
|
47
|
+
return (text or "").strip()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _emit_output(payload: str, args) -> None:
|
|
51
|
+
out_file = getattr(args, "output_file", None)
|
|
52
|
+
if out_file:
|
|
53
|
+
out_path = Path(out_file)
|
|
54
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
out_path.write_text(payload + "\n", encoding="utf-8")
|
|
56
|
+
print(f"[ncm-bench] Wrote output to {out_path}", file=sys.stderr)
|
|
57
|
+
else:
|
|
58
|
+
print(payload)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cmd_serve(args):
|
|
62
|
+
from service.summarize_server import main as serve_main
|
|
63
|
+
|
|
64
|
+
argv = [
|
|
65
|
+
"summarize_server.py",
|
|
66
|
+
"--port",
|
|
67
|
+
str(args.port),
|
|
68
|
+
"--host",
|
|
69
|
+
args.host,
|
|
70
|
+
"--quantize",
|
|
71
|
+
args.quantize,
|
|
72
|
+
"--dtype",
|
|
73
|
+
args.dtype,
|
|
74
|
+
"--max-queue-depth",
|
|
75
|
+
str(args.max_queue_depth),
|
|
76
|
+
"--request-timeout-s",
|
|
77
|
+
str(args.request_timeout_s),
|
|
78
|
+
"--max-body-bytes",
|
|
79
|
+
str(args.max_body_bytes),
|
|
80
|
+
"--rate-limit-rpm",
|
|
81
|
+
str(args.rate_limit_rpm),
|
|
82
|
+
]
|
|
83
|
+
if args.device:
|
|
84
|
+
argv.extend(["--device", args.device])
|
|
85
|
+
if args.uds:
|
|
86
|
+
argv.extend(["--uds", args.uds])
|
|
87
|
+
if getattr(args, "with_sudoku", False):
|
|
88
|
+
argv.append("--with-sudoku")
|
|
89
|
+
if args.skip_checksum:
|
|
90
|
+
argv.append("--skip-checksum")
|
|
91
|
+
if args.strict_checksums:
|
|
92
|
+
argv.append("--strict-checksums")
|
|
93
|
+
if args.compile:
|
|
94
|
+
argv.append("--compile")
|
|
95
|
+
sys.argv = argv
|
|
96
|
+
serve_main()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def cmd_summarize(args):
|
|
100
|
+
text = _read_text_arg(args)
|
|
101
|
+
if not text:
|
|
102
|
+
print("Error: No text provided via --text, --input-file, or standard input.", file=sys.stderr)
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
|
|
105
|
+
from ncm_bench.longdoc import (
|
|
106
|
+
build_audit_receipt,
|
|
107
|
+
redact_pii,
|
|
108
|
+
restore_pii,
|
|
109
|
+
summarize_long_document,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
pii_map: dict[str, str] = {}
|
|
113
|
+
proc_text = text
|
|
114
|
+
if args.redact_pii:
|
|
115
|
+
proc_text, pii_map = redact_pii(text)
|
|
116
|
+
|
|
117
|
+
print(f"[ncm-bench] Loading model (quantize={args.quantize}, dtype={args.dtype})...", file=sys.stderr)
|
|
118
|
+
bundle = get_model_bundle(
|
|
119
|
+
task="xsum",
|
|
120
|
+
quantize=args.quantize,
|
|
121
|
+
device=args.device,
|
|
122
|
+
verify_checksums=not args.skip_checksum,
|
|
123
|
+
strict_checksums=args.strict_checksums,
|
|
124
|
+
dtype=args.dtype,
|
|
125
|
+
compile_model=args.compile,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
t0 = time.perf_counter()
|
|
129
|
+
if args.long_doc or len(proc_text) > 1800:
|
|
130
|
+
res = summarize_long_document(bundle, proc_text, use_mbr=False)
|
|
131
|
+
summary = res["summary"]
|
|
132
|
+
num_chunks = res["num_chunks"]
|
|
133
|
+
else:
|
|
134
|
+
from ncm_bench.inference import summarize_texts
|
|
135
|
+
|
|
136
|
+
summary = summarize_texts(bundle, [proc_text])[0]
|
|
137
|
+
num_chunks = 1
|
|
138
|
+
latency_ms = int((time.perf_counter() - t0) * 1000)
|
|
139
|
+
|
|
140
|
+
if args.restore_pii and pii_map:
|
|
141
|
+
summary = restore_pii(summary, pii_map)
|
|
142
|
+
|
|
143
|
+
if args.json or args.audit_receipt:
|
|
144
|
+
payload: dict[str, object] = {
|
|
145
|
+
"summary": summary,
|
|
146
|
+
"words": len(summary.split()),
|
|
147
|
+
"latency_ms": latency_ms,
|
|
148
|
+
"num_chunks": num_chunks,
|
|
149
|
+
"pii_redactions": len(pii_map),
|
|
150
|
+
}
|
|
151
|
+
if args.audit_receipt:
|
|
152
|
+
payload["audit_receipt"] = build_audit_receipt(
|
|
153
|
+
text,
|
|
154
|
+
summary,
|
|
155
|
+
bundle,
|
|
156
|
+
pii_redactions=len(pii_map),
|
|
157
|
+
mode="long-doc" if num_chunks > 1 else "standard",
|
|
158
|
+
)
|
|
159
|
+
_emit_output(json.dumps(payload, indent=2), args)
|
|
160
|
+
else:
|
|
161
|
+
_emit_output(summary, args)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def cmd_mbr(args):
|
|
165
|
+
text = _read_text_arg(args)
|
|
166
|
+
if not text:
|
|
167
|
+
print("Error: No text provided via --text, --input-file, or standard input.", file=sys.stderr)
|
|
168
|
+
sys.exit(1)
|
|
169
|
+
|
|
170
|
+
print(f"[ncm-bench] Running MBR decoding with N={args.candidates} candidates...", file=sys.stderr)
|
|
171
|
+
bundle = get_model_bundle(
|
|
172
|
+
task="xsum",
|
|
173
|
+
quantize=args.quantize,
|
|
174
|
+
device=args.device,
|
|
175
|
+
verify_checksums=not args.skip_checksum,
|
|
176
|
+
strict_checksums=args.strict_checksums,
|
|
177
|
+
dtype=args.dtype,
|
|
178
|
+
compile_model=args.compile,
|
|
179
|
+
)
|
|
180
|
+
from ncm_bench.mbr import generate_mbr_summary
|
|
181
|
+
|
|
182
|
+
res = generate_mbr_summary(
|
|
183
|
+
bundle,
|
|
184
|
+
text,
|
|
185
|
+
num_candidates=args.candidates,
|
|
186
|
+
temperature=args.temperature,
|
|
187
|
+
utility=args.utility,
|
|
188
|
+
)
|
|
189
|
+
if args.json:
|
|
190
|
+
_emit_output(json.dumps(res, indent=2), args)
|
|
191
|
+
else:
|
|
192
|
+
lines = [
|
|
193
|
+
f"Selected Summary (Cand #{res['selected_index']}):",
|
|
194
|
+
res["summary"],
|
|
195
|
+
"",
|
|
196
|
+
f"Candidate Pool Scores (Utility={args.utility}):",
|
|
197
|
+
]
|
|
198
|
+
for i, (c, score) in enumerate(zip(res["candidates"], res["scores"], strict=True)):
|
|
199
|
+
mark = "-> [BEST]" if i == res["selected_index"] else " "
|
|
200
|
+
lines.append(f" {mark} #{i} (score: {score:.3f}): {c[:80]}...")
|
|
201
|
+
_emit_output("\n".join(lines), args)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def cmd_inspect(args):
|
|
205
|
+
text = _read_text_arg(args)
|
|
206
|
+
if not text:
|
|
207
|
+
text = (
|
|
208
|
+
"The UK government has announced a major investment into renewable energy infrastructure, "
|
|
209
|
+
"aiming to double wind and solar capacity by 2030. Ministers said the move will lower bills "
|
|
210
|
+
"and cut carbon emissions across England and Wales."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
print("[ncm-bench] Inspecting concept hierarchy across all 9 levels...", file=sys.stderr)
|
|
214
|
+
bundle = get_model_bundle(
|
|
215
|
+
task="xsum",
|
|
216
|
+
quantize=args.quantize,
|
|
217
|
+
device=args.device,
|
|
218
|
+
verify_checksums=not args.skip_checksum,
|
|
219
|
+
strict_checksums=args.strict_checksums,
|
|
220
|
+
dtype=args.dtype,
|
|
221
|
+
compile_model=args.compile,
|
|
222
|
+
)
|
|
223
|
+
from ncm_bench.progressive import progressive_decode, render_progressive_html
|
|
224
|
+
|
|
225
|
+
res = progressive_decode(bundle, text)
|
|
226
|
+
|
|
227
|
+
if args.html:
|
|
228
|
+
html_doc = render_progressive_html(res, prompt_text=text)
|
|
229
|
+
out_path = Path(args.html)
|
|
230
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
out_path.write_text(html_doc, encoding="utf-8")
|
|
232
|
+
print(f"[ncm-bench] Wrote interactive visualizer to {out_path.resolve()}", file=sys.stderr)
|
|
233
|
+
if args.json:
|
|
234
|
+
_emit_output(json.dumps(res, indent=2), args)
|
|
235
|
+
elif not args.html:
|
|
236
|
+
print("=" * 70)
|
|
237
|
+
print("HIERARCHICAL COARSE-TO-FINE PROGRESSION (Levels 0 -> 8)")
|
|
238
|
+
print("=" * 70)
|
|
239
|
+
for lvl in res["levels"]:
|
|
240
|
+
print(
|
|
241
|
+
f"Level {lvl['level']:2d} [{lvl['codes_at_level']:2d} codes, "
|
|
242
|
+
f"{lvl['total_codes_so_far']:2d} accum]: {lvl['text'] or '(initial gist)'}"
|
|
243
|
+
)
|
|
244
|
+
print("=" * 70)
|
|
245
|
+
print(f"Final Summary: {res['final_summary']}")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def cmd_sudoku(args):
|
|
249
|
+
from ncm_bench.inference import solve_sudoku, verify_file_integrity
|
|
250
|
+
from ncm_bench.metrics import score_sudoku
|
|
251
|
+
|
|
252
|
+
task_name = f"sudoku/{args.difficulty}"
|
|
253
|
+
bundle = get_model_bundle(
|
|
254
|
+
task=task_name,
|
|
255
|
+
quantize=args.quantize,
|
|
256
|
+
device=args.device,
|
|
257
|
+
verify_checksums=not args.skip_checksum,
|
|
258
|
+
strict_checksums=args.strict_checksums,
|
|
259
|
+
dtype=args.dtype,
|
|
260
|
+
compile_model=args.compile,
|
|
261
|
+
)
|
|
262
|
+
repair = not args.no_repair
|
|
263
|
+
if args.puzzle:
|
|
264
|
+
t0 = time.perf_counter()
|
|
265
|
+
res = solve_sudoku(
|
|
266
|
+
bundle,
|
|
267
|
+
[args.puzzle],
|
|
268
|
+
best_of_k=args.best_of_k,
|
|
269
|
+
temperature=args.temperature,
|
|
270
|
+
repair=repair,
|
|
271
|
+
)[0]
|
|
272
|
+
res["latency_ms"] = int((time.perf_counter() - t0) * 1000)
|
|
273
|
+
if args.json:
|
|
274
|
+
_emit_output(json.dumps(res, indent=2), args)
|
|
275
|
+
else:
|
|
276
|
+
print(f"Valid Sudoku: {res['valid']} ({res['latency_ms']} ms)")
|
|
277
|
+
for row in res["board"].split("|"):
|
|
278
|
+
if row:
|
|
279
|
+
print(" " + " ".join(row))
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
puzzles_path = ROOT_DIR / f"data/sudoku/{args.difficulty}/puzzles.pickle"
|
|
283
|
+
solutions_path = ROOT_DIR / f"data/sudoku/{args.difficulty}/solutions.pickle"
|
|
284
|
+
if not args.skip_checksum:
|
|
285
|
+
verify_file_integrity(puzzles_path, ROOT_DIR, strict=args.strict_checksums)
|
|
286
|
+
verify_file_integrity(solutions_path, ROOT_DIR, strict=args.strict_checksums)
|
|
287
|
+
with open(puzzles_path, "rb") as f:
|
|
288
|
+
puzzles = pickle.load(f)[: args.n] # nosec B301 - verified by SHA256SUMS
|
|
289
|
+
with open(solutions_path, "rb") as f:
|
|
290
|
+
solutions = pickle.load(f)[: args.n] # nosec B301 - verified by SHA256SUMS
|
|
291
|
+
|
|
292
|
+
t0 = time.perf_counter()
|
|
293
|
+
preds_raw = []
|
|
294
|
+
for start in range(0, len(puzzles), args.batch_size):
|
|
295
|
+
batch = puzzles[start : start + args.batch_size]
|
|
296
|
+
preds_raw.extend(
|
|
297
|
+
r["board"]
|
|
298
|
+
for r in solve_sudoku(
|
|
299
|
+
bundle,
|
|
300
|
+
batch,
|
|
301
|
+
best_of_k=args.best_of_k,
|
|
302
|
+
temperature=args.temperature,
|
|
303
|
+
repair=repair,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
elapsed = time.perf_counter() - t0
|
|
307
|
+
|
|
308
|
+
refs_str = [bundle["tk"].pre_tokenizer.decode(s, skip_special_tokens=True) for s in solutions]
|
|
309
|
+
stats = score_sudoku(preds_raw, refs_str)
|
|
310
|
+
stats["wall_s"] = round(elapsed, 3)
|
|
311
|
+
stats["boards_per_sec"] = round(len(puzzles) / max(elapsed, 1e-6), 2)
|
|
312
|
+
stats["best_of_k"] = args.best_of_k
|
|
313
|
+
stats["repair"] = repair
|
|
314
|
+
stats["difficulty"] = args.difficulty
|
|
315
|
+
if args.json:
|
|
316
|
+
_emit_output(json.dumps(stats, indent=2), args)
|
|
317
|
+
else:
|
|
318
|
+
ci = stats["ci_95"]
|
|
319
|
+
print(
|
|
320
|
+
f"Sudoku {args.difficulty} (n={stats['n_boards']}, K={args.best_of_k}, repair={repair}): "
|
|
321
|
+
f"solved={stats['solved_pct']}% ({stats['solved_count']}/{stats['n_boards']}, "
|
|
322
|
+
f"95% CI +/-{ci['margin_pp']}pp) | cell={stats['cell_pct']}% | "
|
|
323
|
+
f"valid={stats['valid_pct']}% | {stats['wall_s']}s ({stats['boards_per_sec']} boards/s)"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def cmd_translate(args):
|
|
328
|
+
text = _read_text_arg(args)
|
|
329
|
+
if not text:
|
|
330
|
+
print("Error: No German source text provided via --text, --input-file, or stdin.", file=sys.stderr)
|
|
331
|
+
sys.exit(1)
|
|
332
|
+
bundle = get_model_bundle(
|
|
333
|
+
task="wmt14",
|
|
334
|
+
quantize=args.quantize,
|
|
335
|
+
device=args.device,
|
|
336
|
+
verify_checksums=not args.skip_checksum,
|
|
337
|
+
strict_checksums=args.strict_checksums,
|
|
338
|
+
dtype=args.dtype,
|
|
339
|
+
compile_model=args.compile,
|
|
340
|
+
)
|
|
341
|
+
from ncm_bench.inference import summarize_texts
|
|
342
|
+
|
|
343
|
+
t0 = time.perf_counter()
|
|
344
|
+
translation = summarize_texts(bundle, [text])[0]
|
|
345
|
+
latency_ms = int((time.perf_counter() - t0) * 1000)
|
|
346
|
+
if args.json:
|
|
347
|
+
_emit_output(json.dumps({"translation": translation, "latency_ms": latency_ms}, indent=2), args)
|
|
348
|
+
else:
|
|
349
|
+
_emit_output(translation, args)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def cmd_benchmark(args):
|
|
353
|
+
from ncm_bench.inference import summarize_texts, verify_file_integrity
|
|
354
|
+
from ncm_bench.metrics import two_size_timing
|
|
355
|
+
|
|
356
|
+
if args.task == "sudoku":
|
|
357
|
+
args.puzzle = None
|
|
358
|
+
args.difficulty = getattr(args, "difficulty", "hard")
|
|
359
|
+
args.best_of_k = getattr(args, "best_of_k", 1)
|
|
360
|
+
args.temperature = getattr(args, "temperature", 0.4)
|
|
361
|
+
args.no_repair = False
|
|
362
|
+
cmd_sudoku(args)
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
passages_path = ROOT_DIR / "data/xsum/passages.pickle"
|
|
366
|
+
if not args.skip_checksum:
|
|
367
|
+
verify_file_integrity(passages_path, ROOT_DIR, strict=args.strict_checksums)
|
|
368
|
+
with open(passages_path, "rb") as f:
|
|
369
|
+
passages = pickle.load(f) # nosec B301 - verified by SHA256SUMS
|
|
370
|
+
|
|
371
|
+
def _timed_run(n_items: int) -> float:
|
|
372
|
+
t0 = time.perf_counter()
|
|
373
|
+
b = get_model_bundle(
|
|
374
|
+
task="xsum",
|
|
375
|
+
quantize=args.quantize,
|
|
376
|
+
device=args.device,
|
|
377
|
+
verify_checksums=not args.skip_checksum,
|
|
378
|
+
strict_checksums=args.strict_checksums,
|
|
379
|
+
dtype=args.dtype,
|
|
380
|
+
compile_model=args.compile,
|
|
381
|
+
)
|
|
382
|
+
for i in range(0, n_items, args.batch_size):
|
|
383
|
+
summarize_texts(b, passages[i : min(i + args.batch_size, n_items)])
|
|
384
|
+
return time.perf_counter() - t0
|
|
385
|
+
|
|
386
|
+
t_small = _timed_run(args.small_n)
|
|
387
|
+
t_large = _timed_run(args.large_n)
|
|
388
|
+
timing = two_size_timing(t_small, args.small_n, t_large, args.large_n)
|
|
389
|
+
timing.update(
|
|
390
|
+
{
|
|
391
|
+
"small_n": args.small_n,
|
|
392
|
+
"small_s": round(t_small, 3),
|
|
393
|
+
"large_n": args.large_n,
|
|
394
|
+
"large_s": round(t_large, 3),
|
|
395
|
+
"batch_size": args.batch_size,
|
|
396
|
+
"quantize": args.quantize,
|
|
397
|
+
"dtype": args.dtype,
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
if args.baseline_results:
|
|
402
|
+
base_data = json.loads(Path(args.baseline_results).read_text(encoding="utf-8"))
|
|
403
|
+
ref_marginal = base_data.get("runs", {}).get("latency_cpu", {}).get("marginal_s_per_doc_bs1")
|
|
404
|
+
if ref_marginal:
|
|
405
|
+
timing["baseline_marginal_s"] = ref_marginal
|
|
406
|
+
timing["delta_pct"] = round(
|
|
407
|
+
(timing["marginal_s_per_doc"] - ref_marginal) / ref_marginal * 100.0, 2
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if args.json:
|
|
411
|
+
_emit_output(json.dumps(timing, indent=2), args)
|
|
412
|
+
else:
|
|
413
|
+
print(
|
|
414
|
+
f"2-Size Latency Benchmark (quantize={args.quantize}, dtype={args.dtype}, bs={args.batch_size}):\n"
|
|
415
|
+
f" T({args.small_n})={timing['small_s']}s, T({args.large_n})={timing['large_s']}s\n"
|
|
416
|
+
f" Load={timing['model_load_s']}s | Marginal={timing['marginal_s_per_doc']}s/doc "
|
|
417
|
+
f"({timing['items_per_min']} docs/min)"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def cmd_convert_safetensors(args):
|
|
422
|
+
from ncm_bench.inference import export_safetensors
|
|
423
|
+
|
|
424
|
+
paths = export_safetensors(
|
|
425
|
+
task=args.task,
|
|
426
|
+
root_dir=ROOT_DIR,
|
|
427
|
+
verify_checksums=not args.skip_checksum,
|
|
428
|
+
)
|
|
429
|
+
print(json.dumps({k: str(v) for k, v in paths.items()}, indent=2))
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def cmd_train_smoke(args):
|
|
433
|
+
import torch
|
|
434
|
+
|
|
435
|
+
from ncm_train import Stage1Config, Stage2Config, train_stage1_vqvae, train_stage2_ncm
|
|
436
|
+
|
|
437
|
+
torch.manual_seed(42)
|
|
438
|
+
# Deterministic synthetic structured sequence-reversal dataset (real gradients, zero mocks)
|
|
439
|
+
n_samples = args.samples
|
|
440
|
+
L = 16
|
|
441
|
+
vocab = 16
|
|
442
|
+
prefixes = torch.randint(1, vocab, (n_samples, L))
|
|
443
|
+
targets = torch.flip(prefixes, dims=[1])
|
|
444
|
+
|
|
445
|
+
s1 = Stage1Config(
|
|
446
|
+
in_vocab_size=vocab,
|
|
447
|
+
context_length=L,
|
|
448
|
+
embed_dim=64,
|
|
449
|
+
levels=(2, 4, 8, 16),
|
|
450
|
+
vq_vocab_size=32,
|
|
451
|
+
n_layers=2,
|
|
452
|
+
batch_size=min(16, n_samples),
|
|
453
|
+
steps=args.s1_steps,
|
|
454
|
+
device="cpu",
|
|
455
|
+
)
|
|
456
|
+
res1 = train_stage1_vqvae(targets, s1, out_path=Path(args.out_dir) / "tokenizer.pt")
|
|
457
|
+
|
|
458
|
+
s2 = Stage2Config(
|
|
459
|
+
prefix_len=L,
|
|
460
|
+
n_layer=2,
|
|
461
|
+
n_head=4,
|
|
462
|
+
n_embd=64,
|
|
463
|
+
batch_size=min(16, n_samples),
|
|
464
|
+
steps=args.s2_steps,
|
|
465
|
+
device="cpu",
|
|
466
|
+
)
|
|
467
|
+
res2 = train_stage2_ncm(
|
|
468
|
+
prefixes,
|
|
469
|
+
targets,
|
|
470
|
+
res1["model"],
|
|
471
|
+
s1,
|
|
472
|
+
s2,
|
|
473
|
+
out_path=Path(args.out_dir) / "generator.pt",
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
with torch.inference_mode():
|
|
477
|
+
codes = res2["model"].generate(None, prefixes[:2], temperature=0.0)
|
|
478
|
+
recon_logits = res1["model"].decode(codes)
|
|
479
|
+
assert recon_logits.shape[:2] == (2, L)
|
|
480
|
+
|
|
481
|
+
print(
|
|
482
|
+
json.dumps(
|
|
483
|
+
{
|
|
484
|
+
"stage1_initial_loss": round(res1["history"][0]["loss"], 4),
|
|
485
|
+
"stage1_final_loss": round(res1["final_loss"], 4),
|
|
486
|
+
"stage2_initial_loss": round(res2["history"][0]["loss"], 4),
|
|
487
|
+
"stage2_final_loss": round(res2["final_loss"], 4),
|
|
488
|
+
"out_dir": str(Path(args.out_dir).resolve()),
|
|
489
|
+
},
|
|
490
|
+
indent=2,
|
|
491
|
+
)
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def cmd_verify(args):
|
|
496
|
+
from scripts.fetch_artifacts import load_checksums, sha256_file
|
|
497
|
+
|
|
498
|
+
artifacts_only = bool(getattr(args, "artifacts_only", False))
|
|
499
|
+
sums = load_checksums()
|
|
500
|
+
if not sums:
|
|
501
|
+
print("Error: SHA256SUMS file not found or empty.", file=sys.stderr)
|
|
502
|
+
sys.exit(1)
|
|
503
|
+
entries = (
|
|
504
|
+
{k: v for k, v in sums.items() if k.startswith("artifacts/")}
|
|
505
|
+
if artifacts_only
|
|
506
|
+
else sums
|
|
507
|
+
)
|
|
508
|
+
all_ok = True
|
|
509
|
+
print(f"Verifying {len(entries)} artifact checksums...")
|
|
510
|
+
for rel_path, exp_hash in entries.items():
|
|
511
|
+
p = ROOT_DIR / rel_path
|
|
512
|
+
if not p.exists():
|
|
513
|
+
print(f" [MISSING] {rel_path}")
|
|
514
|
+
all_ok = False
|
|
515
|
+
continue
|
|
516
|
+
actual = sha256_file(p)
|
|
517
|
+
if actual == exp_hash:
|
|
518
|
+
print(f" [OK] {rel_path}")
|
|
519
|
+
else:
|
|
520
|
+
print(f" [FAILED] {rel_path} (expected {exp_hash[:10]}..., got {actual[:10]}...)")
|
|
521
|
+
all_ok = False
|
|
522
|
+
if all_ok:
|
|
523
|
+
print("\nAll artifact checksums successfully verified against pinned SHA256SUMS!")
|
|
524
|
+
else:
|
|
525
|
+
print("\nVerification failed.", file=sys.stderr)
|
|
526
|
+
sys.exit(1)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _add_common_flags(p, *, with_io: bool = True):
|
|
530
|
+
if with_io:
|
|
531
|
+
p.add_argument("--input-file", "-f", default=None, help="Read input from .txt/.md/.json/.pdf")
|
|
532
|
+
p.add_argument("--output-file", "-o", default=None, help="Write output payload to file")
|
|
533
|
+
p.add_argument("--quantize", choices=["none", "int8"], default="none")
|
|
534
|
+
p.add_argument("--dtype", choices=["fp32", "fp16", "bf16"], default="fp32")
|
|
535
|
+
p.add_argument("--device", default=None)
|
|
536
|
+
p.add_argument("--skip-checksum", action="store_true", help="Skip SHA-256 verification before load")
|
|
537
|
+
p.add_argument("--strict-checksums", action="store_true", help="Fail closed on unlisted artifacts")
|
|
538
|
+
p.add_argument("--compile", action="store_true", help="Apply torch.compile optimization")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def main():
|
|
542
|
+
parser = argparse.ArgumentParser(
|
|
543
|
+
prog="ncm-bench",
|
|
544
|
+
description="NCM Sovereign Inference Engine, Training Suite, Benchmark CLI & Inspector",
|
|
545
|
+
)
|
|
546
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
547
|
+
|
|
548
|
+
# serve
|
|
549
|
+
p_serve = sub.add_parser("serve", help="Run local REST API server")
|
|
550
|
+
p_serve.add_argument("--port", type=int, default=8031)
|
|
551
|
+
p_serve.add_argument("--host", default="127.0.0.1")
|
|
552
|
+
p_serve.add_argument("--uds", default=None, help="Bind to a Unix domain socket path instead of TCP")
|
|
553
|
+
p_serve.add_argument("--max-queue-depth", type=int, default=8)
|
|
554
|
+
p_serve.add_argument("--request-timeout-s", type=float, default=60.0)
|
|
555
|
+
p_serve.add_argument("--max-body-bytes", type=int, default=2_000_000)
|
|
556
|
+
p_serve.add_argument("--rate-limit-rpm", type=int, default=0, help="Requests/min per client IP (0=disabled)")
|
|
557
|
+
p_serve.add_argument("--with-sudoku", action="store_true", help="Also load sudoku/hard model for POST /sudoku")
|
|
558
|
+
_add_common_flags(p_serve, with_io=False)
|
|
559
|
+
p_serve.set_defaults(func=cmd_serve)
|
|
560
|
+
|
|
561
|
+
# summarize
|
|
562
|
+
p_sum = sub.add_parser("summarize", help="Summarize text or document file offline")
|
|
563
|
+
p_sum.add_argument("--text", "-t", default=None, help="Input text to summarize")
|
|
564
|
+
p_sum.add_argument("--long-doc", action="store_true", help="Enable map-reduce chunked summarization")
|
|
565
|
+
p_sum.add_argument("--redact-pii", action="store_true", help="Scrub PII before encoding")
|
|
566
|
+
p_sum.add_argument("--restore-pii", action="store_true", help="Restore PII placeholders in output")
|
|
567
|
+
p_sum.add_argument("--audit-receipt", action="store_true", help="Include SHA-256 compliance audit receipt")
|
|
568
|
+
p_sum.add_argument("--json", action="store_true", help="Output JSON response")
|
|
569
|
+
_add_common_flags(p_sum, with_io=True)
|
|
570
|
+
p_sum.set_defaults(func=cmd_summarize)
|
|
571
|
+
|
|
572
|
+
# mbr
|
|
573
|
+
p_mbr = sub.add_parser("mbr", help="Run MBR consensus decoding")
|
|
574
|
+
p_mbr.add_argument("--text", "-t", default=None, help="Input text to summarize")
|
|
575
|
+
p_mbr.add_argument("--candidates", "-k", type=int, default=4)
|
|
576
|
+
p_mbr.add_argument("--temperature", type=float, default=0.7)
|
|
577
|
+
p_mbr.add_argument("--utility", choices=["rougeL", "jaccard", "cosine"], default="rougeL")
|
|
578
|
+
p_mbr.add_argument("--json", action="store_true")
|
|
579
|
+
_add_common_flags(p_mbr, with_io=True)
|
|
580
|
+
p_mbr.set_defaults(func=cmd_mbr)
|
|
581
|
+
|
|
582
|
+
# inspect
|
|
583
|
+
p_insp = sub.add_parser("inspect", help="Progressively inspect concept hierarchy (Levels 0..8)")
|
|
584
|
+
p_insp.add_argument("--text", "-t", default=None, help="Input text")
|
|
585
|
+
p_insp.add_argument("--html", help="Path to write standalone HTML visualizer slider")
|
|
586
|
+
p_insp.add_argument("--json", action="store_true")
|
|
587
|
+
_add_common_flags(p_insp, with_io=True)
|
|
588
|
+
p_insp.set_defaults(func=cmd_inspect)
|
|
589
|
+
|
|
590
|
+
# sudoku
|
|
591
|
+
p_sud = sub.add_parser("sudoku", help="Solve Sudoku puzzles or run constraint-verified evaluation")
|
|
592
|
+
p_sud.add_argument("--puzzle", "-p", default=None, help="81-char Sudoku puzzle (0, ., or _ for blanks)")
|
|
593
|
+
p_sud.add_argument("--difficulty", choices=["easy", "medium", "hard"], default="hard")
|
|
594
|
+
p_sud.add_argument("--n", type=int, default=20)
|
|
595
|
+
p_sud.add_argument("--batch-size", type=int, default=16)
|
|
596
|
+
p_sud.add_argument("--best-of-k", type=int, default=1, help="Resample failing boards up to K candidates")
|
|
597
|
+
p_sud.add_argument("--temperature", type=float, default=0.4)
|
|
598
|
+
p_sud.add_argument("--no-repair", action="store_true", help="Disable post-prediction Sudoku constraint repair")
|
|
599
|
+
p_sud.add_argument("--json", action="store_true")
|
|
600
|
+
_add_common_flags(p_sud, with_io=True)
|
|
601
|
+
p_sud.set_defaults(func=cmd_sudoku)
|
|
602
|
+
|
|
603
|
+
# translate
|
|
604
|
+
p_tr = sub.add_parser("translate", help="Translate German to English via NCM WMT14")
|
|
605
|
+
p_tr.add_argument("--text", "-t", default=None)
|
|
606
|
+
p_tr.add_argument("--json", action="store_true")
|
|
607
|
+
_add_common_flags(p_tr, with_io=True)
|
|
608
|
+
p_tr.set_defaults(func=cmd_translate)
|
|
609
|
+
|
|
610
|
+
# benchmark
|
|
611
|
+
p_bench = sub.add_parser("benchmark", help="Run timed latency or accuracy benchmarks")
|
|
612
|
+
p_bench.add_argument("--task", choices=["latency", "sudoku"], default="latency")
|
|
613
|
+
p_bench.add_argument("--small-n", type=int, default=4)
|
|
614
|
+
p_bench.add_argument("--large-n", type=int, default=16)
|
|
615
|
+
p_bench.add_argument("--n", type=int, default=20)
|
|
616
|
+
p_bench.add_argument("--batch-size", type=int, default=1)
|
|
617
|
+
p_bench.add_argument("--baseline-results", default=None, help="Compare against baseline results.json")
|
|
618
|
+
p_bench.add_argument("--json", action="store_true")
|
|
619
|
+
_add_common_flags(p_bench, with_io=False)
|
|
620
|
+
p_bench.set_defaults(func=cmd_benchmark)
|
|
621
|
+
|
|
622
|
+
# convert-safetensors
|
|
623
|
+
p_st = sub.add_parser("convert-safetensors", help="Convert verified .pt weights to zero-pickle .safetensors")
|
|
624
|
+
p_st.add_argument("--task", default="xsum", help="Artifact subdirectory under artifacts/ (e.g. xsum, sudoku/hard)")
|
|
625
|
+
p_st.add_argument("--skip-checksum", action="store_true")
|
|
626
|
+
p_st.set_defaults(func=cmd_convert_safetensors)
|
|
627
|
+
|
|
628
|
+
# train-smoke
|
|
629
|
+
p_ts = sub.add_parser("train-smoke", help="Run clean-room Stage-1 + Stage-2 NCM training smoke check")
|
|
630
|
+
p_ts.add_argument("--samples", type=int, default=32)
|
|
631
|
+
p_ts.add_argument("--s1-steps", type=int, default=10)
|
|
632
|
+
p_ts.add_argument("--s2-steps", type=int, default=10)
|
|
633
|
+
p_ts.add_argument("--out-dir", default="artifacts/train_smoke")
|
|
634
|
+
p_ts.set_defaults(func=cmd_train_smoke)
|
|
635
|
+
|
|
636
|
+
# verify
|
|
637
|
+
p_ver = sub.add_parser("verify", help="Verify model and data SHA256 checksums")
|
|
638
|
+
p_ver.add_argument(
|
|
639
|
+
"--artifacts-only",
|
|
640
|
+
action="store_true",
|
|
641
|
+
help="Verify pinned model weights under artifacts/ only (skip optional data/ datasets)",
|
|
642
|
+
)
|
|
643
|
+
p_ver.set_defaults(func=cmd_verify)
|
|
644
|
+
|
|
645
|
+
args = parser.parse_args()
|
|
646
|
+
args.func(args)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
if __name__ == "__main__":
|
|
650
|
+
main()
|