mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-21 09:13:46 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ae100aa1d | |||
| be76a88b80 | |||
| 049d225d5a |
@@ -103,20 +103,25 @@ jobs:
|
|||||||
- run: |
|
- run: |
|
||||||
cd ${{ github.workspace }}/openpilot/openpilot
|
cd ${{ github.workspace }}/openpilot/openpilot
|
||||||
if [ "${{ inputs.target_hardware }}" != "usbgpu" ]; then
|
if [ "${{ inputs.target_hardware }}" != "usbgpu" ]; then
|
||||||
git lfs pull -X "selfdrive/modeld/models/big_*.onnx" -X "selfdrive/modeld/models/dmonitoring_*.onnx"
|
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/dmonitoring_*.onnx"
|
||||||
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx
|
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx
|
||||||
else
|
else
|
||||||
git lfs pull -I "selfdrive/modeld/models/big_*.onnx"
|
git lfs pull -I "**/selfdrive/modeld/models/big_*.onnx" -X ""
|
||||||
find selfdrive/modeld/models -name "*.onnx" ! -name "big_*.onnx" -delete
|
find selfdrive/modeld/models -name "*.onnx" ! -name "big_*.onnx" -delete
|
||||||
fi
|
fi
|
||||||
|
if grep -lIF "version https://git-lfs.github.com/spec/v1" selfdrive/modeld/models/*.onnx; then
|
||||||
|
echo "::error::the ONNX files above are still LFS pointers, not real models"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
- name: 'Upload Artifact'
|
- name: 'Upload Artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
name: models-${{ env.REF }}${{ inputs.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
|
path: ${{ github.workspace }}/openpilot/openpilot/selfdrive/modeld/models/*.onnx
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
build_model:
|
build_model:
|
||||||
runs-on: [self-hosted, tici]
|
runs-on: [self-hosted, usbgpu]
|
||||||
needs: get_model
|
needs: get_model
|
||||||
env:
|
env:
|
||||||
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
||||||
@@ -127,7 +132,6 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- run: git lfs pull
|
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
id: set-env
|
id: set-env
|
||||||
@@ -160,7 +164,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --disable
|
||||||
rm -rf ${{ env.MODELS_DIR }}/*.onnx
|
rm -rf ${{ env.MODELS_DIR }}/*.onnx*
|
||||||
|
|
||||||
- name: Download model artifacts
|
- name: Download model artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -180,6 +184,7 @@ jobs:
|
|||||||
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
MODEL_SIZE=$(python3 -c "from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE as s; print(f'{s[0]}x{s[1]}')")
|
||||||
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||||
|
|
||||||
|
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||||
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||||
echo "USBGPU build"
|
echo "USBGPU build"
|
||||||
export USBGPU=1
|
export USBGPU=1
|
||||||
@@ -187,27 +192,40 @@ jobs:
|
|||||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||||
else
|
else
|
||||||
echo "QCOM build"
|
echo "QCOM build"
|
||||||
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
TG_FLAGS="$TG_FLAGS_QCOM"
|
||||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/driving_tinygrad.pkl"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate metadata for all ONNX files
|
# Generate metadata for all ONNX files
|
||||||
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
find "${{ env.MODELS_DIR }}" -maxdepth 1 -name '*.onnx' | while IFS= read -r onnx_file; do
|
||||||
echo "Generating metadata: $onnx_file"
|
echo "Generating metadata: $onnx_file"
|
||||||
env ${TG_FLAGS} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
env ${TG_FLAGS_QCOM} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Detect model type and build compile args
|
# Detect model type and build compile args
|
||||||
VISION_ONNX="${{ env.MODELS_DIR }}/driving_vision.onnx"
|
VISION_ONNX=""
|
||||||
POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
|
for f in "${{ env.MODELS_DIR }}/driving_vision.onnx" "${{ env.MODELS_DIR }}/big_driving_vision.onnx"; do
|
||||||
OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
|
[ -f "$f" ] && VISION_ONNX="$f" && break
|
||||||
ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
|
done
|
||||||
|
|
||||||
|
POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
OFF_POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_off_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_off_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && OFF_POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
ON_POLICY_ONNX=""
|
||||||
|
for f in "${{ env.MODELS_DIR }}/driving_on_policy.onnx" "${{ env.MODELS_DIR }}/big_driving_on_policy.onnx"; do
|
||||||
|
[ -f "$f" ] && ON_POLICY_ONNX="$f" && break
|
||||||
|
done
|
||||||
|
|
||||||
SUPERCOMBO_ONNX=""
|
SUPERCOMBO_ONNX=""
|
||||||
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx"; do
|
for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx" "${{ env.MODELS_DIR }}/big_supercombo.onnx" "${{ env.MODELS_DIR }}/big_driving_supercombo.onnx"; do
|
||||||
if [ -f "$f" ]; then
|
[ -f "$f" ] && SUPERCOMBO_ONNX="$f" && break
|
||||||
SUPERCOMBO_ONNX="$f"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
|
||||||
|
|||||||
@@ -272,18 +272,17 @@ def _parse_size(size_str: str) -> tuple[int, int]:
|
|||||||
return int(width), int(height)
|
return int(width), int(height)
|
||||||
|
|
||||||
|
|
||||||
def read_file_chunked_to_shm(path):
|
def read_file_chunked_to_disk(path):
|
||||||
if not path:
|
if not path:
|
||||||
return None
|
return None
|
||||||
import atexit
|
import atexit
|
||||||
import shutil
|
import shutil
|
||||||
from openpilot.common.file_chunker import open_file_chunked
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.common.hardware.hw import Paths
|
tmp_path = f'{path}.unchunked'
|
||||||
shm_path = os.path.join(Paths.shm_path(), os.path.basename(path))
|
with open(tmp_path, 'wb') as f, open_file_chunked(path) as src:
|
||||||
atexit.register(lambda: os.path.exists(shm_path) and os.remove(shm_path))
|
shutil.copyfileobj(src, f)
|
||||||
with open(shm_path, 'wb') as dst, open_file_chunked(path) as src:
|
atexit.register(lambda: os.path.exists(tmp_path) and os.remove(tmp_path))
|
||||||
shutil.copyfileobj(src, dst)
|
return tmp_path
|
||||||
return shm_path
|
|
||||||
|
|
||||||
|
|
||||||
def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
|
def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
|
||||||
@@ -327,11 +326,11 @@ if __name__ == "__main__":
|
|||||||
model_w, model_h = args.model_size
|
model_w, model_h = args.model_size
|
||||||
output_data = {}
|
output_data = {}
|
||||||
|
|
||||||
args.vision_onnx = read_file_chunked_to_shm(args.vision_onnx)
|
args.vision_onnx = read_file_chunked_to_disk(args.vision_onnx)
|
||||||
args.policy_onnx = read_file_chunked_to_shm(args.policy_onnx)
|
args.policy_onnx = read_file_chunked_to_disk(args.policy_onnx)
|
||||||
args.off_policy_onnx = read_file_chunked_to_shm(args.off_policy_onnx)
|
args.off_policy_onnx = read_file_chunked_to_disk(args.off_policy_onnx)
|
||||||
args.on_policy_onnx = read_file_chunked_to_shm(args.on_policy_onnx)
|
args.on_policy_onnx = read_file_chunked_to_disk(args.on_policy_onnx)
|
||||||
args.supercombo_onnx = read_file_chunked_to_shm(args.supercombo_onnx)
|
args.supercombo_onnx = read_file_chunked_to_disk(args.supercombo_onnx)
|
||||||
|
|
||||||
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
|
vision_runner = OnnxRunner(args.vision_onnx) if args.vision_onnx else None
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ This file is part of sunnypilot and is licensed under the MIT License.
|
|||||||
See the LICENSE.md file in the root directory for more details.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from openpilot.common.parameterized import parameterized
|
from openpilot.common.parameterized import parameterized
|
||||||
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, _detect_desire_key
|
from openpilot.common.file_chunker import chunk_file, get_chunk_targets
|
||||||
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, _detect_desire_key, read_file_chunked_to_disk
|
||||||
from openpilot.common.test import OpenpilotTestCase
|
from openpilot.common.test import OpenpilotTestCase
|
||||||
|
|
||||||
|
|
||||||
@@ -160,3 +165,33 @@ class TestOutputSlicePreservation(OpenpilotTestCase):
|
|||||||
policy_slices = {'plan': slice(0, 495), 'meta': slice(495, 550)}
|
policy_slices = {'plan': slice(0, 495), 'meta': slice(495, 550)}
|
||||||
assert set(vision_slices.keys()) & set(policy_slices.keys()) == set(), \
|
assert set(vision_slices.keys()) & set(policy_slices.keys()) == set(), \
|
||||||
"vision and policy slices should not overlap in keys"
|
"vision and policy slices should not overlap in keys"
|
||||||
|
|
||||||
|
|
||||||
|
class TestReadFileChunkedToDisk(OpenpilotTestCase):
|
||||||
|
def test_none_passthrough(self):
|
||||||
|
assert read_file_chunked_to_disk(None) is None
|
||||||
|
|
||||||
|
def test_unchunked_source_staged_on_disk(self):
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
src = Path(d) / "driving_supercombo.onnx"
|
||||||
|
payload = os.urandom(1024)
|
||||||
|
src.write_bytes(payload)
|
||||||
|
|
||||||
|
out = Path(read_file_chunked_to_disk(str(src)))
|
||||||
|
|
||||||
|
assert out.parent == Path(d)
|
||||||
|
assert out.name == "driving_supercombo.onnx.unchunked"
|
||||||
|
assert out.read_bytes() == payload
|
||||||
|
|
||||||
|
def test_chunked_source_reassembled_on_disk(self):
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
src = Path(d) / "driving_supercombo.onnx"
|
||||||
|
payload = os.urandom(4096)
|
||||||
|
src.write_bytes(payload)
|
||||||
|
chunk_file(str(src), get_chunk_targets(str(src), len(payload)))
|
||||||
|
assert not src.exists()
|
||||||
|
|
||||||
|
out = Path(read_file_chunked_to_disk(str(src)))
|
||||||
|
|
||||||
|
assert out.parent == Path(d)
|
||||||
|
assert out.read_bytes() == payload
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class ModelCache:
|
|||||||
class ModelFetcher:
|
class ModelFetcher:
|
||||||
"""Handles fetching and caching of model data from remote source"""
|
"""Handles fetching and caching of model data from remote source"""
|
||||||
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v20.json"
|
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v20.json"
|
||||||
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v20.json"
|
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v21.json"
|
||||||
|
|
||||||
def __init__(self, params: Params):
|
def __init__(self, params: Params):
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|||||||
@@ -53,24 +53,28 @@ def create_pkl_name(full_name: str) -> str:
|
|||||||
return pkl
|
return pkl
|
||||||
|
|
||||||
|
|
||||||
def _read_pkl_bytes(pkl_path: Path) -> bytes:
|
def _hash_pkl(pkl_path: Path) -> str:
|
||||||
manifest = Path(f"{pkl_path}.chunkmanifest")
|
manifest = Path(f"{pkl_path}.chunkmanifest")
|
||||||
if manifest.exists():
|
if manifest.exists():
|
||||||
num_chunks = int(manifest.read_text().strip())
|
num_chunks = int(manifest.read_text().strip())
|
||||||
parts = []
|
paths = [Path(f"{pkl_path}.chunk{i + 1:02d}of{num_chunks:02d}") for i in range(num_chunks)]
|
||||||
for i in range(num_chunks):
|
else:
|
||||||
chunk = Path(f"{pkl_path}.chunk{i + 1:02d}of{num_chunks:02d}")
|
paths = [pkl_path]
|
||||||
parts.append(chunk.read_bytes())
|
|
||||||
return b''.join(parts)
|
digest = hashlib.sha256()
|
||||||
return pkl_path.read_bytes()
|
for path in paths:
|
||||||
|
with path.open('rb') as f:
|
||||||
|
while block := f.read(1024 * 1024):
|
||||||
|
digest.update(block)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _find_driving_pkl(output_path: Path) -> Path | None:
|
def _find_driving_pkl(output_path: Path) -> Path | None:
|
||||||
for pattern in ('driving_tinygrad.pkl', 'driving_*_tinygrad.pkl'):
|
for pattern in ('*driving_tinygrad.pkl', '*driving_*_tinygrad.pkl'):
|
||||||
matches = sorted(output_path.glob(pattern))
|
matches = sorted(output_path.glob(pattern))
|
||||||
if matches:
|
if matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
for pattern in ('driving_tinygrad.pkl.chunkmanifest', 'driving_*_tinygrad.pkl.chunkmanifest'):
|
for pattern in ('*driving_tinygrad.pkl.chunkmanifest', '*driving_*_tinygrad.pkl.chunkmanifest'):
|
||||||
matches = sorted(output_path.glob(pattern))
|
matches = sorted(output_path.glob(pattern))
|
||||||
if matches:
|
if matches:
|
||||||
return Path(str(matches[0]).removesuffix('.chunkmanifest'))
|
return Path(str(matches[0]).removesuffix('.chunkmanifest'))
|
||||||
@@ -87,7 +91,7 @@ def _rename_pkl_with_chunks(old_pkl: Path, new_pkl: Path) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def generate_chunked_model(driving_pkl: Path) -> dict:
|
def generate_chunked_model(driving_pkl: Path) -> dict:
|
||||||
tinygrad_hash = hashlib.sha256(_read_pkl_bytes(driving_pkl)).hexdigest()
|
tinygrad_hash = _hash_pkl(driving_pkl)
|
||||||
|
|
||||||
chunks_config = []
|
chunks_config = []
|
||||||
manifest_file = Path(f"{driving_pkl}.chunkmanifest")
|
manifest_file = Path(f"{driving_pkl}.chunkmanifest")
|
||||||
|
|||||||
Reference in New Issue
Block a user