mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-08 04:15:51 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df83374927 |
@@ -34,6 +34,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: 'qcom'
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- usbgpu
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
upstream_branch:
|
upstream_branch:
|
||||||
@@ -81,9 +89,17 @@ on:
|
|||||||
description: 'Minimum selector version'
|
description: 'Minimum selector version'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: 'qcom'
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- usbgpu
|
||||||
env:
|
env:
|
||||||
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
||||||
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
|
JSON_FILE: docs/docs/driving_models_${{ inputs.target_hardware == 'usbgpu' && 'usbgpu_v' || 'v' }}${{ inputs.json_version }}.json
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_model:
|
build_model:
|
||||||
@@ -93,6 +109,7 @@ jobs:
|
|||||||
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
||||||
is_20hz: ${{ inputs.is_20hz }}
|
is_20hz: ${{ inputs.is_20hz }}
|
||||||
artifact_suffix: ${{ inputs.artifact_suffix }}
|
artifact_suffix: ${{ inputs.artifact_suffix }}
|
||||||
|
target_hardware: ${{ inputs.target_hardware }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
publish_model:
|
publish_model:
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||||
echo "USBGPU build"
|
echo "USBGPU build"
|
||||||
export USBGPU=1
|
export USBGPU=1
|
||||||
TG_FLAGS="DEV=AMD USBGPU=1 IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
TG_FLAGS="DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV=QCOM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0"
|
||||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||||
else
|
else
|
||||||
echo "QCOM build"
|
echo "QCOM build"
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
# AI policy
|
|
||||||
|
|
||||||
## Why this exists
|
|
||||||
|
|
||||||
We use AI tools ourselves, so this isn't an anti-AI stance. The problem is people submitting code, issues, or comments they don't actually understand. AI makes that very easy to do, and it creates real work for reviewers who have to figure out what you meant when you can't explain it yourself.
|
|
||||||
|
|
||||||
If you're not going to put effort into understanding and verifying your submission, we're not going to put effort into reviewing it.
|
|
||||||
|
|
||||||
## The rule
|
|
||||||
|
|
||||||
You are responsible for everything you submit: code, PR descriptions, issues, bug reports, comments.
|
|
||||||
|
|
||||||
1. Understand what you submit. If a reviewer asks why you did something, you answer from your own understanding, not by re-prompting. If you can't do that, don't submit it.
|
|
||||||
|
|
||||||
2. Test your change. AI gets things wrong all the time. Run it, break it, confirm it actually works.
|
|
||||||
|
|
||||||
3. Driving fixes need real evidence. Attach a dongle ID, upload logs, and include segments that show the fix working. A route hash by itself proves nothing.
|
|
||||||
|
|
||||||
4. No AI-generated media (images, diagrams, videos) in issues or PRs.
|
|
||||||
|
|
||||||
## Disclosure
|
|
||||||
|
|
||||||
If AI tools helped you write something, say so. Add an `Assisted-by:` line in your commit message:
|
|
||||||
|
|
||||||
```
|
|
||||||
Assisted-by: GitHub Copilot
|
|
||||||
Assisted-by: Claude
|
|
||||||
```
|
|
||||||
|
|
||||||
Disclosing won't count against your PR. It helps reviewers know where to look. Hiding it and getting caught will.
|
|
||||||
|
|
||||||
## How we review
|
|
||||||
|
|
||||||
Reviewers are looking at whether you understand your own change. Can you explain it? Can you respond to feedback without re-prompting? Does your PR description say why you made the change, not just list what changed?
|
|
||||||
|
|
||||||
Good code from someone who used AI and understands what they wrote is fine. How you got there doesn't matter as long as you can stand behind it.
|
|
||||||
|
|
||||||
## What happens
|
|
||||||
|
|
||||||
Submissions that don't meet this bar get closed. If it keeps happening, you get blocked.
|
|
||||||
|
|
||||||
## Maintainers
|
|
||||||
|
|
||||||
Maintainers use AI at their discretion. They've earned that through sustained contribution and they know the codebase.
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
> sunnypilot follows [commaai/openpilot](https://github.com/commaai/openpilot)'s contributing guidelines. The following applies to all contributions here.
|
|
||||||
|
|
||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/).
|
||||||
@@ -37,7 +35,6 @@ All of these are examples of good PRs:
|
|||||||
* **UI design**: we do not have a good review process for this yet
|
* **UI design**: we do not have a good review process for this yet
|
||||||
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't.
|
||||||
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged.
|
||||||
* **AI-generated contributions**: see our [AI policy](AI_POLICY.md)
|
|
||||||
|
|
||||||
### First contribution
|
### First contribution
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: 063414f63f...4c64e8a95b
@@ -197,7 +197,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}},
|
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||||
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
|
{"ModelManager_LastSyncTime_USBGPU", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
{"ModelManager_ModelsCache_USBGPU", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
|
||||||
// Neural Network Lateral Control
|
// Neural Network Lateral Control
|
||||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ class CarSpecificEvents:
|
|||||||
if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001:
|
if self.CP.minEnableSpeed > 0 and CS.vEgo < 0.001:
|
||||||
events.add(EventName.manualRestart)
|
events.add(EventName.manualRestart)
|
||||||
|
|
||||||
if CS.brakeHoldActive and CS.blockPcmEnable: # set by Nidec Hybrid which cannot resume from brakehold
|
|
||||||
events.add(EventName.belowEngageSpeed)
|
|
||||||
|
|
||||||
elif self.CP.brand == 'toyota':
|
elif self.CP.brand == 'toyota':
|
||||||
# TODO: when we check for unexpected disengagement, check gear not S1, S2, S3
|
# TODO: when we check for unexpected disengagement, check gear not S1, S2, S3
|
||||||
if self.CP.openpilotLongitudinalControl:
|
if self.CP.openpilotLongitudinalControl:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import argparse
|
|||||||
import atexit
|
import atexit
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import pickle
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
@@ -13,7 +12,6 @@ from collections import namedtuple
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
||||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
|
||||||
|
|
||||||
def _patch_tinygrad_fetch_fw():
|
def _patch_tinygrad_fetch_fw():
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -31,22 +29,6 @@ def _patch_tinygrad_fetch_fw():
|
|||||||
helpers.fetch_fw = fetch_fw
|
helpers.fetch_fw = fetch_fw
|
||||||
_patch_tinygrad_fetch_fw()
|
_patch_tinygrad_fetch_fw()
|
||||||
|
|
||||||
def _patch_tinygrad_buffer_reduce():
|
|
||||||
from tinygrad.device import Buffer
|
|
||||||
def __reduce_ex__(self, protocol):
|
|
||||||
buf = None
|
|
||||||
if self._base is not None:
|
|
||||||
return self.__class__, (self.device, self.size, self.dtype, None, None, None, 0, self.base, self.offset, self.is_allocated())
|
|
||||||
if self.device == "NPY":
|
|
||||||
return self.__class__, (self.device, self.size, self.dtype, self._buf, self.options, None, self.uop_refcount)
|
|
||||||
if self.is_allocated():
|
|
||||||
buf = bytearray(self.nbytes)
|
|
||||||
self.copyout(memoryview(buf))
|
|
||||||
if protocol >= 5:
|
|
||||||
buf = pickle.PickleBuffer(buf)
|
|
||||||
return self.__class__, (self.device, self.size, self.dtype, None, self.options, buf, self.uop_refcount)
|
|
||||||
Buffer.__reduce_ex__ = __reduce_ex__
|
|
||||||
_patch_tinygrad_buffer_reduce()
|
|
||||||
|
|
||||||
from tinygrad.tensor import Tensor
|
from tinygrad.tensor import Tensor
|
||||||
from tinygrad.helpers import Context
|
from tinygrad.helpers import Context
|
||||||
@@ -312,9 +294,6 @@ if __name__ == "__main__":
|
|||||||
p.add_argument('--frame-skip', type=int, required=True)
|
p.add_argument('--frame-skip', type=int, required=True)
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
if 'USB+AMD' in os.environ.get('DEV', ''):
|
|
||||||
wait_usbgpu_link()
|
|
||||||
|
|
||||||
model_path = read_file_chunked_to_disk(args.onnx)
|
model_path = read_file_chunked_to_disk(args.onnx)
|
||||||
model_w, model_h = args.model_size
|
model_w, model_h = args.model_size
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import struct
|
|||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from openpilot.common.file_chunker import get_manifest_path
|
||||||
|
from openpilot.common.hardware.usb import CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID, USB_DEVICES_PATH
|
||||||
|
|
||||||
MODELS_DIR = Path(__file__).resolve().parent / 'models'
|
MODELS_DIR = Path(__file__).resolve().parent / 'models'
|
||||||
TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json'
|
TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json'
|
||||||
USBGPU_VID = 0xADD1
|
|
||||||
USBGPU_PID = 0x0001
|
|
||||||
|
|
||||||
|
|
||||||
def get_tg_input_devices(process_name: str, usbgpu: bool):
|
def get_tg_input_devices(process_name: str, usbgpu: bool):
|
||||||
@@ -38,22 +39,21 @@ def dump_oob(obj, f):
|
|||||||
def load_oob(f):
|
def load_oob(f):
|
||||||
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
|
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
|
||||||
def buffers():
|
def buffers():
|
||||||
prev = None
|
|
||||||
while (h := f.read(8)):
|
while (h := f.read(8)):
|
||||||
if prev is not None:
|
pb = pickle.PickleBuffer(bytearray(struct.unpack('<q', h)[0]))
|
||||||
prev.release()
|
f.readinto(pb)
|
||||||
buf = bytearray(struct.unpack('<q', h)[0])
|
yield pb
|
||||||
f.readinto(buf)
|
|
||||||
prev = pickle.PickleBuffer(buf)
|
|
||||||
yield prev
|
|
||||||
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
|
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
|
||||||
|
|
||||||
def usbgpu_present() -> bool:
|
def usbgpu_present() -> bool:
|
||||||
for d in Path("/sys/bus/usb/devices").glob("*"):
|
for d in USB_DEVICES_PATH.glob("*"):
|
||||||
try:
|
try:
|
||||||
if int((d / "idVendor").read_text(), 16) == USBGPU_VID and \
|
usb_id = (int((d / "idVendor").read_text(), 16), int((d / "idProduct").read_text(), 16))
|
||||||
int((d / "idProduct").read_text(), 16) == USBGPU_PID:
|
if usb_id == (CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID):
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def usbgpu_compiled() -> bool:
|
||||||
|
return Path(get_manifest_path(modeld_pkl_path(usbgpu=True))).is_file()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom
|
os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom
|
||||||
from tinygrad.tensor import Tensor
|
from tinygrad.tensor import Tensor
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import openpilot.cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
@@ -18,17 +19,13 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
|||||||
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||||
from openpilot.common.transformations.model import get_warp_matrix
|
from openpilot.common.transformations.model import get_warp_matrix
|
||||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan
|
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, should_stop, smooth_value, get_curvature_from_plan
|
||||||
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS
|
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS
|
||||||
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState
|
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState
|
||||||
from openpilot.common.file_chunker import open_file_chunked, get_manifest_path
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||||
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, usbgpu_compiled, modeld_pkl_path, get_tg_input_devices, load_oob
|
||||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
|
||||||
|
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
|
||||||
|
|
||||||
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld"
|
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld"
|
||||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||||
@@ -36,16 +33,17 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
|||||||
LAT_SMOOTH_SECONDS = 0.0
|
LAT_SMOOTH_SECONDS = 0.0
|
||||||
LONG_SMOOTH_SECONDS = 0.3
|
LONG_SMOOTH_SECONDS = 0.3
|
||||||
MIN_LAT_CONTROL_SPEED = 0.3
|
MIN_LAT_CONTROL_SPEED = 0.3
|
||||||
|
BIG_MODEL_TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
||||||
lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action:
|
lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action:
|
||||||
if 'action' not in model_output:
|
if 'action' not in model_output:
|
||||||
plan = model_output['plan'][0]
|
plan = model_output['plan'][0]
|
||||||
desired_accel, should_stop = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0],
|
desired_accel = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0],
|
||||||
plan[:,Plan.ACCELERATION][:,0],
|
plan[:,Plan.ACCELERATION][:,0],
|
||||||
ModelConstants.T_IDXS,
|
ModelConstants.T_IDXS,
|
||||||
action_t=long_action_t)
|
action_t=long_action_t)
|
||||||
desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
|
desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
|
||||||
plan[:,Plan.ORIENTATION_RATE][:,2],
|
plan[:,Plan.ORIENTATION_RATE][:,2],
|
||||||
ModelConstants.T_IDXS,
|
ModelConstants.T_IDXS,
|
||||||
@@ -54,7 +52,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
|
|||||||
else:
|
else:
|
||||||
desired_accel = model_output['action'][0,1]
|
desired_accel = model_output['action'][0,1]
|
||||||
desired_curvature = model_output['action'][0,0] / (max(1.0, v_ego))**2
|
desired_curvature = model_output['action'][0,0] / (max(1.0, v_ego))**2
|
||||||
should_stop = (v_ego < 0.3 and desired_accel < 0.1)
|
stop = should_stop(v_ego, desired_accel)
|
||||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
||||||
if v_ego > MIN_LAT_CONTROL_SPEED:
|
if v_ego > MIN_LAT_CONTROL_SPEED:
|
||||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
||||||
@@ -63,7 +61,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
|
|||||||
|
|
||||||
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
|
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
|
||||||
desiredAcceleration=float(desired_accel),
|
desiredAcceleration=float(desired_accel),
|
||||||
shouldStop=bool(should_stop))
|
shouldStop=bool(stop))
|
||||||
|
|
||||||
|
|
||||||
class FrameMeta:
|
class FrameMeta:
|
||||||
@@ -76,12 +74,10 @@ class FrameMeta:
|
|||||||
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
||||||
|
|
||||||
|
|
||||||
class ModelState(ModelStateBase):
|
class ModelState:
|
||||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||||
|
|
||||||
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool):
|
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool):
|
||||||
ModelStateBase.__init__(self)
|
|
||||||
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
|
|
||||||
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
||||||
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
||||||
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu)))
|
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu)))
|
||||||
@@ -138,16 +134,24 @@ class ModelState(ModelStateBase):
|
|||||||
outputs_dict['raw_pred'] = model_output.copy()
|
outputs_dict['raw_pred'] = model_output.copy()
|
||||||
return outputs_dict
|
return outputs_dict
|
||||||
|
|
||||||
|
def warmup(self) -> None:
|
||||||
|
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self.vision_input_names}
|
||||||
|
eye = np.eye(3, dtype=np.float32)
|
||||||
|
dims = {'desire_pulse': ModelConstants.DESIRE_LEN, 'traffic_convention': 2, 'action_t': 2}
|
||||||
|
self.run(dummy_frames, dict.fromkeys(self.vision_input_names, eye), {k: np.zeros(v, dtype=np.float32) for k, v in dims.items()})
|
||||||
|
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
||||||
|
self.prev_desire[:] = 0
|
||||||
|
self.full_frames.clear()
|
||||||
|
self._blob_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
def main(demo=False):
|
def main(demo=False):
|
||||||
cloudlog.warning("modeld init")
|
cloudlog.warning("modeld init")
|
||||||
|
|
||||||
_present = usbgpu_present()
|
USBGPU = usbgpu_present() and usbgpu_compiled()
|
||||||
_compiled = os.path.isfile(get_manifest_path(modeld_pkl_path(usbgpu=True)))
|
|
||||||
USBGPU = _present and _compiled
|
|
||||||
params = Params()
|
params = Params()
|
||||||
params.put_bool("UsbGpuPresent", _present)
|
params.put_bool("UsbGpuLoading", USBGPU)
|
||||||
params.put_bool("UsbGpuCompiled", _compiled)
|
params.remove("UsbGpuActive")
|
||||||
|
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
@@ -174,15 +178,33 @@ def main(demo=False):
|
|||||||
if use_extra_client:
|
if use_extra_client:
|
||||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||||
|
|
||||||
if USBGPU:
|
|
||||||
wait_usbgpu_link()
|
|
||||||
st = time.monotonic()
|
st = time.monotonic()
|
||||||
cloudlog.warning("loading model")
|
cloudlog.warning("loading model")
|
||||||
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU)
|
model = None
|
||||||
|
if USBGPU:
|
||||||
|
big_model = None
|
||||||
|
def load_big():
|
||||||
|
nonlocal big_model
|
||||||
|
try:
|
||||||
|
m = ModelState(vipc_client_main.width, vipc_client_main.height, True)
|
||||||
|
m.warmup()
|
||||||
|
big_model = m
|
||||||
|
except Exception:
|
||||||
|
cloudlog.exception("big model load failed")
|
||||||
|
loader = threading.Thread(target=load_big, daemon=True)
|
||||||
|
loader.start()
|
||||||
|
loader.join(BIG_MODEL_TIMEOUT)
|
||||||
|
model = big_model
|
||||||
|
params.put_bool("UsbGpuActive", model is not None)
|
||||||
|
|
||||||
|
small_model = ModelState(vipc_client_main.width, vipc_client_main.height, False) if model is None or USBGPU else None
|
||||||
|
if model is None:
|
||||||
|
model = small_model
|
||||||
|
params.put_bool("UsbGpuLoading", False)
|
||||||
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
@@ -252,9 +274,7 @@ def main(demo=False):
|
|||||||
is_rhd = sm["driverMonitoringState"].isRHD
|
is_rhd = sm["driverMonitoringState"].isRHD
|
||||||
frame_id = sm["roadCameraState"].frameId
|
frame_id = sm["roadCameraState"].frameId
|
||||||
v_ego = max(sm["carState"].vEgo, 0.)
|
v_ego = max(sm["carState"].vEgo, 0.)
|
||||||
if sm.frame % 60 == 0:
|
lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||||
model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay)
|
|
||||||
lat_delay = model.lat_delay + LAT_SMOOTH_SECONDS
|
|
||||||
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
||||||
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
||||||
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
|
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
|
||||||
@@ -293,7 +313,17 @@ def main(demo=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
mt1 = time.perf_counter()
|
mt1 = time.perf_counter()
|
||||||
model_output = model.run(bufs, transforms, inputs)
|
try:
|
||||||
|
model_output = model.run(bufs, transforms, inputs)
|
||||||
|
except Exception:
|
||||||
|
if not params.get_bool("UsbGpuActive"):
|
||||||
|
raise
|
||||||
|
# fallback to small model
|
||||||
|
cloudlog.exception("big model failed, fall back to small")
|
||||||
|
params.put_bool("UsbGpuActive", False)
|
||||||
|
model = small_model
|
||||||
|
run_count = 0
|
||||||
|
model_output = None
|
||||||
mt2 = time.perf_counter()
|
mt2 = time.perf_counter()
|
||||||
model_execution_time = mt2 - mt1
|
model_execution_time = mt2 - mt1
|
||||||
|
|
||||||
@@ -301,7 +331,6 @@ def main(demo=False):
|
|||||||
modelv2_send = messaging.new_message('modelV2')
|
modelv2_send = messaging.new_message('modelV2')
|
||||||
drivingdata_send = messaging.new_message('drivingModelData')
|
drivingdata_send = messaging.new_message('drivingModelData')
|
||||||
posenet_send = messaging.new_message('cameraOdometry')
|
posenet_send = messaging.new_message('cameraOdometry')
|
||||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
|
||||||
|
|
||||||
action = get_action_from_model(model_output, prev_action, lat_action_t, long_action_t, v_ego)
|
action = get_action_from_model(model_output, prev_action, lat_action_t, long_action_t, v_ego)
|
||||||
prev_action = action
|
prev_action = action
|
||||||
@@ -316,14 +345,12 @@ def main(demo=False):
|
|||||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
|
||||||
|
|
||||||
fill_driving_model_data(drivingdata_send, modelv2_send)
|
fill_driving_model_data(drivingdata_send, modelv2_send)
|
||||||
fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)
|
fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)
|
||||||
pm.send('modelV2', modelv2_send)
|
pm.send('modelV2', modelv2_send)
|
||||||
pm.send('drivingModelData', drivingdata_send)
|
pm.send('drivingModelData', drivingdata_send)
|
||||||
pm.send('cameraOdometry', posenet_send)
|
pm.send('cameraOdometry', posenet_send)
|
||||||
pm.send('modelDataV2SP', mdv2sp_send)
|
|
||||||
last_vipc_frame_id = meta_main.frame_id
|
last_vipc_frame_id = meta_main.frame_id
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout
|
|||||||
|
|
||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
|
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.settings import SettingsLayoutSP as SettingsLayout
|
||||||
from openpilot.selfdrive.ui.sunnypilot.layouts.home import HomeLayoutSP as HomeLayout
|
|
||||||
|
|
||||||
|
|
||||||
class MainState(IntEnum):
|
class MainState(IntEnum):
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from openpilot.system.ui.lib.application import gui_app
|
|||||||
|
|
||||||
if gui_app.sunnypilot_ui():
|
if gui_app.sunnypilot_ui():
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout
|
||||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.home import MiciHomeLayoutSP as MiciHomeLayout
|
|
||||||
|
|
||||||
ONROAD_DELAY = 2.5 # seconds
|
ONROAD_DELAY = 2.5 # seconds
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +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 pyray as rl
|
|
||||||
from openpilot.selfdrive.ui.layouts.home import HomeLayout, HomeLayoutState, HEAD_BUTTON_FONT_SIZE, SPACING
|
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
|
||||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
|
||||||
from openpilot.system.ui.lib.multilang import tr, trn
|
|
||||||
from openpilot.system.ui.widgets.label import gui_label
|
|
||||||
|
|
||||||
BRAND_FONT_SIZE = 48
|
|
||||||
BRAND_DESC_SPACING = 12
|
|
||||||
|
|
||||||
|
|
||||||
class HomeLayoutSP(HomeLayout):
|
|
||||||
def _render_header(self):
|
|
||||||
font = gui_app.font(FontWeight.MEDIUM)
|
|
||||||
|
|
||||||
version_text_width = self.header_rect.width
|
|
||||||
|
|
||||||
if self.update_available:
|
|
||||||
version_text_width -= self.update_notif_rect.width
|
|
||||||
|
|
||||||
highlight_color = rl.Color(75, 95, 255, 255) if self.current_state == HomeLayoutState.UPDATE else rl.Color(54, 77, 239, 255)
|
|
||||||
rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color)
|
|
||||||
|
|
||||||
text = tr("UPDATE")
|
|
||||||
text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE)
|
|
||||||
text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2
|
|
||||||
text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2
|
|
||||||
rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
|
|
||||||
|
|
||||||
if self.alert_count > 0:
|
|
||||||
version_text_width -= self.alert_notif_rect.width
|
|
||||||
|
|
||||||
highlight_color = rl.Color(255, 70, 70, 255) if self.current_state == HomeLayoutState.ALERTS else rl.Color(226, 44, 44, 255)
|
|
||||||
rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color)
|
|
||||||
|
|
||||||
alert_text = trn("{} ALERT", "{} ALERTS", self.alert_count).format(self.alert_count)
|
|
||||||
text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE)
|
|
||||||
text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2
|
|
||||||
text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2
|
|
||||||
rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
|
|
||||||
|
|
||||||
if self.update_available or self.alert_count > 0:
|
|
||||||
version_text_width -= SPACING * 1.5
|
|
||||||
|
|
||||||
version_right = self.header_rect.x + self.header_rect.width
|
|
||||||
version_left = version_right - version_text_width
|
|
||||||
|
|
||||||
brand = "sunnypilot"
|
|
||||||
description = self.params.get("UpdaterCurrentDescription") or ""
|
|
||||||
|
|
||||||
desc_width = 0
|
|
||||||
if description:
|
|
||||||
desc_size = measure_text_cached(gui_app.font(FontWeight.NORMAL), description, BRAND_FONT_SIZE)
|
|
||||||
desc_width = desc_size.x
|
|
||||||
desc_rect = rl.Rectangle(version_right - desc_width, self.header_rect.y, desc_width, self.header_rect.height)
|
|
||||||
gui_label(desc_rect, description, BRAND_FONT_SIZE, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
|
||||||
|
|
||||||
brand_size = measure_text_cached(gui_app.font(FontWeight.AUDIOWIDE), brand, BRAND_FONT_SIZE)
|
|
||||||
spacing = BRAND_DESC_SPACING if description else 0
|
|
||||||
brand_x = version_right - desc_width - spacing - brand_size.x
|
|
||||||
brand_rect = rl.Rectangle(max(version_left, brand_x), self.header_rect.y, brand_size.x, self.header_rect.height)
|
|
||||||
gui_label(brand_rect, brand, BRAND_FONT_SIZE, rl.WHITE, font_weight=FontWeight.AUDIOWIDE)
|
|
||||||
@@ -20,7 +20,7 @@ class SunnylinkConsentPage(Widget):
|
|||||||
self._done_callback = done_callback
|
self._done_callback = done_callback
|
||||||
self._step = 0
|
self._step = 0
|
||||||
|
|
||||||
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.AUDIOWIDE, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
|
self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT))
|
||||||
|
|
||||||
self._content = [
|
self._content = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +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.
|
|
||||||
"""
|
|
||||||
from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout
|
|
||||||
from openpilot.system.ui.lib.application import FontWeight
|
|
||||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
|
||||||
|
|
||||||
|
|
||||||
class MiciHomeLayoutSP(MiciHomeLayout):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._openpilot_label = UnifiedLabel("sunnypilot", font_size=88, font_weight=FontWeight.AUDIOWIDE, max_width=480, wrap_text=False)
|
|
||||||
@@ -4,6 +4,7 @@ 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.
|
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.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
|
from collections.abc import Callable
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
@@ -47,8 +48,10 @@ class CurrentModelInfo(Widget):
|
|||||||
self.info_text.render()
|
self.info_text.render()
|
||||||
|
|
||||||
class ModelsLayoutMici(NavScroller):
|
class ModelsLayoutMici(NavScroller):
|
||||||
def __init__(self):
|
def __init__(self, back_callback: Callable):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.set_back_callback(back_callback)
|
||||||
|
self.original_back_callback = back_callback
|
||||||
self.focused_widget = None
|
self.focused_widget = None
|
||||||
|
|
||||||
self.current_model_info = CurrentModelInfo()
|
self.current_model_info = CurrentModelInfo()
|
||||||
@@ -82,10 +85,12 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
|
|
||||||
return folders
|
return folders
|
||||||
|
|
||||||
def _push_selection_view(self, items):
|
def _show_selection_view(self, items, back_callback: Callable):
|
||||||
scroller = NavScroller()
|
self._scroller._items = items
|
||||||
scroller._scroller.add_widgets(items)
|
for item in items:
|
||||||
gui_app.push_widget(scroller)
|
item.set_touch_valid_callback(lambda: self._scroller.scroll_panel.is_touch_valid() and self._scroller.enabled)
|
||||||
|
self._scroller.scroll_panel.set_offset(0)
|
||||||
|
self.set_back_callback(back_callback)
|
||||||
|
|
||||||
def _show_folders(self):
|
def _show_folders(self):
|
||||||
self.focused_widget = self.select_model_btn
|
self.focused_widget = self.select_model_btn
|
||||||
@@ -107,18 +112,15 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
folder_buttons.insert(0, btn)
|
folder_buttons.insert(0, btn)
|
||||||
else:
|
else:
|
||||||
folder_buttons.append(btn)
|
folder_buttons.append(btn)
|
||||||
self._push_selection_view(folder_buttons)
|
self._show_selection_view(folder_buttons, self._reset_main_view)
|
||||||
|
|
||||||
def _pop_to_main(self):
|
|
||||||
gui_app.pop_widgets_to(self)
|
|
||||||
|
|
||||||
def _select_model(self, bundle):
|
def _select_model(self, bundle):
|
||||||
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
|
ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
|
||||||
self._pop_to_main()
|
self._reset_main_view()
|
||||||
|
|
||||||
def _select_default(self):
|
def _select_default(self):
|
||||||
ui_state.params.remove("ModelManager_ActiveBundle")
|
ui_state.params.remove("ModelManager_ActiveBundle")
|
||||||
self._pop_to_main()
|
self._reset_main_view()
|
||||||
|
|
||||||
def _select_folder(self, folder_name):
|
def _select_folder(self, folder_name):
|
||||||
favs = ui_state.params.get("ModelManager_Favs")
|
favs = ui_state.params.get("ModelManager_Favs")
|
||||||
@@ -133,7 +135,13 @@ class ModelsLayoutMici(NavScroller):
|
|||||||
btn = BigButton(txt)
|
btn = BigButton(txt)
|
||||||
btn.set_click_callback(lambda b=bundle: self._select_model(b))
|
btn.set_click_callback(lambda b=bundle: self._select_model(b))
|
||||||
btns.append(btn)
|
btns.append(btn)
|
||||||
self._push_selection_view(btns)
|
self._show_selection_view(btns, self._show_folders)
|
||||||
|
|
||||||
|
def _reset_main_view(self):
|
||||||
|
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||||
|
self.set_back_callback(self.original_back_callback)
|
||||||
|
self._scroller.scroll_panel.set_offset(0)
|
||||||
|
self._scroller.scroll_to(0)
|
||||||
|
|
||||||
def hide_event(self):
|
def hide_event(self):
|
||||||
super().hide_event()
|
super().hide_event()
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
|||||||
BIG_ICON_SIZE)
|
BIG_ICON_SIZE)
|
||||||
self.icon_offroad_slider = gui_app.texture("icons_mici/settings/device/lkas.png", BIG_ICON_SIZE, BIG_ICON_SIZE)
|
self.icon_offroad_slider = gui_app.texture("icons_mici/settings/device/lkas.png", BIG_ICON_SIZE, BIG_ICON_SIZE)
|
||||||
|
|
||||||
sunnylink_panel = SunnylinkLayoutMici()
|
sunnylink_panel = SunnylinkLayoutMici(back_callback=gui_app.pop_widget)
|
||||||
sunnylink_btn = SettingsBigButton(tr("sunnylink"), "", gui_app.texture("icons_mici/settings/developer/ssh.png", 55, 55))
|
sunnylink_btn = SettingsBigButton(tr("sunnylink"), "", gui_app.texture("icons_mici/settings/developer/ssh.png", 55, 55))
|
||||||
sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel))
|
sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel))
|
||||||
|
|
||||||
models_panel = ModelsLayoutMici()
|
models_panel = ModelsLayoutMici(back_callback=gui_app.pop_widget)
|
||||||
models_btn = SettingsBigButton(tr("models"), "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE))
|
models_btn = SettingsBigButton(tr("models"), "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE))
|
||||||
models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel))
|
models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel))
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
"""
|
"""
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle
|
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle
|
||||||
@@ -53,8 +54,9 @@ class SunnylinkInfo(Widget):
|
|||||||
self.sponsor_text.render()
|
self.sponsor_text.render()
|
||||||
|
|
||||||
class SunnylinkLayoutMici(NavScroller):
|
class SunnylinkLayoutMici(NavScroller):
|
||||||
def __init__(self):
|
def __init__(self, back_callback: Callable):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.set_back_callback(back_callback)
|
||||||
self._restore_in_progress = False
|
self._restore_in_progress = False
|
||||||
self._backup_in_progress = False
|
self._backup_in_progress = False
|
||||||
self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled")
|
self._sunnylink_enabled = ui_state.params.get("SunnylinkEnabled")
|
||||||
|
|||||||
@@ -338,11 +338,8 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None:
|
|||||||
|
|
||||||
settings_cases: Cases = [
|
settings_cases: Cases = [
|
||||||
lambda: scroll_through_cases(toggle_cases),
|
lambda: scroll_through_cases(toggle_cases),
|
||||||
None, # sunnylink (just open and close)
|
|
||||||
None, # models (just open and close)
|
|
||||||
lambda: scroll_through_cases(network_cases),
|
lambda: scroll_through_cases(network_cases),
|
||||||
lambda: scroll_through_cases(device_cases),
|
lambda: scroll_through_cases(device_cases),
|
||||||
lambda: script.wait(WAIT_SHORT), # software
|
|
||||||
lambda: script.wait(WAIT_SHORT), # pairing
|
lambda: script.wait(WAIT_SHORT), # pairing
|
||||||
lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up)
|
lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up)
|
||||||
lambda: scroll_through_cases(developer_cases),
|
lambda: scroll_through_cases(developer_cases),
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
||||||
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE
|
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE
|
||||||
from openpilot.common.hardware import HARDWARE, PC
|
from openpilot.common.hardware import HARDWARE, PC
|
||||||
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present
|
||||||
|
|
||||||
Import('env', 'arch', 'release')
|
Import('env', 'arch', 'release')
|
||||||
lenv = env.Clone()
|
lenv = env.Clone()
|
||||||
@@ -22,14 +25,21 @@ def get_camera_configs():
|
|||||||
|
|
||||||
CAMERA_CONFIGS = get_camera_configs()
|
CAMERA_CONFIGS = get_camera_configs()
|
||||||
|
|
||||||
tg_flags = {
|
def probe_devices():
|
||||||
'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0',
|
return set(subprocess.run(
|
||||||
'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}',
|
[sys.executable, '-c', 'from tinygrad import Device\nprint("\\n".join(Device.get_available_devices()))'],
|
||||||
}.get(arch, 'DEV=CPU:LLVM')
|
capture_output=True, text=True, check=True).stdout.strip().splitlines())
|
||||||
|
|
||||||
image_flag = {
|
available = probe_devices()
|
||||||
'larch64': 'IMAGE=2',
|
if 'CUDA' in available:
|
||||||
}.get(arch, 'IMAGE=0')
|
tg_backend = 'CUDA'
|
||||||
|
tg_flags = f'DEV={tg_backend}'
|
||||||
|
elif 'QCOM' in available:
|
||||||
|
tg_backend = 'QCOM'
|
||||||
|
tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1'
|
||||||
|
else:
|
||||||
|
tg_backend = 'CPU'
|
||||||
|
tg_flags = f'DEV=CPU HOME={os.path.expanduser("~")}' if arch == 'Darwin' else 'DEV=CPU:LLVM'
|
||||||
|
|
||||||
model_w, model_h = MEDMODEL_INPUT_SIZE
|
model_w, model_h = MEDMODEL_INPUT_SIZE
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
@@ -41,17 +51,30 @@ compile_modeld_script = File("compile_modeld.py").abspath
|
|||||||
upstream_compile_script = File(Dir("#openpilot/selfdrive/modeld").File("compile_modeld.py").abspath)
|
upstream_compile_script = File(Dir("#openpilot/selfdrive/modeld").File("compile_modeld.py").abspath)
|
||||||
script_deps = [File("compile_modeld.py"), upstream_compile_script]
|
script_deps = [File("compile_modeld.py"), upstream_compile_script]
|
||||||
|
|
||||||
|
USBGPU = usbgpu_present()
|
||||||
|
if USBGPU:
|
||||||
|
usbgpu_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0'
|
||||||
|
usbgpu_lock = File("models/.usb_gpu.lock").abspath
|
||||||
|
|
||||||
def compile_combined(model_type, onnx_args, output_name):
|
def compile_combined(model_type, onnx_args, output_name):
|
||||||
output_pkl = File(f"models/{output_name}").abspath
|
for usbgpu in ([False, True] if USBGPU else [False]):
|
||||||
cmd = (f'{pythonpath_string} {tg_flags} {image_flag} python3 {compile_modeld_script} '
|
prefix = 'big_' if usbgpu else ('big_' if os.getenv('BIG_INTO_SMALL') else '')
|
||||||
f'--model-type {model_type} '
|
final_output_name = prefix + output_name
|
||||||
f'--model-size {model_w}x{model_h} '
|
output_pkl = File(f"models/{final_output_name}").abspath
|
||||||
f'--camera-resolutions {camera_res_args} '
|
|
||||||
f'{onnx_args} '
|
active_tg_flags = usbgpu_tg_flags if usbgpu else tg_flags
|
||||||
f'--frame-skip {frame_skip} '
|
|
||||||
f'--output {output_pkl}')
|
cmd = (f'{pythonpath_string} {active_tg_flags} python3 {compile_modeld_script} '
|
||||||
onnx_files = [f for f in onnx_args.split() if f.endswith('.onnx')]
|
f'--model-type {model_type} '
|
||||||
return lenv.Command(output_pkl, tinygrad_files + script_deps + [File(f) for f in onnx_files if os.path.isfile(f)], cmd)
|
f'--model-size {model_w}x{model_h} '
|
||||||
|
f'--camera-resolutions {camera_res_args} '
|
||||||
|
f'{onnx_args} '
|
||||||
|
f'--frame-skip {frame_skip} '
|
||||||
|
f'--output {output_pkl}')
|
||||||
|
onnx_files = [f for f in onnx_args.split() if f.endswith('.onnx')]
|
||||||
|
node = lenv.Command(output_pkl, tinygrad_files + script_deps + [File(f) for f in onnx_files if os.path.isfile(f)], cmd)
|
||||||
|
if usbgpu:
|
||||||
|
lenv.SideEffect(usbgpu_lock, node)
|
||||||
|
|
||||||
# Vision + Policy (stock default model)
|
# Vision + Policy (stock default model)
|
||||||
vision_onnx = File("models/driving_vision.onnx").abspath
|
vision_onnx = File("models/driving_vision.onnx").abspath
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import pickle
|
import tempfile
|
||||||
import time
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
||||||
import numpy as np
|
import numpy as np
|
||||||
os.environ['GMMU'] = '0'
|
os.environ['GMMU'] = '0'
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ def get_policy_npy_shapes(input_shapes: dict, is_supercombo: bool = False) -> tu
|
|||||||
|
|
||||||
|
|
||||||
def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = Device.DEFAULT,
|
def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = Device.DEFAULT,
|
||||||
is_supercombo: bool = False, use_packed: bool = True) -> tuple[dict, dict]:
|
is_supercombo: bool = False) -> tuple[dict, dict]:
|
||||||
road_key, _ = _detect_vision_keys(input_shapes)
|
road_key, _ = _detect_vision_keys(input_shapes)
|
||||||
if not road_key:
|
if not road_key:
|
||||||
raise ValueError("Vision road key missing from input shapes.")
|
raise ValueError("Vision road key missing from input shapes.")
|
||||||
@@ -92,69 +92,44 @@ def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = D
|
|||||||
desire_shape = input_shapes[desire_key]
|
desire_shape = input_shapes[desire_key]
|
||||||
features_buffer = input_shapes.get('features_buffer')
|
features_buffer = input_shapes.get('features_buffer')
|
||||||
|
|
||||||
if use_packed: # remove packed detection block after all models are recompiled
|
npy_arrays = {
|
||||||
npy_arrays = {
|
'tfm': np.zeros((3, 3), dtype=np.float32),
|
||||||
'tfm': np.zeros((3, 3), dtype=np.float32),
|
'big_tfm': np.zeros((3, 3), dtype=np.float32)
|
||||||
'big_tfm': np.zeros((3, 3), dtype=np.float32)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
shapes, sizes = get_policy_npy_shapes(input_shapes, is_supercombo=is_supercombo)
|
shapes, sizes = get_policy_npy_shapes(input_shapes, is_supercombo=is_supercombo)
|
||||||
packed_npy_inputs = np.zeros(sum(sizes), dtype=np.float32)
|
packed_npy_inputs = np.zeros(sum(sizes), dtype=np.float32)
|
||||||
|
|
||||||
split_indices = np.cumsum(sizes[:-1]) if len(sizes) > 1 else []
|
split_indices = np.cumsum(sizes[:-1]) if len(sizes) > 1 else []
|
||||||
split_views = np.split(packed_npy_inputs, split_indices) if len(sizes) > 0 else []
|
split_views = np.split(packed_npy_inputs, split_indices) if len(sizes) > 0 else []
|
||||||
for (k, s), v in zip(shapes.items(), split_views, strict=True):
|
for (k, s), v in zip(shapes.items(), split_views, strict=True):
|
||||||
npy_arrays[k] = v.reshape(s)
|
npy_arrays[k] = v.reshape(s)
|
||||||
|
|
||||||
queues = {
|
queues = {
|
||||||
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
||||||
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
||||||
'desire_q': Tensor(np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]),
|
'desire_q': Tensor(np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]),
|
||||||
dtype=np.float32), device=device).contiguous().realize(),
|
dtype=np.float32), device=device).contiguous().realize(),
|
||||||
'packed_npy_inputs': Tensor(packed_npy_inputs, device='NPY').realize(),
|
'packed_npy_inputs': Tensor(packed_npy_inputs, device='NPY').realize(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if features_buffer:
|
if features_buffer:
|
||||||
queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, features_buffer[0], features_buffer[2]),
|
queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, 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')})
|
||||||
else:
|
|
||||||
# TODO-SP: Remove legacy queuing fallback else block after all models are recompiled
|
|
||||||
npy_arrays = {
|
|
||||||
'desire': np.zeros(desire_shape[2], dtype=np.float32),
|
|
||||||
'tfm': np.zeros((3, 3), dtype=np.float32),
|
|
||||||
'big_tfm': np.zeros((3, 3), dtype=np.float32)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, shape in input_shapes.items():
|
|
||||||
if key not in npy_arrays and 'img' not in key and key not in ('features_buffer', desire_key):
|
|
||||||
npy_arrays[key] = np.zeros(shape, dtype=np.float32)
|
|
||||||
|
|
||||||
queues = {
|
|
||||||
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
|
||||||
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
|
||||||
'desire_q': Tensor(np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]),
|
|
||||||
dtype=np.float32), device=device).contiguous().realize()
|
|
||||||
}
|
|
||||||
|
|
||||||
if features_buffer:
|
|
||||||
queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, features_buffer[0], features_buffer[2]),
|
|
||||||
dtype=np.float32), device=device).contiguous().realize()
|
|
||||||
|
|
||||||
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items()})
|
|
||||||
|
|
||||||
return queues, npy_arrays
|
return queues, npy_arrays
|
||||||
|
|
||||||
|
|
||||||
def make_split_input_queues(vision_input_shapes: dict, policy_input_shapes: dict,
|
def make_split_input_queues(vision_input_shapes: dict, policy_input_shapes: dict,
|
||||||
frame_skip: int, device: str = Device.DEFAULT, use_packed: bool = True) -> tuple[dict, dict]:
|
frame_skip: int, device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
||||||
return generate_queues_and_npy({**vision_input_shapes, **policy_input_shapes}, frame_skip, device, is_supercombo=False, use_packed=use_packed)
|
return generate_queues_and_npy({**vision_input_shapes, **policy_input_shapes}, frame_skip, device, is_supercombo=False)
|
||||||
|
|
||||||
|
|
||||||
def make_supercombo_input_queues(input_shapes: dict, frame_skip: int,
|
def make_supercombo_input_queues(input_shapes: dict, frame_skip: int,
|
||||||
device: str = Device.DEFAULT, use_packed: bool = True) -> tuple[dict, dict]:
|
device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
||||||
return generate_queues_and_npy(input_shapes, frame_skip, device, is_supercombo=True, use_packed=use_packed)
|
return generate_queues_and_npy(input_shapes, frame_skip, device, is_supercombo=True)
|
||||||
|
|
||||||
|
|
||||||
def create_jit_runner(vision_runner, policy_runners: list, nv12: NV12Frame, model_size: tuple[int, int],
|
def create_jit_runner(vision_runner, policy_runners: list, nv12: NV12Frame, model_size: tuple[int, int],
|
||||||
@@ -233,29 +208,34 @@ def compile_and_warmup(nv12: NV12Frame, model_size: tuple[int, int], prepare_onl
|
|||||||
raise ValueError("Could not find vision, model, or policy metadata.")
|
raise ValueError("Could not find vision, model, or policy metadata.")
|
||||||
|
|
||||||
features_slice = feat_meta['output_slices']['hidden_state']
|
features_slice = feat_meta['output_slices']['hidden_state']
|
||||||
WARP_DEV = 'CPU' if "USBGPU" in os.environ else Device.DEFAULT
|
WARP_DEV = os.getenv('WARP_DEV', Device.DEFAULT)
|
||||||
|
|
||||||
is_supercombo = vision_runner is None
|
is_supercombo = vision_runner is None
|
||||||
run_func = create_jit_runner(vision_runner, policy_runners, nv12, model_size, features_slice, frame_skip, all_shapes, prepare_only)
|
run_func = create_jit_runner(vision_runner, policy_runners, nv12, model_size, features_slice, frame_skip, all_shapes, prepare_only)
|
||||||
run_jit = TinyJit(run_func, prune=True)
|
run_jit = TinyJit(run_func, prune=True)
|
||||||
queues, npy_arrays = generate_queues_and_npy(all_shapes, frame_skip, Device.DEFAULT, is_supercombo=is_supercombo)
|
def run_once(seed):
|
||||||
|
queues, npy = generate_queues_and_npy(all_shapes, frame_skip, Device.DEFAULT, is_supercombo=is_supercombo)
|
||||||
for i in range(3):
|
rng = np.random.default_rng(seed)
|
||||||
rng = np.random.default_rng(42 + i)
|
|
||||||
frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
||||||
big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
||||||
for arr in npy_arrays.values():
|
for value in npy.values():
|
||||||
arr[:] = rng.standard_normal(arr.shape).astype(arr.dtype)
|
value[:] = rng.standard_normal(value.shape).astype(value.dtype)
|
||||||
|
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
start_time = time.perf_counter()
|
outs = run_jit(**queues, frame=frame, big_frame=big_frame)
|
||||||
run_jit(**queues, frame=frame, big_frame=big_frame)
|
|
||||||
mid_time = time.perf_counter()
|
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
print(f" [{i + 1}/3] enqueue {(mid_time - start_time) * 1e3:6.2f} ms -- total {(time.perf_counter() - start_time) * 1e3:6.2f} ms")
|
return [np.copy(value.numpy()) for value in (outs if isinstance(outs, tuple) else [outs])] if outs is not None else []
|
||||||
|
|
||||||
# TODO-SP: switch to dump_oob/load_oob on next full recompile of all models
|
for i in range(3):
|
||||||
return pickle.loads(pickle.dumps(run_jit)) if not prepare_only else run_jit
|
run_once(42 + i)
|
||||||
|
|
||||||
|
if not prepare_only:
|
||||||
|
baseline = run_once(42)
|
||||||
|
with tempfile.TemporaryFile(dir=".") as f:
|
||||||
|
dump_oob(run_jit, f)
|
||||||
|
f.seek(0)
|
||||||
|
run_jit = load_oob(f)
|
||||||
|
assert all(np.array_equal(baseline, deserialized) for baseline, deserialized in zip(baseline, run_once(42), strict=True)), "OOB pickling regression"
|
||||||
|
return run_jit
|
||||||
|
|
||||||
|
|
||||||
def _parse_size(size_str: str) -> tuple[int, int]:
|
def _parse_size(size_str: str) -> tuple[int, int]:
|
||||||
@@ -352,8 +332,7 @@ if __name__ == "__main__":
|
|||||||
vision_runner, policy_runners, output_data['metadata']))
|
vision_runner, policy_runners, output_data['metadata']))
|
||||||
|
|
||||||
with open(args.output, "wb") as file:
|
with open(args.output, "wb") as file:
|
||||||
# TODO-SP: switch to dump_oob from openpilot/selfdrive/helpers on next full recompile of all models
|
dump_oob(output_data, file)
|
||||||
pickle.dump(output_data, file)
|
|
||||||
|
|
||||||
pkl_size = os.path.getsize(args.output)
|
pkl_size = os.path.getsize(args.output)
|
||||||
print(f"Saved combined JIT to {args.output} ({pkl_size / 1e6:.2f} MB)")
|
print(f"Saved combined JIT to {args.output} ({pkl_size / 1e6:.2f} MB)")
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import os
|
|||||||
os.environ['GMMU'] = '0'
|
os.environ['GMMU'] = '0'
|
||||||
from openpilot.common.hardware import TICI
|
from openpilot.common.hardware import TICI
|
||||||
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
|
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
|
||||||
USBGPU = "USBGPU" in os.environ
|
|
||||||
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, load_oob
|
||||||
|
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
||||||
|
|
||||||
|
USBGPU = usbgpu_present()
|
||||||
if USBGPU:
|
if USBGPU:
|
||||||
os.environ['DEV'] = 'AMD'
|
os.environ['DEV'] = 'AMD'
|
||||||
os.environ['AMD_IFACE'] = 'USB'
|
os.environ['AMD_IFACE'] = 'USB'
|
||||||
import pickle
|
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import openpilot.cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
@@ -108,11 +111,10 @@ class ModelState(ModelStateBase):
|
|||||||
|
|
||||||
def _init_combined(self, pkl_path, cam_w, cam_h, bundle):
|
def _init_combined(self, pkl_path, cam_w, cam_h, bundle):
|
||||||
cloudlog.warning(f"loading combined pkl: {pkl_path}")
|
cloudlog.warning(f"loading combined pkl: {pkl_path}")
|
||||||
# TODO-SP: switch to load_oob from openpilot/selfdrive/helpers on next full recompile of all models
|
jits = load_oob(open_file_chunked(pkl_path))
|
||||||
jits = pickle.load(open_file_chunked(pkl_path))
|
|
||||||
|
|
||||||
self.DEV = Device.DEFAULT
|
self.DEV = Device.DEFAULT
|
||||||
self.WARP_DEV = 'CPU' if USBGPU else self.DEV
|
self.WARP_DEV = ('QCOM' if TICI else 'CPU') if USBGPU else self.DEV
|
||||||
self.QUEUE_DEV = self.DEV
|
self.QUEUE_DEV = self.DEV
|
||||||
|
|
||||||
metadata = jits['metadata']
|
metadata = jits['metadata']
|
||||||
@@ -120,13 +122,6 @@ class ModelState(ModelStateBase):
|
|||||||
self._run_policy = jits[(cam_w, cam_h)]['run_policy']
|
self._run_policy = jits[(cam_w, cam_h)]['run_policy']
|
||||||
self._warp_enqueue = jits[(cam_w, cam_h)]['warp_enqueue']
|
self._warp_enqueue = jits[(cam_w, cam_h)]['warp_enqueue']
|
||||||
|
|
||||||
# TODO-SP: Remove legacy use_packed detection block after all models are recompiled
|
|
||||||
captured = getattr(self._run_policy, 'captured', None)
|
|
||||||
if captured is not None:
|
|
||||||
use_packed = 'packed_npy_inputs' in getattr(captured, 'expected_names', [])
|
|
||||||
else:
|
|
||||||
use_packed = True
|
|
||||||
|
|
||||||
if 'model' in metadata:
|
if 'model' in metadata:
|
||||||
model_metadata = metadata['model']
|
model_metadata = metadata['model']
|
||||||
self.vision_output_slices = model_metadata['output_slices']
|
self.vision_output_slices = model_metadata['output_slices']
|
||||||
@@ -137,7 +132,7 @@ class ModelState(ModelStateBase):
|
|||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_supercombo_input_queues
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_supercombo_input_queues
|
||||||
frame_skip = derive_frame_skip({}, model_metadata['input_shapes'])
|
frame_skip = derive_frame_skip({}, model_metadata['input_shapes'])
|
||||||
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(model_metadata['input_shapes'],
|
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(model_metadata['input_shapes'],
|
||||||
frame_skip, device=self.QUEUE_DEV, use_packed=use_packed)
|
frame_skip, device=self.QUEUE_DEV)
|
||||||
else:
|
else:
|
||||||
vision_metadata = metadata['vision']
|
vision_metadata = metadata['vision']
|
||||||
policy_keys = [k for k in metadata if k != 'vision']
|
policy_keys = [k for k in metadata if k != 'vision']
|
||||||
@@ -156,7 +151,7 @@ class ModelState(ModelStateBase):
|
|||||||
self._vision_input_names = [k for k in vision_input_shapes if 'img' in k]
|
self._vision_input_names = [k for k in vision_input_shapes if 'img' in k]
|
||||||
frame_skip = derive_frame_skip(vision_input_shapes, policy_input_shapes)
|
frame_skip = derive_frame_skip(vision_input_shapes, policy_input_shapes)
|
||||||
self.input_queues, self.numpy_inputs = make_split_input_queues(vision_input_shapes, policy_input_shapes,
|
self.input_queues, self.numpy_inputs = make_split_input_queues(vision_input_shapes, policy_input_shapes,
|
||||||
frame_skip, device=self.QUEUE_DEV, use_packed=use_packed)
|
frame_skip, device=self.QUEUE_DEV)
|
||||||
|
|
||||||
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
|
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
|
||||||
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
|
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
|
||||||
@@ -189,6 +184,26 @@ class ModelState(ModelStateBase):
|
|||||||
frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize(),
|
frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize(),
|
||||||
big_frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize())
|
big_frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize())
|
||||||
|
|
||||||
|
if USBGPU:
|
||||||
|
self.warmup()
|
||||||
|
|
||||||
|
def warmup(self) -> None:
|
||||||
|
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self._vision_input_names}
|
||||||
|
transforms = {k: np.eye(3, dtype=np.float32) for k in [self._road_key, self._wide_key] if k}
|
||||||
|
|
||||||
|
dummy_inputs = {}
|
||||||
|
for k, v in self.numpy_inputs.items():
|
||||||
|
if k not in ['tfm', 'big_tfm', 'prev_feat']:
|
||||||
|
dummy_inputs[k] = np.zeros(v.shape, dtype=v.dtype)
|
||||||
|
|
||||||
|
self.run(dummy_frames, transforms, dummy_inputs, prepare_only=False)
|
||||||
|
|
||||||
|
for v in self.numpy_inputs.values():
|
||||||
|
v[:] = 0
|
||||||
|
self.prev_desire[:] = 0
|
||||||
|
self.full_frames.clear()
|
||||||
|
self._blob_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mlsim(self) -> bool:
|
def mlsim(self) -> bool:
|
||||||
@@ -265,11 +280,6 @@ class ModelState(ModelStateBase):
|
|||||||
buf[0, :-1] = buf[0, 1:]
|
buf[0, :-1] = buf[0, 1:]
|
||||||
buf[0, -1, :] = outputs['desired_curvature'][0, :] if not self.mlsim else 0
|
buf[0, -1, :] = outputs['desired_curvature'][0, :] if not self.mlsim else 0
|
||||||
|
|
||||||
# TODO-SP: This is a hack to prevent GPU corruption by calculating in CPU space, it can be removed on next recompile
|
|
||||||
if 'prev_feat' not in self.numpy_inputs and 'feat_q' in self.input_queues:
|
|
||||||
feat_val = self.input_queues['feat_q'].numpy()
|
|
||||||
self.input_queues['feat_q'].assign(feat_val).realize()
|
|
||||||
|
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
||||||
@@ -306,6 +316,9 @@ def main(demo=False):
|
|||||||
setproctitle(PROCESS_NAME)
|
setproctitle(PROCESS_NAME)
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
|
if USBGPU:
|
||||||
|
wait_usbgpu_link()
|
||||||
|
|
||||||
# visionipc clients
|
# visionipc clients
|
||||||
while True:
|
while True:
|
||||||
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
||||||
@@ -340,6 +353,9 @@ def main(demo=False):
|
|||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
params = Params()
|
params = Params()
|
||||||
|
|
||||||
|
params.put_bool("UsbGpuPresent", USBGPU)
|
||||||
|
params.put_bool("UsbGpuCompiled", USBGPU)
|
||||||
|
|
||||||
# setup filter to track dropped frames
|
# setup filter to track dropped frames
|
||||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
||||||
frame_id = 0
|
frame_id = 0
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from openpilot.common.params import Params
|
|||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.common.hardware.hw import Paths
|
from openpilot.common.hardware.hw import Paths
|
||||||
from openpilot.sunnypilot.models.helpers import is_bundle_version_compatible
|
from openpilot.sunnypilot.models.helpers import is_bundle_version_compatible
|
||||||
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
|
|
||||||
@@ -103,11 +104,11 @@ class ModelParser:
|
|||||||
class ModelCache:
|
class ModelCache:
|
||||||
"""Handles caching of model data to avoid frequent remote fetches"""
|
"""Handles caching of model data to avoid frequent remote fetches"""
|
||||||
|
|
||||||
def __init__(self, params: Params, cache_timeout: int = int(3600 * 1e9)):
|
def __init__(self, params: Params, cache_timeout: int = int(3600 * 1e9), suffix: str = ""):
|
||||||
self.params = params
|
self.params = params
|
||||||
self.cache_timeout = cache_timeout
|
self.cache_timeout = cache_timeout
|
||||||
self._LAST_SYNC_KEY = "ModelManager_LastSyncTime"
|
self._LAST_SYNC_KEY = f"ModelManager_LastSyncTime{suffix}"
|
||||||
self._CACHE_KEY = "ModelManager_ModelsCache"
|
self._CACHE_KEY = f"ModelManager_ModelsCache{suffix}"
|
||||||
|
|
||||||
def _is_expired(self) -> bool:
|
def _is_expired(self) -> bool:
|
||||||
"""Checks if the cache has expired"""
|
"""Checks if the cache has expired"""
|
||||||
@@ -139,24 +140,28 @@ 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_v18.json"
|
|
||||||
|
|
||||||
def __init__(self, params: Params):
|
def __init__(self, params: Params):
|
||||||
self.params = params
|
self.params = params
|
||||||
self.model_cache = ModelCache(params)
|
|
||||||
self.model_parser = ModelParser()
|
self.model_parser = ModelParser()
|
||||||
|
if usbgpu_present():
|
||||||
|
self.model_cache = ModelCache(params, suffix="_USBGPU")
|
||||||
|
self.model_url = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v18.json"
|
||||||
|
else:
|
||||||
|
self.model_cache = ModelCache(params)
|
||||||
|
self.model_url = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v18.json"
|
||||||
|
|
||||||
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
||||||
"""Fetches fresh model data from remote and updates cache.
|
"""Fetches fresh model data from remote and updates cache.
|
||||||
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
|
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = requests.get(self.MODEL_URL, timeout=10)
|
response = requests.get(self.model_url, timeout=10)
|
||||||
|
|
||||||
# Explicitly handle 404 differently
|
# Explicitly handle 404 differently
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
cloudlog.error(f"Models URL returned 404 Not Found: {self.MODEL_URL}")
|
cloudlog.error(f"Models URL returned 404 Not Found: {self.model_url}")
|
||||||
raise HTTPError(f"404 Not Found: {self.MODEL_URL}", response=response)
|
raise HTTPError(f"404 Not Found: {self.model_url}", response=response)
|
||||||
|
|
||||||
# Raise for any other 4xx/5xx
|
# Raise for any other 4xx/5xx
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from openpilot.common.params import Params
|
||||||
from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref
|
from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref
|
||||||
from openpilot.sunnypilot.models.fetcher import ModelFetcher
|
from openpilot.sunnypilot.models.fetcher import ModelFetcher
|
||||||
|
|
||||||
|
|
||||||
def fetch_tinygrad_ref():
|
def fetch_tinygrad_ref():
|
||||||
response = requests.get(ModelFetcher.MODEL_URL, timeout=10)
|
fetcher = ModelFetcher(Params())
|
||||||
|
response = requests.get(fetcher.model_url, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
json_data = response.json()
|
json_data = response.json()
|
||||||
return json_data.get("tinygrad_ref")
|
return json_data.get("tinygrad_ref")
|
||||||
|
|||||||
Reference in New Issue
Block a user