test1111111 / scripts /bench_overhead.py
spitfire4794's picture
CISM remote autobench: full source + fleet runner, serve results on 7860
28a1a01
Raw History Blame Contribute Delete
26 kB
"""Bench Python harness overhead vs native decode (read-only measurement).
Owns nothing but this file. Reads Engine/TUI/server shapes only; never
modifies other files, never rebuilds native. Prefers the fake/tiny path so
it runs in seconds:
* FakeNative + FakeTokenizer (tests/test_engine.py make_engine pattern):
zero-cost "native" isolates pure-Python loop overhead, compared against the
production reference of ~200 tok/s (~5000 us/token).
* Tiny real native model (test_native.tiny_model shape, hidden=24/2 layers)
when cism._native + numpy are available: measures a real next_tokens-only
baseline on the same box. Tiny != production model; the production ratio
is still computed against the 5000 us/token reference.
Sections:
1. Engine.generate(64) total vs native-only vs decode vs machinery,
plus chunk_size=1 vs 16 amortisation.
2. Tokenizer scaling: single decode(n) vs incremental full-redecode cost.
3. TUI run() with an instant FakeEngine (tests/test_tui.py pattern).
4. Server TestClient timing if fastapi is importable, else code-only.
5. Verdict (a)/(b)/(c) with a >10% threshold for any C++ work.
"""
from __future__ import annotations
import io
import statistics
import sys
import threading
import time
REPO_ROOT = __file__.rsplit("scripts", 1)[0] if "scripts" in __file__ else "."
for _p in (REPO_ROOT + "/src", REPO_ROOT + "src", "G:/CISM/src", "G:\\CISM\\src"):
if _p not in sys.path:
try:
sys.path.insert(0, _p)
except Exception:
pass
from cism.engine import Engine # noqa: E402
try:
from cism import tui # noqa: E402
except Exception: # pragma: no cover
tui = None
REFERENCE_US_PER_TOKEN = 5000.0 # ~200 tok/s production native decode
NTOK = 64
# ---------------------------------------------------------------- fakes
class FakeTokenizer:
bos_token_id = 0
eos_token_id = None
chat_template = "test template"
def __init__(self, pieces=None, encoded=None, special=()):
self.pieces = pieces or {}
self.encoded = [1, 2] if encoded is None else encoded
self.special = set(special)
def encode(self, text, *, add_special_tokens):
return list(self.encoded)
def decode(self, tokens, *, skip_special_tokens, clean_up_tokenization_spaces):
assert skip_special_tokens is True
assert clean_up_tokenization_spaces is False
return b"".join(
self.pieces.get(t, b"x") for t in tokens if t not in self.special
).decode("utf-8", errors="replace")
def apply_chat_template(self, messages, *, tokenize, add_generation_prompt):
return list(self.encoded)
class FakeSession:
def __init__(self, output, options):
self.output = list(output)
self.options = options
self.generated_tokens = 0
self.finish_reason = "length" if options["max_new_tokens"] == 0 else ""
self.cancelled = threading.Event()
self.requests = []
def next_tokens(self, count, spec_k=0):
self.requests.append((count, spec_k))
batch = []
for _ in range(count):
if self.finish_reason:
break
token = self.output[self.generated_tokens]
self.generated_tokens += 1
batch.append(token)
if token in self.options["eos_token_ids"]:
self.finish_reason = "stop"
elif self.generated_tokens == self.options["max_new_tokens"]:
self.finish_reason = "length"
return batch
def cancel(self):
self.cancelled.set()
if not self.finish_reason:
self.finish_reason = "cancelled"
class FakeNative:
info = {"precision": "fp32", "weight_bytes": 123, "threads": 1}
def __init__(self, output):
self.output = output
self.sessions = []
def create_session(self, prompt, **options):
session = FakeSession(self.output, options)
self.sessions.append(session)
return session
def make_fake_engine(ntok=NTOK):
output = tuple(3 + (i % 60) for i in range(ntok)) # ids in [3, 62]
pieces = {tok: b"a" for tok in set(output)}
native = FakeNative(output)
tok = FakeTokenizer(pieces)
engine = Engine(native, tok, {
"model_type": "llama", "vocab_size": 64, "max_position_embeddings": 4096,
}, "fake-model", revision="pinned")
return engine, native
class TuiFakeEngine:
"""Instant engine mirroring tests/test_tui.py FakeEngine (3 tok/prompt)."""
def __init__(self, n_prompts=0):
self.model_id = "fake-model"
self.precision = "int8"
self.threads = 2
self.weight_bytes = 4 * 1024 * 1024
self.context_length = 256
self.pieces = {3: "Hello", 4: " world", 5: "!"}
self.order = [3, 4, 5]
self.engine_time = 0.0
@property
def info(self):
return {"model": self.model_id, "precision": self.precision,
"threads": self.threads, "weight_bytes": self.weight_bytes,
"context_length": self.context_length}
def stream(self, prompt, *, max_new_tokens=128, temperature=0.0, top_p=1.0,
top_k=0, seed=0, stop=None, cancel=None, spec_k=0, **kw):
from cism.engine import GenerationChunk
t0 = time.perf_counter()
try:
if cancel is not None and cancel.is_set():
return
n = min(max_new_tokens, len(self.order))
for i in range(n):
if cancel is not None and cancel.is_set():
return
tok = self.order[i]
yield GenerationChunk(self.pieces[tok], [tok],
"length" if i == n - 1 else None, 2, i + 1, 0, 0)
finally:
self.engine_time += time.perf_counter() - t0
def generate(self, prompt, **kw):
from cism.engine import GenerationResult
texts, ids, last = [], [], None
for chunk in self.stream(prompt, **kw):
texts.append(chunk.text)
ids.extend(chunk.token_ids)
last = chunk
return GenerationResult("".join(texts), ids, last.finish_reason,
last.prompt_tokens, last.completion_tokens, 0, 0)
def nll(self, prompt, window=128):
import math
return {"nll": 0.5, "perplexity": math.exp(0.5), "scored_tokens": 2}
# ---------------------------------------------------------------- helpers
def med(fn, repeats=21, warmup=3):
for _ in range(warmup):
fn()
samples = [fn() for _ in range(repeats)]
return statistics.median(samples), samples
def timed(fn):
t0 = time.perf_counter()
out = fn()
return time.perf_counter() - t0, out
def fmt_us(s):
return f"{s * 1e6:9.1f} us"
def build_tiny_engine():
"""Tiny real native engine (test_native.tiny_model shape) or raise Skip."""
import numpy as np
try:
from cism import _native # noqa: F401
except Exception as exc:
raise RuntimeError(f"no native extension ({exc})")
rng = np.random.default_rng(312)
hidden, inter, vocab = 24, 37, 43
config = dict(model_type="llama", hidden_size=hidden,
intermediate_size=inter, num_hidden_layers=2,
num_attention_heads=4, num_key_value_heads=2, head_dim=8,
vocab_size=vocab, max_position_embeddings=80,
rms_norm_eps=1e-5, rope_theta=10000.0,
tie_word_embeddings=False, hidden_act="silu")
def mat(shape):
return rng.normal(0, 0.14, shape).astype(np.float32)
def norm(w):
return rng.uniform(0.75, 1.25, w).astype(np.float32)
weights = {"model.embed_tokens.weight": mat((vocab, hidden)),
"lm_head.weight": mat((vocab, hidden)),
"model.norm.weight": norm(hidden)}
for i in range(2):
p = f"model.layers.{i}."
weights[p + "input_layernorm.weight"] = norm(hidden)
weights[p + "post_attention_layernorm.weight"] = norm(hidden)
for name, shape in (("q_proj", (32, hidden)), ("k_proj", (16, hidden)),
("v_proj", (16, hidden)), ("o_proj", (hidden, 32))):
weights[p + f"self_attn.{name}.weight"] = mat(shape)
for name, shape in (("gate_proj", (inter, hidden)),
("up_proj", (inter, hidden)),
("down_proj", (hidden, inter))):
weights[p + f"mlp.{name}.weight"] = mat(shape)
pieces = {i: bytes([97 + (i % 26)]) for i in range(vocab)}
engine = Engine.from_weights(config, weights, FakeTokenizer(pieces),
model_id="tiny-llama", revision="bench")
return engine
# ---------------------------------------------------------------- 1: loop
def section1():
print("== [1] Engine loop overhead vs native (fake path: pure-Python cost) ==")
engine, _ = make_fake_engine(NTOK)
def gen16():
return engine.generate([1, 2], max_new_tokens=NTOK, chunk_size=16)
t_med, _ = med(lambda: timed(gen16)[0], repeats=21, warmup=3)
print(f"Engine.generate({NTOK} tok, chunk=16): median {t_med * 1e3:.3f} ms "
f"total = {t_med / NTOK * 1e6:.1f} us/token (fake native ~= 0 cost)")
# Instrumented breakdown: wrap decode (Python obj, wrappable) + wrap
# sessions at create_session (works for FakeSession; real pybind11
# sessions have no __dict__, so the real path uses a direct loop below).
state = {"t_next": 0.0, "t_decode": 0.0, "n_next": 0, "n_decode": 0}
orig_create = engine._native.create_session
orig_decode = engine.tokenizer.decode
def counting_create(prompt, **opts):
sess = orig_create(prompt, **opts)
orig_next = sess.next_tokens
def timed_next(count, spec_k=0):
t0 = time.perf_counter()
try:
return orig_next(count, spec_k=spec_k)
finally:
state["t_next"] += time.perf_counter() - t0
state["n_next"] += 1
sess.next_tokens = timed_next
return sess
def timed_decode(tokens, **kw):
t0 = time.perf_counter()
try:
return orig_decode(tokens, **kw)
finally:
state["t_decode"] += time.perf_counter() - t0
state["n_decode"] += 1
engine._native.create_session = counting_create
engine.tokenizer.decode = timed_decode
# warmup then one measured generation (counters reset for the measure)
engine.generate([1, 2], max_new_tokens=NTOK, chunk_size=16)
state.update(t_next=0.0, t_decode=0.0, n_next=0, n_decode=0)
t0 = time.perf_counter()
engine.generate([1, 2], max_new_tokens=NTOK, chunk_size=16)
t_total = time.perf_counter() - t0
engine._native.create_session = orig_create
engine.tokenizer.decode = orig_decode
t_other = t_total - state["t_next"] - state["t_decode"]
per = lambda s: s / NTOK * 1e6
print(f" breakdown of one {NTOK}-tok generate ({t_total * 1e3:.3f} ms):")
print(f" native next_tokens : {state['t_next'] * 1e6:9.1f} us total "
f"({per(state['t_next']):7.1f} us/tok, {state['n_next']} calls, "
f"{state['t_next'] / max(1, state['n_next']) * 1e6:.1f} us/call)")
print(f" tokenizer.decode : {state['t_decode'] * 1e6:9.1f} us total "
f"({per(state['t_decode']):7.1f} us/tok, {state['n_decode']} calls)")
print(f" chunk/yield/lock : {t_other * 1e6:9.1f} us total "
f"({per(t_other):7.1f} us/tok) [GenerationChunk allocs, stop-check,"
f" cancel-monitor thread start/join, gen lock]")
print(f" python overhead/token ~= {t_total / NTOK * 1e6:.1f} us vs "
f"reference native {REFERENCE_US_PER_TOKEN:.0f} us "
f"({t_total / NTOK * 1e6 / REFERENCE_US_PER_TOKEN * 100:.2f}%)")
# chunk_size amortisation on the fake path
for cs in (1, 16):
e, _ = make_fake_engine(NTOK)
t, _ = med(lambda: timed(
lambda: e.generate([1, 2], max_new_tokens=NTOK, chunk_size=cs))[0],
repeats=15, warmup=2)
sess = e._native.sessions[-1]
print(f" chunk_size={cs:2d}: median {t * 1e3:7.3f} ms "
f"({t / NTOK * 1e6:6.1f} us/tok), native calls={len(sess.requests)}")
# cancel-monitor thread cost (per-generate constant, amortised per token)
def spawn_join():
ev = threading.Event()
th = threading.Thread(target=ev.wait, kwargs={"timeout": 0.0})
th.start()
th.join()
t_thr, _ = med(lambda: timed(spawn_join)[0], repeats=100, warmup=10)
print(f" thread spawn+join microbench: ~{t_thr * 1e6:.1f} us/generate "
f"(~{t_thr / NTOK * 1e6:.2f} us/tok at {NTOK} tok) -> negligible")
# GenerationChunk alloc microbench
from cism.engine import GenerationChunk
def allocs():
for _ in range(1000):
GenerationChunk("a", [3], None, 2, 1, 0, 0)
t_al, _ = med(lambda: timed(allocs)[0], repeats=21, warmup=3)
print(f" GenerationChunk alloc: ~{t_al / 1000 * 1e9:.0f} ns/chunk")
# tiny real-native baseline (same box, NOT the production model)
tiny = None
try:
tiny = build_tiny_engine()
except Exception as exc:
print(f" tiny real model: SKIPPED ({exc})")
if tiny is not None:
tn = 32 # 5 prompt + 32 < 80 context
prompt = [2, 7, 1]
t_eng, _ = med(lambda: timed(
lambda: tiny.generate(prompt, max_new_tokens=tn, chunk_size=16))[0],
repeats=5, warmup=1)
def native_only():
s = tiny._native.create_session(prompt, max_new_tokens=tn,
temperature=0.0, top_p=1.0,
top_k=0, seed=0, eos_token_ids=[])
n = 0
while not s.finish_reason:
n += len(s.next_tokens(16))
return n
t_nat, _ = med(lambda: timed(native_only)[0], repeats=5, warmup=1)
# decode-only share inside Engine.generate on the tiny path
st = {"d": 0.0}
od = tiny.tokenizer.decode
def td(tokens, **kw):
t0 = time.perf_counter()
try:
return od(tokens, **kw)
finally:
st["d"] += time.perf_counter() - t0
tiny.tokenizer.decode = td
tiny.generate(prompt, max_new_tokens=tn, chunk_size=16)
st["d"] = 0.0
tiny.generate(prompt, max_new_tokens=tn, chunk_size=16)
tiny.tokenizer.decode = od
ov = (t_eng - t_nat) / tn * 1e6
print(f" tiny real model ({tn} tok): engine {t_eng * 1e3:.2f} ms "
f"({t_eng / tn * 1e6:.0f} us/tok), native-only {t_nat * 1e3:.2f} ms "
f"({t_nat / tn * 1e6:.0f} us/tok)")
print(f" tiny overhead ~= {ov:.1f} us/tok "
f"(decode {st['d'] / tn * 1e6:.1f} us/tok); "
f"vs PROD ref {REFERENCE_US_PER_TOKEN:.0f} us/tok = "
f"{ov / REFERENCE_US_PER_TOKEN * 100:.2f}%")
print()
return {"fake_us_per_tok": t_total / NTOK * 1e6}
# ---------------------------------------------------------------- 2: decode
def section2():
print("== [2] Tokenizer scaling: full-redecode per chunk (O(n^2/chunk)) ==")
tok = FakeTokenizer({i: b"a" for i in range(512)})
ids512 = list(range(512))
for n in (64, 128, 256, 512):
ids = ids512[:n]
t, _ = med(lambda: timed(
lambda: tok.decode(ids, skip_special_tokens=True,
clean_up_tokenization_spaces=False))[0],
repeats=200, warmup=20)
print(f" single decode({n:3d}): {t * 1e6:8.1f} us")
# incremental totals as Engine.stream does (decode whole prefix per chunk)
for cs in (1, 16):
lens = list(range(cs, 513, cs))
if lens[-1] != 512:
lens.append(512)
def incr():
for k in lens:
tok.decode(ids512[:k], skip_special_tokens=True,
clean_up_tokenization_spaces=False)
t, _ = med(lambda: timed(incr)[0], repeats=5, warmup=1)
single512, _ = med(lambda: timed(
lambda: tok.decode(ids512, skip_special_tokens=True,
clean_up_tokenization_spaces=False))[0],
repeats=200, warmup=20)
print(f" incremental 512 tok, chunk={cs:2d}: {len(lens)} decodes, "
f"total {t * 1e3:.2f} ms ({t / 512 * 1e6:.1f} us/tok) vs single "
f"decode(512) {single512 * 1e6:.1f} us "
f"(ratio {t / single512:.0f}x)")
# what incremental-decode (delta only) would cost instead
t_delta, _ = med(lambda: timed(
lambda: tok.decode(ids512[:16], skip_special_tokens=True,
clean_up_tokenization_spaces=False))[0],
repeats=200, warmup=20)
est = t_delta * (512 / 16)
print(f" delta-only estimate (decode 16 new ids x32): ~{est * 1e3:.2f} ms "
f"-> saves the quadratic re-scan")
print()
# ---------------------------------------------------------------- 3: TUI
def section3():
print("== [3] TUI path overhead (instant FakeEngine => harness only) ==")
if tui is None:
print(" SKIPPED: cism.tui not importable")
print()
return {"tui_us_per_tok": float("nan")}
# direct engine baseline (no TUI)
eng0 = TuiFakeEngine()
t_dir, _ = med(lambda: timed(
lambda: list(eng0.stream("hello", max_new_tokens=3)))[0],
repeats=2000, warmup=200)
print(f" direct eng.stream(3 tok): {t_dir * 1e6:.1f} us "
f"({t_dir / 3 * 1e6:.1f} us/tok) [engine-already-instant baseline]")
# TUI loop over many prompts in one run()
N_PROMPTS, TOK_PER = 50, 3
lines = ["hello"] * N_PROMPTS + ["/quit"]
def tui_run():
eng = TuiFakeEngine()
out, err = io.StringIO(), io.StringIO()
t0 = time.perf_counter()
code = tui.run("fake-model", engine=eng,
stdin=io.StringIO("\n".join(lines) + "\n"),
stdout=out, stderr=err)
dt = time.perf_counter() - t0
assert code == 0
return dt, eng.engine_time
_, _ = tui_run(), None # warmup (imports/regex compile)
dts, eng_ts = [], []
for _ in range(5):
dt, et = tui_run()
dts.append(dt)
eng_ts.append(et)
dtm, etm = statistics.median(dts), statistics.median(eng_ts)
total_tok = N_PROMPTS * TOK_PER
harness = dtm - etm
print(f" tui.run({N_PROMPTS} prompts x {TOK_PER} tok): median {dtm * 1e3:.2f} ms "
f"total; engine-inside {etm * 1e3:.2f} ms")
print(f" TUI harness overhead: {harness * 1e6:.0f} us / {total_tok} tok = "
f"{harness / total_tok * 1e6:.1f} us/tok "
f"({harness / total_tok * 1e6 / REFERENCE_US_PER_TOKEN * 100:.3f}% of "
f"{REFERENCE_US_PER_TOKEN:.0f} us ref token)")
print(" (tui.py run(): direct eng.stream() call per prompt, per-chunk "
"out.write+flush, counters only; no HTTP/JSON.)")
print()
return {"tui_us_per_tok": harness / total_tok * 1e6}
# ---------------------------------------------------------------- 4: server
def section4(tui_us):
print("== [4] Server path overhead (code + optional live TestClient) ==")
print(" code path (src/cism/server.py, read-only): _Worker.submit puts a")
print(" _Job on queue.Queue(maxsize=8); inference thread runs the SAME")
print(" engine.stream(chunk_size=16) as the TUI; each chunk crosses the")
print(" queue + asyncio.Event notify (worker._put/_run/receive), then one")
print(" json.dumps per SSE frame (or one for non-stream) + pydantic")
print(" _CompletionRequest validation. Expect ~10s of us extra vs TUI.")
result = {"server_extra_us_per_tok": float("nan"), "live": False}
try:
from fastapi.testclient import TestClient
from cism.engine import GenerationChunk as Chunk
from cism.server import create_app
except Exception as exc:
print(f" live TestClient: SKIPPED ({exc})")
print()
return result
class SrvFake:
model_id = "bench-model"
context_length = 512
def __init__(self, ntok=8):
self.ntok = ntok
def encode(self, prompt):
return list(prompt) if isinstance(prompt, list) else [1, 2]
def stream(self, prompt, **kw):
n = min(kw.get("max_new_tokens", 8), self.ntok)
for i in range(n):
yield Chunk("a", [3 + (i % 40)], "length" if i == n - 1 else None,
2, i + 1, 0, 0)
NTOK_S, REPS = 8, 10
try:
import os
os.environ.pop("CISM_API_KEY", None)
app = create_app(SrvFake(NTOK_S), api_key="")
with TestClient(app) as client:
assert client.get("/health").json() == {"status": "ok"}
body = {"model": "bench-model", "prompt": [1, 2],
"max_tokens": NTOK_S}
client.post("/v1/completions", json=body) # warmup
t0 = time.perf_counter()
for _ in range(REPS):
r = client.post("/v1/completions", json=body)
assert r.status_code == 200, r.text
t_srv = (time.perf_counter() - t0) / REPS
# streaming sample (one request)
t0 = time.perf_counter()
r = client.post("/v1/completions", json={**body, "stream": True})
assert r.status_code == 200, r.text
t_stream = time.perf_counter() - t0
eng = SrvFake(NTOK_S)
t0 = time.perf_counter()
for _ in range(200):
list(eng.stream([1, 2], max_new_tokens=NTOK_S))
t_dir = (time.perf_counter() - t0) / 200
extra = (t_srv - t_dir) / NTOK_S * 1e6
result = {"server_extra_us_per_tok": extra, "live": True}
print(f" live TestClient non-stream: {t_srv * 1e3:.2f} ms/req "
f"({t_srv / NTOK_S * 1e6:.0f} us/tok); direct stream: "
f"{t_dir * 1e3:.3f} ms ({t_dir / NTOK_S * 1e6:.1f} us/tok)")
print(f" server extra vs direct: ~{extra:.0f} us/tok "
f"({extra / REFERENCE_US_PER_TOKEN * 100:.2f}% of ref token); "
f"stream req sample {t_stream * 1e3:.1f} ms")
print(" (in-process TestClient understates real TCP+uvicorn by "
"~0.2-1 ms/req; per-token delta stays << native token time.)")
except Exception as exc:
print(f" live TestClient: SKIPPED ({type(exc).__name__}: {exc})")
print()
return result
# ---------------------------------------------------------------- 5: verdict
def section5(s1, tui_us, srv):
print("== [5] Verdict: is a C++ inference API worth it? ==")
overhead = s1["fake_us_per_tok"]
ratio = overhead / REFERENCE_US_PER_TOKEN
tui_r = (tui_us / REFERENCE_US_PER_TOKEN) if tui_us == tui_us else float("nan")
print(f" Python loop overhead : {overhead:7.1f} us/tok "
f"({ratio * 100:5.2f}% of {REFERENCE_US_PER_TOKEN:.0f} us ref token)")
print(f" TUI harness overhead : {tui_us:7.1f} us/tok "
f"({tui_r * 100:5.3f}% of ref token)")
if srv["live"]:
e = srv["server_extra_us_per_tok"]
print(f" Server extra (live) : {e:7.1f} us/tok "
f"({e / REFERENCE_US_PER_TOKEN * 100:5.2f}% of ref token)")
else:
print(" Server extra (live) : skipped (code-only: queue+JSON ~10s us)")
print(" Threshold: C++ work only if harness overhead >10% of token time.")
if ratio < 0.05 and (tui_r != tui_r or tui_r < 0.05):
choice = ("(a) NOT worth it -- keep the Python Engine.stream boundary. "
"Overhead is <5% of a batch-1 decode step.")
elif ratio >= 0.10 or (tui_r == tui_r and tui_r >= 0.10):
choice = ("(b)/(c) threshold tripped -- re-measure on the production "
"model before committing to C++.")
else:
choice = ("(b) at most a native generate_text helper (incremental "
"decode + stop-check inside one call) -- not a full C++ API.")
# With the measured sub-5% numbers the evidence points to (a); the
# conditional above encodes the rule, this line records the outcome.
print(f" RECOMMENDATION: {choice}")
if ratio < 0.05:
print(" Chosen: (a). A full C++ inference API / C++ CLI buys <5% at")
print(" batch-1 decode and adds ABI/build/debug cost for zero user-")
print(" visible tok/s. The TUI already has no high harness overhead")
print(" (direct eng.stream calls, ~10s us/tok).")
print(" What WOULD help most instead (in order):")
print(" 1. Incremental decode: decode only the new delta per chunk")
print(" (carry pending UTF-8 bytes) instead of decode(all ids) --")
print(" kills the O(n^2/chunk) re-scan from [2].")
print(" 2. Keep chunk_size=16 whenever no stop strings are set (stops")
print(" force token-at-a-time in engine.py); amortises one pybind11")
print(" next_tokens call over 16 tokens -- see [1] cs=1 vs 16.")
print(" 3. Keep the TUI on direct Engine calls (no HTTP/JSON hop) and")
print(" batch terminal writes; the server path stays opt-in.")
print()
return choice
def main():
print("bench_overhead.py -- Python harness overhead vs native decode")
print(f"reference native: ~200 tok/s = {REFERENCE_US_PER_TOKEN:.0f} us/token; "
f"NTOK={NTOK}")
try:
import cism._native as n # noqa: F401
print("native extension: present (tiny-model section enabled if numpy ok)")
except Exception as exc:
print(f"native extension: MISSING ({exc}); fake-path only")
print()
s1 = section1()
section2()
s3 = section3()
srv = section4(s3["tui_us_per_tok"])
choice = section5(s1, s3["tui_us_per_tok"], srv)
print(f"VERDICT: {choice}")
if __name__ == "__main__":
main()