| """ |
| verify.py — Validate the exported ONNX files against the original PyTorch model. |
| |
| Runs five checks and prints a readable report: |
| |
| 0. Artifact consistency — the shipped tokenizer/config vs the pinned source. |
| 1. Raw-logit parity — PyTorch export wrapper vs ONNX (fp32). Confirms the |
| export itself is faithful (should be ~1e-4 or smaller). |
| 2. End-to-end parity — the full post-processed output (task types + |
| complexity scores) reproduced from the ONNX logits via the model's own |
| post-processing. Should match to the rounding the post-processing applies. |
| 3. Ground-truth anchor — the README example must classify as "Code Generation" |
| with the documented complexity score. |
| 4. fp16 drift — fp16 outputs vs fp32; expected to be negligible (~1e-3). |
| |
| Exit code is non-zero if any hard check fails. |
| """ |
|
|
| import os |
| import sys |
|
|
| import numpy as np |
| import onnxruntime as ort |
| import torch |
| from transformers import AutoConfig, AutoTokenizer |
|
|
| from export import ( |
| MODEL_NAME, |
| MODEL_REVISION, |
| OUT_DIR, |
| OUTPUT_NAMES, |
| ROOT_DIR, |
| ExportWrapper, |
| load_model, |
| ) |
|
|
| |
| PROMPTS = [ |
| "Write a Python script that uses a for loop.", |
| "What is the capital of France?", |
| "Summarize the following report in three concise bullet points, keeping only " |
| "the financial figures and omitting any commentary about strategy.", |
| "Prove, step by step and with full rigor, that the square root of 2 is " |
| "irrational, then explain where the argument would break for the square root of 4.", |
| ] |
|
|
| |
| |
| README_PROMPT = "Prompt: Write a Python script that uses a for loop." |
| README_EXPECTED_TASK_1 = "Code Generation" |
| README_EXPECTED_SCORE = 0.27823 |
|
|
| |
| NUMERIC_FIELDS = [ |
| "task_type_prob", |
| "creativity_scope", |
| "reasoning", |
| "contextual_knowledge", |
| "number_of_few_shots", |
| "domain_knowledge", |
| "no_label_reason", |
| "constraint_ct", |
| "prompt_complexity_score", |
| ] |
| STRING_FIELDS = ["task_type_1", "task_type_2"] |
|
|
|
|
| def encode(tok, prompt): |
| return tok(prompt, return_tensors="pt", truncation=True, max_length=512) |
|
|
|
|
| def run_onnx(sess, enc): |
| """Return the 8 raw logit arrays in OUTPUT_NAMES order.""" |
| return sess.run( |
| None, |
| { |
| "input_ids": enc["input_ids"].numpy(), |
| "attention_mask": enc["attention_mask"].numpy(), |
| }, |
| ) |
|
|
|
|
| def create_session(path): |
| """Create a quiet, deterministic CPU session for release validation.""" |
| options = ort.SessionOptions() |
| options.log_severity_level = 3 |
| return ort.InferenceSession( |
| path, |
| sess_options=options, |
| providers=["CPUExecutionProvider"], |
| ) |
|
|
|
|
| def result_from_onnx(model, onnx_logits): |
| """Reuse the model's own post-processing on ONNX logits -> result dict.""" |
| return model.process_logits([torch.tensor(x) for x in onnx_logits]) |
|
|
|
|
| def dict_diff(a, b): |
| """Max abs numeric drift and any string mismatch between two result dicts.""" |
| max_num = 0.0 |
| string_mismatch = None |
| for f in NUMERIC_FIELDS: |
| av = np.array(a[f], dtype=float) |
| bv = np.array(b[f], dtype=float) |
| max_num = max(max_num, float(np.abs(av - bv).max())) |
| for f in STRING_FIELDS: |
| if a[f] != b[f]: |
| string_mismatch = (f, a[f], b[f]) |
| return max_num, string_mismatch |
|
|
|
|
| def main(): |
| ok = True |
| print("== Check 0: shipped tokenizer/config consistency ==") |
| tok = AutoTokenizer.from_pretrained(ROOT_DIR, local_files_only=True) |
| source_tok = AutoTokenizer.from_pretrained( |
| MODEL_NAME, |
| revision=MODEL_REVISION, |
| ) |
| local_config = AutoConfig.from_pretrained(ROOT_DIR, local_files_only=True) |
| source_config = AutoConfig.from_pretrained( |
| MODEL_NAME, |
| revision=MODEL_REVISION, |
| ) |
|
|
| tokenizer_matches = all( |
| encode(tok, p)["input_ids"].equal(encode(source_tok, p)["input_ids"]) |
| and encode(tok, p)["attention_mask"].equal( |
| encode(source_tok, p)["attention_mask"] |
| ) |
| for p in PROMPTS + [README_PROMPT] |
| ) |
| config_fields = ["target_sizes", "task_type_map", "weights_map", "divisor_map"] |
| config_matches = all( |
| getattr(local_config, field) == getattr(source_config, field) |
| for field in config_fields |
| ) |
| print(f" tokenizer matches pinned source: {tokenizer_matches}") |
| print(f" scoring config matches pinned source: {config_matches}") |
| if not tokenizer_matches or not config_matches: |
| print(" [FAIL] shipped preprocessing artifacts differ from pinned source") |
| ok = False |
| else: |
| print(" [ok]") |
|
|
| print("Loading PyTorch model ...") |
| model = load_model() |
| wrapper = ExportWrapper(model).eval() |
|
|
| fp32 = create_session(os.path.join(OUT_DIR, "model.onnx")) |
| fp16 = create_session(os.path.join(OUT_DIR, "model_fp16.onnx")) |
|
|
| for name, session in [("fp32", fp32), ("fp16", fp16)]: |
| output_names = [output.name for output in session.get_outputs()] |
| if output_names != OUTPUT_NAMES: |
| print( |
| f" [FAIL] {name} output order is {output_names}, " |
| f"expected {OUTPUT_NAMES}" |
| ) |
| ok = False |
|
|
| print("\n== Check 1: raw-logit parity (PyTorch vs ONNX fp32) ==") |
| max_logit_diff = 0.0 |
| for p in PROMPTS: |
| enc = encode(tok, p) |
| with torch.no_grad(): |
| pt_logits = wrapper(enc["input_ids"], enc["attention_mask"]) |
| onnx_logits = run_onnx(fp32, enc) |
| for a, b in zip(pt_logits, onnx_logits): |
| max_logit_diff = max(max_logit_diff, float(np.abs(a.numpy() - b).max())) |
| print(f" max |logit_pt - logit_onnx| = {max_logit_diff:.2e}") |
| if max_logit_diff > 1e-3: |
| print(" [FAIL] logit drift larger than 1e-3") |
| ok = False |
| else: |
| print(" [ok]") |
|
|
| print("\n== Check 2: end-to-end parity (PyTorch vs ONNX-derived) ==") |
| e2e_max = 0.0 |
| for p in PROMPTS: |
| enc = encode(tok, p) |
| ref = model(enc) |
| got = result_from_onnx(model, run_onnx(fp32, enc)) |
| num, mism = dict_diff(ref, got) |
| e2e_max = max(e2e_max, num) |
| if mism: |
| print(f" [FAIL] string mismatch on {p[:40]!r}: {mism}") |
| ok = False |
| print(f" max numeric drift = {e2e_max:.2e}") |
| print(" [ok]" if e2e_max <= 1e-3 else " [FAIL] end-to-end drift > 1e-3") |
| ok = ok and e2e_max <= 1e-3 |
|
|
| print("\n== Check 3: README ground-truth anchor ==") |
| enc = encode(tok, README_PROMPT) |
| ref = result_from_onnx(model, run_onnx(fp32, enc)) |
| got_task = ref["task_type_1"][0] |
| got_score = ref["prompt_complexity_score"][0] |
| print(f" task_type_1 = {got_task!r} (expected {README_EXPECTED_TASK_1!r})") |
| print(f" prompt_complexity_score = {got_score} (expected ~{README_EXPECTED_SCORE})") |
| if got_task != README_EXPECTED_TASK_1 or abs(got_score - README_EXPECTED_SCORE) > 1e-3: |
| print(" [FAIL] does not match documented output") |
| ok = False |
| else: |
| print(" [ok]") |
|
|
| print("\n== Check 4: fp16 drift (fp16 vs fp32) ==") |
| fp16_max = 0.0 |
| for p in PROMPTS: |
| enc = encode(tok, p) |
| ref = result_from_onnx(model, run_onnx(fp32, enc)) |
| got = result_from_onnx(model, run_onnx(fp16, enc)) |
| num, mism = dict_diff(ref, got) |
| fp16_max = max(fp16_max, num) |
| if mism: |
| print(f" [FAIL] fp16 changed a task label on {p[:40]!r}: {mism}") |
| ok = False |
| print(f" max numeric drift (fp16 vs fp32) = {fp16_max:.2e}") |
| if fp16_max > 1e-2: |
| print(" [FAIL] fp16 drift larger than 1e-2") |
| ok = False |
| else: |
| print(" [ok]") |
|
|
| print("\n" + ("ALL HARD CHECKS PASSED" if ok else "SOME CHECKS FAILED")) |
| sys.exit(0 if ok else 1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|