From b67898fac4e99d8850ed39fb545b00badc4ca227 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 11 Sep 2026 20:24:12 -0700 Subject: [PATCH] models: test tinygrad concurrency (#2006) --- .github/workflows/test_models.yml | 79 ++++++++++++++ openpilot/sunnypilot/modeld_v2/helpers.py | 100 ++++++++++++++++++ openpilot/sunnypilot/modeld_v2/modeld.py | 3 +- .../sunnypilot/modeld_v2/tests/test_models.py | 43 ++++++++ .../models/tests/test_tinygrad_ref.py | 24 ----- tinygrad_repo | 2 +- 6 files changed, 225 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/test_models.yml create mode 100644 openpilot/sunnypilot/modeld_v2/helpers.py create mode 100644 openpilot/sunnypilot/modeld_v2/tests/test_models.py delete mode 100644 openpilot/sunnypilot/models/tests/test_tinygrad_ref.py diff --git a/.github/workflows/test_models.yml b/.github/workflows/test_models.yml new file mode 100644 index 000000000..0a1a463aa --- /dev/null +++ b/.github/workflows/test_models.yml @@ -0,0 +1,79 @@ +name: Test Models Compatibility With Tinygrad Changes +on: + pull_request: + paths: + - 'tinygrad_repo' + workflow_dispatch: + +jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + models: ${{ steps.set-matrix.outputs.models }} + steps: + - uses: actions/checkout@v4 + - name: Fetch and Parse json + id: set-matrix + run: | + python3 -c ' + import json, urllib.request, os, re + + with open("openpilot/sunnypilot/models/fetcher.py", "r") as f: + urls = re.findall(r"MODEL_URL(?:_CHESTNUT)?\s*=\s*[\"'"'"']([^\"'"'"']+)[\"'"'"']", f.read()) + + artifacts = [] + for url in urls: + data = json.loads(urllib.request.urlopen(url).read()) + for bundle in data.get("bundles", []): + for model in bundle.get("models", []): + if "artifact" in model: + artifacts.append(model["artifact"]) + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write(f"models={json.dumps(artifacts)}\n") + ' + + test-model: + name: Test ${{ matrix.artifact.file_name }} + needs: generate-matrix + runs-on: ubuntu-latest + container: ghcr.io/commaai/openpilot-base:latest + strategy: + fail-fast: false + matrix: + artifact: ${{ fromJson(needs.generate-matrix.outputs.models) }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Download Model Chunks in Parallel + run: | + mkdir -p /tmp/model_chunks + echo '${{ toJson(matrix.artifact.chunks) }}' > chunks.json + + BASE_URL="${{ matrix.artifact.download_uri.url }}" + export BASE_DIR=$(dirname "$BASE_URL") + + python3 -c ' + import json, os + with open("chunks.json") as f: + chunks = json.load(f) + manifest_path = f"/tmp/model_chunks/${{ matrix.artifact.file_name }}.chunkmanifest" + with open(manifest_path, "w") as f: + f.write(str(len(chunks))) + base_dir = os.environ["BASE_DIR"] + with open("/tmp/curl_config.txt", "w") as f: + for c in chunks: + fn = c["file_name"] + f.write(f"url = \"{base_dir}/{fn}\"\noutput = \"/tmp/model_chunks/{fn}\"\n") + ' + curl -Z --parallel-immediate --parallel-max 16 -s -S -f -L -K /tmp/curl_config.txt + + - name: Run Model Compatibility Test + env: + MODEL_BASE_NAME: ${{ matrix.artifact.file_name }} + MODEL_CHUNK_DIR: "/tmp/model_chunks" + PYTHONPATH: ".:./tinygrad_repo" + run: | + python3 -m pytest openpilot/sunnypilot/modeld_v2/tests/test_models.py diff --git a/openpilot/sunnypilot/modeld_v2/helpers.py b/openpilot/sunnypilot/modeld_v2/helpers.py new file mode 100644 index 000000000..5beb52ac7 --- /dev/null +++ b/openpilot/sunnypilot/modeld_v2/helpers.py @@ -0,0 +1,100 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import io +import struct +import pickle +import inspect +import importlib +import enum + + +def _pad_args(func, args, kwargs): + try: + sig = inspect.signature(func) + except Exception: + return args, kwargs + params = list(sig.parameters.values()) + if inspect.isfunction(func) and params and params[0].name in ('cls', 'self'): + params = params[1:] + + new_args = list(args) + has_varargs = any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params) + if len(new_args) > len(params) and not has_varargs: + new_args = new_args[:len(params)] + + for i in range(len(new_args), len(params)): + param = params[i] + if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): + continue + val = param.default if param.default is not inspect.Parameter.empty else None + new_args.append(val) + return new_args, kwargs + + +def _enum_factory(enum_class): + def factory(*args, **kwargs): + try: + return enum_class(*args, **kwargs) + # OptOps and UOp objects in the .pkl are left over from the compilation phase, + # reassignment does nothing because they aren't tied to the execution graph + # It never executes or evaluates the UOp nodes again. + except ValueError: + return list(enum_class)[0] + factory.__name__ = enum_class.__name__ + factory.__module__ = enum_class.__module__ + return factory + + +def _dynamic_factory(real_class): + if isinstance(real_class, type) and issubclass(real_class, enum.Enum): + return _enum_factory(real_class) + + def factory(*args, **kwargs): + try: + return real_class(*args, **kwargs) + except TypeError: + new_args, new_kwargs = _pad_args(real_class, args, kwargs) + return real_class(*new_args, **new_kwargs) + + class DynamicMeta(type(real_class)): + def __call__(cls, *args, **kwargs): + return factory(*args, **kwargs) + + class DynamicProxy(real_class, metaclass=DynamicMeta): + __slots__ = () + + def __new__(cls, *args, **kwargs): + return factory(*args, **kwargs) + + DynamicProxy.__name__ = real_class.__name__ + DynamicProxy.__module__ = real_class.__module__ + return DynamicProxy + + +class DynamicTinygradUnpickler(pickle.Unpickler): + def find_class(self, module, name): + if module == "tinygrad.ops": + try: + importlib.import_module("tinygrad.uops") + module = "tinygrad.uops" + except ImportError: + pass + real_class = getattr(importlib.import_module(module), name) + if module.startswith("tinygrad"): + return _dynamic_factory(real_class) + return real_class + + +def load_oob(f): + opcodes = f.read(struct.unpack('