mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-06 16:55:51 +08:00
tiny my BUTT
This commit is contained in:
@@ -492,11 +492,6 @@ run_larch64_build() {
|
||||
# it is backend-captured and should come from device/QCOM-compatible artifacts.
|
||||
echo "==> Build pass 2/2: required runtime artifacts"
|
||||
run_larch64_scons "${jobs}" \
|
||||
selfdrive/modeld/models/dmonitoring_model_metadata.pkl \
|
||||
selfdrive/modeld/models/driving_vision_metadata.pkl \
|
||||
selfdrive/modeld/models/driving_policy_metadata.pkl \
|
||||
selfdrive/modeld/models/driving_vision_tinygrad.pkl \
|
||||
selfdrive/modeld/models/driving_policy_tinygrad.pkl \
|
||||
rednose/helpers/ekf_sym_pyx.so \
|
||||
common/params_pyx.so \
|
||||
common/transformations/transformations.so \
|
||||
|
||||
+215
-257
@@ -1,95 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import codecs
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
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"
|
||||
DRIVING_COMPILE_SCRIPT = REPO_ROOT / "selfdrive/modeld/compile_modeld.py"
|
||||
DM_WARP_COMPILE_SCRIPT = REPO_ROOT / "selfdrive/modeld/compile_dm_warp.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}
|
||||
DM_INPUT_CANDIDATES = ("dmonitoring_model.onnx", "dmonitoring.onnx", "dm.onnx")
|
||||
|
||||
COMPONENT_ALIASES = {
|
||||
"driving_supercombo": ("driving_supercombo", "supercombo"),
|
||||
"driving_off_policy": ("driving_off_policy", "off_policy", "offpolicy"),
|
||||
"driving_on_policy": ("driving_on_policy", "on_policy", "onpolicy"),
|
||||
"driving_policy": ("driving_policy", "policy"),
|
||||
"driving_vision": ("driving_vision", "vision"),
|
||||
}
|
||||
DEFAULT_CAMERA_RESOLUTIONS = ((1928, 1208), (1344, 760))
|
||||
MEDMODEL_INPUT_SIZE = (512, 256)
|
||||
DEFAULT_CAMERA_RESOLUTIONS = (
|
||||
(1928, 1208),
|
||||
(1344, 760),
|
||||
)
|
||||
DM_INPUT_SIZE = (1440, 960)
|
||||
MODEL_RUN_FREQ = 20
|
||||
MODEL_CONTEXT_FREQ = 5
|
||||
|
||||
|
||||
def build_compile_env(*, combined: bool = False) -> dict[str, str]:
|
||||
def build_compile_env() -> 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 = {
|
||||
pythonpath = env.get("PYTHONPATH", "")
|
||||
env["PYTHONPATH"] = f"{REPO_ROOT}:{pythonpath}" if pythonpath else str(REPO_ROOT)
|
||||
for key, default in {
|
||||
"DEBUG": "0",
|
||||
"FLOAT16": "1",
|
||||
"IMAGE": "2",
|
||||
"JIT_BATCH_SIZE": "0",
|
||||
"NOLOCALS": "1",
|
||||
}
|
||||
for key, default in numeric_defaults.items():
|
||||
value = env.get(key)
|
||||
"OPENPILOT_HACKS": "1",
|
||||
}.items():
|
||||
try:
|
||||
int(str(value), 0)
|
||||
int(str(env.get(key)), 0)
|
||||
except (TypeError, ValueError):
|
||||
env[key] = default
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compile staged ONNX driving models into tinygrad pkls without touching selfdrive/modeld/models.",
|
||||
description="Compile staged ONNX models into StarPilot's unified tinygrad artifact format.",
|
||||
)
|
||||
parser.add_argument("--model", help="Output model key, for example sc2.")
|
||||
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.")
|
||||
parser.add_argument("--model", help="Output model ID, for example sc2.")
|
||||
parser.add_argument("--dm", action="store_true", help="Build DM model, metadata, and both camera warps.")
|
||||
parser.add_argument("--input-dir", type=Path, default=DEFAULT_INPUT_ROOT)
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_ROOT)
|
||||
parser.add_argument(
|
||||
"--input-format",
|
||||
choices=("auto", "supercombo", "split"),
|
||||
default="auto",
|
||||
help="Source ONNX layout. Auto prefers supercombo when present.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
help="Behavioral model version stored in the artifact. It does not control artifact layout.",
|
||||
)
|
||||
parser.add_argument("--list", action="store_true", help="List staged models and exit.")
|
||||
parser.add_argument("--force", action="store_true", help="Accepted for compatibility; selected outputs are always replaced.")
|
||||
|
||||
args, unknown = parser.parse_known_args()
|
||||
dynamic_model_flags = [arg[2:] for arg in unknown if arg.startswith("--")]
|
||||
invalid = [arg for arg in unknown if not arg.startswith("--")]
|
||||
dynamic_flags = [value[2:] for value in unknown if value.startswith("--")]
|
||||
invalid = [value for value in unknown if not value.startswith("--")]
|
||||
if invalid:
|
||||
parser.error(f"Unexpected arguments: {' '.join(invalid)}")
|
||||
if len(dynamic_model_flags) > 1:
|
||||
if len(dynamic_flags) > 1:
|
||||
parser.error("Pass only one dynamic model flag, for example ./models --sc2")
|
||||
if args.model and dynamic_model_flags and args.model != dynamic_model_flags[0]:
|
||||
parser.error("Use either --model sc2 or --sc2, not both with different values.")
|
||||
args.model = args.model or (dynamic_model_flags[0] if dynamic_model_flags else None)
|
||||
if args.model and dynamic_flags and args.model != dynamic_flags[0]:
|
||||
parser.error("Use either --model sc2 or --sc2, not both.")
|
||||
args.model = args.model or (dynamic_flags[0] if dynamic_flags else None)
|
||||
if args.model and args.model.strip().lower() in DM_TARGET_ALIASES:
|
||||
args.dm = True
|
||||
args.model = None
|
||||
if args.dm and args.model:
|
||||
parser.error("Use either --dm or a driving model key, not both.")
|
||||
parser.error("Use either --dm or a driving model ID.")
|
||||
return args
|
||||
|
||||
|
||||
@@ -101,23 +104,15 @@ def detect_component(path: Path) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def find_staged_dm(input_root: Path) -> Path | None:
|
||||
if not input_root.is_dir():
|
||||
return None
|
||||
|
||||
for candidate in DM_INPUT_CANDIDATES:
|
||||
path = input_root / candidate
|
||||
if path.is_file():
|
||||
return path
|
||||
|
||||
for child in sorted(input_root.iterdir()):
|
||||
if not child.is_dir():
|
||||
continue
|
||||
for candidate in DM_INPUT_CANDIDATES:
|
||||
path = child / candidate
|
||||
if path.is_file():
|
||||
return path
|
||||
|
||||
def _model_key_from_flat_file(path: Path, component: str) -> str | None:
|
||||
lowered = path.stem.lower()
|
||||
for alias in COMPONENT_ALIASES[component]:
|
||||
if lowered == alias:
|
||||
return None
|
||||
suffix = f"_{alias}"
|
||||
if lowered.endswith(suffix):
|
||||
key = path.stem[:-len(suffix)]
|
||||
return None if key in ("", "driving") else key
|
||||
return None
|
||||
|
||||
|
||||
@@ -129,42 +124,26 @@ def find_staged_models(input_root: Path) -> dict[str, dict[str, Path]]:
|
||||
for child in sorted(input_root.iterdir()):
|
||||
if not child.is_dir():
|
||||
continue
|
||||
model_files = {}
|
||||
for onnx_file in sorted(child.glob("*.onnx")):
|
||||
component = detect_component(onnx_file)
|
||||
if component:
|
||||
model_files[component] = onnx_file
|
||||
if model_files:
|
||||
found[child.name] = model_files
|
||||
files = {
|
||||
component: path
|
||||
for path in sorted(child.glob("*.onnx"))
|
||||
if (component := detect_component(path)) is not None
|
||||
}
|
||||
if files:
|
||||
found[child.name] = files
|
||||
|
||||
flat_root_files = {}
|
||||
for onnx_file in sorted(input_root.glob("*.onnx")):
|
||||
component = detect_component(onnx_file)
|
||||
root_files: dict[str, Path] = {}
|
||||
for path in sorted(input_root.glob("*.onnx")):
|
||||
component = detect_component(path)
|
||||
if component is None:
|
||||
continue
|
||||
|
||||
model_key = None
|
||||
lowered = onnx_file.stem.lower()
|
||||
for alias in COMPONENT_ALIASES[component]:
|
||||
if lowered == alias:
|
||||
model_key = None
|
||||
break
|
||||
suffix = f"_{alias}"
|
||||
if lowered.endswith(suffix):
|
||||
model_key = onnx_file.stem[:-len(suffix)]
|
||||
break
|
||||
|
||||
if model_key in ("", "driving"):
|
||||
model_key = None
|
||||
|
||||
model_key = _model_key_from_flat_file(path, component)
|
||||
if model_key:
|
||||
found.setdefault(model_key, {})[component] = onnx_file
|
||||
found.setdefault(model_key, {})[component] = path
|
||||
else:
|
||||
flat_root_files[component] = onnx_file
|
||||
|
||||
if flat_root_files:
|
||||
found["_root"] = flat_root_files
|
||||
|
||||
root_files[component] = path
|
||||
if root_files:
|
||||
found["_root"] = root_files
|
||||
return found
|
||||
|
||||
|
||||
@@ -172,17 +151,30 @@ def resolve_model_files(input_root: Path, model_key: str) -> dict[str, Path]:
|
||||
staged = find_staged_models(input_root)
|
||||
if model_key in staged:
|
||||
return staged[model_key]
|
||||
|
||||
root_files = staged.get("_root")
|
||||
if root_files and len(staged) == 1:
|
||||
if root_files and set(staged) == {"_root"}:
|
||||
return root_files
|
||||
return {
|
||||
component: path
|
||||
for path in sorted(input_root.glob(f"{model_key}_*.onnx"))
|
||||
if (component := detect_component(path)) is not None
|
||||
}
|
||||
|
||||
prefixed_files = {}
|
||||
for onnx_file in sorted(input_root.glob(f"{model_key}_*.onnx")):
|
||||
component = detect_component(onnx_file)
|
||||
if component:
|
||||
prefixed_files[component] = onnx_file
|
||||
return prefixed_files
|
||||
|
||||
def find_staged_dm(input_root: Path) -> Path | None:
|
||||
if not input_root.is_dir():
|
||||
return None
|
||||
for candidate in DM_INPUT_CANDIDATES:
|
||||
path = input_root / candidate
|
||||
if path.is_file():
|
||||
return path
|
||||
for child in sorted(input_root.iterdir()):
|
||||
if child.is_dir():
|
||||
for candidate in DM_INPUT_CANDIDATES:
|
||||
path = child / candidate
|
||||
if path.is_file():
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def get_metadata_value_by_name(model, name: str):
|
||||
@@ -192,7 +184,7 @@ def get_metadata_value_by_name(model, name: str):
|
||||
return None
|
||||
|
||||
|
||||
def write_metadata(onnx_path: Path, output_path: Path) -> None:
|
||||
def write_metadata(onnx_path: Path, output_path: Path) -> dict:
|
||||
import onnx
|
||||
|
||||
model = onnx.load(str(onnx_path))
|
||||
@@ -207,220 +199,186 @@ def write_metadata(onnx_path: Path, output_path: Path) -> None:
|
||||
metadata = {
|
||||
"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),
|
||||
"input_shapes": dict(get_name_and_shape(value) for value in model.graph.input),
|
||||
"output_shapes": dict(get_name_and_shape(value) for value in model.graph.output),
|
||||
}
|
||||
|
||||
with open(output_path, "wb") as f:
|
||||
pickle.dump(metadata, f)
|
||||
|
||||
|
||||
def compile_component(onnx_path: Path, output_path: Path) -> None:
|
||||
subprocess.run(
|
||||
[sys.executable, str(COMPILE_SCRIPT), str(onnx_path), str(output_path)],
|
||||
cwd=REPO_ROOT,
|
||||
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,
|
||||
)
|
||||
with open(output_path, "wb") as metadata_file:
|
||||
pickle.dump(metadata, metadata_file)
|
||||
return metadata
|
||||
|
||||
|
||||
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():
|
||||
version = json.loads(MODEL_VERSIONS_CACHE.read_text()).get(model_key)
|
||||
if isinstance(version, str):
|
||||
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 select_input_format(requested: str, files: dict[str, Path]) -> str:
|
||||
if requested == "supercombo":
|
||||
if "driving_supercombo" not in files:
|
||||
raise SystemExit("--input-format supercombo requires driving_supercombo.onnx")
|
||||
return requested
|
||||
if requested == "split":
|
||||
return requested
|
||||
return "supercombo" if "driving_supercombo" in files else "split"
|
||||
|
||||
|
||||
def resolve_split_component_inputs(model_files: dict[str, Path]) -> dict[str, Path]:
|
||||
resolved: dict[str, Path] = {}
|
||||
def driving_compile_args(files: dict[str, Path], input_format: str) -> tuple[str, list[str]]:
|
||||
if input_format == "supercombo":
|
||||
return "supercombo", ["--supercombo-onnx", str(files["driving_supercombo"])]
|
||||
|
||||
vision_path = model_files.get("driving_vision")
|
||||
if vision_path is not None:
|
||||
resolved["driving_vision"] = vision_path
|
||||
vision = files.get("driving_vision")
|
||||
primary = files.get("driving_on_policy") or files.get("driving_policy")
|
||||
off_policy = files.get("driving_off_policy")
|
||||
if vision is None or primary is None:
|
||||
missing = [
|
||||
name for name, present in (
|
||||
("driving_vision", vision),
|
||||
("driving_policy or driving_on_policy", primary),
|
||||
) if present is None
|
||||
]
|
||||
raise SystemExit(f"Missing required split ONNX files: {', '.join(missing)}")
|
||||
|
||||
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
|
||||
args = ["--vision-onnx", str(vision)]
|
||||
if off_policy is None:
|
||||
args += ["--policy-onnx", str(primary)]
|
||||
return "vision_policy", args
|
||||
|
||||
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
|
||||
args += ["--on-policy-onnx", str(primary), "--off-policy-onnx", str(off_policy)]
|
||||
return "vision_multi_policy", args
|
||||
|
||||
|
||||
def clear_existing_outputs(output_dir: Path) -> list[Path]:
|
||||
removed = []
|
||||
for existing in sorted(output_dir.iterdir()):
|
||||
if existing.is_file() or existing.is_symlink():
|
||||
existing.unlink()
|
||||
elif existing.is_dir():
|
||||
shutil.rmtree(existing)
|
||||
removed.append(existing)
|
||||
return removed
|
||||
def remove_paths(paths: list[Path]) -> int:
|
||||
count = 0
|
||||
for path in paths:
|
||||
if path.is_file() or path.is_symlink():
|
||||
path.unlink()
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def compile_driving(model_key: str, files: dict[str, Path], input_format: str, version: str, output_dir: Path) -> Path:
|
||||
model_type, source_args = driving_compile_args(files, input_format)
|
||||
output_path = output_dir / f"{model_key}_driving_tinygrad.pkl"
|
||||
removed = remove_paths([
|
||||
output_path,
|
||||
*output_dir.glob(f"{model_key}_driving_*_tinygrad.pkl"),
|
||||
*output_dir.glob(f"{model_key}_driving_*_metadata.pkl"),
|
||||
])
|
||||
if removed:
|
||||
print(f" cleared {removed} existing output entries for {model_key}")
|
||||
|
||||
frame_skip = MODEL_RUN_FREQ // MODEL_CONTEXT_FREQ
|
||||
command = [
|
||||
sys.executable,
|
||||
str(DRIVING_COMPILE_SCRIPT),
|
||||
"--model-type",
|
||||
model_type,
|
||||
"--model-size",
|
||||
f"{MEDMODEL_INPUT_SIZE[0]}x{MEDMODEL_INPUT_SIZE[1]}",
|
||||
"--camera-resolutions",
|
||||
*(f"{width}x{height}" for width, height in DEFAULT_CAMERA_RESOLUTIONS),
|
||||
"--output",
|
||||
str(output_path),
|
||||
"--frame-skip",
|
||||
str(frame_skip),
|
||||
*source_args,
|
||||
]
|
||||
if version:
|
||||
command += ["--behavior-version", version]
|
||||
subprocess.run(command, cwd=REPO_ROOT, env=build_compile_env(), check=True)
|
||||
return output_path
|
||||
|
||||
|
||||
def compile_dm(onnx_path: Path, output_dir: Path) -> list[Path]:
|
||||
outputs = [
|
||||
output_dir / f"{DM_MODEL_NAME}_tinygrad.pkl",
|
||||
output_dir / f"{DM_MODEL_NAME}_metadata.pkl",
|
||||
*(output_dir / f"dm_warp_{width}x{height}_tinygrad.pkl" for width, height in DEFAULT_CAMERA_RESOLUTIONS),
|
||||
]
|
||||
removed = remove_paths(outputs)
|
||||
if removed:
|
||||
print(f" cleared {removed} existing DM output entries")
|
||||
|
||||
subprocess.run(
|
||||
[sys.executable, str(COMPILE_SCRIPT), str(onnx_path), str(outputs[0])],
|
||||
cwd=REPO_ROOT,
|
||||
env=build_compile_env(),
|
||||
check=True,
|
||||
)
|
||||
write_metadata(onnx_path, outputs[1])
|
||||
dm_w, dm_h = DM_INPUT_SIZE
|
||||
for (cam_w, cam_h), output_path in zip(DEFAULT_CAMERA_RESOLUTIONS, outputs[2:], strict=True):
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(DM_WARP_COMPILE_SCRIPT),
|
||||
"--camera-resolution",
|
||||
f"{cam_w}x{cam_h}",
|
||||
"--warp-to",
|
||||
f"{dm_w}x{dm_h}",
|
||||
"--output",
|
||||
str(output_path),
|
||||
],
|
||||
cwd=REPO_ROOT,
|
||||
env=build_compile_env(),
|
||||
check=True,
|
||||
)
|
||||
return outputs
|
||||
|
||||
|
||||
def list_models(staged: dict[str, dict[str, Path]], input_root: Path) -> int:
|
||||
dm_path = find_staged_dm(input_root)
|
||||
if not staged and dm_path is None:
|
||||
print(f"No staged models found in {input_root}")
|
||||
return 0
|
||||
|
||||
for model_key, files in sorted(staged.items()):
|
||||
print(model_key)
|
||||
for component, path in sorted(files.items()):
|
||||
print(f" {component}: {path}")
|
||||
|
||||
if dm_path is not None:
|
||||
if (dm_path := find_staged_dm(input_root)) is not None:
|
||||
print(DM_MODEL_KEY)
|
||||
print(f" {DM_MODEL_NAME}: {dm_path}")
|
||||
if not staged and dm_path is None:
|
||||
print(f"No staged models found in {input_root}")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
staged = find_staged_models(args.input_dir)
|
||||
|
||||
if args.list:
|
||||
return list_models(staged, args.input_dir)
|
||||
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
if args.dm:
|
||||
onnx_path = find_staged_dm(args.input_dir)
|
||||
if onnx_path is None:
|
||||
raise SystemExit(
|
||||
f"No staged ONNX file found for {DM_MODEL_NAME} in {args.input_dir}. "
|
||||
f"Use one of: {', '.join(str(args.input_dir / candidate) for candidate in DM_INPUT_CANDIDATES)}"
|
||||
)
|
||||
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Compiling {DM_MODEL_NAME} from {onnx_path} -> {args.output_dir}")
|
||||
|
||||
removed = clear_existing_outputs(args.output_dir)
|
||||
if removed:
|
||||
print(f" cleared {len(removed)} existing output entries")
|
||||
|
||||
output_pkl = args.output_dir / f"{DM_MODEL_NAME}_tinygrad.pkl"
|
||||
output_metadata = args.output_dir / f"{DM_MODEL_NAME}_metadata.pkl"
|
||||
|
||||
compile_component(onnx_path, output_pkl)
|
||||
write_metadata(onnx_path, output_metadata)
|
||||
print(f" saved {output_pkl.name}")
|
||||
print(f" saved {output_metadata.name}")
|
||||
raise SystemExit(f"No staged DM ONNX found in {args.input_dir}")
|
||||
print(f"Compiling DM artifacts from {onnx_path} -> {args.output_dir}")
|
||||
for output in compile_dm(onnx_path, args.output_dir):
|
||||
print(f" saved {output.name}")
|
||||
print("Done.")
|
||||
return 0
|
||||
|
||||
if not args.model:
|
||||
available = ", ".join(sorted(k for k in staged if k != "_root"))
|
||||
if find_staged_dm(args.input_dir) is not None:
|
||||
available = f"{available}, {DM_MODEL_KEY}" if available else DM_MODEL_KEY
|
||||
raise SystemExit(f"Choose a model key, for example ./models --sc2 or ./models --dm. Available staged models: {available or 'none'}")
|
||||
available = ", ".join(sorted(key for key in staged if key != "_root"))
|
||||
raise SystemExit(f"Choose a model ID, for example ./models --sc2. Available: {available or 'none'}")
|
||||
|
||||
model_key = args.model.strip()
|
||||
files = resolve_model_files(args.input_dir, model_key)
|
||||
if not files:
|
||||
raise SystemExit(
|
||||
f"No staged ONNX files found for {model_key} in {args.input_dir}. "
|
||||
f"Use {args.input_dir}/driving_policy.onnx and {args.input_dir}/driving_vision.onnx, "
|
||||
f"or {args.input_dir}/driving_on_policy.onnx with {args.input_dir}/driving_off_policy.onnx, "
|
||||
f"or optionally {args.input_dir / model_key}/*.onnx"
|
||||
)
|
||||
|
||||
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)
|
||||
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")
|
||||
|
||||
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"
|
||||
|
||||
print(f" compiling {component}: {onnx_path.name}")
|
||||
compile_component(onnx_path, output_pkl)
|
||||
write_metadata(onnx_path, output_metadata)
|
||||
print(f" saved {output_pkl.name}")
|
||||
print(f" saved {output_metadata.name}")
|
||||
raise SystemExit(f"No staged ONNX files found for {model_key} in {args.input_dir}")
|
||||
|
||||
input_format = select_input_format(args.input_format, files)
|
||||
version = infer_model_version(model_key, args.version)
|
||||
version_label = version or "unspecified behavior"
|
||||
print(f"Compiling {model_key} ({input_format}, {version_label}) from {args.input_dir} -> {args.output_dir}")
|
||||
output = compile_driving(model_key, files, input_format, version, args.output_dir)
|
||||
print(f" saved {output.name}")
|
||||
print("Done.")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_OPENPILOT = Path.home() / "openpilot"
|
||||
DEFAULT_WORKSPACE = Path("/Volumes/T5/StarPilot-Model-Rebuild-2026-06-22")
|
||||
DEFAULT_SOURCE_MAP = REPO_ROOT / "scripts/model_source_map_v22.json"
|
||||
DEFAULT_MANIFEST = DEFAULT_WORKSPACE / "manifests/model_names_v22.json"
|
||||
REMOTE = "comma@192.168.3.110"
|
||||
REMOTE_ROOT = Path("/data/openpilot")
|
||||
|
||||
MODEL_FILENAMES = (
|
||||
"driving_supercombo.onnx",
|
||||
"driving_vision.onnx",
|
||||
"driving_policy.onnx",
|
||||
"driving_on_policy.onnx",
|
||||
"driving_off_policy.onnx",
|
||||
)
|
||||
MODEL_PATH_PREFIXES = (
|
||||
"openpilot/selfdrive/modeld/models",
|
||||
"selfdrive/modeld/models",
|
||||
"frogpilot/tinygrad_modeld/models",
|
||||
)
|
||||
|
||||
|
||||
def run(command: list[str], *, cwd: Path | None = None, stdout=None, check: bool = True):
|
||||
return subprocess.run(command, cwd=cwd, stdout=stdout, check=check)
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with open(path, "rb") as source:
|
||||
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def load_json(path: Path):
|
||||
return json.loads(path.read_text())
|
||||
|
||||
|
||||
def ensure_workspace(workspace: Path) -> None:
|
||||
for relative in (
|
||||
"onnx",
|
||||
"compiled",
|
||||
"driver-monitoring",
|
||||
"ready-for-resources",
|
||||
"manifests",
|
||||
"logs",
|
||||
"results",
|
||||
"source-maps",
|
||||
"scripts",
|
||||
"external-upload",
|
||||
):
|
||||
(workspace / relative).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def git_object_path(repo: Path, oid: str) -> Path:
|
||||
git_dir = subprocess.check_output(
|
||||
["git", "-C", str(repo), "rev-parse", "--git-dir"], text=True,
|
||||
).strip()
|
||||
git_dir_path = Path(git_dir)
|
||||
if not git_dir_path.is_absolute():
|
||||
git_dir_path = repo / git_dir_path
|
||||
return git_dir_path / "lfs/objects" / oid[:2] / oid[2:4] / oid
|
||||
|
||||
|
||||
def parse_lfs_pointer(path: Path) -> tuple[str, int] | None:
|
||||
with open(path, "rb") as source:
|
||||
head = source.read(512)
|
||||
if not head.startswith(b"version https://git-lfs.github.com/spec/v1"):
|
||||
return None
|
||||
fields = {}
|
||||
for line in head.decode("ascii").splitlines():
|
||||
if " " in line:
|
||||
key, value = line.split(" ", 1)
|
||||
fields[key] = value
|
||||
oid = fields.get("oid", "").removeprefix("sha256:")
|
||||
return (oid, int(fields["size"])) if oid and "size" in fields else None
|
||||
|
||||
|
||||
def resolve_lfs(repo: Path, pointer_path: Path, ref: str, git_path: str) -> None:
|
||||
pointer = parse_lfs_pointer(pointer_path)
|
||||
if pointer is None:
|
||||
return
|
||||
oid, expected_size = pointer
|
||||
object_path = git_object_path(repo, oid)
|
||||
if not object_path.is_file():
|
||||
run(["git", "-C", str(repo), "lfs", "fetch", "origin", ref, "--include", git_path])
|
||||
if not object_path.is_file():
|
||||
raise FileNotFoundError(f"Missing LFS object {oid} for {ref}:{git_path}")
|
||||
if object_path.stat().st_size != expected_size or sha256_file(object_path) != oid:
|
||||
raise ValueError(f"Invalid LFS object {oid} for {ref}:{git_path}")
|
||||
temporary = pointer_path.with_suffix(pointer_path.suffix + ".resolved")
|
||||
shutil.copyfile(object_path, temporary)
|
||||
temporary.replace(pointer_path)
|
||||
|
||||
|
||||
def git_path_exists(repo: Path, ref: str, git_path: str) -> bool:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(repo), "cat-file", "-e", f"{ref}:{git_path}"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def ensure_git_ref(repo: Path, ref: str) -> None:
|
||||
if subprocess.run(
|
||||
["git", "-C", str(repo), "cat-file", "-e", f"{ref}^{{commit}}"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
).returncode == 0:
|
||||
return
|
||||
run(["git", "-C", str(repo), "fetch", "--no-tags", "https://github.com/commaai/openpilot.git", ref])
|
||||
|
||||
|
||||
def extract_git_file(repo: Path, ref: str, git_path: str, destination: Path) -> None:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = destination.with_suffix(destination.suffix + ".tmp")
|
||||
with open(temporary, "wb") as output:
|
||||
run(["git", "-C", str(repo), "show", f"{ref}:{git_path}"], stdout=output)
|
||||
resolve_lfs(repo, temporary, ref, git_path)
|
||||
temporary.replace(destination)
|
||||
|
||||
|
||||
def find_model_paths(repo: Path, ref: str, input_format: str) -> list[str]:
|
||||
requested = (
|
||||
("driving_supercombo.onnx",)
|
||||
if input_format == "supercombo"
|
||||
else (
|
||||
"driving_vision.onnx",
|
||||
"driving_policy.onnx",
|
||||
"driving_on_policy.onnx",
|
||||
"driving_off_policy.onnx",
|
||||
)
|
||||
)
|
||||
found: list[str] = []
|
||||
for filename in requested:
|
||||
for prefix in MODEL_PATH_PREFIXES:
|
||||
git_path = f"{prefix}/{filename}"
|
||||
if git_path_exists(repo, ref, git_path):
|
||||
found.append(git_path)
|
||||
break
|
||||
|
||||
if input_format == "supercombo" and len(found) != 1:
|
||||
raise FileNotFoundError(f"No supercombo ONNX found at {ref}")
|
||||
names = {Path(path).name for path in found}
|
||||
if input_format == "split":
|
||||
if "driving_vision.onnx" not in names:
|
||||
raise FileNotFoundError(f"No driving_vision.onnx found at {ref}")
|
||||
if not names.intersection({"driving_policy.onnx", "driving_on_policy.onnx"}):
|
||||
raise FileNotFoundError(f"No policy ONNX found at {ref}")
|
||||
return found
|
||||
|
||||
|
||||
def extract_model(model_id: str, source: dict, repo: Path, workspace: Path) -> dict:
|
||||
ref = source["ref"]
|
||||
input_format = source["input_format"]
|
||||
ensure_git_ref(repo, ref)
|
||||
output_dir = workspace / "onnx" / model_id
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
extracted = []
|
||||
for git_path in find_model_paths(repo, ref, input_format):
|
||||
filename = Path(git_path).name
|
||||
destination = output_dir / f"{model_id}_{filename}"
|
||||
extract_git_file(repo, ref, git_path, destination)
|
||||
extracted.append({
|
||||
"component": filename,
|
||||
"git_path": git_path,
|
||||
"path": str(destination),
|
||||
"size": destination.stat().st_size,
|
||||
"sha256": sha256_file(destination),
|
||||
})
|
||||
result = {
|
||||
"id": model_id,
|
||||
"ref": ref,
|
||||
"input_format": input_format,
|
||||
"files": extracted,
|
||||
}
|
||||
(workspace / "results" / f"{model_id}_source.json").write_text(json.dumps(result, indent=2) + "\n")
|
||||
return result
|
||||
|
||||
|
||||
def remote(command: str, *, capture: bool = False):
|
||||
result = subprocess.run(
|
||||
["ssh", REMOTE, command],
|
||||
check=False,
|
||||
text=capture,
|
||||
capture_output=capture,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() if capture and result.stderr else f"exit {result.returncode}"
|
||||
raise RuntimeError(f"Remote command failed: {detail}")
|
||||
return result
|
||||
|
||||
|
||||
def compile_model(model_id: str, source: dict, version: str, workspace: Path, force: bool) -> dict:
|
||||
local_output = workspace / "compiled" / f"{model_id}_driving_tinygrad.pkl"
|
||||
if local_output.is_file() and not force:
|
||||
return artifact_result(model_id, local_output, "skipped")
|
||||
|
||||
source_dir = workspace / "onnx" / model_id
|
||||
if not source_dir.is_dir():
|
||||
raise FileNotFoundError(f"Extract sources first: {source_dir}")
|
||||
remote_input = f"{REMOTE_ROOT}/uncompiledmodels/{model_id}"
|
||||
remote_output = f"{REMOTE_ROOT}/compiledmodels/{model_id}_driving_tinygrad.pkl"
|
||||
remote(f"rm -rf {remote_input} && mkdir -p {remote_input} {REMOTE_ROOT}/compiledmodels")
|
||||
run(["rsync", "-az", "--exclude=._*", f"{source_dir}/", f"{REMOTE}:{remote_input}/"])
|
||||
|
||||
log_path = workspace / "logs" / f"{model_id}.log"
|
||||
command = (
|
||||
f"cd {REMOTE_ROOT} && ./models --model {model_id} "
|
||||
f"--input-dir {remote_input} --output-dir {REMOTE_ROOT}/compiledmodels "
|
||||
f"--input-format {source['input_format']} --version {version}"
|
||||
)
|
||||
with open(log_path, "wb") as log_file:
|
||||
process = subprocess.run(["ssh", REMOTE, command], stdout=log_file, stderr=subprocess.STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError(f"Compilation failed for {model_id}; see {log_path}")
|
||||
|
||||
run(["rsync", "-az", f"{REMOTE}:{remote_output}", str(local_output)])
|
||||
local_output.chmod(0o644)
|
||||
ready_path = workspace / "ready-for-resources" / local_output.name
|
||||
shutil.copyfile(local_output, ready_path)
|
||||
ready_path.chmod(0o644)
|
||||
if local_output.stat().st_size > 100 * 1024 * 1024:
|
||||
external_path = workspace / "external-upload" / local_output.name
|
||||
shutil.copyfile(local_output, external_path)
|
||||
external_path.chmod(0o644)
|
||||
result = artifact_result(model_id, local_output, "compiled")
|
||||
(workspace / "results" / f"{model_id}_artifact.json").write_text(json.dumps(result, indent=2) + "\n")
|
||||
return result
|
||||
|
||||
|
||||
def artifact_result(model_id: str, path: Path, status: str) -> dict:
|
||||
return {
|
||||
"id": model_id,
|
||||
"status": status,
|
||||
"path": str(path),
|
||||
"size": path.stat().st_size,
|
||||
"sha256": sha256_file(path),
|
||||
"external_upload": path.stat().st_size > 100 * 1024 * 1024,
|
||||
}
|
||||
|
||||
|
||||
def validate_model(model_id: str, version: str, workspace: Path) -> dict:
|
||||
artifact = workspace / "compiled" / f"{model_id}_driving_tinygrad.pkl"
|
||||
if not artifact.is_file():
|
||||
raise FileNotFoundError(artifact)
|
||||
run(["rsync", "-az", str(artifact), f"{REMOTE}:/data/models/{artifact.name}"])
|
||||
run([
|
||||
"rsync",
|
||||
"-az",
|
||||
str(REPO_ROOT / "scripts/validate_model_artifact.py"),
|
||||
f"{REMOTE}:{REMOTE_ROOT}/scripts/validate_model_artifact.py",
|
||||
])
|
||||
result = remote(
|
||||
f"cd {REMOTE_ROOT} && /usr/local/venv/bin/python3 scripts/validate_model_artifact.py "
|
||||
f"--model {model_id} --version {version}",
|
||||
capture=True,
|
||||
)
|
||||
payload = json.loads(result.stdout.strip().splitlines()[-1])
|
||||
(workspace / "results" / f"{model_id}_validation.json").write_text(
|
||||
json.dumps(payload, indent=2) + "\n",
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
def update_manifest(base_manifest: Path, workspace: Path) -> dict:
|
||||
payload = load_json(base_manifest)
|
||||
models = payload["models"] if isinstance(payload, dict) else payload
|
||||
if not any(model.get("id") == "deeprl3v2" for model in models):
|
||||
models.append({
|
||||
"id": "deeprl3v2",
|
||||
"name": "Deep RL 3 V2 👀📡",
|
||||
"version": "v15",
|
||||
"series": "OP Series",
|
||||
"released": "2026-06-17",
|
||||
"community_favorite": False,
|
||||
})
|
||||
external_handoff = []
|
||||
for model in models:
|
||||
artifact = workspace / "compiled" / f"{model['id']}_driving_tinygrad.pkl"
|
||||
model.pop("artifact_format", None)
|
||||
model.pop("artifact_size", None)
|
||||
model.pop("artifact_sha256", None)
|
||||
model.pop("artifact_urls", None)
|
||||
if not artifact.is_file() or artifact.stat().st_size <= 100 * 1024 * 1024:
|
||||
model.pop("artifact_url", None)
|
||||
else:
|
||||
external_handoff.append({
|
||||
"id": model["id"],
|
||||
"filename": artifact.name,
|
||||
"size": artifact.stat().st_size,
|
||||
"sha256": sha256_file(artifact),
|
||||
"artifact_url": model.get("artifact_url", ""),
|
||||
})
|
||||
output = {"models": models}
|
||||
output_path = workspace / "manifests/model_names_v22.json"
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
|
||||
(workspace / "external-upload" / "handoff.json").write_text(
|
||||
json.dumps(external_handoff, indent=2) + "\n",
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("command", choices=("init", "extract", "compile", "validate", "manifest"))
|
||||
parser.add_argument("--model")
|
||||
parser.add_argument("--workspace", type=Path, default=DEFAULT_WORKSPACE)
|
||||
parser.add_argument("--openpilot", type=Path, default=DEFAULT_OPENPILOT)
|
||||
parser.add_argument("--source-map", type=Path, default=DEFAULT_SOURCE_MAP)
|
||||
parser.add_argument("--base-manifest", type=Path)
|
||||
parser.add_argument("--force", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
ensure_workspace(args.workspace)
|
||||
source_map = load_json(args.source_map)
|
||||
if args.command == "init":
|
||||
shutil.copyfile(args.source_map, args.workspace / "source-maps" / args.source_map.name)
|
||||
shutil.copyfile(Path(__file__), args.workspace / "scripts" / Path(__file__).name)
|
||||
return 0
|
||||
if args.command == "manifest":
|
||||
if args.base_manifest is None:
|
||||
parser.error("--base-manifest is required")
|
||||
update_manifest(args.base_manifest, args.workspace)
|
||||
return 0
|
||||
|
||||
model_ids = [args.model] if args.model else list(source_map)
|
||||
versions = {}
|
||||
if args.base_manifest:
|
||||
base = load_json(args.base_manifest)
|
||||
versions = {model["id"]: model["version"] for model in base.get("models", base)}
|
||||
versions.setdefault("deeprl3v2", "v15")
|
||||
|
||||
for model_id in model_ids:
|
||||
if model_id not in source_map:
|
||||
raise KeyError(f"Unknown model ID: {model_id}")
|
||||
try:
|
||||
if args.command == "extract":
|
||||
result = extract_model(model_id, source_map[model_id], args.openpilot, args.workspace)
|
||||
elif args.command == "compile":
|
||||
result = compile_model(model_id, source_map[model_id], versions.get(model_id, ""), args.workspace, args.force)
|
||||
else:
|
||||
result = validate_model(model_id, versions.get(model_id, ""), args.workspace)
|
||||
(args.workspace / "results" / f"{model_id}_failure.json").unlink(missing_ok=True)
|
||||
print(json.dumps(result), flush=True)
|
||||
except Exception as error:
|
||||
failure = {"id": model_id, "status": "failed", "error": str(error)}
|
||||
(args.workspace / "results" / f"{model_id}_failure.json").write_text(json.dumps(failure, indent=2) + "\n")
|
||||
print(json.dumps(failure), file=sys.stderr, flush=True)
|
||||
if args.model:
|
||||
raise
|
||||
failures = [
|
||||
args.workspace / "results" / f"{model_id}_failure.json"
|
||||
for model_id in model_ids
|
||||
if (args.workspace / "results" / f"{model_id}_failure.json").is_file()
|
||||
]
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"tr1422": {"ref": "64fd3f986081ba6cb488d02e799845367add29e2", "input_format": "split"},
|
||||
"tr1522": {"ref": "ed38ca8cc998f0a2b47cde29da11b782e4551e8b", "input_format": "split"},
|
||||
"letr22": {"ref": "c2fa07b82c2e09964e4b6dccb3e4465a2e4ad138", "input_format": "split"},
|
||||
"spacelab2222": {"ref": "d3a0378ae601f61307a53d1173c251ee36c56317", "input_format": "split"},
|
||||
"vikander22": {"ref": "d134fdd7d51ba84848866c0265e37508c38f9366", "input_format": "split"},
|
||||
"fof22": {"ref": "d807b5c476658dba7760d30949baa619074c4a4e", "input_format": "split"},
|
||||
"vfof22": {"ref": "c790d3f58ad32cf15f384e6018d942b2348f0c5b", "input_format": "split"},
|
||||
"dtr22": {"ref": "5b405920ec9511e146432de08171d6f93295248b", "input_format": "split"},
|
||||
"dtr622": {"ref": "a6cf39c07a67dbc346af03010093fe4788c402d0", "input_format": "split"},
|
||||
"uvdtr622": {"ref": "079d0461604632719e73dc5b6a5eb9897c624da2", "input_format": "split"},
|
||||
"sp222": {"ref": "f1b8f510b2c554191ca15cebe35f2c5b304cf23c", "input_format": "split"},
|
||||
"fp22": {"ref": "95aff72e351e935221ec965aa0384dfd502d383d", "input_format": "split"},
|
||||
"kv22": {"ref": "16e87d4c72e5efec18032f6b4a5f6ec35d0df4e4", "input_format": "split"},
|
||||
"gwm322": {"ref": "93b26a91a62b69b809162ad2bf65e36be2158dc1", "input_format": "split"},
|
||||
"gwm522": {"ref": "e3ee440f7d5ac12c50db6381e292a55a69b745a7", "input_format": "split"},
|
||||
"gwm622": {"ref": "91a1cea814f76d605d5b15de9634a3c8f79df509", "input_format": "split"},
|
||||
"gwm822": {"ref": "96e7b310b164b55fb73abdd505b66fb36630718e", "input_format": "split"},
|
||||
"gwm922": {"ref": "f2413040a8560c0c17c18353a3c75124a2f09d17", "input_format": "split"},
|
||||
"cgwm22": {"ref": "94dd3bd9107cd413ed2418f9e6c0cf805a9ca495", "input_format": "split"},
|
||||
"bd22": {"ref": "b74f5189a74446015c0cf78a4a9f0134a347ae3b", "input_format": "split"},
|
||||
"nr22": {"ref": "266b642180f0e9278e1db46ccf8c1a2a8dae4bb3", "input_format": "split"},
|
||||
"tcp222": {"ref": "19084132cd3956413a0fdfcdc18971a55df5e0ff", "input_format": "split"},
|
||||
"tcp322": {"ref": "5eb912c025a2207c7d428deee74ded49e5905527", "input_format": "split"},
|
||||
"fbw22": {"ref": "c4488e3411285f6fc3d008bca885e05731fd75c2", "input_format": "split"},
|
||||
"nevada22": {"ref": "3193eac5e385aa010694a8ac192ff38ffe000193", "input_format": "split"},
|
||||
"wmi422": {"ref": "2d8e596a0208dafe4ed2d945b07d6a7d72f70fb8", "input_format": "split"},
|
||||
"wmi52": {"ref": "545e0ed13f85e4c744fa1f69095863ea9dbe6d6b", "input_format": "split"},
|
||||
"wmi62": {"ref": "54c6c5776a37dc56fde626314f53ec3316d88a48", "input_format": "split"},
|
||||
"wmi72": {"ref": "aa3d90c92a7bd7a4e7db1b3fdee3d3e522358823", "input_format": "split"},
|
||||
"wmi722": {"ref": "a2ace1ed6b84d7c7a2dbf990c1b566f9a9df167f", "input_format": "split"},
|
||||
"wmi82": {"ref": "4c438f59e7dcf5fddc769032882b62d0cb805e9b", "input_format": "split"},
|
||||
"wmi92": {"ref": "8950897d7e4d2ba2426b2b31cf29f25e87a3d4ba", "input_format": "split"},
|
||||
"wmi102": {"ref": "855f5e4ddefd69a20cc4e9da004eb53f3e00d950", "input_format": "split"},
|
||||
"wmi112": {"ref": "7e3fd7a63c5a09c3fabe108b1b62bba3cf684878", "input_format": "split"},
|
||||
"cd2102": {"ref": "55f66e2246359c6593605399a0199d94d13ad90d", "input_format": "split"},
|
||||
"op22": {"ref": "ae34a24c59d85df7efad5fde2b760fb6d0b9dd6e", "input_format": "split"},
|
||||
"op32": {"ref": "7e548dd765873bed301c0a19cfe10c3ca6be2bbe", "input_format": "split"},
|
||||
"op42": {"ref": "25abcc49fce2f6268d8cabf759e31c626edbf584", "input_format": "split"},
|
||||
"op52": {"ref": "b390b98c5a359c293fdc2501af79b19867fcdac4", "input_format": "split"},
|
||||
"op62": {"ref": "831f13da61d937bb40ea0f50590cdc3b5380f313", "input_format": "split"},
|
||||
"opv7": {"ref": "cb327933002bc1a00bf60a3c20af2eb7a5f653e5", "input_format": "split"},
|
||||
"opv8": {"ref": "052692b25d63c5ddda276b5c2271383b6aff129f", "input_format": "split"},
|
||||
"opv9": {"ref": "eae2a73e0ac600d7f0342fc4397568e0733fe6e2", "input_format": "split"},
|
||||
"opv10": {"ref": "5faf14e04e4038ec4673d119c950b46051630205", "input_format": "split"},
|
||||
"opv11": {"ref": "bbde5ddb5a0ee35c98a962029355f9a95403a825", "input_format": "split"},
|
||||
"opv12": {"ref": "7d4c295c27bc3f72727c49ec73b98e45745b588d", "input_format": "split"},
|
||||
"opv13": {"ref": "faeeaad3a5841694b4c3bd6d37e41e3db4b4873e", "input_format": "split"},
|
||||
"op16": {"ref": "72101c6e50ea594f24e75d3b605abf8b5cab1d6b", "input_format": "split"},
|
||||
"op16d": {"ref": "0a628146d1a9a314e329e2c0b5de20e6223211e4", "input_format": "split"},
|
||||
"op16dv2": {"ref": "0a628146d1a9a314e329e2c0b5de20e6223211e4", "input_format": "split"},
|
||||
"rlv1dl": {"ref": "38c0e3c95de5b63b56ecbbe507abc49b9b0091a3", "input_format": "split"},
|
||||
"deeprl3": {"ref": "a1060427b9b3ac1d11cbad05380929ea8235840a", "input_format": "split"},
|
||||
"ms2": {"ref": "d70b13931793bc0e4d8efd36128dd66f227a3f81", "input_format": "split"},
|
||||
"pp222": {"ref": "50c78a9dd670a305ca898b96245cadbc2ecdc1a6", "input_format": "split"},
|
||||
"ds222": {"ref": "5ff0e48f90d662d8c9d5061adc63e90c198c9b81", "input_format": "split"},
|
||||
"nn222": {"ref": "83b81b83cdf76a577946a1567f4644e1a018443c", "input_format": "split"},
|
||||
"sc2": {"ref": "e4a4b4b1adf2d19fedab4d195faa382b061fa754", "input_format": "split"},
|
||||
"pop2": {"ref": "6f71783a8a8faa07ddaeef5bbb6809b4f4f44a15", "input_format": "split"},
|
||||
"pop22": {"ref": "62bf6fb072880905a4c490f0f4f4a6b3c23346ec", "input_format": "split"},
|
||||
"nid22": {"ref": "13e79e9fad60c19e751e4f9ab0538d39f1bb54dd", "input_format": "split"},
|
||||
"kerrygold22": {"ref": "dc7a92ea630e4f6053082fc25eca83938927b91e", "input_format": "split"},
|
||||
"deeprl3v2": {"ref": "702fa71ad4dd8de08425eb11a1a42aaeb64892c9", "input_format": "supercombo"}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from tinygrad.tensor import Tensor
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.modeld.compile_modeld import WARP_INPUTS
|
||||
from openpilot.selfdrive.modeld.modeld import ModelState
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--model", required=True)
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--camera-resolution", default="1928x1208")
|
||||
args = parser.parse_args()
|
||||
cam_w, cam_h = (int(value) for value in args.camera_resolution.split("x", 1))
|
||||
|
||||
params = Params()
|
||||
params.put("Model", args.model)
|
||||
params.put("DrivingModel", args.model)
|
||||
params.put("ModelVersion", args.version)
|
||||
params.put("DrivingModelVersion", args.version)
|
||||
|
||||
model = ModelState(cam_w, cam_h)
|
||||
frames = [
|
||||
Tensor.randint(model.frame_buf_size, low=0, high=256, dtype="uint8", device=model.WARP_DEV).realize()
|
||||
for _ in range(2)
|
||||
]
|
||||
model.npy["tfm"][:] = np.eye(3, dtype=np.float32)
|
||||
model.npy["big_tfm"][:] = np.eye(3, dtype=np.float32)
|
||||
for key, value in model.npy.items():
|
||||
if key not in ("tfm", "big_tfm"):
|
||||
value[:] = 0
|
||||
if "traffic_convention" in model.npy:
|
||||
model.npy["traffic_convention"][:] = [1, 0]
|
||||
if "action_t" in model.npy:
|
||||
model.npy["action_t"][:] = [0.15, 0.25]
|
||||
|
||||
img, big_img = model.warp_enqueue(
|
||||
**{key: model.input_queues[key] for key in WARP_INPUTS},
|
||||
frame=frames[0],
|
||||
big_frame=frames[1],
|
||||
)
|
||||
outputs = model.run_policy(
|
||||
**{key: model.input_queues[key] for key in model.policy_input_keys},
|
||||
img=img,
|
||||
big_img=big_img,
|
||||
)
|
||||
arrays = [output.numpy().flatten() for output in outputs]
|
||||
if model.model_type == "supercombo":
|
||||
parsed = model.parser.parse_outputs(model.slice_outputs(arrays[0], model.output_slices))
|
||||
else:
|
||||
parsed = model._parse_split_outputs(arrays)
|
||||
|
||||
required = ("plan", "lane_lines", "lane_lines_prob", "road_edges", "lead", "lead_prob", "pose")
|
||||
missing = [key for key in required if key not in parsed]
|
||||
non_finite = [key for key, value in parsed.items() if isinstance(value, np.ndarray) and not np.isfinite(value).all()]
|
||||
has_control_output = "action" in parsed or "desired_curvature" in parsed or "plan" in parsed
|
||||
result = {
|
||||
"id": args.model,
|
||||
"version": args.version,
|
||||
"model_type": model.model_type,
|
||||
"policy_order": model.policy_order,
|
||||
"parsed_inputs": sorted(model.numpy_inputs),
|
||||
"output_sizes": [value.size for value in arrays],
|
||||
"output_shapes": {
|
||||
key: list(parsed[key].shape)
|
||||
for key in required
|
||||
if key in parsed
|
||||
},
|
||||
"missing": missing,
|
||||
"non_finite": non_finite,
|
||||
"has_control_output": has_control_output,
|
||||
}
|
||||
print(json.dumps(result))
|
||||
if missing or non_finite or not has_control_output:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user