mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-07 04:25:41 +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"
|
||||||
|
|||||||
@@ -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"}},
|
||||||
|
|||||||
@@ -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