From f896dfe25a6290e13b11bd90205be74180bdf02c Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Thu, 23 Apr 2026 10:49:44 -0500 Subject: [PATCH] modeld: cached-output standby while gear is in park Narrow re-introduction of the power-save mode: when carState reports gearShifter == park, serve the last rendered model_output instead of running GPU inference. First cycle (or before carState is flowing) still runs one real inference so we have something to cache and serve. Out-of-park returns to per-frame inference immediately. Avoids the pitfall of the earlier variable-rate path: this doesn't change the publish rate on modelV2/cameraOdometry (we still publish every frame), so downstream freq_ok / valid checks in calibrationd / locationd / paramsd stay happy. Only the GPU inference is skipped. Co-Authored-By: Claude Opus 4.7 (1M context) --- selfdrive/modeld/modeld.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index fb7b765..6f7a0de 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -183,6 +183,10 @@ def main(demo=False): model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32) live_calib_seen = False + # CLEARPILOT: cache last model output to serve while gear is in park — saves + # GPU inference cost while still giving downstream a constant publish rate so + # freq_ok / valid checks don't cascade. + last_model_output = None nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None @@ -314,12 +318,21 @@ def main(demo=False): **({'radar_tracks': radar_tracks,} if DISABLE_RADAR else {}), } - mt1 = time.perf_counter() - model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only) - mt2 = time.perf_counter() - model_execution_time = mt2 - mt1 + # CLEARPILOT: in park, serve the cached last model output instead of running + # GPU inference. First cycle (no cache yet) still runs once so we have + # something to serve. Out-of-park resumes fresh inference every frame. + parked = sm['carState'].gearShifter == car.CarState.GearShifter.park + if parked and last_model_output is not None: + model_output = last_model_output + model_execution_time = 0.0 + else: + mt1 = time.perf_counter() + model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only) + mt2 = time.perf_counter() + model_execution_time = mt2 - mt1 if model_output is not None: + last_model_output = model_output modelv2_send = messaging.new_message('modelV2') posenet_send = messaging.new_message('cameraOdometry') fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio,