mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-11-07 07:43:44 +08:00
Compare commits
1 Commits
master-dev
..
emu
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bea2d48c1 |
@@ -34,6 +34,11 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: 'qcom'
|
||||
on_device:
|
||||
description: 'Compile on the tici / chestnut runner instead of on a hosted runner with tinygrad''s qcom / amd mock'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
hf_repo:
|
||||
description: 'Hugging Face dataset repository (e.g. sunnypilot/sunnypilot_models_v1)'
|
||||
required: false
|
||||
@@ -107,6 +112,11 @@ on:
|
||||
options:
|
||||
- qcom
|
||||
- chestnut
|
||||
on_device:
|
||||
description: 'Compile on the tici / chestnut runner instead of on a hosted runner with tinygrad''s qcom / amd mock'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
hf_repo:
|
||||
description: 'Hugging Face dataset repository'
|
||||
required: false
|
||||
@@ -130,11 +140,45 @@ jobs:
|
||||
is_20hz: ${{ inputs.is_20hz }}
|
||||
artifact_suffix: ${{ inputs.artifact_suffix }}
|
||||
target_hardware: ${{ inputs.target_hardware }}
|
||||
on_device: ${{ inputs.on_device || false }}
|
||||
secrets: inherit
|
||||
|
||||
publish_model:
|
||||
mock_parity:
|
||||
if: ${{ !cancelled() && needs.build_model.result == 'success' }}
|
||||
needs: build_model
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-amd-mock
|
||||
if: inputs.target_hardware == 'chestnut'
|
||||
- run: python3 -m pip install -q numpy ${{ inputs.target_hardware != 'chestnut' && 'tinymesa==25.2.7.2' || '' }}
|
||||
- name: Download artifact name file
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifact-name-${{ inputs.custom_name || inputs.upstream_branch }}
|
||||
path: artifact_name
|
||||
- name: Download the built model
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
ARTIFACT_NAME=$(cat artifact_name/artifact_name.txt)
|
||||
id=$(gh api -X GET "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts" -f name="$ARTIFACT_NAME" --jq '.artifacts[0].id')
|
||||
url=$(curl -sf -o /dev/null -w '%{redirect_url}' -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/$GITHUB_REPOSITORY/actions/artifacts/$id/zip")
|
||||
sudo apt-get install -y -qq aria2 > /dev/null
|
||||
aria2c -q -x16 -s16 -k8M --max-tries=5 --file-allocation=none -o model.zip "$url" && unzip -q model.zip -d output && rm model.zip
|
||||
- name: Replay its check frame on the mock and require the hardware's result bit for bit
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}
|
||||
DEV: ${{ inputs.target_hardware == 'chestnut' && 'MOCKUSB+AMD' || 'MOCK+QCOM' }}
|
||||
run: python3 -m openpilot.sunnypilot.modeld_v2.mock_parity check output/${{ inputs.target_hardware == 'chestnut' && 'big_' || '' }}driving_tinygrad.pkl
|
||||
|
||||
publish_model:
|
||||
if: ${{ !cancelled() && needs.build_model.result == 'success' && contains(fromJSON('["success", "skipped"]'), needs.mock_parity.result) }}
|
||||
needs: [build_model, mock_parity]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Chestnut mock build parity
|
||||
description: >-
|
||||
Convert one commaai precompiled chestnut model (LS2) on this runner under tinygrad's amd mock, as a hosted chestnut build does, and require the
|
||||
exact bytes the chestnut runner built (the published artifact). Needs setup-amd-mock, numpy and zstandard; tinygrad_repo checked out.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Rebuild LS2 on the mock and require the chestnut runner's artifact byte for byte
|
||||
shell: bash
|
||||
env:
|
||||
REF: b92fc4828664f8767bce5e92f1630ed319cbe509 # commaai/openpilot: precompiled big_driving_tinygrad.pkl, pins tinygrad ec91b74b5b
|
||||
PKL_OID: b4dead3ee091d66ab002a6b8e716c26f4f79e3c931e4be5f48e017ba92f6e960
|
||||
PKL_SIZE: "767228907"
|
||||
TG_SHA: ec91b74b5b6b0fc924b72d3f27689679ca2438b9
|
||||
WANT: 680a8d629785f2b1d1a2b0e40d0dfbcd0f7291c21e20c51259f1d768a22ec217 # driving_light_speed_2_november_04_2026_tinygrad.pkl, recompiled46
|
||||
run: |
|
||||
set -euo pipefail
|
||||
work="$RUNNER_TEMP/chestnut_parity" && rm -rf "$work" && mkdir -p "$work"
|
||||
href=$(curl -fsSL --retry 3 -X POST -H "Accept: application/vnd.git-lfs+json" -H "Content-Type: application/vnd.git-lfs+json" \
|
||||
https://huggingface.co/commaai/openpilot-lfs.git/info/lfs/objects/batch \
|
||||
-d "{\"operation\":\"download\",\"transfer\":[\"basic\"],\"objects\":[{\"oid\":\"$PKL_OID\",\"size\":$PKL_SIZE}]}" | jq -r '.objects[0].actions.download.href')
|
||||
curl -fsSL --retry 3 -o "$work/big_driving_tinygrad.pkl" "$href"
|
||||
echo "$PKL_OID $work/big_driving_tinygrad.pkl" | sha256sum -c -
|
||||
tg="$work/tinygrad" && git init -q "$tg" && git -C "$tg" fetch -q --depth 1 https://github.com/tinygrad/tinygrad "$TG_SHA" && git -C "$tg" checkout -q FETCH_HEAD
|
||||
rm -rf "$tg/test/mockgpu" && cp -r "$GITHUB_WORKSPACE/tinygrad_repo/test/mockgpu" "$tg/test/mockgpu"
|
||||
LLVM_PATH="${LLVM_PATH:-$(ls /usr/lib/llvm-20/lib/libLLVM.so* | head -1)}" DEV=MOCKUSB+AMD CHECK_OOB=0 GMMU=0 PYTHONPATH="$tg:$GITHUB_WORKSPACE" \
|
||||
python3 openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py "$work/big_driving_tinygrad.pkl" "$work/LS2.pkl"
|
||||
got=$(sha256sum "$work/LS2.pkl" | cut -d' ' -f1)
|
||||
if [ "$got" != "$WANT" ]; then
|
||||
echo "::error::the mock's LS2 build ($got) is not the chestnut runner's ($WANT): this runner's LLVM or the mock no longer builds what the chestnut does"
|
||||
exit 1
|
||||
fi
|
||||
echo "LS2 rebuilt on the mock: byte-identical to the chestnut runner's build"
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Set up tinygrad's amd mock
|
||||
description: LLVM 20 and ROCm comgr, as tinygrad_repo/.github/actions/setup-tinygrad installs them for the mock gpu
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install LLVM 20 and ROCm comgr
|
||||
shell: bash
|
||||
run: |
|
||||
. /etc/os-release
|
||||
curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null
|
||||
echo "deb http://apt.llvm.org/$VERSION_CODENAME/ llvm-toolchain-$VERSION_CODENAME-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://repo.radeon.com/rocm/rocm.gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.1 $VERSION_CODENAME main" | sudo tee /etc/apt/sources.list.d/rocm.list
|
||||
printf 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600\n' | sudo tee /etc/apt/preferences.d/rocm-pin-600
|
||||
sudo apt-get -qq update && sudo apt-get -qq install -y --no-install-recommends libllvm20 clang-20 lld-20 comgr
|
||||
printf '/opt/rocm/lib\n/opt/rocm/lib64\n' | sudo tee /etc/ld.so.conf.d/rocm.conf && sudo ldconfig
|
||||
echo "/usr/lib/llvm-20/bin" >> "$GITHUB_PATH"
|
||||
@@ -35,6 +35,11 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: 'qcom'
|
||||
on_device:
|
||||
description: 'Compile on the tici / chestnut runner instead of on a hosted runner with tinygrad''s qcom / amd mock'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
upstream_branch:
|
||||
@@ -59,6 +64,11 @@ on:
|
||||
- qcom
|
||||
- chestnut
|
||||
default: 'qcom'
|
||||
on_device:
|
||||
description: 'Compile on the tici / chestnut runner instead of on a hosted runner with tinygrad''s qcom / amd mock'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
|
||||
run-name: Build model [${{ inputs.custom_name || inputs.upstream_branch }}] from ref [${{ inputs.upstream_branch }}]
|
||||
@@ -153,18 +163,48 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
build_model:
|
||||
runs-on: [self-hosted, "${{ inputs.target_hardware == 'chestnut' && 'chestnut' || 'tici' }}"]
|
||||
runs-on: ${{ inputs.on_device && fromJSON(inputs.target_hardware == 'chestnut' && '["self-hosted","chestnut"]' || '["self-hosted","tici"]') || 'ubuntu-24.04' }}
|
||||
needs: get_model
|
||||
env:
|
||||
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
||||
REF: ${{ inputs.upstream_branch }}
|
||||
MOCK_BUILD: ${{ !inputs.on_device }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up the hosted mock build
|
||||
if: env.MOCK_BUILD == 'true'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install LLVM 20 and ROCm comgr for the amd mock
|
||||
if: env.MOCK_BUILD == 'true' && inputs.target_hardware == 'chestnut'
|
||||
uses: ./.github/workflows/setup-amd-mock
|
||||
|
||||
- name: Install the amd mock build dependencies
|
||||
if: env.MOCK_BUILD == 'true' && inputs.target_hardware == 'chestnut'
|
||||
run: |
|
||||
python3 -m pip install -q numpy zstandard
|
||||
echo "LLVM_PATH=$(ls /usr/lib/llvm-20/lib/libLLVM.so* | head -1)" >> "$GITHUB_ENV" # the chestnut runner's LLVM, never a newer one
|
||||
|
||||
- name: Check this runner builds what the chestnut runner does
|
||||
if: env.MOCK_BUILD == 'true' && inputs.target_hardware == 'chestnut'
|
||||
uses: ./.github/workflows/chestnut-build-parity
|
||||
|
||||
- name: Install the qcom mock build dependencies and check the compiler
|
||||
if: env.MOCK_BUILD == 'true' && inputs.target_hardware != 'chestnut'
|
||||
run: |
|
||||
sudo apt-get -qq update && sudo apt-get -qq install -y --no-install-recommends qemu-user-static aria2
|
||||
python3 -m pip install -q numpy zstandard tinymesa==25.2.7.2
|
||||
# every kernel an a630 has compiled must compile to the same bytes here before a model is built here
|
||||
PYTHONPATH="$TINYGRAD_PATH" python3 "$TINYGRAD_PATH/test/device/qcom/compiler_parity.py" check "$TINYGRAD_PATH/test/mockgpu/qcom/a630_compiled.pkl.xz"
|
||||
|
||||
- name: Set environment variables
|
||||
if: env.MOCK_BUILD != 'true'
|
||||
id: set-env
|
||||
run: |
|
||||
# Set up common environment
|
||||
@@ -177,6 +217,7 @@ jobs:
|
||||
printenv >> $GITHUB_ENV
|
||||
|
||||
- name: Setup build environment
|
||||
if: env.MOCK_BUILD != 'true'
|
||||
run: |
|
||||
mkdir -p "${BUILD_DIR}/"
|
||||
sudo find $BUILD_DIR/ -mindepth 1 -delete
|
||||
@@ -198,21 +239,28 @@ jobs:
|
||||
|
||||
- name: Build Model
|
||||
run: |
|
||||
source /etc/profile
|
||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||
if [ "$MOCK_BUILD" = "true" ]; then
|
||||
TASKSET=""
|
||||
export CHECK_OOB=0 GMMU=0 # as test/mockgpu runs the amd mock
|
||||
else
|
||||
source /etc/profile
|
||||
export UV_PROJECT_ENVIRONMENT=${HOME}/venv
|
||||
export VIRTUAL_ENV=$UV_PROJECT_ENVIRONMENT
|
||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||
TASKSET="taskset -c 7"
|
||||
fi
|
||||
export PYTHONPATH="${PYTHONPATH}:${{ env.TINYGRAD_PATH }}:${{ github.workspace }}"
|
||||
|
||||
COMPILE_MODELD="${{ github.workspace }}/openpilot/sunnypilot/modeld_v2/compile_modeld.py"
|
||||
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}')")
|
||||
|
||||
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 OPENPILOT_HACKS=1"
|
||||
MOCK=$([ "$MOCK_BUILD" = "true" ] && echo MOCK || true)
|
||||
TG_FLAGS_QCOM="DEV=${MOCK}QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 OPENPILOT_HACKS=1"
|
||||
if [ "${{ inputs.target_hardware }}" == "chestnut" ]; then
|
||||
echo "CHESTNUT build"
|
||||
export CHESTNUT=1
|
||||
TG_FLAGS="DEBUG=1 DEV=USB+AMD:LLVM FRAME_DEV=CPU FLOAT16=1 GMMU=0 TC_OPT=2 TC_MIN_GLOBALS=32"
|
||||
TG_FLAGS="DEBUG=1 DEV=${MOCK}USB+AMD:LLVM FRAME_DEV=CPU FLOAT16=1 GMMU=0 TC_OPT=2 TC_MIN_GLOBALS=32"
|
||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||
else
|
||||
echo "QCOM build"
|
||||
@@ -227,14 +275,17 @@ jobs:
|
||||
echo "Converting commaai's precompiled model with tinygrad $TG_SHA"
|
||||
UPSTREAM_TG="${{ runner.temp }}/upstream_tinygrad"
|
||||
rm -rf "$UPSTREAM_TG" && git init -q "$UPSTREAM_TG" && git -C "$UPSTREAM_TG" fetch -q --depth 1 "$TG_URL" "$TG_SHA" && git -C "$UPSTREAM_TG" checkout -q FETCH_HEAD
|
||||
if [ "$MOCK_BUILD" = "true" ]; then # convert under commaai's tinygrad, on our mock: it is the one measured against a chestnut
|
||||
rm -rf "$UPSTREAM_TG/test/mockgpu" && cp -r "$TINYGRAD_PATH/test/mockgpu" "$UPSTREAM_TG/test/mockgpu"
|
||||
fi
|
||||
CONVERTED="${{ runner.temp }}/big_driving_tinygrad_plain.pkl"
|
||||
env DEV=USB+AMD PYTHONPATH="$UPSTREAM_TG:${{ github.workspace }}" taskset -c 7 python3 openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py \
|
||||
env DEV=${MOCK}USB+AMD PYTHONPATH="$UPSTREAM_TG:${{ github.workspace }}" $TASKSET python3 openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py \
|
||||
"${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl" "$CONVERTED"
|
||||
mv "$CONVERTED" "$OUTPUT_PKL"
|
||||
elif NEW_ARCH_ONNX=$(find "${{ env.MODELS_DIR }}" -maxdepth 1 -name "${{ inputs.target_hardware == 'chestnut' && 'big_' || '' }}*driving*.onnx" -print -quit) &&
|
||||
[ -n "$NEW_ARCH_ONNX" ] && grep -qa new_img "$NEW_ARCH_ONNX"; then # the new architecture: one ONNX taking the warped frames as new_img
|
||||
echo "Compiling new architecture ONNX: $NEW_ARCH_ONNX"
|
||||
env ${TG_FLAGS} taskset -c 7 python3 openpilot/sunnypilot/modeld_v2/compile_modeld_v2.py \
|
||||
env ${TG_FLAGS} $TASKSET python3 openpilot/sunnypilot/modeld_v2/compile_modeld_v2.py \
|
||||
"$NEW_ARCH_ONNX" "$OUTPUT_PKL" --device-input "*" --benchmark-runs 1
|
||||
else
|
||||
echo "Detected onnx architecture. Routing to native compile_modeld.py."
|
||||
@@ -284,7 +335,7 @@ jobs:
|
||||
|
||||
if [ -n "$MODEL_TYPE" ]; then
|
||||
echo "Detected: $MODEL_TYPE -> $OUTPUT_PKL"
|
||||
env ${TG_FLAGS} taskset -c 7 python3 "$COMPILE_MODELD" \
|
||||
env ${TG_FLAGS} $TASKSET python3 "$COMPILE_MODELD" \
|
||||
--model-type $MODEL_TYPE \
|
||||
--model-size $MODEL_SIZE \
|
||||
--camera-resolutions $CAMERA_RES \
|
||||
@@ -304,7 +355,7 @@ jobs:
|
||||
--include='/${{ inputs.target_hardware == 'chestnut' && 'big_' || '' }}driving_tinygrad.pkl' \
|
||||
--exclude='*' \
|
||||
--delete-excluded \
|
||||
--chown=comma:comma \
|
||||
$([ "$MOCK_BUILD" = "true" ] || echo --chown=comma:comma) \
|
||||
${{ env.MODELS_DIR }}/ ${{ env.OUTPUT_DIR }}/
|
||||
|
||||
python3 "${{ github.workspace }}/release/ci/model_generator.py" \
|
||||
@@ -333,7 +384,7 @@ jobs:
|
||||
path: ${{ env.OUTPUT_DIR }}/artifact_name.txt
|
||||
|
||||
- name: Re-enable powersave
|
||||
if: always()
|
||||
if: always() && env.MOCK_BUILD != 'true'
|
||||
run: |
|
||||
source ${UV_PROJECT_ENVIRONMENT}/bin/activate
|
||||
PYTHONPATH=$PYTHONPATH:${{ github.workspace }}/ ${{ github.workspace }}/scripts/manage-powersave.py --enable
|
||||
|
||||
@@ -28,18 +28,7 @@ jobs:
|
||||
env:
|
||||
DEV: CPU
|
||||
run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_plain_artifact.py
|
||||
- name: Install LLVM 20 and ROCm comgr for the amd mock, as tinygrad_repo/.github/actions/setup-tinygrad does
|
||||
run: |
|
||||
. /etc/os-release
|
||||
curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null
|
||||
echo "deb http://apt.llvm.org/$VERSION_CODENAME/ llvm-toolchain-$VERSION_CODENAME-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://repo.radeon.com/rocm/rocm.gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.1 $VERSION_CODENAME main" | sudo tee /etc/apt/sources.list.d/rocm.list
|
||||
printf 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600\n' | sudo tee /etc/apt/preferences.d/rocm-pin-600
|
||||
sudo apt-get -qq update && sudo apt-get -qq install -y --no-install-recommends libllvm20 clang-20 lld-20 comgr
|
||||
printf '/opt/rocm/lib\n/opt/rocm/lib64\n' | sudo tee /etc/ld.so.conf.d/rocm.conf && sudo ldconfig
|
||||
echo "/usr/lib/llvm-20/bin" >> "$GITHUB_PATH"
|
||||
- uses: ./.github/workflows/setup-amd-mock
|
||||
- name: Convert a pickled USB+AMD jit and replay it under tinygrad's amd mock
|
||||
env:
|
||||
DEV: MOCKUSB+AMD
|
||||
@@ -80,6 +69,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
models: ${{ steps.set-matrix.outputs.models }}
|
||||
chestnut: ${{ steps.set-matrix.outputs.chestnut }}
|
||||
qcom: ${{ steps.set-matrix.outputs.qcom }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Fetch and Parse json
|
||||
@@ -91,16 +82,22 @@ jobs:
|
||||
with open("openpilot/sunnypilot/models/fetcher.py", "r") as f:
|
||||
urls = re.findall(r"MODEL_URL(?:_CHESTNUT)?\s*=\s*[\"'"'"']([^\"'"'"']+)[\"'"'"']", f.read())
|
||||
|
||||
artifacts = []
|
||||
artifacts, chestnut, qcom = [], [], []
|
||||
for url in urls:
|
||||
data = json.loads(urllib.request.urlopen(url).read())
|
||||
for bundle in data.get("bundles", []):
|
||||
for model in bundle.get("models", []):
|
||||
if "artifact" in model:
|
||||
artifacts.append(model["artifact"])
|
||||
if "chestnut" in url:
|
||||
chestnut.append(model["artifact"])
|
||||
elif "tinygrad" in model["artifact"]["file_name"]:
|
||||
qcom.append(model["artifact"])
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
||||
f.write(f"models={json.dumps(artifacts)}\n")
|
||||
f.write(f"chestnut={json.dumps(chestnut)}\n")
|
||||
f.write(f"qcom={json.dumps(qcom)}\n")
|
||||
'
|
||||
|
||||
test-model:
|
||||
@@ -137,3 +134,74 @@ jobs:
|
||||
export PYTHONPATH="$GITHUB_WORKSPACE/tinygrad_repo:$GITHUB_WORKSPACE:$PYTHONPATH"
|
||||
python3 -m pip uninstall -y tinygrad || uv pip uninstall -y tinygrad --system || true
|
||||
python3 -m pytest openpilot/sunnypilot/modeld_v2/tests/test_models.py
|
||||
|
||||
chestnut-mock-parity:
|
||||
name: Mock parity ${{ matrix.artifact.file_name }}
|
||||
needs: generate-matrix
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
artifact: ${{ fromJson(needs.generate-matrix.outputs.chestnut) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-amd-mock
|
||||
- run: python3 -m pip install -q numpy
|
||||
- name: Download model
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
MODEL_URL: ${{ matrix.artifact.download_uri.url }}
|
||||
MODEL_SHA: ${{ matrix.artifact.download_uri.sha256 }}
|
||||
shell: bash
|
||||
run: |
|
||||
AUTH=()
|
||||
if [ -n "$HF_TOKEN" ]; then AUTH=(-H "Authorization: Bearer $HF_TOKEN"); fi
|
||||
curl --retry 3 --retry-all-errors -s -S -f -L "${AUTH[@]}" -o "$RUNNER_TEMP/model.pkl" "$MODEL_URL"
|
||||
if [ -n "$MODEL_SHA" ]; then echo "$MODEL_SHA $RUNNER_TEMP/model.pkl" | sha256sum -c -; fi
|
||||
- name: Replay its check frame on the amd mock and require the chestnut's result bit for bit
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}
|
||||
run: python3 -m openpilot.sunnypilot.modeld_v2.mock_parity check "$RUNNER_TEMP/model.pkl"
|
||||
|
||||
qcom-mock-parity:
|
||||
name: QCOM mock parity ${{ matrix.artifact.file_name }}
|
||||
needs: generate-matrix
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
artifact: ${{ fromJson(needs.generate-matrix.outputs.qcom) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: python3 -m pip install -q numpy tinymesa==25.2.7.2
|
||||
- name: Download model
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
MODEL_URL: ${{ matrix.artifact.download_uri.url }}
|
||||
MODEL_SHA: ${{ matrix.artifact.download_uri.sha256 }}
|
||||
shell: bash
|
||||
run: |
|
||||
AUTH=()
|
||||
if [ -n "$HF_TOKEN" ]; then AUTH=(-H "Authorization: Bearer $HF_TOKEN"); fi
|
||||
curl --retry 3 --retry-all-errors -s -S -f -L "${AUTH[@]}" -o "$RUNNER_TEMP/model.pkl" "$MODEL_URL"
|
||||
if [ -n "$MODEL_SHA" ]; then echo "$MODEL_SHA $RUNNER_TEMP/model.pkl" | sha256sum -c -; fi
|
||||
- name: Replay its check frame on the qcom mock and require the comma device's result bit for bit
|
||||
env:
|
||||
DEV: MOCK+QCOM
|
||||
PYTHONPATH: ${{ github.workspace }}/tinygrad_repo:${{ github.workspace }}
|
||||
run: python3 -m openpilot.sunnypilot.modeld_v2.mock_parity check "$RUNNER_TEMP/model.pkl"
|
||||
|
||||
chestnut-mock-build-parity:
|
||||
name: Chestnut build on the amd mock = chestnut runner build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/workflows/setup-amd-mock
|
||||
- run: python3 -m pip install -q numpy zstandard
|
||||
- uses: ./.github/workflows/chestnut-build-parity
|
||||
|
||||
@@ -371,7 +371,7 @@ def _compile_warp_resolution_worker(cam_w, cam_h, model_w, model_h, benchmark_ru
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if 'USB' in os.getenv('DEV', '') or os.getenv('CHESTNUT'):
|
||||
if ('USB' in os.getenv('DEV', '') or os.getenv('CHESTNUT')) and not os.getenv('DEV', '').startswith('MOCK'):
|
||||
from openpilot.system.hardware.chestnut.flash import link_up
|
||||
for _ in range(10):
|
||||
if link_up():
|
||||
|
||||
@@ -194,7 +194,8 @@ def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]:
|
||||
base, at, nbytes = _address(argument_words[slot])
|
||||
args.append({"buffer": buffers.setdefault(base, len(buffers)), "offset": at, "nbytes": nbytes or base.max_numel() * base.dtype.itemsize, "dtype": u8})
|
||||
outs, ins = kernel[6]
|
||||
calls.append({"kind": "program", "name": re.sub(r"\x1b\[[0-9;]*m", "", kernel[1]), "lib": programs[image_alloc], "target": dataclasses.asdict(target),
|
||||
device_target = dataclasses.asdict(dataclasses.replace(target, interface=target.interface.removeprefix("MOCK")))
|
||||
calls.append({"kind": "program", "name": re.sub(r"\x1b\[[0-9;]*m", "", kernel[1]), "lib": programs[image_alloc], "target": device_target,
|
||||
"signature": [[f"data{i}", i, u8, [a.get("nbytes", 1)]] for i, a in enumerate(args)], "global_size": grid, "local_size": local,
|
||||
"globals": list(range(len(args))), "outs": list(outs), "ins": list(ins), "args": args})
|
||||
needs_init |= {args[i]["buffer"] for i in ins if "buffer" in args[i] and args[i]["buffer"] not in written}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
os.environ.setdefault("DEV", "MOCKUSB+AMD")
|
||||
QCOM = "QCOM" in os.environ["DEV"]
|
||||
ON_MOCK = os.environ["DEV"].startswith("MOCK")
|
||||
ARCH, HARDWARE = ("a630", "comma 3x/4 (a630)") if QCOM else ("gfx1200", "chestnut")
|
||||
os.environ.setdefault("CHECK_OOB", "0")
|
||||
os.environ.setdefault("GMMU", "0")
|
||||
if sys.platform == "darwin" and os.environ["DEV"].startswith("MOCKUSB"): # the mock's VRAM is a memfd, which macOS lacks: an unlinked file is the same
|
||||
from tinygrad.runtime.autogen import libc
|
||||
|
||||
def _memfd_create(name, flags):
|
||||
fd, path = tempfile.mkstemp()
|
||||
os.unlink(path)
|
||||
return fd
|
||||
libc.memfd_create = _memfd_create
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openpilot.sunnypilot.modeld_v2.helpers import ALIGNMENT, MAGIC, PlainJit, _device, read_parts
|
||||
|
||||
|
||||
class ParityError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _emulator():
|
||||
try:
|
||||
if QCOM:
|
||||
from test.mockgpu.qcom import emu
|
||||
else:
|
||||
from test.mockgpu.amd import emu
|
||||
except ImportError as e:
|
||||
raise ParityError(f"tinygrad's {'qcom' if QCOM else 'amd'} mock is not importable: put tinygrad_repo on PYTHONPATH") from e
|
||||
if not hasattr(emu, "hw_unverified"):
|
||||
raise ParityError(f"this tinygrad's emulator has no {ARCH} rounding model, its check frames would not match a {HARDWARE}")
|
||||
return emu
|
||||
|
||||
|
||||
def unverified(header: dict, blob: memoryview) -> dict[str, int]:
|
||||
emu, out = _emulator(), {}
|
||||
for call in header["calls"]:
|
||||
if call["kind"] != "program":
|
||||
continue
|
||||
if (arch := call["target"].get("arch", "")).split(",")[0] != ARCH: # IMAGE builds append ",IMAGE_PITCH_ALIGNMENT=64"
|
||||
out[f"arch={arch}"] = out.get(f"arch={arch}", 0) + 1
|
||||
continue
|
||||
for sig, n in emu.hw_unverified(bytes(blob[slice(*call["lib"])])).items():
|
||||
out[sig] = out.get(sig, 0) + n
|
||||
return out
|
||||
|
||||
|
||||
def _array(blob: memoryview, stored: dict) -> np.ndarray:
|
||||
return np.frombuffer(blob[slice(*stored["data"])], dtype=np.dtype(stored["dtype"])).reshape(stored["shape"]).copy()
|
||||
|
||||
|
||||
def replay(header: dict, blob: memoryview, shared: dict) -> list[np.ndarray]:
|
||||
from tinygrad import Tensor
|
||||
check = header["check"]
|
||||
jit = PlainJit(header, blob, None, verify=False, shared=shared)
|
||||
kwargs: dict = {}
|
||||
for path, stored, device in check["inputs"]:
|
||||
tensor = Tensor(_array(blob, stored), device=_device(device, None)).realize()
|
||||
kwargs[path[0]] = tensor if len(path) == 1 else {**kwargs.get(path[0], {}), path[1]: tensor}
|
||||
result = jit(**kwargs)
|
||||
outputs = kwargs[check["output_container"]].values() if check["output_container"] else result
|
||||
return [t.numpy() for t in (outputs if isinstance(outputs, (list, tuple)) or check["output_container"] else [outputs])]
|
||||
|
||||
|
||||
def _parts_with_offsets(path: str) -> tuple[dict, dict]:
|
||||
parts, _ = read_parts(path)
|
||||
with open(path, "rb") as f:
|
||||
head = f.read(len(MAGIC) + 8)
|
||||
start = len(MAGIC) + 8 + struct.unpack_from("<Q", head, len(MAGIC))[0]
|
||||
f.seek(len(MAGIC) + 8)
|
||||
entries = json.loads(f.read(start - len(MAGIC) - 8))["parts"]
|
||||
base = start + -start % ALIGNMENT
|
||||
offsets = {tuple(e["key"]) if isinstance(e["key"], list) else e["key"]: base + e["blob"][0] for e in entries}
|
||||
return parts, offsets
|
||||
|
||||
|
||||
def _gate(parts: dict) -> None:
|
||||
missing: dict[str, int] = {}
|
||||
for header, blob in parts.values():
|
||||
if "check" not in header:
|
||||
raise ParityError("artifact has no check frame")
|
||||
for sig, n in unverified(header, blob).items():
|
||||
missing[sig] = missing.get(sig, 0) + n
|
||||
if missing:
|
||||
raise ParityError(f"kernels use instructions the emulator is not verified against a {HARDWARE} for: " +
|
||||
", ".join(f"{s} ({n}x)" for s, n in sorted(missing.items(), key=lambda kv: -kv[1])))
|
||||
|
||||
|
||||
def check(path: str) -> None:
|
||||
parts, _ = read_parts(path)
|
||||
if ON_MOCK:
|
||||
_gate(parts)
|
||||
shared: dict = {}
|
||||
for key, (header, blob) in parts.items():
|
||||
got = replay(header, blob, shared)
|
||||
for i, (g, stored) in enumerate(zip(got, header["check"]["outputs"], strict=True)):
|
||||
want = _array(blob, stored)
|
||||
if g.shape != want.shape or not np.array_equal(g.view(np.uint8), want.view(np.uint8)):
|
||||
n = int(np.count_nonzero(g != want)) if g.shape == want.shape else -1
|
||||
raise ParityError(f"part {key} output {i}: the {'mock' if ON_MOCK else 'device'}'s check frame differs from the stored one in {n} of {want.size} values")
|
||||
|
||||
|
||||
def record(path: str, out: str) -> None:
|
||||
parts, offsets = _parts_with_offsets(path)
|
||||
_gate(parts)
|
||||
shared, writes = {}, []
|
||||
for key, (header, blob) in parts.items():
|
||||
for g, stored in zip(replay(header, blob, shared), header["check"]["outputs"], strict=True):
|
||||
want = np.dtype(stored["dtype"])
|
||||
if tuple(g.shape) != tuple(stored["shape"]) or g.dtype != want:
|
||||
raise ParityError(f"part {key}: replayed output {g.dtype}{g.shape} does not fit the stored {want}{tuple(stored['shape'])}")
|
||||
writes.append((offsets[key] + stored["data"][0], np.ascontiguousarray(g).tobytes()))
|
||||
shutil.copyfile(path, out)
|
||||
with open(out, "r+b") as f:
|
||||
for offset, data in writes:
|
||||
f.seek(offset)
|
||||
f.write(data)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if len(argv) < 2 or argv[0] not in ("check", "record") or (argv[0] == "record" and len(argv) != 3):
|
||||
print("usage: mock_parity check ARTIFACT... | record ARTIFACT OUT", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
if argv[0] == "record":
|
||||
st = time.perf_counter()
|
||||
record(argv[1], argv[2])
|
||||
print(f"{argv[2]}: check frame recorded on the mock in {time.perf_counter() - st:.0f}s")
|
||||
return 0
|
||||
for path in argv[1:]:
|
||||
st = time.perf_counter()
|
||||
check(path)
|
||||
print(f"{os.path.basename(path)}: bit-identical to its {HARDWARE} check frame on the {'mock' if ON_MOCK else 'device'} ({time.perf_counter() - st:.0f}s)")
|
||||
return 0
|
||||
except ParityError as e:
|
||||
print(f"PARITY FAIL: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -80,7 +80,8 @@ def serialize_jit(jit, kernel_linear, check: dict | None = None) -> tuple[dict,
|
||||
if ast.op is Ops.PROGRAM:
|
||||
elf, info = ast.to_elf(), ast.arg
|
||||
global_size, local_size = info.launch_dims({})
|
||||
calls.append({"kind": "program", "name": elf.name, "lib": blob.add(bytes(elf.lib)), "target": dataclasses.asdict(elf.target),
|
||||
device_target = dataclasses.asdict(dataclasses.replace(elf.target, interface=elf.target.interface.removeprefix("MOCK")))
|
||||
calls.append({"kind": "program", "name": elf.name, "lib": blob.add(bytes(elf.lib)), "target": device_target,
|
||||
"signature": kernel_signature(ast, elf),
|
||||
"global_size": [int(x) for x in global_size], "local_size": [int(x) for x in local_size],
|
||||
"globals": list(info.globals), "outs": list(info.outs), "ins": list(info.ins), "args": arguments})
|
||||
|
||||
+1
-1
Submodule tinygrad_repo updated: 92b742f711...cb2fb4ef10
Reference in New Issue
Block a user