Compare commits

..

5 Commits

Author SHA1 Message Date
nayan bc6e823976 don't like this. but whatever 2026-08-20 15:17:31 -04:00
nayan cee6a6bdac more 2026-08-20 15:09:29 -04:00
nayan 8d9f3971b0 no 2026-08-20 14:59:12 -04:00
Nayan 931ebf1f5a Merge branch 'master' into model-panel-upgrades 2026-08-20 14:47:30 -04:00
nayan bdda9006fd init 2026-08-20 14:29:52 -04:00
23 changed files with 543 additions and 574 deletions
@@ -1,83 +0,0 @@
name: Build default big model
on:
workflow_dispatch:
env:
HF_REPO: sunnypilot/sunnypilot_models_v1
HF_DEFAULTS_PATH: models/defaults/big
jobs:
resolve_name:
runs-on: ubuntu-24.04
outputs:
model_name: ${{ steps.name.outputs.model_name }}
onnx_ref: ${{ steps.name.outputs.onnx_ref }}
steps:
- uses: actions/checkout@v4
- id: name
run: |
NAME=$(PYTHONPATH=${{ github.workspace }} python3 -c "from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL; print(DEFAULT_BIG_MODEL)")
ONNX_REF=$(git log -1 --format='%H' -- openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx)
echo "model_name=${NAME}" >> $GITHUB_OUTPUT
echo "onnx_ref=$ONNX_REF" >> $GITHUB_OUTPUT
build_model:
needs: resolve_name
uses: ./.github/workflows/sunnypilot-build-model.yaml
with:
upstream_branch: ${{ needs.resolve_name.outputs.onnx_ref }}
custom_name: ${{ needs.resolve_name.outputs.model_name }}
target_hardware: usbgpu
secrets: inherit
upload_defaults:
needs: [ resolve_name, build_model ]
runs-on: ubuntu-24.04
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- run: git lfs pull -I "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx"
- name: Install huggingface_hub
run: pip install --upgrade "huggingface_hub>=0.22.0"
- name: Download artifact name
uses: actions/download-artifact@v4
with:
name: artifact-name-${{ needs.resolve_name.outputs.model_name }}
path: artifact_name
- name: Read artifact name
id: artifact
run: |
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
- name: Download model artifact
uses: actions/download-artifact@v4
with:
name: ${{ steps.artifact.outputs.artifact_name }}
path: output
- name: Upload to HF and update default_models.json
env:
HF_OIDC_RESOURCE: datasets/${{ env.HF_REPO }}
ARTIFACT_NAME: ${{ steps.artifact.outputs.artifact_name }}
run: |
rm -f output/artifact_name.txt
export PYTHONPATH=$(pwd)
python3 release/ci/upload_default_model.py \
--hf-repo "${{ env.HF_REPO }}" \
--hf-defaults-path "${{ env.HF_DEFAULTS_PATH }}" \
--artifact-name "$ARTIFACT_NAME" \
--model-dir output \
--onnx-path "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx" \
--onnx-ref "${{ needs.resolve_name.outputs.onnx_ref }}" \
--model-name "${{ needs.resolve_name.outputs.model_name }}" \
--tinygrad-ref "$(python3 openpilot/sunnypilot/models/tinygrad_ref.py)" \
--run-number "${{ github.run_number }}"
+16 -34
View File
@@ -103,25 +103,20 @@ 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,**/selfdrive/modeld/models/dmonitoring_*.onnx" git lfs pull -X "selfdrive/modeld/models/big_*.onnx" -X "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" -X "" git lfs pull -I "selfdrive/modeld/models/big_*.onnx"
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, usbgpu] runs-on: [self-hosted, tici]
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 }})
@@ -132,6 +127,7 @@ 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
@@ -164,7 +160,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
@@ -184,7 +180,6 @@ 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
@@ -192,40 +187,27 @@ 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="$TG_FLAGS_QCOM" TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
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_QCOM} python3 "${{ env.MODELS_DIR }}/../get_model_metadata.py" "$onnx_file" || true env ${TG_FLAGS} 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="" VISION_ONNX="${{ env.MODELS_DIR }}/driving_vision.onnx"
for f in "${{ env.MODELS_DIR }}/driving_vision.onnx" "${{ env.MODELS_DIR }}/big_driving_vision.onnx"; do POLICY_ONNX="${{ env.MODELS_DIR }}/driving_policy.onnx"
[ -f "$f" ] && VISION_ONNX="$f" && break OFF_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_off_policy.onnx"
done ON_POLICY_ONNX="${{ env.MODELS_DIR }}/driving_on_policy.onnx"
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" "${{ env.MODELS_DIR }}/big_supercombo.onnx" "${{ env.MODELS_DIR }}/big_driving_supercombo.onnx"; do for f in "${{ env.MODELS_DIR }}/supercombo.onnx" "${{ env.MODELS_DIR }}/driving_supercombo.onnx"; do
[ -f "$f" ] && SUPERCOMBO_ONNX="$f" && break if [ -f "$f" ]; then
SUPERCOMBO_ONNX="$f"
break
fi
done done
MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME="" MODEL_TYPE="" ONNX_ARGS="" OUTPUT_NAME=""
@@ -36,7 +36,6 @@ jobs:
publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }} publish_concurrency_group: ${{ steps.strategy.outputs.publish_concurrency_group }}
is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }} is_stable_branch: ${{ steps.strategy.outputs.is_stable_branch }}
build: ${{ steps.strategy.outputs.build }} build: ${{ steps.strategy.outputs.build }}
include_big_model: ${{ steps.strategy.outputs.include_big_model }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Extract deploy strategy - name: Extract deploy strategy
@@ -79,9 +78,6 @@ jobs:
stable_version=$(cat openpilot/sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g'); stable_version=$(cat openpilot/sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
include_big_model="$(echo "$CONFIG" | jq -r '.include_big_model // false')";
echo "include_big_model=$include_big_model" >> $GITHUB_OUTPUT
fi fi
echo "build=$BUILD" >> $GITHUB_OUTPUT echo "build=$BUILD" >> $GITHUB_OUTPUT
cat $GITHUB_OUTPUT cat $GITHUB_OUTPUT
@@ -207,101 +203,6 @@ jobs:
source ${UV_PROJECT_ENVIRONMENT}/bin/activate source ${UV_PROJECT_ENVIRONMENT}/bin/activate
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
prepare_chestnut:
needs: [ prepare_strategy ]
runs-on: ubuntu-24.04
if: ${{ needs.prepare_strategy.outputs.include_big_model == 'true' }}
env:
HF_REPO: sunnypilot/sunnypilot_models_v1
HF_DEFAULTS_PATH: models/defaults/big
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref || github.ref_name }}
- run: git lfs pull -I "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx"
- name: Check HF defaults and build if needed
id: resolve
run: |
ACTUAL_ONNX_HASH=$(sha256sum "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx" | cut -d' ' -f1)
echo "Repo ONNX hash: $ACTUAL_ONNX_HASH"
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
check_hash() {
DEFAULTS=$(curl -fsSL "$JSON_URL" 2>/dev/null) || return 1
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ACTUAL_ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)' 2>/dev/null)
[ -n "$BUNDLE" ] && [ "$BUNDLE" != "null" ]
}
if check_hash; then
echo "HF defaults match repo ONNX"
else
echo "No matching model on HF — triggering build"
gh workflow run build-default-big-model.yaml --ref "${{ github.head_ref || github.ref_name }}"
echo "Waiting for build to start..."
sleep 120
RUN_ID=$(gh run list --workflow=build-default-big-model.yaml --branch="${{ github.head_ref || github.ref_name }}" --limit=1 --json databaseId --jq '.[0].databaseId')
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "::error::Failed to find build-default-big-model run"
exit 1
fi
echo "Waiting for run $RUN_ID..."
gh run watch "$RUN_ID"
CONCLUSION=$(gh run view "$RUN_ID" --json conclusion --jq '.conclusion')
if [ "$CONCLUSION" != "success" ]; then
echo "::error::build-default-big-model failed: $CONCLUSION"
exit 1
fi
if ! check_hash; then
echo "::error::HF defaults still don't match after build"
exit 1
fi
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download big model chunks
run: |
ACTUAL_ONNX_HASH=$(sha256sum "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx" | cut -d' ' -f1)
JSON_URL="https://huggingface.co/datasets/${HF_REPO}/resolve/main/${HF_DEFAULTS_PATH}/default_models.json"
DEFAULTS=$(curl -fsSL "$JSON_URL")
BUNDLE=$(echo "$DEFAULTS" | jq --arg hash "$ACTUAL_ONNX_HASH" '.bundles[] | select(.onnx_sha256 == $hash)')
mkdir -p big_model_chunks
ARTIFACT=$(echo "$BUNDLE" | jq -r '.models[0].artifact')
BASE_URL=$(echo "$ARTIFACT" | jq -r '.download_uri.url' | sed 's|/[^/]*$||')
NUM_CHUNKS=$(echo "$ARTIFACT" | jq -r '.chunks | length')
CANONICAL="big_driving_tinygrad.pkl"
echo "$ARTIFACT" | jq -r '.chunks[].file_name' | while read CHUNK_NAME; do
CHUNK_IDX=$(echo "$CHUNK_NAME" | grep -oP 'chunk\K[0-9]+of[0-9]+')
CANONICAL_CHUNK="${CANONICAL}.chunk${CHUNK_IDX}"
ENCODED_URL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BASE_URL}/${CHUNK_NAME}', safe=':/'))")
echo "Downloading $CHUNK_NAME -> $CANONICAL_CHUNK"
curl -fsSL -o "big_model_chunks/${CANONICAL_CHUNK}" "$ENCODED_URL"
done
echo "$NUM_CHUNKS" > "big_model_chunks/${CANONICAL}.chunkmanifest"
- name: Upload big model chunks
uses: actions/upload-artifact@v4
with:
name: big-model-chunks
path: big_model_chunks/
compression-level: 0
- name: Cancel run on failure
if: failure()
run: gh run cancel ${{ github.run_id }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish: publish:
concurrency: concurrency:
@@ -310,20 +211,14 @@ jobs:
# Otherwise, if a job is waiting to be published due to environment wait time, it would be canceled by a new commit and restart the wait time. # Otherwise, if a job is waiting to be published due to environment wait time, it would be canceled by a new commit and restart the wait time.
group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }} group: ${{ needs.prepare_strategy.outputs.publish_concurrency_group }}
cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }} cancel-in-progress: ${{ needs.prepare_strategy.outputs.cancel_publish_in_progress == 'true' }}
if: ${{ if: ${{ (always() && !cancelled() && !failure()) && needs.build.result == 'success' && needs.prepare_strategy.result == 'success' && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
always() && !cancelled() && needs: [ build, prepare_strategy ]
needs.build.result == 'success' &&
needs.prepare_strategy.result == 'success' &&
(!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) &&
(needs.prepare_strategy.outputs.include_big_model != 'true' || needs.prepare_chestnut.result == 'success')
}}
needs: [ build, prepare_strategy, prepare_chestnut ]
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
environment: ${{ needs.prepare_strategy.outputs.environment }} environment: ${{ needs.prepare_strategy.outputs.environment }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Download prebuilt artifact - name: Download build artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: prebuilt name: prebuilt
@@ -333,24 +228,6 @@ jobs:
mkdir -p ${{ env.OUTPUT_DIR }} mkdir -p ${{ env.OUTPUT_DIR }}
tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }} tar xzf prebuilt.tar.gz -C ${{ env.OUTPUT_DIR }}
- name: Prepare chestnut output
if: ${{ needs.prepare_chestnut.result == 'success' }}
run: |
mkdir -p "${{ github.workspace }}/chestnut_output"
tar xzf prebuilt.tar.gz -C "${{ github.workspace }}/chestnut_output"
- name: Download big model chunks
if: ${{ needs.prepare_chestnut.result == 'success' }}
uses: actions/download-artifact@v4
with:
name: big-model-chunks
path: big_model_chunks
- name: Inject big model into chestnut
if: ${{ needs.prepare_chestnut.result == 'success' }}
run: |
cp big_model_chunks/* "${{ github.workspace }}/chestnut_output/openpilot/selfdrive/modeld/models/"
- name: Configure Git - name: Configure Git
run: | run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.email "github-actions[bot]@users.noreply.github.com"
@@ -371,22 +248,6 @@ jobs:
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \ "https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}" "${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
- name: Publish chestnut branch
if: ${{ needs.prepare_chestnut.result == 'success' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHESTNUT_BRANCH="${{ needs.prepare_strategy.outputs.new_branch }}-chestnut"
CHESTNUT_DIR="${{ github.workspace }}/chestnut_output"
${{ env.CI_DIR }}/publish.sh \
"${{ github.workspace }}" \
"$CHESTNUT_DIR" \
"$CHESTNUT_BRANCH" \
"${{ needs.prepare_strategy.outputs.version }}" \
"https://x-access-token:${{github.token}}@github.com/sunnypilot/sunnypilot.git" \
"${{ needs.prepare_strategy.outputs.extra_version_identifier }}"
- name: Tag ${{ needs.prepare_strategy.outputs.environment }} - name: Tag ${{ needs.prepare_strategy.outputs.environment }}
if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }} if: ${{ needs.prepare_strategy.outputs.is_stable_branch == 'true' && (github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/')) }}
run: | run: |
@@ -399,7 +260,6 @@ jobs:
- prepare_strategy - prepare_strategy
- build - build
- publish - publish
- prepare_chestnut
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
if: ${{ (always() && !cancelled() && !failure()) if: ${{ (always() && !cancelled() && !failure())
&& needs.publish.result == 'success' && needs.publish.result == 'success'
@@ -419,7 +279,6 @@ jobs:
export commit_short_sha="${commit_short_sha:0:7}" export commit_short_sha="${commit_short_sha:0:7}"
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}" export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}" export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
export chestnut_branch="${{ needs.prepare_chestnut.result == 'success' && format('{0}-chestnut', needs.prepare_strategy.outputs.new_branch) || '' }}"
MESSAGE=$(cat << 'EOF' | envsubst MESSAGE=$(cat << 'EOF' | envsubst
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }} ${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
-9
View File
@@ -16,15 +16,6 @@ MASTER_SP_BRANCHES = ['master']
RELEASE_BRANCHES = ['release-tizi-staging', 'release-mici-staging', 'release-tizi', 'release-mici', 'nightly'] RELEASE_BRANCHES = ['release-tizi-staging', 'release-mici-staging', 'release-tizi', 'release-mici', 'nightly']
TESTED_BRANCHES = RELEASE_BRANCHES + ['devel-staging', 'nightly-dev'] + RELEASE_SP_BRANCHES + TESTED_SP_BRANCHES TESTED_BRANCHES = RELEASE_BRANCHES + ['devel-staging', 'nightly-dev'] + RELEASE_SP_BRANCHES + TESTED_SP_BRANCHES
CHESTNUT_BRANCHES = {
"staging": "staging-chestnut",
"dev": "dev-chestnut",
"release-mici": "release-chestnut",
"release-tizi": "release-chestnut",
"release-mici-staging": "release-chestnut-staging",
"release-tizi-staging": "release-chestnut-staging",
}
SP_BRANCH_MIGRATIONS = { SP_BRANCH_MIGRATIONS = {
("tici", "staging-c3-new"): "staging-tici", ("tici", "staging-c3-new"): "staging-tici",
("tici", "dev-c3-new"): "staging-tici", ("tici", "dev-c3-new"): "staging-tici",
@@ -18,7 +18,7 @@
"_comment": "Set extra field to the failed reason." "_comment": "Set extra field to the failed reason."
}, },
"Offroad_ChestnutBranch": { "Offroad_ChestnutBranch": {
"text": "Chestnut detected! Switch to the %1 branch to use chestnut-class models.", "text": "Chestnut detected! Switch to the release-chestnut branch to use chestnut-class models.",
"severity": 0 "severity": 0
}, },
"Offroad_UnregisteredHardware": { "Offroad_UnregisteredHardware": {
@@ -192,7 +192,7 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP):
max_idx = self._get_path_length_idx(path_x_array, max_distance) max_idx = self._get_path_length_idx(path_x_array, max_distance)
self._path.projected_points = self._map_line_to_polygon( self._path.projected_points = self._map_line_to_polygon(
self._path.raw_points, self._get_path_half_width(), self._path_offset_z, max_idx, max_distance, allow_invert=False self._path.raw_points, 0.9, self._path_offset_z, max_idx, max_distance, allow_invert=False
) )
self._update_experimental_gradient() self._update_experimental_gradient()
@@ -292,7 +292,7 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP):
allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control
self._blend_filter.update(int(allow_throttle)) self._blend_filter.update(int(allow_throttle))
if ui_state.rainbow_path and self._lateral_active: if ui_state.rainbow_path:
self.rainbow_path.draw_rainbow_path(self._rect, self._path) self.rainbow_path.draw_rainbow_path(self._rect, self._path)
return return
@@ -10,7 +10,7 @@ import time
import pyray as rl import pyray as rl
from openpilot.cereal import custom from openpilot.cereal import custom
from openpilot.sunnypilot.models.default_model import get_default_model from openpilot.sunnypilot.models.default_model import DEFAULT_MODEL
from openpilot.common.constants import CV from openpilot.common.constants import CV
from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.multilang import tr
@@ -211,8 +211,7 @@ class ModelsLayout(Widget):
for bundle in bundles: for bundle in bundles:
folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle) folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle)
folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': f"{get_default_model()} (Default)", folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': f"{DEFAULT_MODEL} (Default)", 'short_name': "Default"})])]
'short_name': "Default"})])]
for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True): for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True):
folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True) folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True)
name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "") name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "")
@@ -250,8 +249,7 @@ class ModelsLayout(Widget):
self._update_lagd_description(live_delay) self._update_lagd_description(live_delay)
self.model_manager = ui_state.sm["modelManagerSP"] self.model_manager = ui_state.sm["modelManagerSP"]
self._handle_bundle_download_progress() self._handle_bundle_download_progress()
default_label = f"{get_default_model()} (Default)" active_name = self.model_manager.activeBundle.displayName if self.model_manager and self.model_manager.activeBundle.ref else f"{DEFAULT_MODEL} (Default)"
active_name = self.model_manager.activeBundle.displayName if self.model_manager and self.model_manager.activeBundle.ref else default_label
self.current_model_item.action_item.set_value(active_name) self.current_model_item.action_item.set_value(active_name)
if not ui_state.is_offroad(): if not ui_state.is_offroad():
@@ -4,11 +4,13 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License. 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.
""" """
from collections.abc import Callable
import pyray as rl import pyray as rl
from openpilot.cereal import custom from openpilot.cereal import custom
from openpilot.sunnypilot.models.default_model import get_default_model from openpilot.sunnypilot.models.default_model import DEFAULT_MODEL
from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout
from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.ui_state import ui_state, device
from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.application import FontWeight, gui_app
@@ -17,6 +19,24 @@ from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.system.ui.widgets.scroller import NavScroller
def _build_folders() -> dict[str, list]:
manager = ui_state.sm["modelManagerSP"]
bundles = manager.availableBundles
folders = {}
for bundle in bundles:
folder = next((override.value for override in bundle.overrides if override.key == "folder"), "")
folders.setdefault(folder, []).append(bundle)
favs = ui_state.params.get("ModelManager_Favs")
favorites = set(favs.split(';')) if favs else set()
if favorites:
for fav_bundle in [bundle for bundle in bundles if bundle.ref in favorites]:
folders.setdefault("favorites", []).append(fav_bundle)
return folders
class CurrentModelInfo(Widget): class CurrentModelInfo(Widget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -27,7 +47,7 @@ class CurrentModelInfo(Widget):
subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)) subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))
max_width = int(self._rect.width - 20) max_width = int(self._rect.width - 20)
self.current_model_header = UnifiedLabel(tr("active model"), 48, max_width=max_width, text_color=header_color, font_weight=FontWeight.DISPLAY) self.current_model_header = UnifiedLabel(tr("active model"), 48, max_width=max_width, text_color=header_color, font_weight=FontWeight.DISPLAY)
default_text = f"{get_default_model()} (Default)".lower() default_text = f"{DEFAULT_MODEL} (Default)".lower()
self.current_model_text = UnifiedLabel(default_text, 32, max_width=max_width, text_color=subheader_color, font_weight=FontWeight.ROMAN, scroll=True) self.current_model_text = UnifiedLabel(default_text, 32, max_width=max_width, text_color=subheader_color, font_weight=FontWeight.ROMAN, scroll=True)
self.info_header = UnifiedLabel("cache size", 48, max_width=max_width, text_color=header_color, font_weight=FontWeight.DISPLAY) self.info_header = UnifiedLabel("cache size", 48, max_width=max_width, text_color=header_color, font_weight=FontWeight.DISPLAY)
@@ -46,6 +66,41 @@ class CurrentModelInfo(Widget):
self.info_text.set_position(self._rect.x + 20, self._rect.y + 161 - 25) self.info_text.set_position(self._rect.x + 20, self._rect.y + 161 - 25)
self.info_text.render() self.info_text.render()
class FolderSelectionMici(NavScroller):
def __init__(self, folder_name: str | None = None,
select_default_callback: Callable | None = None,
select_folder_callback: Callable | None = None,
select_model_callback: Callable | None = None):
super().__init__()
folders = _build_folders()
btns = []
if folder_name is None:
assert select_default_callback is not None and select_folder_callback is not None
default_btn = BigButton(f"{DEFAULT_MODEL} (Default)".lower())
default_btn.set_click_callback(select_default_callback)
btns.append(default_btn)
for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True):
btn = BigButton(folder.lower())
btn.set_click_callback(lambda f=folder: select_folder_callback(f))
if folder.lower() == "favorites":
btns.insert(0, btn)
else:
btns.append(btn)
else:
assert select_model_callback is not None
for bundle in sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True):
btn = BigButton(bundle.displayName.lower())
btn.set_click_callback(lambda b=bundle: select_model_callback(b))
btns.append(btn)
self._scroller.add_widgets(btns)
class ModelsLayoutMici(NavScroller): class ModelsLayoutMici(NavScroller):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -59,81 +114,47 @@ class ModelsLayoutMici(NavScroller):
self.select_model_btn = BigButton(tr("select model")) self.select_model_btn = BigButton(tr("select model"))
self.select_model_btn.set_click_callback(self._show_folders) self.select_model_btn.set_click_callback(self._show_folders)
self.clear_cache_btn = BigButton(tr("clear cache"), "")
self.clear_cache_btn.set_click_callback(self._clear_cache)
self.cancel_download_btn = BigButton(tr("cancel download")) self.cancel_download_btn = BigButton(tr("cancel download"))
self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex")) self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex"))
self.main_items = [self.current_model_info, self.select_model_btn, self.cancel_download_btn] self.main_items = [self.current_model_info, self.select_model_btn, self.clear_cache_btn, self.cancel_download_btn]
self._scroller.add_widgets(self.main_items) self._scroller.add_widgets(self.main_items)
@property @property
def model_manager(self): def model_manager(self):
return ui_state.sm["modelManagerSP"] return ui_state.sm["modelManagerSP"]
def _get_grouped_bundles(self, favorites = None):
bundles = self.model_manager.availableBundles
folders = {}
for bundle in bundles:
folder = next((override.value for override in bundle.overrides if override.key == "folder"), "")
folders.setdefault(folder, []).append(bundle)
if favorites:
for fav_bundle in [bundle for bundle in bundles if bundle.ref in favorites]:
folders.setdefault("favorites", []).append(fav_bundle)
return folders
def _push_selection_view(self, items):
scroller = NavScroller()
scroller._scroller.add_widgets(items)
gui_app.push_widget(scroller)
def _show_folders(self): def _show_folders(self):
self.focused_widget = self.select_model_btn self.focused_widget = self.select_model_btn
favs = ui_state.params.get("ModelManager_Favs") def select_default():
favorites = set(favs.split(';')) if favs else set() ui_state.params.remove("ModelManager_ActiveBundle")
gui_app.pop_widgets_to(self, instant=True)
self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(0)
folders = self._get_grouped_bundles(favorites) def select_model(bundle):
folder_buttons = [] ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
default_btn = BigButton(f"{get_default_model()} (Default)".lower()) gui_app.pop_widgets_to(self, instant=True)
default_btn.set_click_callback(self._select_default) self._scroller.scroll_panel.set_offset(0)
folder_buttons.append(default_btn) self._scroller.scroll_to(0)
for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True): def select_folder(folder_name):
if folder.lower() in ["release models", "master models", "favorites"]: gui_app.push_widget(FolderSelectionMici(folder_name, select_model_callback=select_model))
btn = BigButton(folder.lower())
btn.set_click_callback(lambda f=folder: self._select_folder(f))
if folder.lower() == "favorites":
folder_buttons.insert(0, btn)
else:
folder_buttons.append(btn)
self._push_selection_view(folder_buttons)
def _pop_to_main(self): gui_app.push_widget(FolderSelectionMici(select_default_callback=select_default, select_folder_callback=select_folder))
gui_app.pop_widgets_to(self)
def _select_model(self, bundle): def _clear_cache(self):
ui_state.params.put("ModelManager_DownloadIndex", bundle.index) def confirm_callback():
self._pop_to_main() ui_state.params.put_bool("ModelManager_ClearCache", True)
def _select_default(self): lbl = tr("slide to clear cache")
ui_state.params.remove("ModelManager_ActiveBundle") icon = gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64)
self._pop_to_main() dlg = BigConfirmationDialog(lbl, icon, confirm_callback=confirm_callback, red=True)
gui_app.push_widget(dlg)
def _select_folder(self, folder_name):
favs = ui_state.params.get("ModelManager_Favs")
favorites = set(favs.split(';')) if favs else set()
folders = self._get_grouped_bundles(favorites)
bundles = sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True)
btns = []
for bundle in bundles:
txt = bundle.displayName.lower()
btn = BigButton(txt)
btn.set_click_callback(lambda b=bundle: self._select_model(b))
btns.append(btn)
self._push_selection_view(btns)
def hide_event(self): def hide_event(self):
super().hide_event() super().hide_event()
@@ -145,6 +166,7 @@ class ModelsLayoutMici(NavScroller):
super()._update_state() super()._update_state()
self.select_model_btn.set_enabled(ui_state.is_offroad()) self.select_model_btn.set_enabled(ui_state.is_offroad())
self.clear_cache_btn.set_enabled(ui_state.is_offroad())
self.cancel_download_btn.set_visible(False) self.cancel_download_btn.set_visible(False)
self.current_model_info.current_model_header._shimmer = False self.current_model_info.current_model_header._shimmer = False
self.current_model_info.info_header._shimmer = False self.current_model_info.info_header._shimmer = False
@@ -162,8 +184,7 @@ class ModelsLayoutMici(NavScroller):
self._was_downloading = is_downloading self._was_downloading = is_downloading
self.current_model_info.current_model_header.set_text(tr("active model")) self.current_model_info.current_model_header.set_text(tr("active model"))
default_model_text = f"{get_default_model()} (Default)".lower() model_text = manager.activeBundle.displayName.lower() if manager.activeBundle.ref else f"{DEFAULT_MODEL} (Default)".lower()
model_text = manager.activeBundle.displayName.lower() if manager.activeBundle.ref else default_model_text
self.current_model_info.current_model_text.set_text(model_text) self.current_model_info.current_model_text.set_text(model_text)
self.current_model_info.info_header.set_text(tr("cache size")) self.current_model_info.info_header.set_text(tr("cache size"))
self.current_model_info.info_text.set_text(f"{ModelsLayout.calculate_cache_size():.2f} MB") self.current_model_info.info_text.set_text(f"{ModelsLayout.calculate_cache_size():.2f} MB")
@@ -4,23 +4,11 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License. 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.
""" """
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.selfdrive.ui.sunnypilot.onroad.chevron_metrics import ChevronMetrics from openpilot.selfdrive.ui.sunnypilot.onroad.chevron_metrics import ChevronMetrics
from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath
from openpilot.system.ui.lib.application import gui_app
class ModelRendererSP: class ModelRendererSP:
def __init__(self): def __init__(self):
self.rainbow_path = RainbowPath() self.rainbow_path = RainbowPath()
self.chevron_metrics = ChevronMetrics() self.chevron_metrics = ChevronMetrics()
self._width_filter = FirstOrderFilter(0.9, 0.1, 1 / gui_app.target_fps)
@property
def _lateral_active(self) -> bool:
return ui_state.status in (UIStatus.ENGAGED, UIStatus.LAT_ONLY)
def _get_path_half_width(self) -> float:
target = 0.9 if self._lateral_active else 0.40
return self._width_filter.update(target)
@@ -272,17 +272,18 @@ def _parse_size(size_str: str) -> tuple[int, int]:
return int(width), int(height) return int(width), int(height)
def read_file_chunked_to_disk(path): def read_file_chunked_to_shm(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
tmp_path = f'{path}.unchunked' from openpilot.common.hardware.hw import Paths
with open(tmp_path, 'wb') as f, open_file_chunked(path) as src: shm_path = os.path.join(Paths.shm_path(), os.path.basename(path))
shutil.copyfileobj(src, f) atexit.register(lambda: os.path.exists(shm_path) and os.remove(shm_path))
atexit.register(lambda: os.path.exists(tmp_path) and os.remove(tmp_path)) with open(shm_path, 'wb') as dst, open_file_chunked(path) as src:
return tmp_path shutil.copyfileobj(src, dst)
return shm_path
def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]: def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
@@ -326,11 +327,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_disk(args.vision_onnx) args.vision_onnx = read_file_chunked_to_shm(args.vision_onnx)
args.policy_onnx = read_file_chunked_to_disk(args.policy_onnx) args.policy_onnx = read_file_chunked_to_shm(args.policy_onnx)
args.off_policy_onnx = read_file_chunked_to_disk(args.off_policy_onnx) args.off_policy_onnx = read_file_chunked_to_shm(args.off_policy_onnx)
args.on_policy_onnx = read_file_chunked_to_disk(args.on_policy_onnx) args.on_policy_onnx = read_file_chunked_to_shm(args.on_policy_onnx)
args.supercombo_onnx = read_file_chunked_to_disk(args.supercombo_onnx) args.supercombo_onnx = read_file_chunked_to_shm(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,15 +5,10 @@ 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.common.file_chunker import chunk_file, get_chunk_targets from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, _detect_desire_key
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
@@ -165,33 +160,3 @@ 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
+30 -26
View File
@@ -3,17 +3,8 @@ import os
import hashlib import hashlib
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.sunnypilot import get_file_hash from openpilot.sunnypilot import get_file_hash
from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL, DEFAULT_BIG_MODEL from openpilot.sunnypilot.models.model_name import DEFAULT_MODEL
def get_default_model() -> str:
show_big_model = (ui_state.usbgpu and ui_state.usbgpu_compiled
and (ui_state.usbgpu_active or ui_state.usbgpu_loading or ui_state.is_offroad()))
return DEFAULT_BIG_MODEL if show_big_model else DEFAULT_MODEL
DEFAULT_MODEL_NAME_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "model_name.py") DEFAULT_MODEL_NAME_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "model_name.py")
MODEL_HASH_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "tests", "model_hash") MODEL_HASH_PATH = os.path.join(BASEDIR, "openpilot", "sunnypilot", "models", "tests", "model_hash")
@@ -22,6 +13,7 @@ SUPERCOMBO_ONNX_PATH = os.path.join(BASEDIR, "openpilot", "selfdrive", "modeld",
def update_model_hash(): def update_model_hash():
supercombo_hash = get_file_hash(SUPERCOMBO_ONNX_PATH) supercombo_hash = get_file_hash(SUPERCOMBO_ONNX_PATH)
combined_hash = hashlib.sha256(supercombo_hash.encode()).hexdigest() combined_hash = hashlib.sha256(supercombo_hash.encode()).hexdigest()
with open(MODEL_HASH_PATH, "w") as f: with open(MODEL_HASH_PATH, "w") as f:
@@ -30,28 +22,40 @@ def update_model_hash():
print(f"Generated and updated new combined model hash to {MODEL_HASH_PATH}") print(f"Generated and updated new combined model hash to {MODEL_HASH_PATH}")
def update_default_model_names(default_model_name: str, default_big_model_name: str): def get_current_default_model_name():
print("[CHANGE DEFAULT MODEL NAMES]") print("[GET DEFAULT MODEL NAME]")
with open(DEFAULT_MODEL_NAME_PATH, "w") as f: name = DEFAULT_MODEL
f.write(f'DEFAULT_MODEL = "{default_model_name}"\n') print(f'Current default model name: "{name}"')
f.write(f'DEFAULT_BIG_MODEL = "{default_big_model_name}"\n')
print(f'New default small model name: "{default_model_name}"') return name
print(f'New default big model name: "{default_big_model_name}"')
def update_default_model_name(name: str):
print("[CHANGE DEFAULT MODEL NAME]")
with open(DEFAULT_MODEL_NAME_PATH, "w") as f:
f.write(f'DEFAULT_MODEL = "{name}"\n')
print(f'New default model name: "{name}"')
print("[DONE]") print("[DONE]")
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Update default model names and hash") parser = argparse.ArgumentParser(description="Update default model name and hash")
parser.add_argument("--new_small_model_name", type=str, help="New default small model name") parser.add_argument("--new_name", type=str, help="New default model name")
parser.add_argument("--new_big_model_name", type=str, help="New default big model name")
args = parser.parse_args() args = parser.parse_args()
if args.new_small_model_name is None and args.new_big_model_name is None: if not args.new_name:
new_name = input(f'Enter new default small model name (current: "{DEFAULT_MODEL}", leave empty to keep): ').strip() print("Warning: No new default model name provided. Use --new_name to specify")
new_big_model_name = input(f'Enter new default big model name (current: "{DEFAULT_BIG_MODEL}", leave empty to keep): ').strip() print("Default model name and hash will not be updated! (aborted)")
else: exit(0)
new_name, new_big_model_name = args.new_small_model_name, args.new_big_model_name
update_default_model_names(new_name or DEFAULT_MODEL, new_big_model_name or DEFAULT_BIG_MODEL) current_name = get_current_default_model_name()
new_name = args.new_name
if current_name == new_name:
print(f'Proposed default model name: "{new_name}"')
confirm = input("Proposed default model name is the same as the current default model name. Confirm? (y/n): ").upper().strip()
if confirm != "Y":
print("Default model name and hash will not be updated! (aborted)")
exit(0)
update_default_model_name(new_name)
update_model_hash() update_model_hash()
+1 -1
View File
@@ -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_v21.json" MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v20.json"
def __init__(self, params: Params): def __init__(self, params: Params):
self.params = params self.params = params
@@ -1,2 +1 @@
DEFAULT_MODEL = "CD210" DEFAULT_MODEL = "CD210"
DEFAULT_BIG_MODEL = "Lebowski"
@@ -20,4 +20,4 @@ class TestDefaultModel(OpenpilotTestCase):
with open(MODEL_HASH_PATH) as f: with open(MODEL_HASH_PATH) as f:
current_hash = f.read().strip() current_hash = f.read().strip()
assert combined_hash == current_hash, "Run openpilot/sunnypilot/models/default_model.py to update the default model name and hash" assert combined_hash == current_hash, "Run sunnypilot/models/default_model.py to update the default model name and hash"
@@ -28,7 +28,7 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce
create_connection, WebSocketConnectionClosedException) create_connection, WebSocketConnectionClosedException)
import openpilot.cereal.messaging as messaging import openpilot.cereal.messaging as messaging
from openpilot.sunnypilot.models.default_model import get_default_model from openpilot.sunnypilot.models.default_model import DEFAULT_MODEL
from openpilot.sunnypilot.selfdrive.car.sync_sunnylink_params import update_car_list_param from openpilot.sunnypilot.selfdrive.car.sync_sunnylink_params import update_car_list_param
from openpilot.sunnypilot.sunnylink.api import SunnylinkApi from openpilot.sunnypilot.sunnylink.api import SunnylinkApi
from openpilot.sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, get_param_as_byte, save_param_from_base64_encoded_string from openpilot.sunnypilot.sunnylink.utils import sunnylink_need_register, sunnylink_ready, get_param_as_byte, save_param_from_base64_encoded_string
@@ -181,7 +181,7 @@ def getParamsMetadata() -> str:
schema = generate_schema() schema = generate_schema()
schema["capabilities"] = generate_capabilities() schema["capabilities"] = generate_capabilities()
schema["capability_labels"] = CAPABILITY_LABELS schema["capability_labels"] = CAPABILITY_LABELS
schema["default_model"] = get_default_model() schema["default_model"] = DEFAULT_MODEL
raw = json.dumps(schema, separators=(",", ":")).encode("utf-8") raw = json.dumps(schema, separators=(",", ":")).encode("utf-8")
return base64.b64encode(gzip.compress(raw)).decode("utf-8") return base64.b64encode(gzip.compress(raw)).decode("utf-8")
except Exception: except Exception:
+2 -6
View File
@@ -27,7 +27,7 @@ from openpilot.common.swaglog import cloudlog
from openpilot.sunnypilot.system.statsd import statlog from openpilot.sunnypilot.system.statsd import statlog
from openpilot.system.hardware.power_monitoring import PowerMonitoring from openpilot.system.hardware.power_monitoring import PowerMonitoring
from openpilot.system.hardware.fan_controller import FanController from openpilot.system.hardware.fan_controller import FanController
from openpilot.common.version import terms_version, training_version, get_build_metadata, terms_version_sp, CHESTNUT_BRANCHES from openpilot.common.version import terms_version, training_version, get_build_metadata, terms_version_sp
ThermalStatus = log.DeviceState.ThermalStatus ThermalStatus = log.DeviceState.ThermalStatus
@@ -301,11 +301,7 @@ def hardware_thread(end_event, hw_queue) -> None:
set_usb_state(msg.deviceState, last_hw_state.usb_state) set_usb_state(msg.deviceState, last_hw_state.usb_state)
chestnut.update(started_ts is None, last_hw_state.usb_state) chestnut.update(started_ts is None, last_hw_state.usb_state)
current_channel = get_build_metadata().channel set_offroad_alert_if_changed("Offroad_ChestnutBranch", msg.deviceState.chestnutPresent and not big_model_available)
chestnut_target = CHESTNUT_BRANCHES.get(current_channel)
chestnut_needs_switch = msg.deviceState.chestnutPresent and not big_model_available and chestnut_target is not None
set_offroad_alert_if_changed("Offroad_ChestnutBranch", chestnut_needs_switch,
extra_text=chestnut_target if chestnut_needs_switch else None)
# this subset is only used for offroad # this subset is only used for offroad
temp_sources = [ temp_sources = [
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Define the service name
SERVICE_NAME="actions.runner.sunnypilot.$(uname -n)"
# Function to control the service
control_service() {
local action=$1 # Store the function argument in a local variable
sudo systemctl $action ${SERVICE_NAME}
}
service_exists_and_is_loaded() {
sudo systemctl status ${SERVICE_NAME} &>/dev/null
if [[ $? -ne 4 ]]; then
return 0 # Service is known to systemd (i.e., loaded)
else
return 1 # Service is unknown to systemd (i.e., not loaded)
fi
}
# Check for required argument
if [[ -z $1 ]] || { [[ $1 != "start" ]] && [[ $1 != "stop" ]]; }; then
echo "Usage: $0 {start|stop}"
exit 1
fi
# Store the script argument in a descriptive variable
ACTION=$1
# Trap EXIT signal (Ctrl+C) and stop the service
trap 'control_service stop ; exit' SIGINT SIGKILL EXIT
# Enter the main loop
while true; do
# Check if the service is actually present on the system
if service_exists_and_is_loaded; then
control_service $ACTION # Call the function with the specified action
fi
sleep 1 # Pause before the next iteration
done
@@ -68,6 +68,10 @@ def only_offroad(started: bool, params: Params, CP: car.CarParams) -> bool:
def livestream(started: bool, params: Params, CP: car.CarParams) -> bool: def livestream(started: bool, params: Params, CP: car.CarParams) -> bool:
return params.get_bool("IsLiveStreaming") return params.get_bool("IsLiveStreaming")
def use_github_runner(started, params, CP: car.CarParams) -> bool:
return not PC and params.get_bool("EnableGithubRunner") and (
not params.get_bool("NetworkMetered") and not params.get_bool("GithubRunnerSufficientVoltage"))
def use_copyparty(started, params, CP: car.CarParams) -> bool: def use_copyparty(started, params, CP: car.CarParams) -> bool:
return bool(params.get_bool("EnableCopyparty")) return bool(params.get_bool("EnableCopyparty"))
@@ -185,6 +189,10 @@ procs += [
NativeProcess("locationd_llk", "openpilot/sunnypilot/selfdrive/locationd", ["./locationd"], only_onroad), NativeProcess("locationd_llk", "openpilot/sunnypilot/selfdrive/locationd", ["./locationd"], only_onroad),
] ]
if os.path.exists("./github_runner.sh"):
procs += [NativeProcess("github_runner_start", "openpilot/system/manager",
["./github_runner.sh", "start"], and_(only_offroad, use_github_runner), sigkill=False)]
if os.path.exists("../../sunnypilot/sunnylink/uploader.py"): if os.path.exists("../../sunnypilot/sunnylink/uploader.py"):
procs += [PythonProcess("sunnylink_uploader", "openpilot.sunnypilot.sunnylink.uploader", use_sunnylink_uploader_shim)] procs += [PythonProcess("sunnylink_uploader", "openpilot.sunnypilot.sunnylink.uploader", use_sunnylink_uploader_shim)]
+260
View File
@@ -0,0 +1,260 @@
#!/usr/bin/env bash
set -e
# Default values
DEFAULT_REPO_URL="https://github.com/sunnypilot"
START_AT_BOOT=false
RESTORE_MODE=false
RUNNER_VERSION="2.325.0"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--start-at-boot)
START_AT_BOOT=true
shift
;;
--token)
GITHUB_TOKEN="$2"
shift 2
;;
--repo)
REPO_URL="$2"
shift 2
;;
--restore)
RESTORE_MODE=true
shift
;;
*)
if [ -z "$GITHUB_TOKEN" ]; then
GITHUB_TOKEN="$1"
elif [ -z "$REPO_URL" ]; then
REPO_URL="$1"
fi
shift
;;
esac
done
# Determine BASE_DIR based on mount point
if mountpoint -q /data/media; then
BASE_DIR="/data/media/0/github"
else
BASE_DIR="/data/github"
fi
# Constants
RUNNER_USER="github-runner"
USER_GROUPS="comma,gpu,gpio,sudo"
RUNNER_DIR="${BASE_DIR}/runner"
BUILDS_DIR="${BASE_DIR}/builds"
LOGS_DIR="${BASE_DIR}/logs"
CACHE_DIR="${BASE_DIR}/cache"
OPENPILOT_DIR="${BASE_DIR}/openpilot"
# Basic utility functions (no dependencies)
remount_rw() {
sudo mount -o remount,rw /
}
remount_ro() {
sync || true # Try to sync but continue even if it fails
sudo mount -o remount,ro / # Always try to remount as read-only
}
# Always ensure we try to remount as read-only on exit
trap remount_ro EXIT
setup_runner_user() {
sudo useradd --comment 'GitHub Runner' --create-home --home-dir ${BASE_DIR} ${RUNNER_USER} --shell /bin/bash -G ${USER_GROUPS} || sudo usermod -aG ${USER_GROUPS} ${RUNNER_USER}
}
create_sudoers_entry() {
sudo grep -qxF "${RUNNER_USER} ALL=(ALL) NOPASSWD: ALL" /etc/sudoers || echo "${RUNNER_USER} ALL=(ALL) NOPASSWD: ALL" | sudo tee -a /etc/sudoers
}
set_directory_permissions() {
sudo chown -R ${RUNNER_USER}:comma "$BASE_DIR"
sudo chmod -R g+rwx "$BASE_DIR"
sudo find "$BASE_DIR" -type d -exec chmod g+s {} +
}
setup_directories() {
echo "Creating necessary directories..."
sudo mkdir -p "$RUNNER_DIR" "$BUILDS_DIR" "$LOGS_DIR" "$CACHE_DIR" "$OPENPILOT_DIR"
mkdir -p "/data/openpilot"
sudo chown -R comma:comma "/data/openpilot"
sync
}
wipe_bash_logout() {
export BASE_DIR
sudo -u ${RUNNER_USER} bash -c "touch ${BASE_DIR}/.bash_logout"
sudo -u ${RUNNER_USER} bash -c "truncate -s 0 '${BASE_DIR}/.bash_logout'"
}
# System configuration functions (depends on basic utility functions)
setup_system_configs() {
echo "Setting up system configurations..."
remount_rw
setup_runner_user
create_sudoers_entry
remount_ro
set_directory_permissions
wipe_bash_logout
}
# Runner setup functions
install_runner() {
echo "Downloading and setting up runner..."
cd "$RUNNER_DIR"
curl -o actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
sudo -u ${RUNNER_USER} tar -xzf ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
sudo rm ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
sudo chmod +x ./config.sh
}
configure_runner() {
remount_rw
echo "Configuring runner..."
cd "$RUNNER_DIR"
sudo -u ${RUNNER_USER} ./config.sh --url "$REPO_URL" --token "$GITHUB_TOKEN" --name $(hostname) --runnergroup "tici-tizi" --labels "tici" --work "$BUILDS_DIR" --unattended
remount_ro
}
create_service_template() {
echo "Creating service template..."
cat <<EOL > "$RUNNER_DIR/bin/actions.runner.service.template"
[Unit]
Description={{Description}}
After=network-online.target nss-lookup.target time-sync.target
Wants=network-online.target nss-lookup.target time-sync.target
StartLimitInterval=5
StartLimitBurst=10
[Service]
Type=simple
User=root
ExecStart=/usr/bin/unshare -m -- /bin/bash -c 'mount --bind ${OPENPILOT_DIR} /data/openpilot && setpriv --reuid={{User}} --regid={{User}} --init-groups env HOME=${BASE_DIR} USER={{User}} LOGNAME={{User}} MAIL=/var/mail/{{User}} {{RunnerRoot}}/runsvc.sh'
WorkingDirectory={{RunnerRoot}}
KillMode=process
KillSignal=SIGTERM
TimeoutStopSec=5min
Restart=always
RestartSec=120
[Install]
WantedBy=multi-user.target
EOL
}
install_service() {
local service_name
if [ -f "${RUNNER_DIR}/.service" ]; then
service_name=$(cat "${RUNNER_DIR}/.service")
else
service_name="actions.runner.sunnypilot.$(uname -n)"
fi
create_service_template
remount_rw
local service_path="/etc/systemd/system/${service_name}"
echo "Installing systemd service..."
if [ -f "${service_path}" ]; then
echo "Service ${service_path} found in systemd, we will delete it"
sudo rm -f "${service_path}"
fi
cd "$RUNNER_DIR"
sudo ./svc.sh install $RUNNER_USER
if [ "$START_AT_BOOT" = false ]; then
sudo systemctl disable "${service_name}"
fi
remount_ro
}
check_restore_prerequisites() {
local can_restore=false
local service_name=""
# Check if base runner directory exists
if [ ! -d "${RUNNER_DIR}" ]; then
echo "ERROR: Runner directory ${RUNNER_DIR} does not exist"
echo "This directory is required for restore operations"
exit 1
fi
# First check if we have the required files for restoration
if [ -f "${RUNNER_DIR}/.credentials" ] && [ -f "${RUNNER_DIR}/.service" ]; then
can_restore=true
service_name=$(cat "${RUNNER_DIR}/.service")
echo "Found required runner configuration files"
else
echo "Missing required runner configuration files"
echo "Required: .credentials and .service files in ${RUNNER_DIR}"
exit 1
fi
if ! id "${RUNNER_USER}" &>/dev/null; then
echo "User ${RUNNER_USER} does not exist"
fi
# Only proceed if we can restore AND need to restore
if [ "$can_restore" = true ]; then
echo "Restoration is possible"
return 0
else
echo "No restoration possible"
exit 0
fi
}
perform_restore() {
echo "Starting runner restoration..."
setup_directories
setup_system_configs
install_service
echo "Runner restoration completed successfully"
}
perform_install() {
echo "Starting fresh installation..."
setup_directories
setup_system_configs
install_runner
set_directory_permissions
configure_runner
install_service
echo "Installation completed successfully"
}
main() {
if [ "$RESTORE_MODE" = true ]; then
echo "Running in restore mode - will only restore system configurations..."
check_restore_prerequisites
perform_restore
else
# Check required arguments for normal installation
if [ -z "$GITHUB_TOKEN" ]; then
echo "Usage: $0 [--start-at-boot] [--token <github_token>] [--repo <repository_url>] [--restore]"
echo "Required argument (except for --restore): github_token"
echo "Optional arguments:"
echo " --start-at-boot Enable auto-start at boot (default: false)"
echo " --repo Repository URL (default: ${DEFAULT_REPO_URL})"
echo " --restore Restore existing runner configuration"
exit 1
fi
# Set repository URL if not provided
REPO_URL="${REPO_URL:-$DEFAULT_REPO_URL}"
perform_install
fi
echo "Starting runner service..."
cd "$RUNNER_DIR"
sudo ./svc.sh start
}
main
+12 -34
View File
@@ -53,28 +53,24 @@ def create_pkl_name(full_name: str) -> str:
return pkl return pkl
def _hash_pkl(pkl_path: Path) -> str: def _read_pkl_bytes(pkl_path: Path) -> bytes:
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())
paths = [Path(f"{pkl_path}.chunk{i + 1:02d}of{num_chunks:02d}") for i in range(num_chunks)] parts = []
else: for i in range(num_chunks):
paths = [pkl_path] chunk = Path(f"{pkl_path}.chunk{i + 1:02d}of{num_chunks:02d}")
parts.append(chunk.read_bytes())
digest = hashlib.sha256() return b''.join(parts)
for path in paths: return pkl_path.read_bytes()
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'))
@@ -90,20 +86,8 @@ def _rename_pkl_with_chunks(old_pkl: Path, new_pkl: Path) -> Path:
return old_pkl.rename(new_pkl) return old_pkl.rename(new_pkl)
def _hash_onnx_files(model_dir: Path) -> str | None:
onnx_files = sorted(model_dir.glob("*.onnx"))
if not onnx_files:
return None
digest = hashlib.sha256()
for f in onnx_files:
with f.open('rb') as fh:
while block := fh.read(1024 * 1024):
digest.update(block)
return digest.hexdigest()
def generate_chunked_model(driving_pkl: Path) -> dict: def generate_chunked_model(driving_pkl: Path) -> dict:
tinygrad_hash = _hash_pkl(driving_pkl) tinygrad_hash = hashlib.sha256(_read_pkl_bytes(driving_pkl)).hexdigest()
chunks_config = [] chunks_config = []
manifest_file = Path(f"{driving_pkl}.chunkmanifest") manifest_file = Path(f"{driving_pkl}.chunkmanifest")
@@ -135,8 +119,7 @@ def generate_chunked_model(driving_pkl: Path) -> dict:
} }
def create_metadata_json(models: list, output_dir: Path, custom_name=None, short_name=None, is_20hz=False, upstream_branch="unknown", def create_metadata_json(models: list, output_dir: Path, custom_name=None, short_name=None, is_20hz=False, upstream_branch="unknown") -> None:
onnx_sha256=None) -> None:
bundle_json = { bundle_json = {
"short_name": short_name, "short_name": short_name,
"display_name": custom_name or upstream_branch, "display_name": custom_name or upstream_branch,
@@ -152,9 +135,6 @@ def create_metadata_json(models: list, output_dir: Path, custom_name=None, short
"models": models, "models": models,
} }
if onnx_sha256:
bundle_json["onnx_sha256"] = onnx_sha256
# Write metadata to output_dir # Write metadata to output_dir
metadata_json = { metadata_json = {
"bundles": [bundle_json] "bundles": [bundle_json]
@@ -194,6 +174,4 @@ if __name__ == "__main__":
_driving_pkl = new_pkl _driving_pkl = new_pkl
_model_metadata = generate_chunked_model(_driving_pkl) _model_metadata = generate_chunked_model(_driving_pkl)
_onnx_sha256 = _hash_onnx_files(Path(args.model_dir)) create_metadata_json([_model_metadata], _output_dir, args.custom_name, _short_name, args.is_20hz, args.upstream_branch)
create_metadata_json([_model_metadata], _output_dir, args.custom_name, _short_name, args.is_20hz, args.upstream_branch,
onnx_sha256=_onnx_sha256)
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Determine BASE_DIR based on mount point
if mountpoint -q /data/media; then
GITHUB_BASE_DIR="/data/media/0/github"
else
GITHUB_BASE_DIR="/data/github"
fi
# Define directories and user
BIN_DIR="$GITHUB_BASE_DIR/bin"
BUILDS_DIR="$GITHUB_BASE_DIR/builds"
OPENPILOT_DIR="$GITHUB_BASE_DIR/openpilot"
LOGS_DIR="$GITHUB_BASE_DIR/logs"
CACHE_DIR="$GITHUB_BASE_DIR/cache"
RUNNER_USERNAME="github-runner"
# Define the systemd service name
SERVICE_NAME="github-runner"
USER_GROUPS="comma,gpu,gpio,sudo"
# Function to stop and disable the systemd service
stop_and_uninstall_service() {
cd $GITHUB_BASE_DIR/runner
sudo ./svc.sh stop
sudo ./svc.sh uninstall
}
# Function to remove the systemd service file
remove_runner() {
cd $GITHUB_BASE_DIR/runner
sudo rm .runner
sudo su -c './config.sh remove' github-runner
}
# Function to delete the Github Runner directories
delete_directories() {
sudo rm -rf "$BIN_DIR/github-runner"
sudo rm -rf "$GITHUB_BASE_DIR" "$BIN_DIR" "$BUILDS_DIR" "$LOGS_DIR" "$CACHE_DIR" "$OPENPILOT_DIR"
}
# Function to remove the Github Runner user
delete_user() {
for group in ${USER_GROUPS//,/ }
do
sudo gpasswd -d ${RUNNER_USERNAME} ${group}
done
sudo userdel -r ${RUNNER_USERNAME}
}
# Function to remove sudoers entry
remove_sudoers_entry() {
sudo sed -i.bak "/${RUNNER_USERNAME} ALL=(ALL) NOPASSWD: ALL/d" /etc/sudoers
}
# Make filesystem writable
sudo mount -o remount rw /
# Ensure filesystem is remounted as read-only on script exit
trap "sudo mount -o remount ro /" EXIT
# Call functions
stop_and_uninstall_service
remove_runner
delete_directories
delete_user
remove_sudoers_entry
# End of uninstall script
-104
View File
@@ -1,104 +0,0 @@
#!/usr/bin/env python3
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
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.
"""
import argparse
import hashlib
import json
import tempfile
from huggingface_hub import HfApi, hf_hub_download
def hash_file(path: str) -> str:
digest = hashlib.sha256()
with open(path, 'rb') as f:
while block := f.read(1024 * 1024):
digest.update(block)
return digest.hexdigest()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--hf-repo", required=True)
parser.add_argument("--hf-defaults-path", required=True)
parser.add_argument("--artifact-name", required=True)
parser.add_argument("--model-dir", required=True)
parser.add_argument("--onnx-path", required=True)
parser.add_argument("--onnx-ref", required=True)
parser.add_argument("--model-name", required=True)
parser.add_argument("--tinygrad-ref", required=True)
parser.add_argument("--run-number", required=True)
args = parser.parse_args()
api = HfApi()
onnx_sha256 = hash_file(args.onnx_path)
short_ref = args.onnx_ref[:8]
folder_name = f"model-{args.model_name}-{short_ref}-{args.run_number}"
print(f"ONNX hash: {onnx_sha256}")
print(f"ONNX ref: {args.onnx_ref} (short: {short_ref})")
print(f"Folder: {folder_name}")
metadata_path = f"{args.model_dir}/metadata.json"
with open(metadata_path) as f:
metadata = json.load(f)
bundle = metadata['bundles'][0]
bundle['display_name'] = args.model_name
bundle['onnx_sha256'] = onnx_sha256
bundle['onnx_ref'] = args.onnx_ref
artifact = bundle['models'][0]['artifact']
hf_base = f"https://huggingface.co/datasets/{args.hf_repo}/resolve/main/{args.hf_defaults_path}/{folder_name}"
artifact['download_uri']['url'] = f"{hf_base}/{artifact['file_name']}"
for chunk in artifact.get('chunks', []):
chunk['url'] = f"{hf_base}/{chunk['file_name']}"
print(f"Uploading model to {args.hf_defaults_path}/{folder_name}/")
api.upload_folder(
folder_path=args.model_dir,
path_in_repo=f"{args.hf_defaults_path}/{folder_name}",
repo_id=args.hf_repo,
repo_type="dataset",
)
json_filename = f"{args.hf_defaults_path}/default_models.json"
try:
local_path = hf_hub_download(repo_id=args.hf_repo, repo_type='dataset', filename=json_filename)
with open(local_path) as f:
defaults_json = json.load(f)
except Exception:
defaults_json = {"tinygrad_ref": args.tinygrad_ref, "bundles": []}
defaults_json['tinygrad_ref'] = args.tinygrad_ref
existing_idx = next((i for i, b in enumerate(defaults_json['bundles'])
if b.get('onnx_sha256') == onnx_sha256), None)
if existing_idx is not None:
defaults_json['bundles'][existing_idx] = bundle
else:
defaults_json['bundles'].append(bundle)
print(json.dumps(defaults_json, indent=2))
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump(defaults_json, f, indent=2)
tmp_path = f.name
api.upload_file(
path_or_fileobj=tmp_path,
path_in_repo=json_filename,
repo_id=args.hf_repo,
repo_type="dataset",
)
print(f"Updated {json_filename}")
if __name__ == "__main__":
main()