mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-10 00:34:55 +08:00
Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e459f9987 | |||
| 66fc6cfac8 | |||
| a43f9055fe | |||
| 9ca34dee2a | |||
| 0999b0cbe9 | |||
| ed1121e61c | |||
| 50d7f75bfc | |||
| 15c7f52e40 | |||
| 8dfe04a318 | |||
| 9648ac2f04 | |||
| a03a11e333 | |||
| 53b1070b09 | |||
| 68bf6d8162 | |||
| 8b472291d0 | |||
| 797c8d4293 | |||
| 7568528507 | |||
| 6665c3acf6 | |||
| 3b13eb4d0d | |||
| f7eb4c2561 | |||
| be9255358c | |||
| f48ccb5d7a | |||
| 99559d6749 | |||
| 4b6f0ffb46 | |||
| 55415382ce | |||
| 9f30901eb6 | |||
| d2045d24fb | |||
| 6488a11e4d | |||
| 7b22b65313 | |||
| b59a50101e | |||
| 3e30962bd3 | |||
| 3274043063 | |||
| c624550d2e | |||
| 6759038671 | |||
| aa636e75c8 | |||
| 5ff3c3bd8d | |||
| 1e11b52290 | |||
| 76e0204025 | |||
| cc88f2cbd6 | |||
| ee0ac199a4 | |||
| d5ed828eaa | |||
| 9fca585f2a | |||
| bb1259303e | |||
| e135051ca8 | |||
| 12bef55d8a | |||
| 2f7a45e6c8 | |||
| 936ebfc12b | |||
| aa0c9dc0eb | |||
| 7476a866e7 | |||
| 610d857e33 | |||
| d2f47407d0 | |||
| db75ec76ea | |||
| 24066465d7 | |||
| a7abbd6e25 | |||
| 878982447c | |||
| 576527a36b | |||
| ef8c35da24 | |||
| 85688b1040 | |||
| 0fb2199130 | |||
| d48d756c1d | |||
| 2ed298a0c9 | |||
| d68f038949 | |||
| 7231571e57 | |||
| b37f1419d3 | |||
| cd85a66790 | |||
| 305ea87daf | |||
| 4bbfc793e0 | |||
| d5d983676e | |||
| de8a96a398 | |||
| 0cbf45f699 | |||
| 0d68a3a2ab | |||
| 9e85a85059 | |||
| 0373c327c0 | |||
| efe9e5c200 | |||
| 8a249a45dc | |||
| bdbefe67f6 | |||
| 675bb166ad | |||
| 1b717a7e88 | |||
| 86f55a8ba9 | |||
| 629392d2f7 | |||
| bc414bdc8b | |||
| 7ca5649f2c | |||
| 641ee8fa87 | |||
| 56c276158c | |||
| c65308a8bd | |||
| 994e526460 | |||
| 1defae36b7 | |||
| 8f029fd0ef | |||
| ddb46284dc | |||
| 9effc754d9 | |||
| e49ffc2a2d | |||
| 2cacd0b3e5 | |||
| c4b8859dff | |||
| 8fb0953205 | |||
| 63d1c8835f | |||
| 17a185606d | |||
| da10131392 | |||
| 7107c2ba14 | |||
| 95b6e877ac | |||
| eb02c6570e | |||
| 1be8ae31c4 | |||
| 04dcd38856 | |||
| 22ccf0d72f | |||
| 3c969bb627 | |||
| 20f8011feb | |||
| 9cf17e74a1 | |||
| 2c4efdf557 | |||
| 4cd3d3c16c | |||
| 637f3ae9c8 | |||
| 464ee80f71 | |||
| 2743a04613 | |||
| 7f9978d001 | |||
| 4b83961c67 | |||
| c00eaf428a | |||
| 0a9993e8d4 | |||
| 0af214a985 | |||
| af43385e3a | |||
| 0ab2b8c590 | |||
| 67ab18a0de | |||
| e87dc15b30 | |||
| 192d08516c | |||
| 3cf001c59c | |||
| f2ccd021da | |||
| c9fc900f64 | |||
| 3c37c5ce5d | |||
| 7c45889e4e | |||
| 2aabb7aee8 | |||
| 3859e9962f | |||
| 810efbab72 | |||
| ec27bec326 | |||
| 250d553157 | |||
| cea54a0ca8 | |||
| 8e72d783bd | |||
| 1b0dc103dc | |||
| 6c364d292b | |||
| bcdec2ce84 | |||
| 3deaeb3759 | |||
| c669f0984a | |||
| 46dd946740 | |||
| 9da4b3653e | |||
| 4e21ae7c50 | |||
| bb91e92237 | |||
| 14b4c4f85b | |||
| 0660b542c3 | |||
| 2b893b90c9 | |||
| f5139178ed | |||
| fb43b755f2 | |||
| 07f5b967d8 | |||
| ea19c7d3bb | |||
| e461842cbb | |||
| a73c9659d5 | |||
| cb796fbc76 | |||
| 6bf75fc557 | |||
| 9a1fc28819 | |||
| 0741d05e92 | |||
| 1ad008107d | |||
| feebd9df93 | |||
| c2e5ced3e5 | |||
| 15e5d2efb9 | |||
| a3929d0b54 | |||
| 794f8f9991 | |||
| 68fa5e3f21 | |||
| 86c6cc1f48 | |||
| eb7ffbf093 | |||
| 3919095752 | |||
| 74d63be1c3 | |||
| 8894486a1a | |||
| 810599315d | |||
| 6f3ab810c8 | |||
| 230f78b8d3 | |||
| f1affec088 | |||
| 97d8ef242c | |||
| a63fff9b45 | |||
| cb3893daaa | |||
| 29f60df74b | |||
| c6c072e1f4 | |||
| d101cbb83e | |||
| 1536d59633 | |||
| dc99b865ae | |||
| e59bc027ff | |||
| cf7e5efaca | |||
| 4b44f2eb31 | |||
| 107d2ab400 | |||
| 5432d9062c | |||
| f533f6c843 | |||
| 58e9ac763c | |||
| cb50d54169 | |||
| bd5de4ed0a | |||
| 0d4073fadb | |||
| ebc70dcb52 | |||
| 4d0426999e | |||
| 286da42573 | |||
| 8a836710a9 | |||
| 5d515bcf33 | |||
| 1c7f6d5133 | |||
| 05d57c7aeb | |||
| e4b0eaf352 | |||
| a710276472 | |||
| af086db671 | |||
| 0d9eb0e25e | |||
| 0616caed6d | |||
| 095337b3c1 | |||
| 1edec2d22c | |||
| affabb9ee0 | |||
| dc27e8711c | |||
| cf7329a264 | |||
| 5ee5ecd820 | |||
| b064f730dd |
@@ -1,148 +0,0 @@
|
||||
name: Test Stock vs Sunnypilot Model Equivalence
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
model_ref:
|
||||
description: 'Upstream openpilot commit ref'
|
||||
required: false
|
||||
default: ''
|
||||
pull_request:
|
||||
paths:
|
||||
- 'openpilot/selfdrive/modeld/**'
|
||||
- 'openpilot/sunnypilot/modeld_v2/**'
|
||||
|
||||
jobs:
|
||||
test_stock_parity:
|
||||
name: Compare Stock vs Sunnypilot Model Replay
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: ./tools/op.sh setup
|
||||
- run: scons -j$(nproc 2>/dev/null || sysctl -n hw.logicalcpu) openpilot/cereal msgq_repo openpilot/common
|
||||
- name: Fetch Big Model ONNX
|
||||
run: |
|
||||
mkdir -p /tmp/onnx_models
|
||||
if [ -n "${{ inputs.model_ref }}" ]; then
|
||||
echo "Fetching ONNX from upstream openpilot ref ${{ inputs.model_ref }}..."
|
||||
git clone --depth 1 https://github.com/commaai/openpilot.git /tmp/upstream_openpilot
|
||||
cd /tmp/upstream_openpilot
|
||||
git fetch --depth 1 origin ${{ inputs.model_ref }}
|
||||
git checkout ${{ inputs.model_ref }}
|
||||
git lfs pull -I "**/selfdrive/modeld/models/big_driving_supercombo.onnx"
|
||||
find . -name "big_driving_supercombo.onnx" -exec cp {} /tmp/onnx_models/ \;
|
||||
else
|
||||
cp openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx /tmp/onnx_models/
|
||||
fi
|
||||
|
||||
- name: Compile models
|
||||
env:
|
||||
DEV: "CPU"
|
||||
JIT_BATCH_SIZE: "0"
|
||||
run: |
|
||||
BIG_ONNX="/tmp/onnx_models/big_driving_supercombo.onnx"
|
||||
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||
CAMERA_RESOLUTIONS=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||
|
||||
python3 openpilot/selfdrive/modeld/compile_modeld.py \
|
||||
--onnx "$BIG_ONNX" \
|
||||
--model-size "$MODEL_SIZE" \
|
||||
--camera-resolutions $CAMERA_RESOLUTIONS \
|
||||
--output /tmp/stock_model.pkl \
|
||||
--frame-skip 4 \
|
||||
--benchmark-runs 1 &
|
||||
|
||||
python3 openpilot/sunnypilot/modeld_v2/compile_modeld.py \
|
||||
--model-type supercombo \
|
||||
--supercombo-onnx "$BIG_ONNX" \
|
||||
--model-size "$MODEL_SIZE" \
|
||||
--camera-resolutions $CAMERA_RESOLUTIONS \
|
||||
--output /tmp/sunnypilot_model.pkl \
|
||||
--frame-skip 4 \
|
||||
--benchmark-runs 1 &
|
||||
wait
|
||||
|
||||
- name: Run model replay
|
||||
env:
|
||||
DEV: "CPU"
|
||||
run: |
|
||||
python3 openpilot/sunnypilot/modeld_v2/model_replay.py \
|
||||
--sunnypilot-model /tmp/sunnypilot_model.pkl \
|
||||
--stock-model /tmp/stock_model.pkl \
|
||||
--frames 20 \
|
||||
--plot-dir /tmp/replay_plots
|
||||
|
||||
- name: Upload Replay Plots
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: model_replay_plots_${{ github.event.number || github.sha }}
|
||||
path: /tmp/replay_plots
|
||||
|
||||
- name: Checkout ci-artifacts
|
||||
if: github.repository == 'sunnypilot/sunnypilot' && github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sunnypilot/ci-artifacts
|
||||
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
|
||||
path: ${{ github.workspace }}/ci-artifacts
|
||||
|
||||
- name: Push plots to ci-artifacts
|
||||
if: github.repository == 'sunnypilot/sunnypilot' && github.event_name == 'pull_request'
|
||||
working-directory: ${{ github.workspace }}/ci-artifacts
|
||||
run: |
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "<>"
|
||||
BRANCH="model_replay_pr_${{ github.event.number }}"
|
||||
git fetch origin $BRANCH || true
|
||||
git checkout $BRANCH 2>/dev/null || git checkout --orphan $BRANCH
|
||||
rm -rf plots && mkdir -p plots
|
||||
cp /tmp/replay_plots/*.png plots/
|
||||
echo "${{ github.sha }}" > ref_commit
|
||||
git add plots ref_commit
|
||||
git commit -m "Model replay plots for PR #${{ github.event.number }}@${{ github.sha }}" || echo "No changes to commit"
|
||||
git push origin $BRANCH --force
|
||||
|
||||
- name: Comment Model Replay Report on PR
|
||||
if: github.repository == 'sunnypilot/sunnypilot' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const branch = `model_replay_pr_${prNumber}`;
|
||||
const baseUrl = `https://raw.githubusercontent.com/sunnypilot/ci-artifacts/refs/heads/${branch}/plots`;
|
||||
|
||||
const priorityPlots = ['desiredCurvature.png', 'desiredAcceleration.png', 'velocity.x.png', 'leadsV3.x.png', 'execution_timings.png'];
|
||||
const allFiles = fs.readdirSync('/tmp/replay_plots').filter(f => f.endsWith('.png'));
|
||||
const orderedFiles = [
|
||||
...priorityPlots.filter(f => allFiles.includes(f)),
|
||||
...allFiles.filter(f => !priorityPlots.includes(f)).sort()
|
||||
];
|
||||
|
||||
let table = '<table>';
|
||||
for (let i = 0; i < orderedFiles.length; i += 2) {
|
||||
table += '<tr>';
|
||||
table += `<td><img src="${baseUrl}/${orderedFiles[i]}" alt="${orderedFiles[i]}"><br><b>${orderedFiles[i].replace('.png', '')}</b></td>`;
|
||||
if (i + 1 < orderedFiles.length) {
|
||||
table += `<td><img src="${baseUrl}/${orderedFiles[i+1]}" alt="${orderedFiles[i+1]}"><br><b>${orderedFiles[i+1].replace('.png', '')}</b></td>`;
|
||||
} else {
|
||||
table += '<td></td>';
|
||||
}
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table>';
|
||||
|
||||
const body = `### Model Replay Parity Report for PR #${prNumber} (@${context.sha.substring(0, 7)})\n\n` +
|
||||
`<details><summary>All Model Replay Plots</summary>\n\n${table}\n\n</details>`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: body
|
||||
});
|
||||
+1
-1
Submodule opendbc_repo updated: f95f996f59...f9f1223de9
@@ -62,7 +62,25 @@ class MiciFccModal(NavRawScrollPanel):
|
||||
rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE)
|
||||
|
||||
|
||||
def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False):
|
||||
class DisengageDialog(BigDialog):
|
||||
def __init__(self, title: str, description: str, disengaged_callback: Callable[[], None]):
|
||||
super().__init__(title, description)
|
||||
self._disengaged_callback = disengaged_callback
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
if not ui_state.engaged and not self.is_dismissing:
|
||||
self.dismiss(self._disengaged_callback)
|
||||
|
||||
|
||||
class EngagedConfirmationDialog(BigConfirmationDialog):
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
if ui_state.engaged and not self.is_dismissing:
|
||||
self.dismiss()
|
||||
|
||||
|
||||
def engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False):
|
||||
if not ui_state.engaged:
|
||||
def confirm_callback():
|
||||
# Check engaged again in case it changed while the dialog was open
|
||||
@@ -70,23 +88,26 @@ def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.T
|
||||
if not ui_state.engaged:
|
||||
callback()
|
||||
|
||||
gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red))
|
||||
gui_app.push_widget(EngagedConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback,
|
||||
exit_on_confirm=exit_on_confirm, red=red))
|
||||
else:
|
||||
gui_app.push_widget(BigDialog("", f"Disengage to {action_text}"))
|
||||
gui_app.push_widget(DisengageDialog("", f"Disengage to {action_text}",
|
||||
lambda: engaged_confirmation_click(callback, action_text, icon,
|
||||
exit_on_confirm=exit_on_confirm, red=red)))
|
||||
|
||||
|
||||
class EngagedConfirmationCircleButton(BigCircleButton):
|
||||
def __init__(self, title: str, icon: rl.Texture, callback: Callable[[], None], exit_on_confirm: bool = True,
|
||||
red: bool = False, icon_offset: tuple[int, int] = (0, 0)):
|
||||
super().__init__(icon, red, icon_offset)
|
||||
self.set_click_callback(lambda: _engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red))
|
||||
self.set_click_callback(lambda: engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red))
|
||||
|
||||
|
||||
class EngagedConfirmationButton(BigButton):
|
||||
def __init__(self, text: str, action_text: str, icon: rl.Texture, callback: Callable[[], None],
|
||||
exit_on_confirm: bool = True, red: bool = False):
|
||||
super().__init__(text, "", icon)
|
||||
self.set_click_callback(lambda: _engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red))
|
||||
self.set_click_callback(lambda: engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red))
|
||||
|
||||
|
||||
class DeviceInfoLayoutMici(Widget):
|
||||
|
||||
@@ -6,9 +6,9 @@ See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsBigButton
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, engaged_confirmation_click
|
||||
from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
@@ -93,10 +93,6 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
dlg = BigConfirmationDialog(tr("slide to exit always offroad"), self.icon_offroad_slider, red=False,
|
||||
confirm_callback=lambda: _set_offroad_status(False))
|
||||
else:
|
||||
if ui_state.engaged:
|
||||
gui_app.push_widget(BigDialog(tr("disengage to enable always offroad"), "", ))
|
||||
return
|
||||
|
||||
dlg = BigConfirmationDialog(tr("slide to force offroad"), self.icon_offroad_slider, red=True,
|
||||
confirm_callback=lambda: _set_offroad_status(True))
|
||||
engaged_confirmation_click(lambda: _set_offroad_status(True), "force offroad", self.icon_offroad_slider, red=True)
|
||||
return
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
"""
|
||||
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 argparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from tinygrad.device import Device
|
||||
|
||||
from openpilot.common.file_chunker import open_file_chunked
|
||||
from openpilot.selfdrive.modeld.compile_modeld import MODELD_INPUTS, make_input_queues, nv12_copy_size
|
||||
from openpilot.selfdrive.modeld.helpers import load_oob
|
||||
from openpilot.selfdrive.test.process_replay.model_replay import SEGMENT, TEST_ROUTE
|
||||
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||
from openpilot.tools.lib.framereader import FrameReader
|
||||
from openpilot.tools.lib.openpilotci import get_url
|
||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
||||
|
||||
|
||||
def get_replay_video_source(route_or_path=None, segment_index=SEGMENT, camera_type="fcamera.hevc"):
|
||||
if route_or_path and os.path.exists(route_or_path):
|
||||
return route_or_path
|
||||
selected_route = route_or_path or TEST_ROUTE
|
||||
return get_url(selected_route, segment_index, camera_type)
|
||||
|
||||
|
||||
def initialize_replay_queues(model_dictionary, device="CPU"):
|
||||
metadata = model_dictionary.get("metadata", {})
|
||||
model_meta = metadata.get("model", metadata)
|
||||
input_shapes = model_meta.get("input_shapes", {})
|
||||
cam_resolutions = list(model_dictionary.get("run_model", {}).keys())
|
||||
cam_width, cam_height = cam_resolutions[0] if cam_resolutions else (1928, 1208)
|
||||
nv12_info = get_nv12_info(cam_width, cam_height)
|
||||
frame_copy_size = nv12_copy_size(nv12_info[0], nv12_info[1], nv12_info[2])
|
||||
frame_skip = model_meta.get("frame_skip") or derive_frame_skip({}, input_shapes)
|
||||
queues, npy_views, frame_views = make_input_queues(input_shapes, frame_skip, device, frame_copy_size)
|
||||
if "tfm" in npy_views:
|
||||
npy_views["tfm"][:] = np.eye(3, dtype=np.float32)
|
||||
if "big_tfm" in npy_views:
|
||||
npy_views["big_tfm"][:] = np.eye(3, dtype=np.float32)
|
||||
if "traffic_convention" in npy_views:
|
||||
npy_views["traffic_convention"][:] = np.array([1.0, 0.0], dtype=np.float32)
|
||||
return queues, npy_views, frame_views, model_meta
|
||||
|
||||
|
||||
def replay_model_on_frames(model_path, frame_reader, number_of_frames=20):
|
||||
with open_file_chunked(model_path) as file_handle:
|
||||
model_data = load_oob(file_handle)
|
||||
run_model_dict = model_data.get("run_model", {})
|
||||
runner = next(iter(run_model_dict.values()), None)
|
||||
if runner is None:
|
||||
raise ValueError("Failed to resolve runner from model dictionary")
|
||||
|
||||
queues, npy_views, frame_views, model_meta = initialize_replay_queues(model_data)
|
||||
output_slices = model_meta.get("output_slices", {})
|
||||
hidden_state_slice = output_slices.get("hidden_state")
|
||||
parser = Parser(ignore_missing=True)
|
||||
recorded_outputs = []
|
||||
max_frames = min(number_of_frames, getattr(frame_reader, "frame_count", number_of_frames))
|
||||
|
||||
for frame_index in range(max_frames):
|
||||
frame_raw = frame_reader.get(frame_index)
|
||||
if frame_raw is not None:
|
||||
for view in frame_views.values():
|
||||
copy_length = min(view.size, frame_raw.size)
|
||||
view.flat[:copy_length] = frame_raw.flat[:copy_length]
|
||||
|
||||
execution_arguments = {key: queues[key] for key in MODELD_INPUTS if key in queues}
|
||||
execution_start = time.perf_counter()
|
||||
step_output = runner(**execution_arguments)
|
||||
Device.default.synchronize()
|
||||
step_duration = time.perf_counter() - execution_start
|
||||
|
||||
output_array = (step_output[0].numpy() if hasattr(step_output[0], "numpy") else np.array(step_output[0]))
|
||||
flat_output = output_array.flatten()
|
||||
|
||||
if hidden_state_slice and "prev_feat" in npy_views:
|
||||
features_flat = flat_output[hidden_state_slice]
|
||||
target_slice = min(features_flat.size, npy_views["prev_feat"].size)
|
||||
npy_views["prev_feat"].flat[:target_slice] = features_flat[:target_slice]
|
||||
|
||||
sliced_outputs = {slice_name: flat_output[np.newaxis, slice_range] for slice_name, slice_range in output_slices.items()}
|
||||
parser.parse_outputs(sliced_outputs)
|
||||
|
||||
recorded_outputs.append({
|
||||
"frame_index": frame_index,
|
||||
"raw_output": output_array,
|
||||
"parsed_outputs": sliced_outputs,
|
||||
"execution_time": step_duration,
|
||||
})
|
||||
return recorded_outputs
|
||||
|
||||
|
||||
def plot_comparison(series_a, series_b, title, output_directory, label_a="modeld_v2 model", label_b="stock"):
|
||||
os.makedirs(output_directory, exist_ok=True)
|
||||
figure, axis = plt.subplots()
|
||||
axis.plot(series_b, label=label_b)
|
||||
axis.plot(series_a, label=label_a, linestyle="--")
|
||||
axis.set_title(title)
|
||||
axis.legend(loc="best")
|
||||
plot_path = os.path.join(output_directory, f"{title}.png")
|
||||
figure.savefig(plot_path)
|
||||
plt.close(figure)
|
||||
return plot_path
|
||||
|
||||
|
||||
def compare_models_on_route(new_model_path, old_model_path, route_or_path=None, segment_index=SEGMENT,
|
||||
number_of_frames=20, tolerance=1e-4, label_a="modeld_v2 model", label_b="stock",
|
||||
plot_directory=None, enforce_timings=False):
|
||||
video_url_or_path = get_replay_video_source(route_or_path, segment_index)
|
||||
frame_reader = FrameReader(video_url_or_path, pix_fmt="nv12")
|
||||
old_results = replay_model_on_frames(old_model_path, frame_reader, number_of_frames)
|
||||
new_results = replay_model_on_frames(new_model_path, frame_reader, number_of_frames)
|
||||
|
||||
for step_index, (new_step, old_step) in enumerate(zip(new_results, old_results, strict=True)):
|
||||
new_array = new_step["raw_output"]
|
||||
old_array = old_step["raw_output"]
|
||||
if not np.allclose(new_array, old_array, atol=tolerance, rtol=tolerance):
|
||||
max_absolute_error = np.max(np.abs(new_array - old_array))
|
||||
sys.stderr.write(f"Replay mismatch at frame {step_index}: max absolute error {max_absolute_error:.6f} exceeds tolerance {tolerance}\n")
|
||||
return False
|
||||
|
||||
if len(new_results) > 1 and len(old_results) > 1:
|
||||
new_timings = [step["execution_time"] * 1000.0 for step in new_results[1:] if "execution_time" in step]
|
||||
old_timings = [step["execution_time"] * 1000.0 for step in old_results[1:] if "execution_time" in step]
|
||||
if new_timings and old_timings:
|
||||
print("------------------------------------------------")
|
||||
print("----------------- Model Timing -----------------")
|
||||
print("------------------------------------------------")
|
||||
print(f"{label_a}: avg {np.mean(new_timings):6.2f} ms | max {np.max(new_timings):6.2f} ms")
|
||||
print(f"{label_b}: avg {np.mean(old_timings):6.2f} ms | max {np.max(old_timings):6.2f} ms")
|
||||
|
||||
if plot_directory:
|
||||
first_step_outputs = new_results[0].get("parsed_outputs", {})
|
||||
if "action" in first_step_outputs:
|
||||
series_a_curv = [step["parsed_outputs"]["action"].flatten()[0] for step in new_results]
|
||||
series_b_curv = [step["parsed_outputs"]["action"].flatten()[0] for step in old_results]
|
||||
plot_comparison(series_a_curv, series_b_curv, "desiredCurvature", plot_directory, label_a, label_b)
|
||||
|
||||
series_a_accel = [step["parsed_outputs"]["action"].flatten()[1] for step in new_results]
|
||||
series_b_accel = [step["parsed_outputs"]["action"].flatten()[1] for step in old_results]
|
||||
plot_comparison(series_a_accel, series_b_accel, "desiredAcceleration", plot_directory, label_a, label_b)
|
||||
|
||||
if "plan" in first_step_outputs:
|
||||
series_a_vel = [step["parsed_outputs"]["plan"].flatten()[0] for step in new_results]
|
||||
series_b_vel = [step["parsed_outputs"]["plan"].flatten()[0] for step in old_results]
|
||||
plot_comparison(series_a_vel, series_b_vel, "velocity.x", plot_directory, label_a, label_b)
|
||||
|
||||
if "lead" in first_step_outputs:
|
||||
series_a_lead = [step["parsed_outputs"]["lead"].flatten()[0] for step in new_results]
|
||||
series_b_lead = [step["parsed_outputs"]["lead"].flatten()[0] for step in old_results]
|
||||
plot_comparison(series_a_lead, series_b_lead, "leadsV3.x", plot_directory, label_a, label_b)
|
||||
|
||||
plot_comparison(new_timings, old_timings, "execution_timings", plot_directory, label_a, label_b)
|
||||
|
||||
for slice_name in first_step_outputs:
|
||||
series_a = [np.mean(step["parsed_outputs"][slice_name]) for step in new_results if slice_name in step["parsed_outputs"]]
|
||||
series_b = [np.mean(step["parsed_outputs"][slice_name]) for step in old_results if slice_name in step["parsed_outputs"]]
|
||||
if series_a and series_b:
|
||||
plot_comparison(series_a, series_b, f"output_{slice_name}", plot_directory, label_a, label_b)
|
||||
print(f"Replay comparison result on route ({label_a} vs {label_b}): True")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argument_parser = argparse.ArgumentParser(description="Model Replay on Real Driving Video")
|
||||
argument_parser.add_argument("--sunnypilot-model", dest="model_a", default=None)
|
||||
argument_parser.add_argument("--stock-model", dest="model_b", default=None)
|
||||
argument_parser.add_argument("--route", default=TEST_ROUTE)
|
||||
argument_parser.add_argument("--segment", type=int, default=SEGMENT)
|
||||
argument_parser.add_argument("--frames", type=int, default=20)
|
||||
argument_parser.add_argument("--plot-dir", default=None)
|
||||
parsed_arguments = argument_parser.parse_args()
|
||||
|
||||
matches = compare_models_on_route(parsed_arguments.model_a, parsed_arguments.model_b, route_or_path=parsed_arguments.route,
|
||||
segment_index=parsed_arguments.segment, number_of_frames=parsed_arguments.frames,
|
||||
tolerance=1e-4, label_a="modeld_v2 model",
|
||||
label_b="stock", plot_directory=parsed_arguments.plot_dir)
|
||||
if not matches:
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user