import os, sys, pickle, time, re, tempfile, struct, shutil, io import numpy as np if "JIT_BATCH_SIZE" not in os.environ: os.environ["JIT_BATCH_SIZE"] = "0" from tinygrad import fetch, Tensor, TinyJit, Context, GlobalCounters, Device, dtypes from tinygrad.helpers import DEBUG, getenv from tinygrad.uop.ops import Ops from tinygrad.nn.onnx import OnnxRunner OPENPILOT_MODEL = sys.argv[1] if len(sys.argv) > 1 else "https://github.com/commaai/openpilot/raw/v0.9.7/selfdrive/modeld/models/supercombo.onnx" OUTPUT = sys.argv[2] if len(sys.argv) > 2 else "/tmp/openpilot.pkl" PICKLE_OOB = getenv("PICKLE_OOB") def dump_pickle(obj, f): if PICKLE_OOB: # allows pickling when buffers don't fit in (CPU) RAM # from openpilot/selfdrive/modeld/helpers.py with tempfile.TemporaryFile(dir=".") as tmp: def buffer_callback(pb: pickle.PickleBuffer): m = pb.raw() tmp.write(struct.pack(' 0: gated_read_image_count += 1 print(f"{kernel_count=}, {read_image_count=}, {gated_read_image_count=}") if (allowed_kernel_count:=getenv("ALLOWED_KERNEL_COUNT", -1)) != -1: assert kernel_count == allowed_kernel_count, f"different kernels! {kernel_count=}, {allowed_kernel_count=}" if (allowed_read_image:=getenv("ALLOWED_READ_IMAGE", -1)) != -1: assert read_image_count == allowed_read_image, f"different read_image! {read_image_count=}, {allowed_read_image=}" if (allowed_gated_read_image:=getenv("ALLOWED_GATED_READ_IMAGE", -1)) != -1: assert gated_read_image_count == allowed_gated_read_image, f"different gated read_image! {gated_read_image_count=}, {allowed_gated_read_image=}" with open(OUTPUT, "wb") as f: dump_pickle(run_onnx_jit, f) mdl_sz = os.path.getsize(onnx_file) pkl_sz = os.path.getsize(OUTPUT) print(f"mdl size is {mdl_sz/1e6:.2f}M") print(f"pkl size is {pkl_sz/1e6:.2f}M") print("**** compile done ****") return inputs, test_val def test_vs_compile(run, inputs, test_val=None): # run 20 times step_times = [] for _ in range(20): st = time.perf_counter() out = run(**inputs) mt = time.perf_counter() val = out.numpy() et = time.perf_counter() step_times.append((et-st)*1e3) print(f"enqueue {(mt-st)*1e3:6.2f} ms -- total run {step_times[-1]:6.2f} ms") if (assert_time:=getenv("ASSERT_MIN_STEP_TIME", 0.0)): min_time = min(step_times) assert min_time < assert_time, f"Speed regression, expected min step time of < {assert_time} ms but took: {min_time} ms" if test_val is not None: np.testing.assert_equal(test_val, val) print("**** test done ****") # test that changing the numpy changes the model outputs inputs_2x = {k: Tensor(v.numpy()*2, device=v.device) for k,v in inputs.items()} out = run(**inputs_2x) changed_val = out.numpy() np.testing.assert_raises(AssertionError, np.testing.assert_array_equal, val, changed_val) return val def test_vs_onnx(new_inputs, test_val, onnx_file, tol): import onnx import onnxruntime as ort onnx_inputs = {k:v.numpy() for k,v in new_inputs.items()} onnx_model = onnx.load(onnx_file) ORT_TO_NP_DTYPES: dict[str, np.dtype] = { 'tensor(float)': np.dtype('float32'), 'tensor(float16)': np.dtype('float16'), 'tensor(uint8)': np.dtype('uint8'), } timings = [] onnx_session = ort.InferenceSession(onnx_file) onnx_types = {x.name: ORT_TO_NP_DTYPES[x.type] for x in onnx_session.get_inputs()} onnx_inputs = {k:onnx_inputs[k].astype(onnx_types[k]) for k in onnx_inputs} for _ in range(1 if test_val is not None else 5): st = time.perf_counter() onnx_output = onnx_session.run([onnx_model.graph.output[0].name], onnx_inputs) timings.append(time.perf_counter() - st) np.testing.assert_allclose(onnx_output[0].reshape(test_val.shape), test_val, atol=tol, rtol=tol) print("test vs onnx passed") return timings def bench(run, inputs): from extra.bench_log import WallTimeEvent, BenchEvent for _ in range(10): with WallTimeEvent(BenchEvent.STEP): run(**inputs).numpy() if __name__ == "__main__": if getenv("RUN_PICKLE"): with open(OUTPUT, "rb") as f: pickle_loaded = load_pickle(f) inputs = {name: Tensor(Tensor.randn(*view.shape, dtype=dtype).numpy(), device=device) for name, (view, _vars, dtype, device) in zip(pickle_loaded.captured.expected_names, pickle_loaded.captured.expected_input_info)} test_vs_compile(pickle_loaded, inputs) else: onnx_file = fetch(OPENPILOT_MODEL) inputs, outputs = compile(onnx_file) with open(OUTPUT, "rb") as f: pickle_loaded = load_pickle(f) test_vs_compile(pickle_loaded, inputs, outputs) if getenv("SELFTEST"): test_vs_onnx(inputs, outputs, onnx_file, 1e-4) if getenv("BENCHMARK_LOG", ""): bench(pickle_loaded, inputs)