from __future__ import annotations import json from pathlib import Path import numpy as np import onnxruntime as ort import torch from export_decoder_fp32 import ( MODEL_DIR, OPSET, OUTPUT_DIR, REPORT_DIR, inspect_graph, load_model, ) ROOT = Path(__file__).resolve().parent OFFICIAL_FP16_PATH = MODEL_DIR / "onnx" / "vision_fp16.onnx" class VisionEncoderProjector(torch.nn.Module): """Exact Baberu DINOv2 encoder plus its trained 768 -> 512 projector.""" def __init__(self, ocr_model): super().__init__() self.vision_encoder = ocr_model.vision_encoder self.projector = ocr_model.projector def forward(self, pixel_values: torch.Tensor) -> torch.Tensor: vision_states = self.vision_encoder( pixel_values=pixel_values, return_dict=False, )[0] # DINOv2 position zero is CLS. Baberu uses only the 16 x 16 patch grid. return self.projector(vision_states[:, 1:, :]) def comparison(expected: np.ndarray, actual: np.ndarray) -> dict[str, float]: difference = np.abs(expected - actual) expected_flat = expected.reshape(-1).astype(np.float64) actual_flat = actual.reshape(-1).astype(np.float64) cosine = float( np.dot(expected_flat, actual_flat) / (np.linalg.norm(expected_flat) * np.linalg.norm(actual_flat)) ) return { "max_abs": float(difference.max()), "mean_abs": float(difference.mean()), "cosine_similarity": cosine, } def run_cpu(path: Path, pixels: np.ndarray) -> np.ndarray: session = ort.InferenceSession(str(path), providers=["CPUExecutionProvider"]) return session.run(["vision_embeds"], {"pixel_values": pixels})[0] def main() -> None: if not (MODEL_DIR / "model.safetensors").exists(): raise SystemExit("Model is missing. Run download_model.py first.") if not OFFICIAL_FP16_PATH.exists(): raise SystemExit("Official vision_fp16.onnx is missing. Run download_model.py.") OUTPUT_DIR.mkdir(parents=True, exist_ok=True) REPORT_DIR.mkdir(parents=True, exist_ok=True) torch.manual_seed(23) torch.set_grad_enabled(False) model = load_model() wrapper = VisionEncoderProjector(model).float().eval() pixels = torch.randn(1, 3, 224, 224, dtype=torch.float32) * 0.25 fp32_path = OUTPUT_DIR / "vision_fp32.onnx" print(f"Exporting {fp32_path}") torch.onnx.export( wrapper, (pixels,), fp32_path, input_names=["pixel_values"], output_names=["vision_embeds"], opset_version=OPSET, do_constant_folding=True, dynamo=False, ) with torch.inference_mode(): expected = wrapper(pixels).cpu().numpy() pixels_numpy = pixels.cpu().numpy() fp32_actual = run_cpu(fp32_path, pixels_numpy) fp16_actual = run_cpu(OFFICIAL_FP16_PATH, pixels_numpy) report = { "fp32": { "graph": inspect_graph(fp32_path), "parity": comparison(expected, fp32_actual), }, "official_fp16": { "graph": inspect_graph(OFFICIAL_FP16_PATH), "parity_to_pytorch_fp32": comparison(expected, fp16_actual), }, } report_path = REPORT_DIR / "encoder-report.json" report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") print(f"Wrote {report_path}") print(json.dumps({key: value.get("parity", value.get("parity_to_pytorch_fp32")) for key, value in report.items()}, indent=2)) if __name__ == "__main__": main()