Skip to content

Deployment parity

Does the model you ship behave like the model you tested? Every helper runs the eager model and the deployment artifact on the same inputs and reports mismatches path by path.

torch.compile

assert_compile_parity uses torch.compile's default backend (inductor) unless you pass backend=; backend="eager" checks graph capture without needing a C++ compiler. assert_no_graph_breaks, assert_no_recompiles and ModelSuite's compile check use the eager backend by default.

from pytest_torch import assert_compile_parity, assert_no_graph_breaks, assert_no_recompiles

assert_compile_parity(model, x, backend="eager")   # checks dynamo capture; no C++ toolchain needed
assert_compile_parity(model, x)                    # full inductor compilation
assert_no_graph_breaks(model, x)                   # one graph, with torch's reason if not
assert_no_recompiles(model, x, x_bigger, max_recompiles=1)

torch.export

from pytest_torch import assert_export_parity

program = assert_export_parity(model, x)                                # static shapes
assert_export_parity(model, x, x_small, x_large, dynamic_batch=True)    # one program, any batch size
assert_export_parity(model, x, dynamic_shapes={"x": {0: Dim("batch")}}) # torch.export's own spec

dynamic_batch=True marks dim 0 of every batch-aligned input as dynamic. Extra positional inputs must run through the same program; without dynamic shapes they fail with a hint. If export itself fails (data-dependent control flow, .item(), unsupported Python), you get an AssertionError with torch's explanation. The ExportedProgram is returned so you can save it or lower it further.

AOTInductor

from pytest_torch import assert_aoti_parity

assert_aoti_parity(model, x, x_small, dynamic_batch=True)

Exports, compiles ahead of time into a .pt2 package, loads it back and compares: the artifact a C++ server or edge runtime would load. Needs torch 2.6+ and a C++ compiler (the test skips otherwise); compilation takes tens of seconds, so keep these tests few, or mark them slow.

Mixed precision

from pytest_torch import assert_autocast_parity, check_inference_contract

assert_autocast_parity(model, x, dtype=torch.bfloat16)        # fp32 weights, bf16 matmuls (torch.autocast)
check_inference_contract(model, x, checks=["dtype_parity"], dtypes=(torch.bfloat16,))  # whole model cast

torch.autocast is how mixed precision usually runs; casting the whole model is a stricter test of a pure low-precision deployment.

TorchScript and ONNX

from pytest_torch import assert_torchscript_parity, assert_onnx_parity

assert_torchscript_parity(model, x, mode="trace")   # or mode="script"; TorchScript is deprecated upstream
assert_onnx_parity(model, x, export_kwargs={"dynamo": True})   # pin the exporter explicitly

Both accept only positional tensor inputs. The ONNX exporter's defaults differ between torch versions, so pin them with export_kwargs. Needs pip install "pytest-torch[onnx]".

Accuracy budgets and references

from pytest_torch import assert_models_agree, assert_matches_reference, assert_generation_equal

metrics = assert_models_agree(fp32, quantized, x, min_top1=0.99, min_cosine=0.999)
assert_matches_reference(my_bert, hf_bert, batch, select=lambda o: o.last_hidden_state)
assert_generation_equal(my_gpt, hf_gpt, input_ids, max_new_tokens=16, pad_token_id=0)

assert_models_agree returns the measured metrics so you can log them; assert_generation_equal reports the first diverging token per row.