mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-23 02:23:47 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10e05bbf32 |
@@ -16,15 +16,6 @@ MASTER_SP_BRANCHES = ['master']
|
|||||||
RELEASE_BRANCHES = ['release-tizi-staging', 'release-mici-staging', 'release-tizi', 'release-mici', 'nightly']
|
RELEASE_BRANCHES = ['release-tizi-staging', 'release-mici-staging', 'release-tizi', 'release-mici', 'nightly']
|
||||||
TESTED_BRANCHES = RELEASE_BRANCHES + ['devel-staging', 'nightly-dev'] + RELEASE_SP_BRANCHES + TESTED_SP_BRANCHES
|
TESTED_BRANCHES = RELEASE_BRANCHES + ['devel-staging', 'nightly-dev'] + RELEASE_SP_BRANCHES + TESTED_SP_BRANCHES
|
||||||
|
|
||||||
CHESTNUT_BRANCHES = {
|
|
||||||
"staging": "staging-chestnut",
|
|
||||||
"dev": "dev-chestnut",
|
|
||||||
"release-mici": "release-chestnut",
|
|
||||||
"release-tizi": "release-chestnut",
|
|
||||||
"release-mici-staging": "release-chestnut-staging",
|
|
||||||
"release-tizi-staging": "release-chestnut-staging",
|
|
||||||
}
|
|
||||||
|
|
||||||
SP_BRANCH_MIGRATIONS = {
|
SP_BRANCH_MIGRATIONS = {
|
||||||
("tici", "staging-c3-new"): "staging-tici",
|
("tici", "staging-c3-new"): "staging-tici",
|
||||||
("tici", "dev-c3-new"): "staging-tici",
|
("tici", "dev-c3-new"): "staging-tici",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"_comment": "Set extra field to the failed reason."
|
"_comment": "Set extra field to the failed reason."
|
||||||
},
|
},
|
||||||
"Offroad_ChestnutBranch": {
|
"Offroad_ChestnutBranch": {
|
||||||
"text": "Chestnut detected! Switch to the %1 branch to use chestnut-class models.",
|
"text": "Chestnut detected! Switch to the release-chestnut branch to use chestnut-class models.",
|
||||||
"severity": 0
|
"severity": 0
|
||||||
},
|
},
|
||||||
"Offroad_UnregisteredHardware": {
|
"Offroad_UnregisteredHardware": {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define SUNNYPILOT_VERSION "2026.08.22-4721"
|
#define SUNNYPILOT_VERSION "2026.08.22-4716"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import math
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -67,15 +66,14 @@ def get_policy_npy_shapes(input_shapes: dict, is_supercombo: bool = False) -> tu
|
|||||||
if desire_key:
|
if desire_key:
|
||||||
shapes['desire'] = (input_shapes[desire_key][2],)
|
shapes['desire'] = (input_shapes[desire_key][2],)
|
||||||
|
|
||||||
|
if is_supercombo and 'features_buffer' in input_shapes:
|
||||||
|
fb = input_shapes['features_buffer']
|
||||||
|
shapes['prev_feat'] = (fb[0], fb[2])
|
||||||
|
|
||||||
for key, shape in input_shapes.items():
|
for key, shape in input_shapes.items():
|
||||||
if key not in (desire_key, 'features_buffer') and 'img' not in key:
|
if key not in (desire_key, 'features_buffer') and 'img' not in key:
|
||||||
shapes[key] = tuple(shape)
|
shapes[key] = tuple(shape)
|
||||||
|
|
||||||
if is_supercombo and 'features_buffer' in input_shapes:
|
|
||||||
fb = input_shapes['features_buffer']
|
|
||||||
feat_dim = math.prod(fb[2:])
|
|
||||||
shapes['prev_feat'] = (fb[0], feat_dim)
|
|
||||||
|
|
||||||
sizes = [int(np.prod(size)) for size in shapes.values()]
|
sizes = [int(np.prod(size)) for size in shapes.values()]
|
||||||
return shapes, sizes
|
return shapes, sizes
|
||||||
|
|
||||||
@@ -119,9 +117,8 @@ def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = D
|
|||||||
}
|
}
|
||||||
|
|
||||||
if features_buffer:
|
if features_buffer:
|
||||||
feat_dim = math.prod(features_buffer[2:])
|
|
||||||
feat_q_len = frame_skip * features_buffer[1] if is_supercombo else frame_skip * (features_buffer[1] - 1) + 1
|
feat_q_len = frame_skip * features_buffer[1] if is_supercombo else frame_skip * (features_buffer[1] - 1) + 1
|
||||||
queues['feat_q'] = Tensor(np.zeros((feat_q_len, features_buffer[0], feat_dim),
|
queues['feat_q'] = Tensor(np.zeros((feat_q_len, features_buffer[0], features_buffer[2]),
|
||||||
dtype=np.float32), device=device).contiguous().realize()
|
dtype=np.float32), device=device).contiguous().realize()
|
||||||
|
|
||||||
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')})
|
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')})
|
||||||
@@ -186,14 +183,14 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
|
|||||||
warped_dev = warped.to(Device.DEFAULT)
|
warped_dev = warped.to(Device.DEFAULT)
|
||||||
Tensor.realize(packed_npy_inputs_dev, warped_dev)
|
Tensor.realize(packed_npy_inputs_dev, warped_dev)
|
||||||
|
|
||||||
img = shift_and_sample(img_q, warped_dev[0:1], sample_skip_fn)
|
img = shift_and_sample(img_q, warped_dev[0:1], sample_skip_fn).realize()
|
||||||
big_img = shift_and_sample(big_img_q, warped_dev[1:2], sample_skip_fn)
|
big_img = shift_and_sample(big_img_q, warped_dev[1:2], sample_skip_fn).realize()
|
||||||
|
|
||||||
unpacked_tensors = [tensor.reshape(shape) for tensor, shape in zip(packed_npy_inputs_dev.split(npy_sizes), npy_shapes.values(), strict=True)]
|
unpacked_tensors = [tensor.reshape(shape) for tensor, shape in zip(packed_npy_inputs_dev.split(npy_sizes), npy_shapes.values(), strict=True)]
|
||||||
unpacked_dict = dict(zip(npy_shapes.keys(), unpacked_tensors, strict=True))
|
unpacked_dict = dict(zip(npy_shapes.keys(), unpacked_tensors, strict=True))
|
||||||
|
|
||||||
desire_dev = unpacked_dict['desire']
|
desire_dev = unpacked_dict['desire']
|
||||||
desire_buf = shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn)
|
desire_buf = shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn).realize()
|
||||||
|
|
||||||
inputs = {desire_key: desire_buf}
|
inputs = {desire_key: desire_buf}
|
||||||
for key, tensor_val in unpacked_dict.items():
|
for key, tensor_val in unpacked_dict.items():
|
||||||
@@ -202,22 +199,19 @@ def make_run_policy(vision_runner, policy_runners: list, features_slice: slice,
|
|||||||
|
|
||||||
if 'prev_feat' in unpacked_dict:
|
if 'prev_feat' in unpacked_dict:
|
||||||
prev_feat_dev = unpacked_dict['prev_feat']
|
prev_feat_dev = unpacked_dict['prev_feat']
|
||||||
feat_buf = shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn)
|
inputs['features_buffer'] = shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn).realize()
|
||||||
inputs['features_buffer'] = feat_buf if len(fb := input_shapes['features_buffer']) <= 3 else feat_buf.reshape(fb)
|
|
||||||
|
|
||||||
if vision_runner:
|
if vision_runner:
|
||||||
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
|
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
|
||||||
if 'features_buffer' not in inputs:
|
if 'features_buffer' not in inputs:
|
||||||
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
|
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
|
||||||
feat_buf = shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
inputs['features_buffer'] = shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
||||||
inputs['features_buffer'] = feat_buf if len(fb := input_shapes['features_buffer']) <= 3 else feat_buf.reshape(fb)
|
|
||||||
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
|
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
|
||||||
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
|
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
|
||||||
|
|
||||||
inputs.update({road_key: img, wide_key: big_img})
|
inputs.update({road_key: img, wide_key: big_img})
|
||||||
if 'features_buffer' not in inputs:
|
if 'features_buffer' not in inputs:
|
||||||
feat_buf = sample_skip_fn(feat_q)
|
inputs['features_buffer'] = sample_skip_fn(feat_q)
|
||||||
inputs['features_buffer'] = feat_buf if len(fb := input_shapes['features_buffer']) <= 3 else feat_buf.reshape(fb)
|
|
||||||
|
|
||||||
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
|
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
|
||||||
if 'features_buffer' not in inputs and features_slice is not None:
|
if 'features_buffer' not in inputs and features_slice is not None:
|
||||||
|
|||||||
@@ -195,85 +195,3 @@ class TestReadFileChunkedToDisk(OpenpilotTestCase):
|
|||||||
|
|
||||||
assert out.parent == Path(d)
|
assert out.parent == Path(d)
|
||||||
assert out.read_bytes() == payload
|
assert out.read_bytes() == payload
|
||||||
|
|
||||||
|
|
||||||
class Test4DFeaturesBuffer(OpenpilotTestCase):
|
|
||||||
def test_get_policy_npy_shapes_4d(self):
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import get_policy_npy_shapes
|
|
||||||
input_shapes = {
|
|
||||||
'desire_pulse': (1, 25, 8),
|
|
||||||
'features_buffer': (1, 24, 32, 512), # compare 4d to 3d for regression
|
|
||||||
'traffic_convention': (1, 2),
|
|
||||||
'action_t': (1, 2)
|
|
||||||
}
|
|
||||||
shapes, sizes = get_policy_npy_shapes(input_shapes, is_supercombo=True)
|
|
||||||
assert shapes['prev_feat'] == (1, 16384)
|
|
||||||
assert sizes == [8, 2, 2, 16384]
|
|
||||||
|
|
||||||
def test_get_policy_npy_shapes_3d(self):
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import get_policy_npy_shapes
|
|
||||||
input_shapes = {
|
|
||||||
'desire_pulse': (1, 25, 8),
|
|
||||||
'features_buffer': (1, 24, 512),
|
|
||||||
'traffic_convention': (1, 2),
|
|
||||||
'action_t': (1, 2)
|
|
||||||
}
|
|
||||||
shapes, sizes = get_policy_npy_shapes(input_shapes, is_supercombo=True)
|
|
||||||
assert shapes['prev_feat'] == (1, 512)
|
|
||||||
assert sizes == [8, 2, 2, 512]
|
|
||||||
|
|
||||||
|
|
||||||
class TestStockCompileModeldEquivalence(OpenpilotTestCase):
|
|
||||||
def test_get_policy_npy_shapes_matches_stock(self):
|
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import get_policy_npy_shapes as stock_get_policy_npy_shapes
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import get_policy_npy_shapes as sunny_get_policy_npy_shapes
|
|
||||||
|
|
||||||
stock_input_shapes = {
|
|
||||||
'desire_pulse': (1, 25, 8),
|
|
||||||
'features_buffer': (1, 24, 512), # see below comment
|
|
||||||
'traffic_convention': (1, 2),
|
|
||||||
'action_t': (1, 2),
|
|
||||||
}
|
|
||||||
|
|
||||||
stock_shapes, stock_sizes = stock_get_policy_npy_shapes(stock_input_shapes)
|
|
||||||
sunny_shapes, sunny_sizes = sunny_get_policy_npy_shapes(stock_input_shapes, is_supercombo=True)
|
|
||||||
|
|
||||||
assert sunny_shapes == stock_shapes
|
|
||||||
assert sunny_sizes == stock_sizes
|
|
||||||
assert sunny_shapes['prev_feat'] == (1, 512)
|
|
||||||
|
|
||||||
def test_make_input_queues_full_stock_equivalence(self):
|
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues as stock_make_input_queues
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_supercombo_input_queues as sunny_make_supercombo_input_queues
|
|
||||||
input_shapes = {
|
|
||||||
'img': (1, 12, 128, 256),
|
|
||||||
'desire_pulse': (1, 25, 8),
|
|
||||||
'features_buffer': (1, 24, 512), # when https://github.com/commaai/openpilot/pull/38681 merges, update to 1,24,32,512
|
|
||||||
'traffic_convention': (1, 2),
|
|
||||||
'action_t': (1, 2),
|
|
||||||
}
|
|
||||||
frame_skip = 4
|
|
||||||
|
|
||||||
stock_queues, stock_npy = stock_make_input_queues(input_shapes, frame_skip, device='NPY')
|
|
||||||
sunny_queues, sunny_npy = sunny_make_supercombo_input_queues(input_shapes, frame_skip, device='NPY')
|
|
||||||
assert set(sunny_queues.keys()) == set(stock_queues.keys())
|
|
||||||
for key in stock_queues:
|
|
||||||
assert sunny_queues[key].shape == stock_queues[key].shape, \
|
|
||||||
f"Queue shape mismatch for {key}: sunny {sunny_queues[key].shape} != stock {stock_queues[key].shape}"
|
|
||||||
assert set(sunny_npy.keys()) == set(stock_npy.keys())
|
|
||||||
for key in stock_npy:
|
|
||||||
assert sunny_npy[key].shape == stock_npy[key].shape, \
|
|
||||||
f"Numpy array shape mismatch for {key}: sunny {sunny_npy[key].shape} != stock {stock_npy[key].shape}"
|
|
||||||
|
|
||||||
def test_make_warp_queues_stock_equivalence(self):
|
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import make_warp_input_queues as stock_make_warp_queues
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_warp_queues as sunny_make_warp_queues
|
|
||||||
stock_vision_shapes = {'img': (1, 12, 128, 256)} # for now?
|
|
||||||
stock_queues, stock_npy = stock_make_warp_queues(stock_vision_shapes, frame_skip=4, device='NPY')
|
|
||||||
sunny_queues, sunny_npy = sunny_make_warp_queues(device='NPY')
|
|
||||||
|
|
||||||
assert set(sunny_npy.keys()) == set(stock_npy.keys()) == {'tfm', 'big_tfm'}
|
|
||||||
for key in sunny_npy:
|
|
||||||
assert sunny_npy[key].shape == stock_npy[key].shape == (3, 3)
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class ModelCache:
|
|||||||
class ModelFetcher:
|
class ModelFetcher:
|
||||||
"""Handles fetching and caching of model data from remote source"""
|
"""Handles fetching and caching of model data from remote source"""
|
||||||
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v20.json"
|
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v20.json"
|
||||||
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v22.json"
|
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v21.json"
|
||||||
|
|
||||||
def __init__(self, params: Params):
|
def __init__(self, params: Params):
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from openpilot.common.swaglog import cloudlog
|
|||||||
from openpilot.sunnypilot.system.statsd import statlog
|
from openpilot.sunnypilot.system.statsd import statlog
|
||||||
from openpilot.system.hardware.power_monitoring import PowerMonitoring
|
from openpilot.system.hardware.power_monitoring import PowerMonitoring
|
||||||
from openpilot.system.hardware.fan_controller import FanController
|
from openpilot.system.hardware.fan_controller import FanController
|
||||||
from openpilot.common.version import terms_version, training_version, get_build_metadata, terms_version_sp, CHESTNUT_BRANCHES
|
from openpilot.common.version import terms_version, training_version, get_build_metadata, terms_version_sp
|
||||||
|
|
||||||
|
|
||||||
ThermalStatus = log.DeviceState.ThermalStatus
|
ThermalStatus = log.DeviceState.ThermalStatus
|
||||||
@@ -301,11 +301,7 @@ def hardware_thread(end_event, hw_queue) -> None:
|
|||||||
|
|
||||||
set_usb_state(msg.deviceState, last_hw_state.usb_state)
|
set_usb_state(msg.deviceState, last_hw_state.usb_state)
|
||||||
chestnut.update(started_ts is None, last_hw_state.usb_state)
|
chestnut.update(started_ts is None, last_hw_state.usb_state)
|
||||||
current_channel = get_build_metadata().channel
|
set_offroad_alert_if_changed("Offroad_ChestnutBranch", msg.deviceState.chestnutPresent and not big_model_available)
|
||||||
chestnut_target = CHESTNUT_BRANCHES.get(current_channel)
|
|
||||||
chestnut_needs_switch = msg.deviceState.chestnutPresent and not big_model_available and chestnut_target is not None
|
|
||||||
set_offroad_alert_if_changed("Offroad_ChestnutBranch", chestnut_needs_switch,
|
|
||||||
extra_text=chestnut_target if chestnut_needs_switch else None)
|
|
||||||
|
|
||||||
# this subset is only used for offroad
|
# this subset is only used for offroad
|
||||||
temp_sources = [
|
temp_sources = [
|
||||||
|
|||||||
Reference in New Issue
Block a user