mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-23 18:22:13 +08:00
smoosh smoosh
This commit is contained in:
+145
-22
@@ -3,6 +3,7 @@ import argparse
|
||||
import codecs
|
||||
import os
|
||||
import pickle
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -10,11 +11,18 @@ import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.starpilot.common.model_versions import uses_combined_driving_artifacts
|
||||
|
||||
DEFAULT_INPUT_ROOT = Path("/data/openpilot/uncompiledmodels")
|
||||
DEFAULT_OUTPUT_ROOT = Path("/data/openpilot/compiledmodels")
|
||||
COMPILE_SCRIPT = REPO_ROOT / "tinygrad_repo/examples/openpilot/compile3.py"
|
||||
COMBINED_COMPILE_SCRIPT = REPO_ROOT / "selfdrive/modeld/compile_modeld.py"
|
||||
MODEL_VERSIONS_CACHE = Path("/data/models/.model_versions.json")
|
||||
DM_MODEL_KEY = "dm"
|
||||
DM_MODEL_NAME = "dmonitoring_model"
|
||||
DM_TARGET_ALIASES = {DM_MODEL_KEY, "dmonitoring", DM_MODEL_NAME}
|
||||
@@ -26,7 +34,33 @@ COMPONENT_ALIASES = {
|
||||
"driving_policy": ("driving_policy", "policy"),
|
||||
"driving_vision": ("driving_vision", "vision"),
|
||||
}
|
||||
REQUIRED_COMPONENTS = {"driving_policy", "driving_vision"}
|
||||
MEDMODEL_INPUT_SIZE = (512, 256)
|
||||
DEFAULT_CAMERA_RESOLUTIONS = (
|
||||
(1928, 1208),
|
||||
(1344, 760),
|
||||
)
|
||||
|
||||
|
||||
def build_compile_env(*, combined: bool = False) -> dict[str, str]:
|
||||
env = os.environ.copy()
|
||||
existing_pythonpath = env.get("PYTHONPATH", "")
|
||||
env["PYTHONPATH"] = f"{REPO_ROOT}:{existing_pythonpath}" if existing_pythonpath else str(REPO_ROOT)
|
||||
|
||||
numeric_defaults = {
|
||||
"DEBUG": "0",
|
||||
"FLOAT16": "1",
|
||||
"IMAGE": "2",
|
||||
"JIT_BATCH_SIZE": "0",
|
||||
"NOLOCALS": "1",
|
||||
}
|
||||
for key, default in numeric_defaults.items():
|
||||
value = env.get(key)
|
||||
try:
|
||||
int(str(value), 0)
|
||||
except (TypeError, ValueError):
|
||||
env[key] = default
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
@@ -37,6 +71,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--dm", action="store_true", help="Compile the driver monitoring model into dmonitoring_model_tinygrad.pkl.")
|
||||
parser.add_argument("--input-dir", type=Path, default=DEFAULT_INPUT_ROOT, help="Directory containing staged ONNX files. Flat root files like driving_policy.onnx are preferred.")
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_ROOT, help="Directory for compiled tinygrad pkls and metadata.")
|
||||
parser.add_argument("--version", help="Model version. v16+ uses the combined driving_tinygrad artifact path. If omitted, split-policy staged models default to the combined build.")
|
||||
parser.add_argument("--list", action="store_true", help="List detected staged models and exit.")
|
||||
parser.add_argument("--force", action="store_true", help="Legacy no-op. Compiled outputs are always cleared before a build.")
|
||||
|
||||
@@ -66,14 +101,6 @@ def detect_component(path: Path) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def normalize_model_files(model_files: dict[str, Path]) -> dict[str, Path]:
|
||||
normalized = dict(model_files)
|
||||
on_policy_path = normalized.pop("driving_on_policy", None)
|
||||
if on_policy_path is not None and "driving_policy" not in normalized and "driving_off_policy" in normalized:
|
||||
normalized["driving_policy"] = on_policy_path
|
||||
return normalized
|
||||
|
||||
|
||||
def find_staged_dm(input_root: Path) -> Path | None:
|
||||
if not input_root.is_dir():
|
||||
return None
|
||||
@@ -107,7 +134,6 @@ def find_staged_models(input_root: Path) -> dict[str, dict[str, Path]]:
|
||||
component = detect_component(onnx_file)
|
||||
if component:
|
||||
model_files[component] = onnx_file
|
||||
model_files = normalize_model_files(model_files)
|
||||
if model_files:
|
||||
found[child.name] = model_files
|
||||
|
||||
@@ -137,7 +163,7 @@ def find_staged_models(input_root: Path) -> dict[str, dict[str, Path]]:
|
||||
flat_root_files[component] = onnx_file
|
||||
|
||||
if flat_root_files:
|
||||
found["_root"] = normalize_model_files(flat_root_files)
|
||||
found["_root"] = flat_root_files
|
||||
|
||||
return found
|
||||
|
||||
@@ -156,7 +182,7 @@ def resolve_model_files(input_root: Path, model_key: str) -> dict[str, Path]:
|
||||
component = detect_component(onnx_file)
|
||||
if component:
|
||||
prefixed_files[component] = onnx_file
|
||||
return normalize_model_files(prefixed_files)
|
||||
return prefixed_files
|
||||
|
||||
|
||||
def get_metadata_value_by_name(model, name: str):
|
||||
@@ -190,17 +216,94 @@ def write_metadata(onnx_path: Path, output_path: Path) -> None:
|
||||
|
||||
|
||||
def compile_component(onnx_path: Path, output_path: Path) -> None:
|
||||
env = os.environ.copy()
|
||||
existing_pythonpath = env.get("PYTHONPATH", "")
|
||||
env["PYTHONPATH"] = f"{REPO_ROOT}:{existing_pythonpath}" if existing_pythonpath else str(REPO_ROOT)
|
||||
subprocess.run(
|
||||
[sys.executable, str(COMPILE_SCRIPT), str(onnx_path), str(output_path)],
|
||||
cwd=REPO_ROOT,
|
||||
env=env,
|
||||
env=build_compile_env(combined=False),
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def compile_combined_model(component_paths: dict[str, Path], output_path: Path) -> None:
|
||||
vision_path = component_paths["driving_vision"]
|
||||
off_policy_path = component_paths["driving_off_policy"]
|
||||
on_policy_path = component_paths.get("driving_on_policy") or component_paths.get("driving_policy")
|
||||
if on_policy_path is None:
|
||||
raise ValueError("Combined compile requires driving_on_policy.onnx (or driving_policy.onnx) alongside driving_off_policy.onnx")
|
||||
|
||||
frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||
camera_resolutions = [f"{width}x{height}" for width, height in DEFAULT_CAMERA_RESOLUTIONS]
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(COMBINED_COMPILE_SCRIPT),
|
||||
"--model-size",
|
||||
f"{MEDMODEL_INPUT_SIZE[0]}x{MEDMODEL_INPUT_SIZE[1]}",
|
||||
"--camera-resolutions",
|
||||
*camera_resolutions,
|
||||
"--vision-onnx",
|
||||
str(vision_path),
|
||||
"--off-policy-onnx",
|
||||
str(off_policy_path),
|
||||
"--on-policy-onnx",
|
||||
str(on_policy_path),
|
||||
"--output",
|
||||
str(output_path),
|
||||
"--frame-skip",
|
||||
str(frame_skip),
|
||||
],
|
||||
cwd=REPO_ROOT,
|
||||
env=build_compile_env(combined=True),
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def infer_model_version(model_key: str, explicit_version: str | None) -> str:
|
||||
if explicit_version:
|
||||
return explicit_version.strip()
|
||||
|
||||
if MODEL_VERSIONS_CACHE.is_file():
|
||||
try:
|
||||
version_map = json.loads(MODEL_VERSIONS_CACHE.read_text())
|
||||
version = version_map.get(model_key)
|
||||
if isinstance(version, str) and version.strip():
|
||||
return version.strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def should_use_combined_artifacts(model_version: str, model_files: dict[str, Path]) -> bool:
|
||||
if uses_combined_driving_artifacts(model_version):
|
||||
return True
|
||||
if model_version.strip():
|
||||
return False
|
||||
|
||||
has_vision = "driving_vision" in model_files
|
||||
has_off_policy = "driving_off_policy" in model_files
|
||||
has_on_policy = "driving_on_policy" in model_files or "driving_policy" in model_files
|
||||
return has_vision and has_off_policy and has_on_policy
|
||||
|
||||
|
||||
def resolve_split_component_inputs(model_files: dict[str, Path]) -> dict[str, Path]:
|
||||
resolved: dict[str, Path] = {}
|
||||
|
||||
vision_path = model_files.get("driving_vision")
|
||||
if vision_path is not None:
|
||||
resolved["driving_vision"] = vision_path
|
||||
|
||||
policy_path = model_files.get("driving_policy") or model_files.get("driving_on_policy")
|
||||
if policy_path is not None:
|
||||
resolved["driving_policy"] = policy_path
|
||||
|
||||
off_policy_path = model_files.get("driving_off_policy")
|
||||
if off_policy_path is not None:
|
||||
resolved["driving_off_policy"] = off_policy_path
|
||||
|
||||
return resolved
|
||||
|
||||
|
||||
def clear_existing_outputs(output_dir: Path) -> list[Path]:
|
||||
removed = []
|
||||
for existing in sorted(output_dir.iterdir()):
|
||||
@@ -277,18 +380,38 @@ def main() -> int:
|
||||
f"or optionally {args.input_dir / model_key}/*.onnx"
|
||||
)
|
||||
|
||||
missing = sorted(REQUIRED_COMPONENTS - set(files))
|
||||
if missing:
|
||||
raise SystemExit(f"Missing required ONNX files for {model_key}: {', '.join(missing)}")
|
||||
model_version = infer_model_version(model_key, args.version)
|
||||
use_combined_artifacts = should_use_combined_artifacts(model_version, files)
|
||||
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Compiling {model_key} from {args.input_dir} -> {args.output_dir}")
|
||||
mode_label = "combined" if use_combined_artifacts else "split"
|
||||
version_label = model_version or ("auto-combined" if use_combined_artifacts else "legacy-default")
|
||||
print(f"Compiling {model_key} ({version_label}, {mode_label}) from {args.input_dir} -> {args.output_dir}")
|
||||
|
||||
removed = clear_existing_outputs(args.output_dir)
|
||||
if removed:
|
||||
print(f" cleared {len(removed)} existing output entries")
|
||||
|
||||
for component, onnx_path in sorted(files.items()):
|
||||
if use_combined_artifacts:
|
||||
required_components = {"driving_vision", "driving_off_policy"}
|
||||
if not (files.get("driving_on_policy") or files.get("driving_policy")):
|
||||
required_components.add("driving_on_policy")
|
||||
missing = sorted(component for component in required_components if component not in files)
|
||||
if missing:
|
||||
raise SystemExit(f"Missing required ONNX files for combined compile of {model_key}: {', '.join(missing)}")
|
||||
|
||||
output_pkl = args.output_dir / f"{model_key}_driving_tinygrad.pkl"
|
||||
compile_combined_model(files, output_pkl)
|
||||
print(f" saved {output_pkl.name}")
|
||||
print("Done.")
|
||||
return 0
|
||||
|
||||
split_components = resolve_split_component_inputs(files)
|
||||
missing = sorted(component for component in ("driving_policy", "driving_vision") if component not in split_components)
|
||||
if missing:
|
||||
raise SystemExit(f"Missing required ONNX files for {model_key}: {', '.join(missing)}")
|
||||
|
||||
for component, onnx_path in sorted(split_components.items()):
|
||||
output_pkl = args.output_dir / f"{model_key}_{component}_tinygrad.pkl"
|
||||
output_metadata = args.output_dir / f"{model_key}_{component}_metadata.pkl"
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from openpilot.common.constants import CV
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.starpilot.common.model_versions import is_tinygrad_model_version
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance
|
||||
@@ -434,7 +435,7 @@ class LongitudinalPlanner:
|
||||
|
||||
@property
|
||||
def mlsim(self):
|
||||
return self.generation in ("v8", "v10", "v11", "v12", "v13", "v14", "v15")
|
||||
return is_tinygrad_model_version(self.generation)
|
||||
|
||||
def get_mpc_mode(self) -> str:
|
||||
if not self.mlsim:
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import atexit
|
||||
import os
|
||||
import pickle
|
||||
import time
|
||||
from collections import defaultdict, namedtuple
|
||||
from functools import partial
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _patch_tinygrad_fetch_fw():
|
||||
import hashlib
|
||||
import pathlib
|
||||
|
||||
import zstandard
|
||||
from tinygrad import helpers
|
||||
|
||||
original_fetch_fw = getattr(helpers, "fetch_fw", None)
|
||||
if original_fetch_fw is None:
|
||||
return
|
||||
|
||||
def fetch_fw(path, name, sha256):
|
||||
firmware_path = pathlib.Path(f"/lib/firmware/{path}/{name}.zst")
|
||||
if firmware_path.is_file():
|
||||
blob = zstandard.ZstdDecompressor().stream_reader(firmware_path.read_bytes()).read()
|
||||
if hashlib.sha256(blob).hexdigest() == sha256:
|
||||
return blob
|
||||
return original_fetch_fw(path, name, sha256)
|
||||
|
||||
helpers.fetch_fw = fetch_fw
|
||||
|
||||
|
||||
_patch_tinygrad_fetch_fw()
|
||||
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.engine.jit import TinyJit
|
||||
from tinygrad.helpers import Context
|
||||
from tinygrad.tensor import Tensor
|
||||
|
||||
|
||||
NV12Frame = namedtuple("NV12Frame", ["width", "height", "stride", "y_height", "uv_height", "size"])
|
||||
WARP_INPUTS = ["img_q", "big_img_q", "tfm", "big_tfm"]
|
||||
POLICY_INPUTS = ["feat_q", "desire_q", "desire", "traffic_convention", "action_t"]
|
||||
|
||||
WARP_DEV = os.getenv("WARP_DEV")
|
||||
|
||||
|
||||
def make_random_images(keys, shape, device=None):
|
||||
return {key: Tensor.randint(shape, low=0, high=256, dtype="uint8", device=device).realize() for key in keys}
|
||||
|
||||
|
||||
def warp_perspective_tinygrad(src_flat, matrix_inverse, dst_shape, src_shape, stride_pad, border_fill_val=None):
|
||||
width_dst, height_dst = dst_shape
|
||||
height_src, width_src = src_shape
|
||||
|
||||
x = Tensor.arange(width_dst, device=WARP_DEV).reshape(1, width_dst).expand(height_dst, width_dst).reshape(-1)
|
||||
y = Tensor.arange(height_dst, device=WARP_DEV).reshape(height_dst, 1).expand(height_dst, width_dst).reshape(-1)
|
||||
|
||||
# Inline 3x3 matmul as elementwise to avoid reduce ops and enable fusion with gather.
|
||||
src_x = matrix_inverse[0, 0] * x + matrix_inverse[0, 1] * y + matrix_inverse[0, 2]
|
||||
src_y = matrix_inverse[1, 0] * x + matrix_inverse[1, 1] * y + matrix_inverse[1, 2]
|
||||
src_w = matrix_inverse[2, 0] * x + matrix_inverse[2, 1] * y + matrix_inverse[2, 2]
|
||||
|
||||
src_x = src_x / src_w
|
||||
src_y = src_y / src_w
|
||||
|
||||
x_round = Tensor.round(src_x)
|
||||
y_round = Tensor.round(src_y)
|
||||
x_nn_clipped = x_round.clip(0, width_src - 1).cast("int")
|
||||
y_nn_clipped = y_round.clip(0, height_src - 1).cast("int")
|
||||
idx = y_nn_clipped * (width_src + stride_pad) + x_nn_clipped
|
||||
sampled = src_flat[idx]
|
||||
|
||||
if border_fill_val is None:
|
||||
return sampled
|
||||
|
||||
in_bounds = ((x_round >= 0) & (x_round <= width_src - 1) &
|
||||
(y_round >= 0) & (y_round <= height_src - 1)).cast(sampled.dtype)
|
||||
return sampled * in_bounds + Tensor(border_fill_val, dtype=sampled.dtype) * (1 - in_bounds)
|
||||
|
||||
|
||||
def frames_to_tensor(frames):
|
||||
height = (frames.shape[0] * 2) // 3
|
||||
width = frames.shape[1]
|
||||
return Tensor.cat(
|
||||
frames[0:height:2, 0::2],
|
||||
frames[1:height:2, 0::2],
|
||||
frames[0:height:2, 1::2],
|
||||
frames[1:height:2, 1::2],
|
||||
frames[height:height + height // 4].reshape((height // 2, width // 2)),
|
||||
frames[height + height // 4:height + height // 2].reshape((height // 2, width // 2)),
|
||||
dim=0,
|
||||
).reshape((6, height // 2, width // 2))
|
||||
|
||||
|
||||
def make_frame_prepare(nv12: NV12Frame, model_w, model_h):
|
||||
cam_w, cam_h, stride, y_height, uv_height, _ = nv12
|
||||
uv_offset = stride * y_height
|
||||
stride_pad = stride - cam_w
|
||||
|
||||
def frame_prepare_tinygrad(input_frame, matrix_inverse):
|
||||
# UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling.
|
||||
matrix_inverse_uv = matrix_inverse * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=WARP_DEV)
|
||||
# Deinterleave NV12 UV plane (UVUV... -> separate U, V).
|
||||
uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride)
|
||||
with Context(SPLIT_REDUCEOP=0):
|
||||
y = warp_perspective_tinygrad(
|
||||
input_frame[:cam_h * stride],
|
||||
matrix_inverse,
|
||||
(model_w, model_h),
|
||||
(cam_h, cam_w),
|
||||
stride_pad,
|
||||
).realize()
|
||||
u = warp_perspective_tinygrad(
|
||||
uv[:cam_h // 2, :cam_w:2].flatten(),
|
||||
matrix_inverse_uv,
|
||||
(model_w // 2, model_h // 2),
|
||||
(cam_h // 2, cam_w // 2),
|
||||
0,
|
||||
).realize()
|
||||
v = warp_perspective_tinygrad(
|
||||
uv[:cam_h // 2, 1:cam_w:2].flatten(),
|
||||
matrix_inverse_uv,
|
||||
(model_w // 2, model_h // 2),
|
||||
(cam_h // 2, cam_w // 2),
|
||||
0,
|
||||
).realize()
|
||||
yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w))
|
||||
return frames_to_tensor(yuv)
|
||||
|
||||
return frame_prepare_tinygrad
|
||||
|
||||
|
||||
def make_input_queues(vision_input_shapes, policy_input_shapes, frame_skip, device):
|
||||
img = vision_input_shapes["img"]
|
||||
n_frames = img[1] // 6
|
||||
img_buf_shape = (frame_skip * (n_frames - 1) + 1, 6, img[2], img[3])
|
||||
|
||||
features_buffer = policy_input_shapes["features_buffer"]
|
||||
desire_pulse = policy_input_shapes["desire_pulse"]
|
||||
traffic_convention = policy_input_shapes["traffic_convention"]
|
||||
|
||||
npy = {
|
||||
"desire": np.zeros(desire_pulse[2], dtype=np.float32),
|
||||
"traffic_convention": np.zeros(traffic_convention, dtype=np.float32),
|
||||
"tfm": np.zeros((3, 3), dtype=np.float32),
|
||||
"big_tfm": np.zeros((3, 3), dtype=np.float32),
|
||||
}
|
||||
if "action_t" in policy_input_shapes:
|
||||
npy["action_t"] = np.zeros(policy_input_shapes["action_t"], dtype=np.float32)
|
||||
|
||||
input_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(),
|
||||
"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(),
|
||||
"desire_q": Tensor(
|
||||
np.zeros((frame_skip * desire_pulse[1], desire_pulse[0], desire_pulse[2]), dtype=np.float32),
|
||||
device=device,
|
||||
).contiguous().realize(),
|
||||
**{key: Tensor(value, device="NPY").realize() for key, value in npy.items()},
|
||||
}
|
||||
return input_queues, npy
|
||||
|
||||
|
||||
def shift_and_sample(buf, new_val, sample_fn):
|
||||
buf.assign(buf[1:].cat(new_val, dim=0).contiguous())
|
||||
return sample_fn(buf)
|
||||
|
||||
|
||||
def sample_skip(buf, frame_skip):
|
||||
return buf[::frame_skip].contiguous().flatten(0, 1).unsqueeze(0)
|
||||
|
||||
|
||||
def sample_desire(buf, frame_skip):
|
||||
return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
|
||||
|
||||
|
||||
def make_warp(nv12, model_w, model_h, frame_skip):
|
||||
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
|
||||
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
|
||||
|
||||
def warp_enqueue(img_q, big_img_q, tfm, big_tfm, frame, big_frame):
|
||||
tfm = tfm.to(WARP_DEV)
|
||||
big_tfm = big_tfm.to(WARP_DEV)
|
||||
Tensor.realize(tfm, big_tfm)
|
||||
|
||||
warped_frame = frame_prepare(frame, tfm).unsqueeze(0).to(Device.DEFAULT)
|
||||
warped_big_frame = frame_prepare(big_frame, big_tfm).unsqueeze(0).to(Device.DEFAULT)
|
||||
img = shift_and_sample(img_q, warped_frame, sample_skip_fn)
|
||||
big_img = shift_and_sample(big_img_q, warped_big_frame, sample_skip_fn)
|
||||
return img, big_img
|
||||
|
||||
return warp_enqueue
|
||||
|
||||
|
||||
def make_run_policy(vision_runner, off_policy_runner, on_policy_runner, vision_features_slice, frame_skip):
|
||||
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
|
||||
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
|
||||
|
||||
def run_policy(img, big_img, feat_q, desire_q, desire, traffic_convention, action_t):
|
||||
desire = desire.to(Device.DEFAULT)
|
||||
traffic_convention = traffic_convention.to(Device.DEFAULT)
|
||||
action_t = action_t.to(Device.DEFAULT)
|
||||
Tensor.realize(desire, traffic_convention, action_t)
|
||||
|
||||
desire_buf = shift_and_sample(desire_q, desire.reshape(1, 1, -1), sample_desire_fn)
|
||||
vision_out = next(iter(vision_runner({"img": img, "big_img": big_img}).values())).cast("float32")
|
||||
|
||||
new_feat = vision_out[:, vision_features_slice].reshape(1, -1).unsqueeze(0)
|
||||
feat_buf = shift_and_sample(feat_q, new_feat, sample_skip_fn)
|
||||
|
||||
inputs = {
|
||||
"features_buffer": feat_buf,
|
||||
"desire_pulse": desire_buf,
|
||||
"traffic_convention": traffic_convention,
|
||||
"action_t": action_t,
|
||||
}
|
||||
on_policy_out = next(iter(on_policy_runner(inputs).values())).cast("float32")
|
||||
off_policy_out = next(iter(off_policy_runner(inputs).values())).cast("float32")
|
||||
return vision_out, on_policy_out, off_policy_out
|
||||
|
||||
return run_policy
|
||||
|
||||
|
||||
def compile_jit(jit, make_random_inputs, input_keys, frame_skip, vision_metadata, policy_metadata):
|
||||
vision_input_shapes = vision_metadata["input_shapes"]
|
||||
policy_input_shapes = policy_metadata["input_shapes"]
|
||||
|
||||
seed = 42
|
||||
validation_rtol = 5e-3 if Device.DEFAULT == "QCOM" else 0.0
|
||||
validation_atol = 5e-3 if Device.DEFAULT == "QCOM" else 0.0
|
||||
|
||||
def arrays_match(lhs, rhs):
|
||||
if lhs.shape != rhs.shape:
|
||||
return False
|
||||
if np.issubdtype(lhs.dtype, np.floating) or np.issubdtype(rhs.dtype, np.floating):
|
||||
return np.allclose(lhs, rhs, rtol=validation_rtol, atol=validation_atol, equal_nan=True)
|
||||
return np.array_equal(lhs, rhs)
|
||||
|
||||
def random_inputs_run(fn, current_seed, test_val=None, test_buffers=None, expect_match=True):
|
||||
input_queues, npy = make_input_queues(vision_input_shapes, policy_input_shapes, frame_skip, Device.DEFAULT)
|
||||
np.random.seed(current_seed)
|
||||
Tensor.manual_seed(current_seed)
|
||||
|
||||
testing = test_val is not None or test_buffers is not None
|
||||
n_runs = 1 if testing else 3
|
||||
|
||||
for idx in range(n_runs):
|
||||
for value in npy.values():
|
||||
value[:] = np.random.randn(*value.shape).astype(value.dtype)
|
||||
Device.default.synchronize()
|
||||
random_inputs = make_random_inputs()
|
||||
start = time.perf_counter()
|
||||
outs = fn(**{key: input_queues[key] for key in input_keys}, **random_inputs)
|
||||
mid = time.perf_counter()
|
||||
Device.default.synchronize()
|
||||
end = time.perf_counter()
|
||||
print(f" [{idx + 1}/{n_runs}] enqueue {(mid - start) * 1e3:6.2f} ms -- total {(end - start) * 1e3:6.2f} ms")
|
||||
|
||||
if idx == 0:
|
||||
val = [np.copy(value.numpy()) for value in outs]
|
||||
buffers = [np.copy(value.numpy().copy()) for value in input_queues.values()]
|
||||
|
||||
if Device.DEFAULT != "QCOM":
|
||||
if test_val is not None:
|
||||
match = all(arrays_match(lhs, rhs) for lhs, rhs in zip(val, test_val, strict=True))
|
||||
assert match == expect_match, f"outputs {'differ from' if expect_match else 'match'} baseline (seed={current_seed})"
|
||||
if test_buffers is not None:
|
||||
match = all(arrays_match(lhs, rhs) for lhs, rhs in zip(buffers, test_buffers, strict=True))
|
||||
assert match == expect_match, f"buffers {'differ from' if expect_match else 'match'} baseline (seed={current_seed})"
|
||||
return val, buffers
|
||||
|
||||
print("capture + replay")
|
||||
test_val, test_buffers = random_inputs_run(jit, seed)
|
||||
print("pickle round trip")
|
||||
jit = pickle.loads(pickle.dumps(jit))
|
||||
random_inputs_run(jit, seed, test_val, test_buffers, expect_match=True)
|
||||
random_inputs_run(jit, seed + 1, test_val, test_buffers, expect_match=False)
|
||||
return jit
|
||||
|
||||
|
||||
def _parse_size(size):
|
||||
width, height = size.lower().split("x")
|
||||
return int(width), int(height)
|
||||
|
||||
|
||||
def read_file_chunked_to_shm(path):
|
||||
from openpilot.common.file_chunker import read_file_chunked
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
|
||||
shm_path = os.path.join(Paths.shm_path(), os.path.basename(path))
|
||||
atexit.register(lambda: os.path.exists(shm_path) and os.remove(shm_path))
|
||||
with open(shm_path, "wb") as f:
|
||||
f.write(read_file_chunked(path))
|
||||
return shm_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tinygrad.nn.onnx import OnnxRunner
|
||||
from openpilot.selfdrive.modeld.get_model_metadata import make_metadata_dict
|
||||
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--model-size", type=_parse_size, required=True, help="model input WxH")
|
||||
parser.add_argument("--camera-resolutions", type=_parse_size, nargs="+", required=True, help="camera resolutions WxH (one or more)")
|
||||
parser.add_argument("--vision-onnx", required=True)
|
||||
parser.add_argument("--off-policy-onnx", required=True)
|
||||
parser.add_argument("--on-policy-onnx", required=True)
|
||||
parser.add_argument("--output", required=True)
|
||||
parser.add_argument("--frame-skip", type=int, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
out = defaultdict(dict)
|
||||
vision_path = read_file_chunked_to_shm(args.vision_onnx)
|
||||
off_policy_path = read_file_chunked_to_shm(args.off_policy_onnx)
|
||||
on_policy_path = read_file_chunked_to_shm(args.on_policy_onnx)
|
||||
model_w, model_h = args.model_size
|
||||
|
||||
vision_runner = OnnxRunner(vision_path)
|
||||
off_policy_runner = OnnxRunner(off_policy_path)
|
||||
on_policy_runner = OnnxRunner(on_policy_path)
|
||||
vision_metadata = make_metadata_dict(vision_path)
|
||||
off_policy_metadata = make_metadata_dict(off_policy_path)
|
||||
on_policy_metadata = make_metadata_dict(on_policy_path)
|
||||
assert off_policy_metadata["input_shapes"] == on_policy_metadata["input_shapes"]
|
||||
|
||||
run_policy_jit = TinyJit(
|
||||
make_run_policy(
|
||||
vision_runner,
|
||||
off_policy_runner,
|
||||
on_policy_runner,
|
||||
vision_metadata["output_slices"]["hidden_state"],
|
||||
args.frame_skip,
|
||||
),
|
||||
prune=True,
|
||||
)
|
||||
|
||||
out["metadata"]["vision"] = vision_metadata
|
||||
out["metadata"]["off_policy"] = off_policy_metadata
|
||||
out["metadata"]["on_policy"] = on_policy_metadata
|
||||
|
||||
make_random_model_inputs = partial(make_random_images, keys=["img", "big_img"], shape=vision_metadata["input_shapes"]["img"])
|
||||
out["run_policy"] = compile_jit(run_policy_jit, make_random_model_inputs, POLICY_INPUTS, args.frame_skip, vision_metadata, on_policy_metadata)
|
||||
|
||||
for cam_w, cam_h in args.camera_resolutions:
|
||||
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
|
||||
make_random_warp_inputs = partial(make_random_images, keys=["frame", "big_frame"], shape=nv12.size, device=WARP_DEV)
|
||||
warp_enqueue = TinyJit(make_warp(nv12, model_w, model_h, args.frame_skip), prune=True)
|
||||
out[(cam_w, cam_h)] = compile_jit(warp_enqueue, make_random_warp_inputs, WARP_INPUTS, args.frame_skip, vision_metadata, on_policy_metadata)
|
||||
|
||||
with open(args.output, "wb") as f:
|
||||
pickle.dump(out, f)
|
||||
print(f"Saved JITs to {args.output} ({os.path.getsize(args.output) / 1e6:.2f} MB)")
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import pathlib
|
||||
import onnx
|
||||
import codecs
|
||||
import pickle
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import onnx
|
||||
|
||||
def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]:
|
||||
shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim])
|
||||
name = value_info.name
|
||||
@@ -17,19 +18,23 @@ def get_metadata_value_by_name(model:onnx.ModelProto, name:str) -> str | Any:
|
||||
return prop.value
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
model_path = pathlib.Path(sys.argv[1])
|
||||
|
||||
def make_metadata_dict(model_path: str | pathlib.Path) -> dict[str, Any]:
|
||||
model = onnx.load(str(model_path))
|
||||
output_slices = get_metadata_value_by_name(model, 'output_slices')
|
||||
assert output_slices is not None, 'output_slices not found in metadata'
|
||||
|
||||
metadata = {
|
||||
return {
|
||||
'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'),
|
||||
'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")),
|
||||
'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]),
|
||||
'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output])
|
||||
'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
model_path = pathlib.Path(sys.argv[1])
|
||||
metadata = make_metadata_dict(model_path)
|
||||
|
||||
metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl')
|
||||
with open(metadata_path, 'wb') as f:
|
||||
pickle.dump(metadata, f)
|
||||
|
||||
@@ -28,6 +28,11 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||
from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
|
||||
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
|
||||
from openpilot.starpilot.common.model_versions import (
|
||||
is_tinygrad_model_version,
|
||||
uses_combined_driving_artifacts,
|
||||
uses_split_off_policy_artifacts,
|
||||
)
|
||||
from openpilot.starpilot.common.starpilot_variables import get_starpilot_toggles, MODELS_PATH, params_memory
|
||||
|
||||
|
||||
@@ -309,7 +314,7 @@ class ModelState:
|
||||
self.is_v14 = (self.policy_generation == "v14")
|
||||
self.is_v15 = (self.policy_generation == "v15")
|
||||
self.is_v9 = (self.policy_generation == "v9")
|
||||
self.mlsim = (self.policy_generation in ("v8", "v10", "v11", "v12", "v13", "v14", "v15"))
|
||||
self.mlsim = is_tinygrad_model_version(self.policy_generation)
|
||||
self.policy_has_plan = 'plan' in self.policy_output_slices
|
||||
|
||||
self.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
|
||||
@@ -334,7 +339,7 @@ class ModelState:
|
||||
self.off_policy_output: np.ndarray | None = None
|
||||
|
||||
off_policy_metadata = None
|
||||
if self.policy_generation in ("v12", "v13", "v14", "v15") or OFF_POLICY_METADATA_PATH.is_file() or OFF_POLICY_PKL_PATH.is_file():
|
||||
if uses_split_off_policy_artifacts(self.policy_generation) or OFF_POLICY_METADATA_PATH.is_file() or OFF_POLICY_PKL_PATH.is_file():
|
||||
resolved_off_policy_meta = ensure_artifact(OFF_POLICY_METADATA_PATH, "driving_off_policy_metadata.pkl", optional=True)
|
||||
if resolved_off_policy_meta is not None:
|
||||
with open(resolved_off_policy_meta, 'rb') as f:
|
||||
@@ -453,14 +458,14 @@ class ModelState:
|
||||
self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
|
||||
|
||||
if self.prev_desired_curv_key is not None:
|
||||
# v9/v10/v11/v12/v13/v14/v15 models expect zeros for prev_desired_curv(s); others use history
|
||||
if self.is_v9 or self.is_v10 or self.is_v11 or self.is_v12 or self.is_v13 or self.is_v14 or self.is_v15:
|
||||
# Tinygrad-era policy models expect zeros for prev_desired_curv(s); older ones use history.
|
||||
if is_tinygrad_model_version(self.policy_generation):
|
||||
self.numpy_inputs[self.prev_desired_curv_key][:] = 0 * self.full_prev_desired_curv[0, self.temporal_idxs]
|
||||
else:
|
||||
self.numpy_inputs[self.prev_desired_curv_key][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
|
||||
|
||||
if self.off_policy_enabled and self.off_policy_prev_desired_curv_key is not None:
|
||||
if self.is_v9 or self.is_v12 or self.is_v13 or self.is_v14 or self.is_v15:
|
||||
if self.is_v9 or uses_split_off_policy_artifacts(self.policy_generation) or uses_combined_driving_artifacts(self.policy_generation):
|
||||
self.off_policy_numpy_inputs[self.off_policy_prev_desired_curv_key][:] = 0 * self.full_prev_desired_curv[0, self.temporal_idxs]
|
||||
else:
|
||||
self.off_policy_numpy_inputs[self.off_policy_prev_desired_curv_key][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
|
||||
@@ -486,6 +491,13 @@ class ModelState:
|
||||
|
||||
|
||||
def main(demo=False):
|
||||
params = Params()
|
||||
selected_version = _resolve_mirrored_param(params, "ModelVersion", "DrivingModelVersion")
|
||||
if uses_combined_driving_artifacts(selected_version):
|
||||
from openpilot.selfdrive.modeld.modeld_v16 import main as combined_main
|
||||
|
||||
return combined_main(demo=demo)
|
||||
|
||||
cloudlog.warning("modeld init")
|
||||
|
||||
sentry.set_tag("daemon", PROCESS_NAME)
|
||||
@@ -527,8 +539,6 @@ def main(demo=False):
|
||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay", "starpilotPlan"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
|
||||
# setup filter to track dropped frames
|
||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ)
|
||||
frame_id = 0
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.system.hardware import TICI
|
||||
|
||||
os.environ["DEV"] = "QCOM" if TICI else "LLVM"
|
||||
|
||||
import cereal.messaging as messaging
|
||||
import numpy as np
|
||||
from cereal import car, log
|
||||
from msgq.visionipc import VisionBuf, VisionIpcClient, VisionStreamType
|
||||
from opendbc.car.car_helpers import get_demo_car_params
|
||||
from setproctitle import setproctitle
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.tensor import Tensor
|
||||
|
||||
from openpilot.common.file_chunker import read_file_chunked
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL, config_realtime_process
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.common.transformations.model import get_warp_matrix
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import smooth_value
|
||||
from openpilot.selfdrive.modeld.compile_modeld import POLICY_INPUTS, WARP_INPUTS, make_input_queues
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.selfdrive.modeld.fill_model_msg import PublishState, fill_model_msg, fill_pose_msg
|
||||
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
||||
from openpilot.starpilot.assets.model_manager import ModelManager
|
||||
from openpilot.starpilot.common.model_versions import uses_combined_driving_artifacts
|
||||
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH, get_starpilot_toggles, params_memory
|
||||
from openpilot.system import sentry
|
||||
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||
|
||||
|
||||
PROCESS_NAME = "selfdrive.modeld.modeld"
|
||||
SEND_RAW_PRED = os.getenv("SEND_RAW_PRED")
|
||||
|
||||
BUILTIN_MODEL_KEY = "sc2"
|
||||
BUILTIN_MODEL_ALIASES = {BUILTIN_MODEL_KEY, "sc"}
|
||||
|
||||
LAT_SMOOTH_SECONDS = 0.0
|
||||
LONG_SMOOTH_SECONDS = 0.3
|
||||
MIN_LAT_CONTROL_SPEED = 0.3
|
||||
|
||||
|
||||
def _get_param_str(params: Params, key: str, default: str = "") -> str:
|
||||
try:
|
||||
value = params.get(key)
|
||||
except Exception:
|
||||
return default
|
||||
if value is None:
|
||||
return default
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode("utf-8")
|
||||
except Exception:
|
||||
return default
|
||||
if isinstance(value, (dict, list)):
|
||||
return default
|
||||
return str(value)
|
||||
|
||||
|
||||
def _get_default_param_str(params: Params, key: str) -> str:
|
||||
try:
|
||||
value = params.get_default_value(key)
|
||||
except Exception:
|
||||
return ""
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode("utf-8")
|
||||
except Exception:
|
||||
return ""
|
||||
return str(value)
|
||||
|
||||
|
||||
def _resolve_mirrored_param(params: Params, primary_key: str, secondary_key: str) -> str:
|
||||
primary_val = _get_param_str(params, primary_key).strip()
|
||||
secondary_val = _get_param_str(params, secondary_key).strip()
|
||||
if primary_val == secondary_val:
|
||||
return secondary_val or primary_val
|
||||
|
||||
primary_default = _get_default_param_str(params, primary_key).strip()
|
||||
secondary_default = _get_default_param_str(params, secondary_key).strip()
|
||||
primary_non_default = bool(primary_val) and primary_val != primary_default
|
||||
secondary_non_default = bool(secondary_val) and secondary_val != secondary_default
|
||||
|
||||
if secondary_non_default:
|
||||
return secondary_val
|
||||
if primary_non_default:
|
||||
return primary_val
|
||||
return secondary_val or primary_val
|
||||
|
||||
|
||||
def _canonical_model_id(model_id: str) -> str:
|
||||
key = (model_id or "").strip().lower()
|
||||
return BUILTIN_MODEL_KEY if key in BUILTIN_MODEL_ALIASES else key
|
||||
|
||||
|
||||
def _combined_model_path(model_id: str, use_builtin_model: bool) -> Path:
|
||||
if use_builtin_model:
|
||||
return Path(__file__).parent / "models" / "driving_tinygrad.pkl"
|
||||
return MODELS_PATH / f"{model_id}_driving_tinygrad.pkl"
|
||||
|
||||
|
||||
def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, v_ego: float) -> log.ModelDataV2.Action:
|
||||
desired_curv_unscaled, desired_accel = model_output["action"][0]
|
||||
desired_curvature = float(desired_curv_unscaled) / max(1.0, v_ego) ** 2
|
||||
should_stop = (v_ego < 0.3 and desired_accel < 0.1)
|
||||
|
||||
desired_accel = smooth_value(float(desired_accel), prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
||||
if v_ego > MIN_LAT_CONTROL_SPEED:
|
||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
||||
else:
|
||||
desired_curvature = prev_action.desiredCurvature
|
||||
|
||||
return log.ModelDataV2.Action(
|
||||
desiredCurvature=float(desired_curvature),
|
||||
desiredAcceleration=float(desired_accel),
|
||||
shouldStop=bool(should_stop),
|
||||
)
|
||||
|
||||
|
||||
class FrameMeta:
|
||||
frame_id: int = 0
|
||||
timestamp_sof: int = 0
|
||||
timestamp_eof: int = 0
|
||||
|
||||
def __init__(self, vipc=None):
|
||||
if vipc is not None:
|
||||
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
||||
|
||||
|
||||
class ModelState:
|
||||
prev_desire: np.ndarray
|
||||
|
||||
def __init__(self, cam_w: int, cam_h: int):
|
||||
params = Params()
|
||||
model_id_raw = _resolve_mirrored_param(params, "Model", "DrivingModel") or BUILTIN_MODEL_KEY
|
||||
self.model_id = _canonical_model_id(model_id_raw)
|
||||
self.model_version = _resolve_mirrored_param(params, "ModelVersion", "DrivingModelVersion")
|
||||
if not uses_combined_driving_artifacts(self.model_version):
|
||||
raise ValueError(f"Combined runtime requested for non-combined version {self.model_version!r}")
|
||||
|
||||
use_builtin_model = self.model_id == BUILTIN_MODEL_KEY
|
||||
model_path = _combined_model_path(self.model_id, use_builtin_model)
|
||||
if not model_path.is_file():
|
||||
if use_builtin_model:
|
||||
raise FileNotFoundError(
|
||||
f"Missing builtin combined model artifact: {model_path}. "
|
||||
"Rebuild/deploy the combined builtin model before selecting this version."
|
||||
)
|
||||
|
||||
cloudlog.error(f"Missing combined model artifact {model_path}, downloading {self.model_id}...")
|
||||
ModelManager(params, params_memory).download_model(self.model_id)
|
||||
if not model_path.is_file():
|
||||
raise FileNotFoundError(model_path)
|
||||
|
||||
jits = pickle.loads(read_file_chunked(model_path))
|
||||
|
||||
vision_metadata = jits["metadata"]["vision"]
|
||||
off_policy_metadata = jits["metadata"]["off_policy"]
|
||||
on_policy_metadata = jits["metadata"]["on_policy"]
|
||||
|
||||
self.vision_input_shapes = vision_metadata["input_shapes"]
|
||||
self.vision_input_names = list(self.vision_input_shapes.keys())
|
||||
self.vision_output_slices = vision_metadata["output_slices"]
|
||||
self.off_policy_output_slices = off_policy_metadata["output_slices"]
|
||||
self.policy_input_shapes = on_policy_metadata["input_shapes"]
|
||||
self.policy_output_slices = on_policy_metadata["output_slices"]
|
||||
self.desire_key = next(key for key in self.policy_input_shapes if key.startswith("desire"))
|
||||
|
||||
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||
self.dev = Device.DEFAULT
|
||||
self.input_queues, self.npy = make_input_queues(self.vision_input_shapes, self.policy_input_shapes, self.frame_skip, device=self.dev)
|
||||
self.full_frames: dict[str, Tensor] = {}
|
||||
self._blob_cache: dict[tuple[str, int], Tensor] = {}
|
||||
self.parser = Parser()
|
||||
self.frame_buf_params = {key: get_nv12_info(cam_w, cam_h) for key in ("img", "big_img")}
|
||||
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||
|
||||
camera_jit = jits[(cam_w, cam_h)]
|
||||
self.split_warp_layout = "run_policy" in jits and not isinstance(camera_jit, dict)
|
||||
if self.split_warp_layout:
|
||||
self.run_policy = jits["run_policy"]
|
||||
self.warp_enqueue = camera_jit
|
||||
else:
|
||||
self.run_policy = camera_jit["run_policy"]
|
||||
self.warp_enqueue = camera_jit["warp_enqueue"]
|
||||
|
||||
def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]:
|
||||
return {key: model_outputs[np.newaxis, value] for key, value in output_slices.items()}
|
||||
|
||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
||||
for key in bufs.keys():
|
||||
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
|
||||
yuv_size = self.frame_buf_params[key][3]
|
||||
cache_key = (key, ptr)
|
||||
if cache_key not in self._blob_cache:
|
||||
self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype="uint8", device=self.dev)
|
||||
self.full_frames[key] = self._blob_cache[cache_key]
|
||||
|
||||
inputs[self.desire_key][0] = 0
|
||||
self.npy["desire"][:] = np.where(inputs[self.desire_key] - self.prev_desire > 0.99, inputs[self.desire_key], 0)
|
||||
self.prev_desire[:] = inputs[self.desire_key]
|
||||
self.npy["traffic_convention"][:] = inputs["traffic_convention"]
|
||||
if "action_t" in self.npy:
|
||||
self.npy["action_t"][:] = inputs["action_t"]
|
||||
self.npy["tfm"][:, :] = transforms["img"][:, :]
|
||||
self.npy["big_tfm"][:, :] = transforms["big_img"][:, :]
|
||||
|
||||
if self.split_warp_layout:
|
||||
img, big_img = self.warp_enqueue(
|
||||
**{key: self.input_queues[key] for key in WARP_INPUTS},
|
||||
frame=self.full_frames["img"],
|
||||
big_frame=self.full_frames["big_img"],
|
||||
)
|
||||
if prepare_only:
|
||||
return None
|
||||
policy_inputs = {key: self.input_queues[key] for key in POLICY_INPUTS if key in self.input_queues}
|
||||
vision_output, policy_output, off_policy_output = self.run_policy(**policy_inputs, img=img, big_img=big_img)
|
||||
else:
|
||||
if prepare_only:
|
||||
self.warp_enqueue(**self.input_queues, frame=self.full_frames["img"], big_frame=self.full_frames["big_img"])
|
||||
return None
|
||||
vision_output, policy_output, off_policy_output = self.run_policy(
|
||||
**self.input_queues,
|
||||
frame=self.full_frames["img"],
|
||||
big_frame=self.full_frames["big_img"],
|
||||
)
|
||||
|
||||
vision_output = vision_output.numpy().flatten()
|
||||
policy_output = policy_output.numpy().flatten()
|
||||
off_policy_output = off_policy_output.numpy().flatten()
|
||||
|
||||
vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(vision_output, self.vision_output_slices))
|
||||
off_policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(off_policy_output, self.off_policy_output_slices))
|
||||
policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(policy_output, self.policy_output_slices))
|
||||
combined_outputs_dict = {**vision_outputs_dict, **off_policy_outputs_dict, **policy_outputs_dict}
|
||||
|
||||
if SEND_RAW_PRED:
|
||||
combined_outputs_dict["raw_pred"] = np.concatenate([vision_output.copy(), policy_output.copy(), off_policy_output.copy()])
|
||||
return combined_outputs_dict
|
||||
|
||||
|
||||
def main(demo=False):
|
||||
cloudlog.warning("modeld init")
|
||||
|
||||
sentry.set_tag("daemon", PROCESS_NAME)
|
||||
cloudlog.bind(daemon=PROCESS_NAME)
|
||||
setproctitle(PROCESS_NAME)
|
||||
config_realtime_process(7, 54)
|
||||
|
||||
while True:
|
||||
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
||||
if available_streams:
|
||||
use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams
|
||||
main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD
|
||||
vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True)
|
||||
vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False)
|
||||
cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}")
|
||||
|
||||
while not vipc_client_main.connect(False):
|
||||
time.sleep(0.1)
|
||||
while use_extra_client and not vipc_client_extra.connect(False):
|
||||
time.sleep(0.1)
|
||||
|
||||
cloudlog.warning(f"connected main cam with buffer size: {vipc_client_main.buffer_len} ({vipc_client_main.width} x {vipc_client_main.height})")
|
||||
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})")
|
||||
|
||||
start_time = time.monotonic()
|
||||
cloudlog.warning("loading combined model")
|
||||
model = ModelState(vipc_client_main.width, vipc_client_main.height)
|
||||
cloudlog.warning(f"combined model loaded in {time.monotonic() - start_time:.1f}s, modeld starting")
|
||||
|
||||
pm = messaging.PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "starpilotModelV2"])
|
||||
sm = messaging.SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay", "starpilotPlan"])
|
||||
|
||||
publish_state = PublishState()
|
||||
params = Params()
|
||||
|
||||
frame_dropped_filter = FirstOrderFilter(0.0, 10.0, 1.0 / ModelConstants.MODEL_RUN_FREQ)
|
||||
last_vipc_frame_id = 0
|
||||
run_count = 0
|
||||
|
||||
model_transform_main = np.zeros((3, 3), dtype=np.float32)
|
||||
model_transform_extra = np.zeros((3, 3), dtype=np.float32)
|
||||
live_calib_seen = False
|
||||
buf_main, buf_extra = None, None
|
||||
meta_main = FrameMeta()
|
||||
meta_extra = FrameMeta()
|
||||
|
||||
if demo:
|
||||
CP = get_demo_car_params()
|
||||
else:
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("modeld got CarParams: %s", CP.brand)
|
||||
|
||||
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
|
||||
prev_action = log.ModelDataV2.Action()
|
||||
desire_helper = DesireHelper()
|
||||
starpilot_toggles = get_starpilot_toggles(sm)
|
||||
|
||||
while True:
|
||||
while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000:
|
||||
buf_main = vipc_client_main.recv()
|
||||
meta_main = FrameMeta(vipc_client_main)
|
||||
if buf_main is None:
|
||||
break
|
||||
|
||||
if buf_main is None:
|
||||
cloudlog.debug("vipc_client_main no frame")
|
||||
continue
|
||||
|
||||
if use_extra_client:
|
||||
while True:
|
||||
buf_extra = vipc_client_extra.recv()
|
||||
meta_extra = FrameMeta(vipc_client_extra)
|
||||
if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000:
|
||||
break
|
||||
|
||||
if buf_extra is None:
|
||||
cloudlog.debug("vipc_client_extra no frame")
|
||||
continue
|
||||
|
||||
if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000:
|
||||
cloudlog.error(
|
||||
f"frames out of sync! main: {meta_main.frame_id} ({meta_main.timestamp_sof / 1e9:.5f}), "
|
||||
f"extra: {meta_extra.frame_id} ({meta_extra.timestamp_sof / 1e9:.5f})"
|
||||
)
|
||||
else:
|
||||
buf_extra = buf_main
|
||||
meta_extra = meta_main
|
||||
|
||||
sm.update(0)
|
||||
desire = desire_helper.desire
|
||||
is_rhd = sm["driverMonitoringState"].isRHD
|
||||
frame_id = sm["roadCameraState"].frameId
|
||||
v_ego = max(sm["carState"].vEgo, 0.0)
|
||||
lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||
|
||||
if sm.updated["liveCalibration"] and sm.seen["roadCameraState"] and sm.seen["deviceState"]:
|
||||
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
||||
dc = DEVICE_CAMERAS[(str(sm["deviceState"].deviceType), str(sm["roadCameraState"].sensor))]
|
||||
model_transform_main = get_warp_matrix(
|
||||
device_from_calib_euler,
|
||||
dc.ecam.intrinsics if main_wide_camera else dc.fcam.intrinsics,
|
||||
False,
|
||||
).astype(np.float32)
|
||||
model_transform_extra = get_warp_matrix(device_from_calib_euler, dc.ecam.intrinsics, True).astype(np.float32)
|
||||
live_calib_seen = True
|
||||
|
||||
traffic_convention = np.zeros(2, dtype=np.float32)
|
||||
traffic_convention[int(is_rhd)] = 1
|
||||
|
||||
vec_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
|
||||
if 0 <= desire < ModelConstants.DESIRE_LEN:
|
||||
vec_desire[desire] = 1
|
||||
|
||||
vipc_dropped_frames = max(0, meta_main.frame_id - last_vipc_frame_id - 1)
|
||||
frames_dropped = frame_dropped_filter.update(min(vipc_dropped_frames, 10))
|
||||
if run_count < 10:
|
||||
frame_dropped_filter.x = 0.0
|
||||
frames_dropped = 0.0
|
||||
run_count += 1
|
||||
|
||||
frame_drop_ratio = frames_dropped / (1 + frames_dropped)
|
||||
prepare_only = vipc_dropped_frames > 0
|
||||
if prepare_only:
|
||||
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
|
||||
|
||||
bufs = {name: buf_extra if "big" in name else buf_main for name in model.vision_input_names}
|
||||
transforms = {name: model_transform_extra if "big" in name else model_transform_main for name in model.vision_input_names}
|
||||
|
||||
frame_delay = DT_MDL
|
||||
action_delay = DT_MDL / 2
|
||||
lat_action_t = lat_delay + frame_delay + action_delay
|
||||
long_action_t = long_delay + frame_delay + action_delay
|
||||
|
||||
inputs: dict[str, np.ndarray] = {
|
||||
model.desire_key: vec_desire,
|
||||
"traffic_convention": traffic_convention,
|
||||
}
|
||||
if "action_t" in model.npy:
|
||||
inputs["action_t"] = np.array([lat_action_t, long_action_t], dtype=np.float32)
|
||||
|
||||
start = time.perf_counter()
|
||||
model_output = model.run(bufs, transforms, inputs, prepare_only)
|
||||
end = time.perf_counter()
|
||||
model_execution_time = end - start
|
||||
|
||||
if model_output is not None:
|
||||
modelv2_send = messaging.new_message("modelV2")
|
||||
starpilot_modelv2_send = messaging.new_message("starpilotModelV2")
|
||||
drivingdata_send = messaging.new_message("drivingModelData")
|
||||
posenet_send = messaging.new_message("cameraOdometry")
|
||||
|
||||
action = get_action_from_model(model_output, prev_action, v_ego)
|
||||
prev_action = action
|
||||
fill_model_msg(
|
||||
drivingdata_send,
|
||||
modelv2_send,
|
||||
model_output,
|
||||
action,
|
||||
publish_state,
|
||||
meta_main.frame_id,
|
||||
meta_extra.frame_id,
|
||||
frame_id,
|
||||
frame_drop_ratio,
|
||||
meta_main.timestamp_eof,
|
||||
model_execution_time,
|
||||
live_calib_seen,
|
||||
)
|
||||
|
||||
desire_state = modelv2_send.modelV2.meta.desireState
|
||||
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
||||
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
|
||||
lane_change_prob = l_lane_change_prob + r_lane_change_prob
|
||||
desire_helper.update(sm["carState"], sm["carControl"].latActive, lane_change_prob, sm["starpilotPlan"], starpilot_toggles)
|
||||
modelv2_send.modelV2.meta.laneChangeState = desire_helper.lane_change_state
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = desire_helper.lane_change_direction
|
||||
starpilot_modelv2_send.starpilotModelV2.turnDirection = desire_helper.turn_direction
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = desire_helper.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = desire_helper.lane_change_direction
|
||||
|
||||
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("starpilotModelV2", starpilot_modelv2_send)
|
||||
pm.send("drivingModelData", drivingdata_send)
|
||||
pm.send("cameraOdometry", posenet_send)
|
||||
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
if sm.updated["starpilotPlan"]:
|
||||
starpilot_toggles = get_starpilot_toggles(sm)
|
||||
@@ -17,11 +17,15 @@ from openpilot.starpilot.assets.model_manager import (
|
||||
MODEL_DOWNLOAD_ALL_PARAM,
|
||||
MODEL_DOWNLOAD_PARAM,
|
||||
ModelManager,
|
||||
TINYGRAD_VERSIONS,
|
||||
canonical_model_key,
|
||||
is_builtin_model_key,
|
||||
model_key_aliases,
|
||||
)
|
||||
from openpilot.starpilot.common.model_versions import (
|
||||
is_tinygrad_model_version,
|
||||
uses_combined_driving_artifacts,
|
||||
uses_split_off_policy_artifacts,
|
||||
)
|
||||
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH, update_starpilot_toggles
|
||||
from openpilot.system.ui.lib.application import FontWeight, MouseEvent, MousePos, gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
@@ -674,7 +678,7 @@ class StarPilotDrivingModelLayout(_SettingsPage):
|
||||
if f"{model_key}.thneed" in files:
|
||||
return True
|
||||
|
||||
if version in TINYGRAD_VERSIONS:
|
||||
if is_tinygrad_model_version(version):
|
||||
required_files = set(self._required_files_for_version(model_key, version))
|
||||
return required_files.issubset(files)
|
||||
|
||||
@@ -684,6 +688,9 @@ class StarPilotDrivingModelLayout(_SettingsPage):
|
||||
return any(file.startswith(f"{model_key}.") or file.startswith(f"{model_key}_") for file in files)
|
||||
|
||||
def _required_files_for_version(self, key: str, version: str) -> list[str]:
|
||||
if uses_combined_driving_artifacts(version):
|
||||
return [f"{key}_driving_tinygrad.pkl"]
|
||||
|
||||
files = [
|
||||
f"{key}_driving_policy_tinygrad.pkl",
|
||||
f"{key}_driving_vision_tinygrad.pkl",
|
||||
@@ -691,7 +698,7 @@ class StarPilotDrivingModelLayout(_SettingsPage):
|
||||
f"{key}_driving_vision_metadata.pkl",
|
||||
]
|
||||
|
||||
if version in {"v12", "v13", "v14", "v15"}:
|
||||
if uses_split_off_policy_artifacts(version):
|
||||
files.extend(
|
||||
[
|
||||
f"{key}_driving_off_policy_tinygrad.pkl",
|
||||
|
||||
@@ -11,7 +11,11 @@ from openpilot.starpilot.assets.model_manager import (
|
||||
CANCEL_DOWNLOAD_PARAM,
|
||||
DOWNLOAD_PROGRESS_PARAM,
|
||||
ModelManager,
|
||||
TINYGRAD_VERSIONS,
|
||||
)
|
||||
from openpilot.starpilot.common.model_versions import (
|
||||
is_tinygrad_model_version,
|
||||
uses_combined_driving_artifacts,
|
||||
uses_split_off_policy_artifacts,
|
||||
)
|
||||
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH
|
||||
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
|
||||
@@ -715,9 +719,12 @@ class DrivingModelBigButton(BigButton):
|
||||
return False
|
||||
|
||||
def _required_files_for_version(self, key: str, version: str) -> list[str]:
|
||||
if version not in TINYGRAD_VERSIONS:
|
||||
if not is_tinygrad_model_version(version):
|
||||
return []
|
||||
|
||||
if uses_combined_driving_artifacts(version):
|
||||
return [f"{key}_driving_tinygrad.pkl"]
|
||||
|
||||
files = [
|
||||
f"{key}_driving_policy_tinygrad.pkl",
|
||||
f"{key}_driving_vision_tinygrad.pkl",
|
||||
@@ -725,7 +732,7 @@ class DrivingModelBigButton(BigButton):
|
||||
f"{key}_driving_vision_metadata.pkl",
|
||||
]
|
||||
|
||||
if version in {"v12", "v13", "v14", "v15"}:
|
||||
if uses_split_off_policy_artifacts(version):
|
||||
files.extend([
|
||||
f"{key}_driving_off_policy_tinygrad.pkl",
|
||||
f"{key}_driving_off_policy_metadata.pkl",
|
||||
|
||||
@@ -83,7 +83,7 @@ def download_file(cancel_param, destination, progress_param, url, download_param
|
||||
|
||||
def get_remote_file_size(url, suppress_errors=False):
|
||||
try:
|
||||
response = requests.head(url, headers={"Accept-Encoding": "identity"}, timeout=10)
|
||||
response = requests.head(url, headers={"Accept-Encoding": "identity"}, timeout=10, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
return int(response.headers.get("Content-Length", 0))
|
||||
except Exception as error:
|
||||
|
||||
@@ -14,12 +14,18 @@ from openpilot.starpilot.assets.download_functions import (
|
||||
handle_request_error,
|
||||
verify_download,
|
||||
)
|
||||
from openpilot.starpilot.common.model_versions import (
|
||||
is_tinygrad_model_version,
|
||||
uses_combined_driving_artifacts,
|
||||
uses_split_off_policy_artifacts,
|
||||
)
|
||||
from openpilot.starpilot.common.starpilot_utilities import delete_file
|
||||
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH
|
||||
|
||||
MANIFEST_CANDIDATES = ("v21",)
|
||||
TINYGRAD_VERSIONS = {"v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15"}
|
||||
TINYGRAD_VERSIONS = {f"v{i}" for i in range(8, 33)}
|
||||
DEFAULT_MODEL_KEY = "sc2"
|
||||
ARTIFACT_URLS_CACHE = ".model_artifact_urls.json"
|
||||
MODEL_KEY_CANONICAL_MAP = {
|
||||
"sc": DEFAULT_MODEL_KEY,
|
||||
}
|
||||
@@ -130,6 +136,13 @@ class ModelManager:
|
||||
self.model_series = [entry for entry in self._param_text("AvailableModelSeries").split(",") if entry]
|
||||
self.available_model_names = [entry for entry in self._param_text("AvailableModelNames").split(",") if entry]
|
||||
|
||||
@staticmethod
|
||||
def _manifest_paths(manifest_version: str) -> tuple[str, ...]:
|
||||
return (
|
||||
f"Versions/model_names_{manifest_version}.json",
|
||||
f"model_names_{manifest_version}.json",
|
||||
)
|
||||
|
||||
def _set_model_param_keys(self, model_key: str | None = None, model_name: str | None = None, model_version: str | None = None):
|
||||
if model_key is not None and model_key != "":
|
||||
canonical_key = self._canonical_model_key(model_key)
|
||||
@@ -182,9 +195,12 @@ class ModelManager:
|
||||
return DEFAULT_MODEL_KEY
|
||||
|
||||
def _required_files(self, model_key: str, model_version: str) -> list[str]:
|
||||
if model_version not in TINYGRAD_VERSIONS:
|
||||
if not is_tinygrad_model_version(model_version):
|
||||
return []
|
||||
|
||||
if uses_combined_driving_artifacts(model_version):
|
||||
return [f"{model_key}_driving_tinygrad.pkl"]
|
||||
|
||||
filenames = [
|
||||
f"{model_key}_driving_policy_tinygrad.pkl",
|
||||
f"{model_key}_driving_vision_tinygrad.pkl",
|
||||
@@ -192,7 +208,7 @@ class ModelManager:
|
||||
f"{model_key}_driving_vision_metadata.pkl",
|
||||
]
|
||||
|
||||
if model_version in {"v12", "v13", "v14", "v15"}:
|
||||
if uses_split_off_policy_artifacts(model_version):
|
||||
filenames += [
|
||||
f"{model_key}_driving_off_policy_tinygrad.pkl",
|
||||
f"{model_key}_driving_off_policy_metadata.pkl",
|
||||
@@ -200,6 +216,72 @@ class ModelManager:
|
||||
|
||||
return filenames
|
||||
|
||||
@staticmethod
|
||||
def _artifact_urls_cache_path() -> Path:
|
||||
return MODELS_PATH / ARTIFACT_URLS_CACHE
|
||||
|
||||
def _load_artifact_url_map(self) -> dict[str, dict[str, str]]:
|
||||
try:
|
||||
cache_path = self._artifact_urls_cache_path()
|
||||
if not cache_path.is_file():
|
||||
return {}
|
||||
|
||||
payload = json.loads(cache_path.read_text())
|
||||
if not isinstance(payload, dict):
|
||||
return {}
|
||||
|
||||
normalized: dict[str, dict[str, str]] = {}
|
||||
for model_key, urls in payload.items():
|
||||
if not isinstance(urls, dict):
|
||||
continue
|
||||
normalized[str(model_key)] = {
|
||||
str(filename): str(url)
|
||||
for filename, url in urls.items()
|
||||
if filename and url
|
||||
}
|
||||
return normalized
|
||||
except Exception as error:
|
||||
print(f"Failed to load artifact URL cache: {error}")
|
||||
return {}
|
||||
|
||||
def _build_artifact_url_map(self, model_info: list[dict]) -> dict[str, dict[str, str]]:
|
||||
artifact_url_map: dict[str, dict[str, str]] = {}
|
||||
|
||||
for model in model_info:
|
||||
model_key = self._canonical_model_key(str(model.get("id") or "").strip())
|
||||
model_version = str(model.get("version") or "").strip()
|
||||
required_files = self._required_files(model_key, model_version)
|
||||
if not model_key or not required_files:
|
||||
continue
|
||||
|
||||
urls: dict[str, str] = {}
|
||||
|
||||
explicit_urls = model.get("artifact_urls") or model.get("download_urls")
|
||||
if isinstance(explicit_urls, dict):
|
||||
for filename, url in explicit_urls.items():
|
||||
if filename and url:
|
||||
urls[str(filename).strip()] = str(url).strip()
|
||||
|
||||
base_url = str(model.get("artifact_base_url") or model.get("download_base_url") or "").strip()
|
||||
if base_url:
|
||||
base_url = base_url.rstrip("/")
|
||||
for filename in required_files:
|
||||
urls.setdefault(filename, f"{base_url}/{filename}")
|
||||
|
||||
direct_url = str(model.get("artifact_url") or model.get("download_url") or "").strip()
|
||||
if direct_url:
|
||||
if len(required_files) == 1:
|
||||
urls.setdefault(required_files[0], direct_url)
|
||||
else:
|
||||
matched_filename = next((filename for filename in required_files if Path(filename).name == Path(direct_url).name), None)
|
||||
if matched_filename is not None:
|
||||
urls.setdefault(matched_filename, direct_url)
|
||||
|
||||
if urls:
|
||||
artifact_url_map[model_key] = urls
|
||||
|
||||
return artifact_url_map
|
||||
|
||||
def _is_model_downloaded(self, model_key: str, model_version: str) -> bool:
|
||||
if is_builtin_model_key(model_key):
|
||||
return True
|
||||
@@ -296,16 +378,17 @@ class ModelManager:
|
||||
|
||||
def _get_manifest(self, repo_url: str) -> tuple[str | None, list[dict]]:
|
||||
for manifest_version in MANIFEST_CANDIDATES:
|
||||
model_info = self._fetch_manifest(f"{repo_url}/Versions/model_names_{manifest_version}.json")
|
||||
if not model_info:
|
||||
continue
|
||||
for manifest_path in self._manifest_paths(manifest_version):
|
||||
model_info = self._fetch_manifest(f"{repo_url}/{manifest_path}")
|
||||
if not model_info:
|
||||
continue
|
||||
|
||||
# Desktop/dev build is tinygrad-only.
|
||||
filtered = [model for model in model_info if model.get("version") in TINYGRAD_VERSIONS]
|
||||
if not filtered:
|
||||
continue
|
||||
# Desktop/dev build is tinygrad-only.
|
||||
filtered = [model for model in model_info if is_tinygrad_model_version(model.get("version"))]
|
||||
if not filtered:
|
||||
continue
|
||||
|
||||
return manifest_version, filtered
|
||||
return manifest_version, filtered
|
||||
|
||||
return None, []
|
||||
|
||||
@@ -366,6 +449,9 @@ class ModelManager:
|
||||
versions_file = MODELS_PATH / ".model_versions.json"
|
||||
versions_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
versions_file.write_text(json.dumps(version_map))
|
||||
|
||||
artifact_urls_file = self._artifact_urls_cache_path()
|
||||
artifact_urls_file.write_text(json.dumps(self._build_artifact_url_map(model_info)))
|
||||
except Exception as error:
|
||||
print(f"Failed to write model versions cache: {error}")
|
||||
|
||||
@@ -411,6 +497,8 @@ class ModelManager:
|
||||
self._load_catalog_from_params()
|
||||
version_map = self._model_version_map()
|
||||
model_version = version_map.get(model_to_download)
|
||||
model_artifact_urls = self._load_artifact_url_map()
|
||||
artifact_urls = model_artifact_urls.get(self._canonical_model_key(model_to_download)) or model_artifact_urls.get(model_to_download) or {}
|
||||
required_files = self._required_files(model_to_download, model_version or "")
|
||||
if not required_files:
|
||||
handle_error(None, f"Unsupported model format for {model_to_download}", "Model download failed", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
|
||||
@@ -419,25 +507,40 @@ class ModelManager:
|
||||
|
||||
for filename in required_files:
|
||||
file_path = MODELS_PATH / filename
|
||||
candidate_urls: list[tuple[str, bool]] = []
|
||||
|
||||
custom_url = artifact_urls.get(filename, "").strip()
|
||||
if custom_url:
|
||||
candidate_urls.append((custom_url, True))
|
||||
|
||||
file_url = f"{repo_url}/Models/{filename}"
|
||||
|
||||
download_file(CANCEL_DOWNLOAD_PARAM, file_path, DOWNLOAD_PROGRESS_PARAM, file_url, MODEL_DOWNLOAD_PARAM, self.params_memory)
|
||||
if self.params_memory.get_bool(CANCEL_DOWNLOAD_PARAM):
|
||||
handle_error(None, "Download cancelled...", "Download cancelled...", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
|
||||
self.downloading_model = False
|
||||
return
|
||||
|
||||
if verify_download(file_path, file_url):
|
||||
continue
|
||||
candidate_urls.append((file_url, False))
|
||||
|
||||
fallback_url = f"{GITLAB_URL}/Models/{filename}"
|
||||
download_file(CANCEL_DOWNLOAD_PARAM, file_path, DOWNLOAD_PROGRESS_PARAM, fallback_url, MODEL_DOWNLOAD_PARAM, self.params_memory)
|
||||
if self.params_memory.get_bool(CANCEL_DOWNLOAD_PARAM):
|
||||
handle_error(None, "Download cancelled...", "Download cancelled...", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
|
||||
self.downloading_model = False
|
||||
return
|
||||
if fallback_url != file_url:
|
||||
candidate_urls.append((fallback_url, False))
|
||||
|
||||
if not verify_download(file_path, fallback_url):
|
||||
download_succeeded = False
|
||||
for candidate_url, allow_unknown_size in candidate_urls:
|
||||
download_file(
|
||||
CANCEL_DOWNLOAD_PARAM,
|
||||
file_path,
|
||||
DOWNLOAD_PROGRESS_PARAM,
|
||||
candidate_url,
|
||||
MODEL_DOWNLOAD_PARAM,
|
||||
self.params_memory,
|
||||
allow_unknown_size=allow_unknown_size,
|
||||
)
|
||||
if self.params_memory.get_bool(CANCEL_DOWNLOAD_PARAM):
|
||||
handle_error(None, "Download cancelled...", "Download cancelled...", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
|
||||
self.downloading_model = False
|
||||
return
|
||||
|
||||
if verify_download(file_path, candidate_url, allow_unknown_size=allow_unknown_size):
|
||||
download_succeeded = True
|
||||
break
|
||||
|
||||
if not download_succeeded:
|
||||
handle_error(file_path, "Verification failed...", f"Verification failed for {filename}", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
|
||||
self.downloading_model = False
|
||||
return
|
||||
@@ -489,6 +592,10 @@ class ModelManager:
|
||||
if model_versions_file.is_file():
|
||||
delete_file(model_versions_file, print_error=False)
|
||||
|
||||
artifact_urls_file = self._artifact_urls_cache_path()
|
||||
if artifact_urls_file.is_file():
|
||||
delete_file(artifact_urls_file, print_error=False)
|
||||
|
||||
self.params.put_bool("TinygradUpdateAvailable", False)
|
||||
self.params_memory.remove(UPDATE_TINYGRAD_PARAM)
|
||||
self.params_memory.remove(CANCEL_DOWNLOAD_PARAM)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def parse_model_version(version: str | None) -> int | None:
|
||||
text = str(version or "").strip().lower()
|
||||
if not text.startswith("v"):
|
||||
return None
|
||||
|
||||
raw_number = text[1:]
|
||||
if not raw_number.isdigit():
|
||||
return None
|
||||
return int(raw_number)
|
||||
|
||||
|
||||
def is_tinygrad_model_version(version: str | None) -> bool:
|
||||
parsed = parse_model_version(version)
|
||||
return parsed is not None and parsed >= 8
|
||||
|
||||
|
||||
def uses_split_off_policy_artifacts(version: str | None) -> bool:
|
||||
parsed = parse_model_version(version)
|
||||
return parsed is not None and 12 <= parsed < 16
|
||||
|
||||
|
||||
def uses_combined_driving_artifacts(version: str | None) -> bool:
|
||||
parsed = parse_model_version(version)
|
||||
return parsed is not None and parsed >= 16
|
||||
@@ -26,6 +26,7 @@ from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import KP
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||
from openpilot.starpilot.common.model_versions import is_tinygrad_model_version
|
||||
from openpilot.starpilot.common.accel_profile import (
|
||||
ACCELERATION_PROFILES,
|
||||
CUSTOM_ACCEL_PROFILE_PARAM_KEYS,
|
||||
@@ -1074,7 +1075,7 @@ class StarPilotVariables:
|
||||
if isinstance(toggle.model_version, bytes):
|
||||
toggle.model_version = toggle.model_version.decode("utf-8", "ignore")
|
||||
toggle.classic_model = toggle.model_version in {"v1", "v2", "v3", "v4"}
|
||||
toggle.tinygrad_model = toggle.model_version in {"v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15"}
|
||||
toggle.tinygrad_model = is_tinygrad_model_version(toggle.model_version)
|
||||
toggle.tomb_raider = toggle.model == "space-lab"
|
||||
|
||||
toggle.model_ui = self.get_value("ModelUI")
|
||||
|
||||
@@ -60,6 +60,11 @@ from openpilot.starpilot.common.maps_catalog import (
|
||||
schedule_label,
|
||||
schedule_param_value,
|
||||
)
|
||||
from openpilot.starpilot.common.model_versions import (
|
||||
is_tinygrad_model_version,
|
||||
uses_combined_driving_artifacts,
|
||||
uses_split_off_policy_artifacts,
|
||||
)
|
||||
from openpilot.starpilot.common.experimental_state import sync_persist_experimental_state
|
||||
from openpilot.starpilot.common.starpilot_utilities import delete_file, get_lock_status, run_cmd
|
||||
from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH, ERROR_LOGS_PATH, EXCLUDED_KEYS, LEGACY_STARPILOT_PARAM_RENAMES, MAPS_PATH, MODELS_PATH, RESOURCES_REPO, SCREEN_RECORDINGS_PATH, STOCK_THEME_PATH, THEME_SAVE_PATH,\
|
||||
@@ -4330,14 +4335,17 @@ def setup(app):
|
||||
if f"{model_key}.thneed" in on_disk_files:
|
||||
return True
|
||||
|
||||
if model_version in ("v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15"):
|
||||
if is_tinygrad_model_version(model_version):
|
||||
if uses_combined_driving_artifacts(model_version):
|
||||
return f"{model_key}_driving_tinygrad.pkl" in on_disk_files
|
||||
|
||||
required_files = {
|
||||
f"{model_key}_driving_policy_tinygrad.pkl",
|
||||
f"{model_key}_driving_vision_tinygrad.pkl",
|
||||
f"{model_key}_driving_policy_metadata.pkl",
|
||||
f"{model_key}_driving_vision_metadata.pkl",
|
||||
}
|
||||
if model_version in ("v12", "v13", "v14", "v15"):
|
||||
if uses_split_off_policy_artifacts(model_version):
|
||||
required_files |= {
|
||||
f"{model_key}_driving_off_policy_tinygrad.pkl",
|
||||
f"{model_key}_driving_off_policy_metadata.pkl",
|
||||
|
||||
@@ -615,6 +615,7 @@ bool StarPilotModelPanel::isModelInstalled(const QString &key) const {
|
||||
}
|
||||
|
||||
bool has_thneed = false;
|
||||
bool has_combined_tg = false;
|
||||
bool has_policy_meta = false;
|
||||
bool has_policy_tg = false;
|
||||
bool has_vision_meta = false;
|
||||
@@ -635,7 +636,9 @@ bool StarPilotModelPanel::isModelInstalled(const QString &key) const {
|
||||
if (ext == "thneed") {
|
||||
has_thneed = true;
|
||||
} else if (ext == "pkl") {
|
||||
if (base.contains("_driving_policy_metadata")) {
|
||||
if (base.contains("_driving_tinygrad")) {
|
||||
has_combined_tg = true;
|
||||
} else if (base.contains("_driving_policy_metadata")) {
|
||||
has_policy_meta = true;
|
||||
} else if (base.contains("_driving_policy_tinygrad")) {
|
||||
has_policy_tg = true;
|
||||
@@ -655,6 +658,10 @@ bool StarPilotModelPanel::isModelInstalled(const QString &key) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (has_combined_tg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (has_policy_meta && has_policy_tg && has_vision_meta && has_vision_tg) {
|
||||
if (has_off_policy_meta || has_off_policy_tg) {
|
||||
return has_off_policy_meta && has_off_policy_tg;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+1224
-1212
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user