| from __future__ import annotations |
|
|
| import hashlib |
| import json |
| from collections import Counter |
| from pathlib import Path |
|
|
| import numpy as np |
| import onnx |
| import onnxruntime as ort |
| from onnxruntime.transformers.float16 import convert_float_to_float16 |
| from onnxruntime.quantization.onnx_model import ONNXModel |
|
|
|
|
| ROOT = Path(__file__).resolve().parent |
| OUTPUT_DIR = ROOT / "output" |
| REPORT_DIR = ROOT / "reports" |
| GRAPH_NAMES = ("decoder_prefill", "decoder_step") |
|
|
|
|
| def sha256(path: Path) -> str: |
| digest = hashlib.sha256() |
| with path.open("rb") as handle: |
| for chunk in iter(lambda: handle.read(1024 * 1024), b""): |
| digest.update(chunk) |
| return digest.hexdigest() |
|
|
|
|
| def make_inputs(session: ort.InferenceSession) -> dict[str, np.ndarray]: |
| rng = np.random.default_rng(20260716) |
| feeds: dict[str, np.ndarray] = {} |
| for value in session.get_inputs(): |
| shape = [257 if dimension == "past_len" else dimension for dimension in value.shape] |
| if value.name == "vision_embeds": |
| feeds[value.name] = rng.normal(0, 0.2, shape).astype(np.float32) |
| elif value.name == "token_one_hot": |
| token = np.zeros(shape, dtype=np.float32) |
| token[0, 0, 4] = 1 |
| feeds[value.name] = token |
| elif value.name == "position_ids": |
| feeds[value.name] = np.array([[257]], dtype=np.int32) |
| else: |
| feeds[value.name] = rng.normal(0, 0.02, shape).astype(np.float32) |
| return feeds |
|
|
|
|
| def compare(reference: Path, candidate: Path) -> dict: |
| expected_session = ort.InferenceSession( |
| str(reference), providers=["CPUExecutionProvider"] |
| ) |
| actual_session = ort.InferenceSession( |
| str(candidate), providers=["CPUExecutionProvider"] |
| ) |
| feeds = make_inputs(expected_session) |
| expected = expected_session.run(None, feeds) |
| actual = actual_session.run(None, feeds) |
| differences = [ |
| float(np.max(np.abs(expected_value - actual_value))) |
| for expected_value, actual_value in zip(expected, actual) |
| ] |
| expected_token = int(expected[0][0, -1].argmax()) |
| actual_token = int(actual[0][0, -1].argmax()) |
| return { |
| "max_abs": max(differences), |
| "logits_max_abs": differences[0], |
| "cache_max_abs": max(differences[1:]), |
| "reference_top_token": expected_token, |
| "candidate_top_token": actual_token, |
| "top_token_matches": expected_token == actual_token, |
| } |
|
|
|
|
| def main() -> None: |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) |
| REPORT_DIR.mkdir(parents=True, exist_ok=True) |
| report = { |
| "description": ( |
| "Complete Baberu decoder converted to FP16 internal tensors with FP32 " |
| "public inputs and outputs; no layer, hidden-size, vocabulary, or " |
| "generation-logic reduction" |
| ), |
| "graphs": {}, |
| } |
| for name in GRAPH_NAMES: |
| source = OUTPUT_DIR / f"{name}_fp32.onnx" |
| destination = OUTPUT_DIR / f"{name}_fp16.onnx" |
| if not source.exists(): |
| raise SystemExit(f"Missing {source}. Run export_decoder_fp32.py first.") |
| print(f"Converting {source.name} -> {destination.name}", flush=True) |
| model = convert_float_to_float16(onnx.load(source), keep_io_types=True) |
| sortable = ONNXModel(model) |
| sortable.topological_sort() |
| model = sortable.model |
| model.producer_name = "vibe-manga-baberu-webgpu-fp16" |
| model.producer_version = "1" |
| onnx.checker.check_model(model) |
| onnx.save(model, destination) |
| operators = Counter(node.op_type for node in model.graph.node) |
| parity = compare(source, destination) |
| if not parity["top_token_matches"]: |
| raise RuntimeError(f"{destination.name}: synthetic top-token parity failed") |
| report["graphs"][name] = { |
| "source": source.name, |
| "destination": destination.name, |
| "bytes": destination.stat().st_size, |
| "sha256": sha256(destination), |
| "operators": dict(sorted(operators.items())), |
| "fp32_cpu_parity": parity, |
| } |
|
|
| destination = REPORT_DIR / "decoder-fp16-report.json" |
| destination.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") |
| print(json.dumps(report, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|