From 776965ad3de204cdb48e141f126a4ebff4e8a6d3 Mon Sep 17 00:00:00 2001 From: discountchubbs Date: Mon, 10 Aug 2026 13:10:32 -0700 Subject: [PATCH] egpu --- openpilot/selfdrive/modeld/SConscript | 35 ++++---- openpilot/selfdrive/modeld/helpers.py | 5 +- openpilot/selfdrive/modeld/modeld.py | 98 +++++++++++++++++++---- openpilot/sunnypilot/modeld_v2/SConscript | 13 +-- openpilot/sunnypilot/modeld_v2/modeld.py | 34 ++++++-- 5 files changed, 133 insertions(+), 52 deletions(-) diff --git a/openpilot/selfdrive/modeld/SConscript b/openpilot/selfdrive/modeld/SConscript index 5f6281556f..30b008078e 100644 --- a/openpilot/selfdrive/modeld/SConscript +++ b/openpilot/selfdrive/modeld/SConscript @@ -1,7 +1,7 @@ import glob import json import os -import sys, subprocess +import time from SCons.Script import Action, Value from openpilot.common.file_chunker import chunk_file, get_chunk_targets, get_existing_chunks from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye @@ -26,18 +26,7 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + " def estimate_pickle_max_size(onnx_size): return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -# get fastest TG config -# probe in subprocess so usbgpu locks gets released on process exit -def probe_devices(): - return set(subprocess.run( - [sys.executable, '-c', 'from tinygrad import Device\nprint("\\n".join(Device.get_available_devices()))'], - capture_output=True, text=True, check=True).stdout.strip().splitlines()) - -available = probe_devices() -if 'CUDA' in available: - tg_backend = 'CUDA' - tg_flags = f'DEV={tg_backend}' -elif 'QCOM' in available: +if arch == 'comma_arm64': tg_backend = 'QCOM' tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1' else: @@ -54,9 +43,9 @@ tg_devices = { # which device to put jit inputs to at runtime }, } -USBGPU = usbgpu_present() # or release # TODO always build big model on release +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_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2' # the USB+AMD GPU takes an exclusive flock; serialize all targets that touch it usbgpu_lock = File("models/.usb_gpu.lock").abspath @@ -97,12 +86,26 @@ for usbgpu in [False, True] if USBGPU else [False]: f'--output {target_pkl_path} --frame-skip {frame_skip}') onnx_sizes_sum = sum(os.path.getsize(f) for f in driving_onnx_deps) chunk_targets = get_chunk_targets(target_pkl_path, estimate_pickle_max_size(onnx_sizes_sum)) + def do_compile(target, source, env, command=cmd, pkl=target_pkl_path, chunks=chunk_targets): + from openpilot.system.hardware.chestnut.flash import link_up + # chestnut can enumerate before its PCIe link is up due to varying 12V power behavior across cars + for _ in range(10): + if link_up(): + break + time.sleep(1) + else: + print("Chestnut not ready, skipping big model build") + return + if ret := env.Execute(command): + return ret + chunk_file(pkl, chunks) def do_chunk(target, source, env, pkl=target_pkl_path, chunks=chunk_targets): chunk_file(pkl, chunks) + actions = Action(do_compile, " [USBGPU] $TARGET") if usbgpu else [cmd, Action(do_chunk, " [CHUNK] $TARGET")] node = lenv.Command( chunk_targets, tinygrad_files + compile_modeld_script + driving_onnx_deps + [Value(chunk_targets), chunker_file], - [cmd, Action(do_chunk, " [CHUNK] $TARGET")], + actions, ) if usbgpu: lenv.SideEffect(usbgpu_lock, node) diff --git a/openpilot/selfdrive/modeld/helpers.py b/openpilot/selfdrive/modeld/helpers.py index 606ad44ae1..37ab0b26d7 100644 --- a/openpilot/selfdrive/modeld/helpers.py +++ b/openpilot/selfdrive/modeld/helpers.py @@ -7,7 +7,7 @@ import tempfile 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 +from openpilot.common.hardware.usb import CHESTNUT_FW_VERSION, CHESTNUT_USB_IDS, USB_DEVICES_PATH MODELS_DIR = Path(__file__).resolve().parent / 'models' TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json' @@ -49,7 +49,8 @@ def usbgpu_present() -> bool: for d in USB_DEVICES_PATH.glob("*"): try: usb_id = (int((d / "idVendor").read_text(), 16), int((d / "idProduct").read_text(), 16)) - if usb_id == (CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID): + product = (d / "product").read_text().strip() + if usb_id in CHESTNUT_USB_IDS and product == f"custom {CHESTNUT_FW_VERSION}-CLEAN": return True except Exception: pass diff --git a/openpilot/selfdrive/modeld/modeld.py b/openpilot/selfdrive/modeld/modeld.py index d7ddce60b9..e87d4f3111 100755 --- a/openpilot/selfdrive/modeld/modeld.py +++ b/openpilot/selfdrive/modeld/modeld.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 +from functools import cached_property import os os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom from tinygrad.tensor import Tensor +from tinygrad.device import Device +import struct import threading import time import numpy as np @@ -9,7 +12,9 @@ import openpilot.cereal.messaging as messaging from openpilot.cereal import log from opendbc.car.structs import car from openpilot.cereal.messaging import PubMaster, SubMaster -from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from openpilot.cereal.services import SERVICE_LIST +from openpilot.cereal.visionipc import VisionStreamType +from msgq.visionipc import VisionIpcClient, VisionBuf from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params @@ -64,6 +69,50 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. shouldStop=bool(stop)) +class ChestnutState: + # only modeld can access chestnut + def __init__(self, pm: PubMaster): + self.pm = pm + self.valid = True + + @cached_property + def power_limit(self) -> int: + smu = Device["AMD"].iface.dev_impl.smu + return smu._send_msg(smu.smu_mod.PPSMC_MSG_GetPptLimit, 0, read_back_arg=True, timeout=100) + + def send(self) -> None: + msg = messaging.new_message('chestnutState') + state = msg.chestnutState + valid = False + if "AMD" in Device._opened_devices: + try: + smu = Device["AMD"].iface.dev_impl.smu + smu._send_msg(smu.smu_mod.PPSMC_MSG_TransferTableSmu2Dram, smu.smu_mod.TABLE_SMU_METRICS, timeout=100) + metrics = smu.read_table(smu.smu_mod.SmuMetricsExternal_t, smu.smu_mod.TABLE_SMU_METRICS).SmuMetrics + state.tempC = metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT] + state.memoryTempC = metrics.AvgTemperature[smu.smu_mod.TEMP_MEM] + state.powerDrawW = metrics.AverageSocketPower + state.powerLimitW = self.power_limit + state.gpuUsagePercent = metrics.AverageGfxActivity + state.gpuClockMhz = metrics.AverageGfxclkFrequencyPostDs + state.fanSpeedRpm = metrics.AvgFanRpm + valid = True + except Exception: + if self.valid: + cloudlog.exception("chestnut state read failed") + try: + # ASM runs on USB-C power, these still read without a gpu + asm = Device["AMD"].iface.pci_dev.usb + state.pcieLtssm = asm.read(0xB450, 1)[0] + state.supplyVoltage, state.supplyCurrent = struct.unpack('