mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
159 lines
7.6 KiB
Python
Executable File
159 lines
7.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Compile staged ONNX sources without installing or selecting the result."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
COMPONENTS = {
|
|
"supercombo": ("driving_supercombo", "supercombo"),
|
|
"off-policy": ("driving_off_policy", "off_policy", "offpolicy"),
|
|
"on-policy": ("driving_on_policy", "on_policy", "onpolicy"),
|
|
"policy": ("driving_policy", "policy"),
|
|
"vision": ("driving_vision", "vision"),
|
|
}
|
|
|
|
|
|
def source_files(directory: Path, model: str) -> dict[str, Path]:
|
|
selected = directory / model if (directory / model).is_dir() else directory
|
|
found = {}
|
|
for path in sorted(selected.glob("*.onnx")):
|
|
stem = path.stem.lower()
|
|
for component, aliases in COMPONENTS.items():
|
|
if any(stem == alias or stem == f"{model}_{alias}" for alias in aliases):
|
|
if component in found:
|
|
raise ValueError(f"Ambiguous {component} source: {found[component]} and {path}")
|
|
found[component] = path.resolve()
|
|
break
|
|
return found
|
|
|
|
|
|
def driving_compile_args(files: dict[str, Path], requested: str) -> tuple[str, list[str]]:
|
|
selected = "supercombo" if requested == "auto" and "supercombo" in files else requested
|
|
if selected == "supercombo":
|
|
if "supercombo" not in files:
|
|
raise ValueError("Supercombo input requires driving_supercombo.onnx")
|
|
return "supercombo", ["--supercombo-onnx", str(files["supercombo"])]
|
|
if "vision" not in files or not ({"policy", "on-policy"} & files.keys()):
|
|
raise ValueError("Split input requires driving_vision.onnx and driving_policy.onnx or driving_on_policy.onnx")
|
|
result = ["--vision-onnx", str(files["vision"])]
|
|
primary = files.get("on-policy", files.get("policy"))
|
|
if "off-policy" in files:
|
|
return "vision_multi_policy", [*result, "--on-policy-onnx", str(primary), "--off-policy-onnx", str(files["off-policy"])]
|
|
return "vision_policy", [*result, "--policy-onnx", str(primary)]
|
|
|
|
|
|
def sha256_file(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as source:
|
|
for block in iter(lambda: source.read(1024 * 1024), b""):
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def compile_env(gpu: bool = False) -> dict[str, str]:
|
|
env = dict(os.environ)
|
|
env["PYTHONPATH"] = os.pathsep.join((str(REPO_ROOT), str(REPO_ROOT / "tinygrad_repo"), env.get("PYTHONPATH", "")))
|
|
defaults = {"DEV": "QCOM", "IMAGE": "1", "FLOAT16": "1", "NOLOCALS": "1", "JIT_BATCH_SIZE": "0", "OPENPILOT_HACKS": "1"}
|
|
for key, value in defaults.items():
|
|
env.setdefault(key, value)
|
|
if gpu:
|
|
for key in ("IMAGE", "NOLOCALS", "OPENPILOT_HACKS"):
|
|
env.pop(key, None)
|
|
env.update(DEV="USB+AMD:LLVM", WARP_DEV="QCOM", GMMU="0", TC_OPT="2", TC_MIN_GLOBALS="32")
|
|
return env
|
|
|
|
|
|
def parse_args(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--model", help="Stable output model ID")
|
|
parser.add_argument("--version", required=True, choices=tuple(f"v{value}" for value in range(8, 17)))
|
|
parser.add_argument("--input-dir", type=Path, default=Path("/data/openpilot/uncompiledmodels"))
|
|
parser.add_argument("--output-dir", type=Path, default=Path("/data/openpilot/compiledmodels"))
|
|
parser.add_argument("--input-format", choices=("auto", "supercombo", "split"), default="auto")
|
|
parser.add_argument("--gpu", action="store_true")
|
|
parser.add_argument("--image-history-pipeline", choices=("policy", "warp"), default="policy")
|
|
parser.add_argument("--frame-skip", type=int)
|
|
args, unknown = parser.parse_known_args(argv)
|
|
if unknown:
|
|
if len(unknown) != 1 or not unknown[0].startswith("--") or args.model:
|
|
parser.error("Use --model ID or one --ID flag")
|
|
args.model = unknown[0][2:]
|
|
if not args.model or not re.fullmatch(r"[a-z0-9][a-z0-9_-]{0,63}", args.model):
|
|
parser.error("A lowercase model ID containing letters, digits, hyphens or underscores is required")
|
|
if args.frame_skip is not None and args.frame_skip < 1:
|
|
parser.error("--frame-skip must be positive")
|
|
return args
|
|
|
|
|
|
def bank_compiler_revision() -> str:
|
|
# The global runtime can advance while the compiled bank keeps its own ABI.
|
|
old_path = sys.path[:]
|
|
try:
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
from openpilot.starpilot.models import catalog
|
|
from openpilot.starpilot.models.runtime_bridge import bootstrap, runtime
|
|
for module, relative in ((catalog, "catalog.py"), (bootstrap, "runtime_bridge/bootstrap.py"),
|
|
(runtime, "runtime_bridge/runtime.py")):
|
|
if Path(module.__file__).resolve() != (REPO_ROOT / "openpilot/starpilot/models" / relative).resolve():
|
|
raise ValueError("Model compiler identity came from a different checkout")
|
|
revision = catalog.COMPILER_REVISION
|
|
if not isinstance(revision, str) or re.fullmatch(r"[0-9a-f]{40}", revision) is None:
|
|
raise ValueError("Invalid bank compiler revision")
|
|
if revision != bootstrap.LEGACY_REVISION or revision != runtime.LEGACY_REVISION:
|
|
raise ValueError("Bank compiler revision does not match the selected compiler runtime")
|
|
return revision
|
|
finally:
|
|
sys.path[:] = old_path
|
|
|
|
|
|
def main(argv=None):
|
|
args = parse_args(argv)
|
|
model_type, source_args = driving_compile_args(source_files(args.input_dir, args.model), args.input_format)
|
|
sources = [Path(source_args[index]) for index in range(1, len(source_args), 2)]
|
|
for source in sources:
|
|
with source.open("rb") as file:
|
|
prefix = file.read(128)
|
|
if not prefix or prefix.startswith(b"version https://git-lfs.github.com/spec/v1"):
|
|
raise ValueError(f"Expected ONNX model bytes, not an empty file or Git LFS pointer: {source}")
|
|
before = {path.name: sha256_file(path) for path in sources}
|
|
output_dir = args.output_dir.resolve()
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
output = output_dir / f"{args.model}_driving_tinygrad.pkl"
|
|
env = compile_env(args.gpu)
|
|
with tempfile.TemporaryDirectory(prefix=f".{args.model}-compile-", dir=output_dir) as temporary:
|
|
env["TMPDIR"] = temporary
|
|
staged = Path(temporary) / output.name
|
|
command = [sys.executable, "-m", "openpilot.starpilot.models.compile", "--model-type", model_type,
|
|
"--model-size", "512x256", "--camera-resolutions", "1928x1208", "1344x760",
|
|
"--behavior-version", args.version, "--output", str(staged), "--out-of-band",
|
|
"--image-history-pipeline", args.image_history_pipeline, *source_args]
|
|
if args.frame_skip is not None:
|
|
command += ["--frame-skip", str(args.frame_skip)]
|
|
subprocess.run(command, cwd=REPO_ROOT, env=env, check=True)
|
|
if {path.name: sha256_file(path) for path in sources} != before:
|
|
raise ValueError("ONNX source changed during compilation")
|
|
revision = bank_compiler_revision()
|
|
receipt = {"model_id": args.model, "behavior_version": args.version, "model_type": model_type,
|
|
"artifact_abi": "tinygrad_single_v1_arena", "compiler_revision": revision,
|
|
"source_sha256": before, "artifact_sha256": sha256_file(staged), "artifact_size": staged.stat().st_size,
|
|
"execution_device": env["DEV"], "warp_device": env.get("WARP_DEV", env["DEV"]),
|
|
"image_history_pipeline": args.image_history_pipeline, "camera_resolutions": [[1928, 1208], [1344, 760]]}
|
|
staged.replace(output)
|
|
receipt_path = output.with_suffix(".build.json")
|
|
temporary_receipt = Path(temporary) / receipt_path.name
|
|
temporary_receipt.write_text(json.dumps(receipt, indent=2) + "\n")
|
|
temporary_receipt.replace(receipt_path)
|
|
print(f"Compiled {output}; build receipt {receipt_path}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|