Files
github-actions[bot] f9fcc7adab sunnypilot v2026.002.000 release
date: 2026-06-28T09:48:35
master commit: da6313dbe95b3f24bb5d8018b0e5f950f5823ca7
2026-06-28 09:49:29 +08:00

205 lines
7.0 KiB
Python

"""
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 pickle
import pytest
import openpilot.sunnypilot.models.helpers as helpers
import openpilot.sunnypilot.modeld_v2.modeld as modeld_module
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
class DummyOverride:
def __init__(self, key, value):
self.key = key
self.value = value
class DummyArtifact:
def __init__(self, file_name):
self.fileName = file_name
class DummyModelType:
def __init__(self, raw):
self.raw = raw
class DummyModel:
def __init__(self, type_str, artifact_file):
self.type = DummyModelType(type_str)
self.artifact = DummyArtifact(artifact_file)
class DummyBundle:
def __init__(self, is_20hz=False, models=None, generation=10):
self.overrides = [DummyOverride('lat', '.1'), DummyOverride('long', '.3')]
self.generation = generation
self.is20hz = is_20hz
self.models = models or []
class Archetype:
def __init__(self, name, metadata_structure, model_stubs, is_20hz,
expected_model_type, expected_constants_class, expected_parser_module,
expected_desire_key):
self.name = name
self.metadata_structure = metadata_structure
self.model_stubs = model_stubs
self.is_20hz = is_20hz
self.expected_model_type = expected_model_type
self.expected_constants_class = expected_constants_class
self.expected_parser_module = expected_parser_module
self.expected_desire_key = expected_desire_key
def _noop_jit(**kwargs):
pass
def _make_vision_policy_metadata(vision_input_shapes, policy_input_shapes,
vision_output_slices, policy_output_slices):
return {
'vision': {'input_shapes': vision_input_shapes, 'output_slices': vision_output_slices},
'policy': {'input_shapes': policy_input_shapes, 'output_slices': policy_output_slices},
}
def _make_multi_policy_metadata(vision_input_shapes, policy_input_shapes,
vision_output_slices, policy_output_slices):
return {
'vision': {'input_shapes': vision_input_shapes, 'output_slices': vision_output_slices},
'offPolicy': {'input_shapes': policy_input_shapes, 'output_slices': policy_output_slices},
}
def _make_tri_policy_metadata(vision_input_shapes, policy_input_shapes,
vision_output_slices, policy_output_slices):
return {
'vision': {'input_shapes': vision_input_shapes, 'output_slices': vision_output_slices},
'onPolicy': {'input_shapes': policy_input_shapes, 'output_slices': policy_output_slices},
'offPolicy': {'input_shapes': policy_input_shapes, 'output_slices': policy_output_slices},
}
def _make_supercombo_metadata(input_shapes, output_slices):
return {'model': {'input_shapes': input_shapes, 'output_slices': output_slices}}
SPLIT_VISION_INPUT_SHAPES = {'img': (1, 12, 128, 256), 'big_img': (1, 12, 128, 256)}
SPLIT_POLICY_INPUT_SHAPES = {'features_buffer': (1, 25, 512), 'desire_pulse': (1, 25, 8), 'traffic_convention': (1, 2)}
SPLIT_VISION_SLICES = {'hidden_state': slice(0, 512), 'pose': slice(512, 524)}
SPLIT_POLICY_SLICES = {'plan': slice(0, 495), 'meta': slice(495, 550)}
SUPERCOMBO_INPUT_SHAPES = {
'img': (1, 12, 128, 256), 'big_img': (1, 12, 128, 256),
'desire': (1, 100, 8), 'features_buffer': (1, 99, 512),
'lateral_control_params': (1, 2), 'prev_desired_curv': (1, 100, 1),
'traffic_convention': (1, 2),
}
SUPERCOMBO_SLICES = {'plan': slice(0, 495), 'hidden_state': slice(495, 1007), 'meta': slice(1007, 1062)}
CAM_W, CAM_H = 1928, 1208
ARCHETYPES = {
'vision_policy_split': Archetype(
name='vision_policy_split',
metadata_structure=_make_vision_policy_metadata(
SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES,
SPLIT_VISION_SLICES, SPLIT_POLICY_SLICES),
model_stubs=[DummyModel('vision', 'driving_test_tinygrad.pkl'),
DummyModel('policy', 'driving_test_tinygrad.pkl')],
is_20hz=True,
expected_model_type='split',
expected_constants_class=SplitModelConstants,
expected_parser_module='parse_model_outputs_split',
expected_desire_key='desire',
),
'vision_multi_policy': Archetype(
name='vision_multi_policy',
metadata_structure=_make_multi_policy_metadata(
SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES,
SPLIT_VISION_SLICES, SPLIT_POLICY_SLICES),
model_stubs=[DummyModel('vision', 'driving_test_tinygrad.pkl'),
DummyModel('offPolicy', 'driving_test_tinygrad.pkl')],
is_20hz=True,
expected_model_type='multi_policy',
expected_constants_class=SplitModelConstants,
expected_parser_module='parse_model_outputs_split',
expected_desire_key='desire',
),
'tri_policy': Archetype(
name='tri_policy',
metadata_structure=_make_tri_policy_metadata(
SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES,
SPLIT_VISION_SLICES, SPLIT_POLICY_SLICES),
model_stubs=[DummyModel('vision', 'driving_test_tinygrad.pkl'),
DummyModel('onPolicy', 'driving_test_tinygrad.pkl'),
DummyModel('offPolicy', 'driving_test_tinygrad.pkl')],
is_20hz=True,
expected_model_type='multi_policy',
expected_constants_class=SplitModelConstants,
expected_parser_module='parse_model_outputs_split',
expected_desire_key='desire',
),
'supercombo_non20hz': Archetype(
name='supercombo_non20hz',
metadata_structure=_make_supercombo_metadata(SUPERCOMBO_INPUT_SHAPES, SUPERCOMBO_SLICES),
model_stubs=[DummyModel('supercombo', 'driving_test_tinygrad.pkl')],
is_20hz=False,
expected_model_type='supercombo',
expected_constants_class=ModelConstants,
expected_parser_module='parse_model_outputs',
expected_desire_key='desire',
),
}
def make_pkl_data(archetype):
return {
'metadata': archetype.metadata_structure,
(CAM_W, CAM_H): {'run_policy': _noop_jit, 'warp_enqueue': _noop_jit},
}
def write_pkl(tmp_path, archetype):
pkl_path = tmp_path / 'driving_test_tinygrad.pkl'
with open(pkl_path, 'wb') as f:
pickle.dump(make_pkl_data(archetype), f)
return pkl_path
def make_bundle(archetype):
return DummyBundle(
models=archetype.model_stubs,
is_20hz=archetype.is_20hz,
)
@pytest.fixture
def patch_modeld(monkeypatch):
def _patch(bundle):
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None: bundle, raising=False)
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None: bundle, raising=False)
return _patch
@pytest.fixture
def model_state_factory(tmp_path, monkeypatch, patch_modeld):
from openpilot.system.hardware import hw
def _create(archetype):
write_pkl(tmp_path, archetype)
bundle = make_bundle(archetype)
patch_modeld(bundle)
monkeypatch.setattr(hw.Paths, 'model_root', staticmethod(lambda: str(tmp_path)))
return modeld_module.ModelState(cam_w=CAM_W, cam_h=CAM_H)
return _create