diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c382a1bd..f25e662f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 # # CI for the three-step flow (cbuild setup, create_ai_layer.py, cbuild) -# followed by an FVP run, with each of the three toolchains. +# followed by an FVP run that checks the generated face against the host +# reference, with each of the three toolchains. name: Build and Run on: @@ -17,11 +18,77 @@ env: TARGET: SSE-320-U85 jobs: + # Step 1 and 2 of the flow, once: the AI layer is the same for every + # toolchain and both target-types. The model data (ai_layer/model_pte.c) is + # generated here rather than committed; the job also renders the host's + # fake-quant reference image of the boot demo for the FVP check below. + export: + name: Create the AI layer + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v7 + + - name: Install tools + uses: ARM-software/cmsis-actions/vcpkg@v1 + + # cbuild setup of CMSIS-Toolbox 2.15 configures the CMake project, which + # probes the solution's compiler (AC6). + - name: Activate Arm tool license + uses: ARM-software/cmsis-actions/armlm@v1 + + - name: Cache packs + uses: actions/cache@v6 + with: + key: cmsis-packs-${{ hashFiles('*.csolution.yml') }} + path: /home/runner/.cache/arm/packs + + # torch + executorch is a multi-gigabyte install and otherwise dominates + # the job. Keyed on the requirements files so a pin change rebuilds it. + - name: Cache model-export venv + uses: actions/cache@v6 + with: + key: venv-${{ runner.os }}-${{ hashFiles('requirements*.txt', 'setup_venv.py') }} + path: .venv + + - name: Cache pico-faces checkpoints + uses: actions/cache@v6 + with: + key: pico-faces-${{ hashFiles('setup_venv.py') }} + path: model/pico_faces + + - name: Create model-export venv and fetch the checkpoints + run: ./setup_venv.sh + + # The committed AI layer lists ai_layer/model_pte.c, which only step 2 + # generates, and csolution refuses a layer whose files are missing. An + # empty placeholder lets step 1 run; create_ai_layer.py overwrites it. + - name: Generate the MLOps information + run: | + touch ai_layer/model_pte.c + cbuild setup ${SOLUTION}.csolution.yml --active ${TARGET} --packs + + - name: Create the AI layer + run: python3 create_ai_layer.py ${SOLUTION}.cbuild-mlops.yml + + - name: Render the host reference of the boot demo + run: python3 model/verify_export.py --stage fakequant --seed 3 --class 1 --w 4 --k 4 --out reference/pf_fakequant.png + + - name: Upload the AI layer + uses: actions/upload-artifact@v4 + with: + name: ai-layer + path: | + ai_layer/ + reference/ + # Full build plus an FVP run, once per toolchain. Linux only: the FVP needs # an Arm user-based license, so matrixing this across hosts is not cheap. build-and-run: name: Build and run on Corstone-320 (${{ matrix.toolchain }}) runs-on: ubuntu-latest + needs: export strategy: fail-fast: false @@ -32,6 +99,11 @@ jobs: - name: Checkout repo uses: actions/checkout@v7 + - name: Download the AI layer + uses: actions/download-artifact@v4 + with: + name: ai-layer + - name: Install tools uses: ARM-software/cmsis-actions/vcpkg@v1 @@ -41,7 +113,8 @@ jobs: # The FVP loads the runner's libpython3.11, whose standard library is # not where that build's prefix says; the model then dies at start-up # with "Fatal Python error: init_fs_encoding". PYTHONHOME (set on the - # FVP step only) points it at a complete Python 3.11. + # FVP step only) points it at a complete Python 3.11, which also runs + # the image comparison. - name: Set up Python 3.11 for the FVP uses: actions/setup-python@v5 with: @@ -53,25 +126,8 @@ jobs: key: cmsis-packs-${{ hashFiles('*.csolution.yml') }} path: /home/runner/.cache/arm/packs - # torch + executorch is a multi-gigabyte install and otherwise dominates - # the job. Keyed on the requirements files so a pin change rebuilds it. - - name: Cache model-export venv - uses: actions/cache@v6 - with: - key: venv-${{ runner.os }}-${{ hashFiles('requirements*.txt', 'setup_venv.py') }} - path: .venv - - - name: Create model-export venv - run: ./setup_venv.sh - - - name: Generate the MLOps information - run: cbuild setup ${SOLUTION}.csolution.yml --active ${TARGET} --packs - - - name: Create the AI layer - run: python3 create_ai_layer.py ${SOLUTION}.cbuild-mlops.yml - - name: Build - run: cbuild ${SOLUTION}.csolution.yml --active ${TARGET} --toolchain ${{ matrix.toolchain }} + run: cbuild ${SOLUTION}.csolution.yml --active ${TARGET} --toolchain ${{ matrix.toolchain }} --packs - name: Run on the FVP env: @@ -80,7 +136,7 @@ jobs: IMAGE=$(find out -name "${SOLUTION}.axf" -o -name "${SOLUTION}.elf" | head -1) echo "Running ${IMAGE}" # stdio is retargeted to semihosting, so the output arrives on the - # FVP's own stdout. + # FVP's own stdout; the application ends the simulation itself. FVP_Corstone_SSE-320 \ -f board/Corstone-320/fvp_config.txt \ -a "${IMAGE}" \ @@ -90,6 +146,15 @@ jobs: echo "Checking simulation output" grep -q "Test_result: PASS" fvp_stdout.log + # The run writes the generated image to out/fvp_image.bin through + # semihosting; it has to match what the quantized graphs compute on the + # host (the NPU's integer arithmetic differs from the fake-quant graphs + # only by rounding). + - name: Compare the image with the host reference + run: | + python -m pip install --quiet numpy pillow + python model/verify_export.py --compare out/fvp_image.bin reference/pf_fakequant.png --min-psnr 35 + - name: Upload artifacts if: always() uses: actions/upload-artifact@v4 @@ -97,16 +162,17 @@ jobs: name: corstone-320-${{ matrix.toolchain }} path: | fvp_stdout.log + out/fvp_image.bin + out/fvp_result.txt out/**/*.axf out/**/*.elf - ai_layer/ # The same solution for the Alif Ensemble E8 DevKit (target-type DevKit-E8), - # once per toolchain. Build only: the hardware is not in CI. The shipped - # ai_layer/ is used, so no venv is needed. + # once per toolchain. Build only: the hardware is not in CI. build-devkit-e8: name: Build for the Alif Ensemble E8 DevKit (${{ matrix.toolchain }}) runs-on: ubuntu-latest + needs: export strategy: fail-fast: false @@ -117,6 +183,11 @@ jobs: - name: Checkout repo uses: actions/checkout@v7 + - name: Download the AI layer + uses: actions/download-artifact@v4 + with: + name: ai-layer + - name: Install tools uses: ARM-software/cmsis-actions/vcpkg@v1 @@ -126,14 +197,11 @@ jobs: - name: Cache packs uses: actions/cache@v6 with: - key: cmsis-packs + key: cmsis-packs-${{ hashFiles('*.csolution.yml') }} path: /home/runner/.cache/arm/packs - - name: Generate the MLOps information - run: cbuild setup ${SOLUTION}.csolution.yml --active DevKit-E8 --packs - - name: Build - run: cbuild ${SOLUTION}.csolution.yml --active DevKit-E8 --toolchain ${{ matrix.toolchain }} + run: cbuild ${SOLUTION}.csolution.yml --active DevKit-E8 --toolchain ${{ matrix.toolchain }} --packs - name: Upload artifacts if: always() @@ -142,6 +210,7 @@ jobs: name: devkit-e8-${{ matrix.toolchain }} path: | out/**/*.axf + out/**/*.elf out/**/*.hex # The export flow must work on all three host OSes -- that is the point of a @@ -186,6 +255,8 @@ jobs: # No cbuild-mlops.yml without cbuild; importing the script checks # that it parses under this interpreter. "${PY}" -c "import create_ai_layer; print('create_ai_layer.py OK')" + # The checkpoints setup_venv.py downloaded load on this host. + "${PY}" -c "import sys; sys.path.insert(0, 'model'); import model; model.load_checkpoint(); print('checkpoints OK')" - name: Report resolved versions shell: bash diff --git a/.gitignore b/.gitignore index 0a0cfc37..9596aa98 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,15 @@ __pycache__/ *.pyc -# The exported ExecuTorch program. create_ai_layer.py writes it next to the -# generated AI layer; the C array in ai_layer/model_pte.c is what gets built. +# The exported ExecuTorch program and its C array. create_ai_layer.py writes +# both into the generated AI layer; at ~2.8 MB (a ~14 MB C source) they are +# regenerated by "Create AI layer" rather than committed. The clayer and the +# headers next to them are committed. *.pte +ai_layer/model_pte.c + +# The pico-faces checkpoints setup_venv.py downloads (see PICO_FACES_FILES). +model/pico_faces/ # CMSIS-Toolbox build outputs. RTE/ is tracked in full: the layers' configuration # files and the per-context RTE/__/ headers the toolbox writes. diff --git a/.vscode.d/tasks.json b/.vscode.d/tasks.json index 2db55b93..de6e5950 100644 --- a/.vscode.d/tasks.json +++ b/.vscode.d/tasks.json @@ -29,7 +29,7 @@ }, { "label": "Create AI layer", - "detail": "Export model/model.py for the NPU in cmsis-executorch.cbuild-mlops.yml and write ai_layer/ (run a CMSIS build or 'cbuild setup' first)", + "detail": "Export model/model.py (pico-faces) for the NPU in cmsis-executorch.cbuild-mlops.yml and write ai_layer/ (run a CMSIS build or 'cbuild setup' first; takes a few minutes)", "type": "shell", "command": "python3", "args": ["create_ai_layer.py", "cmsis-executorch.cbuild-mlops.yml"], @@ -43,12 +43,14 @@ }, { "label": "Alif: Install M55_HP debug stubs (DevKit-E8, single core configuration)", - "detail": "Programs the ATOC/MRAM stubs with Alif SETOOLS so J-Link can load and debug the M55_HP core. Overwrites build/config/M55_HP_mram_cfg.json and build/images/M55_HP_mram_stub.bin in the SETOOLS tree, then runs app-gen-toc and app-write-mram; stops at the first failing step. Set SW4 to SEUART, set 'alif.setools.root' in your VS Code settings.", + "detail": "Programs the ATOC/MRAM stubs with Alif SETOOLS so J-Link can load and debug the M55_HP core. Selects the DevKit-E8's part in SETOOLS first (E8 AE822FA0E5597LS0, revision A0): app-gen-toc writes it into the ATOC, and the Secure Enclave does not boot an ATOC built for another part. The part and revision are written into SETOOLS' utils/global-cfg.db before tools-config runs, because tools-config refuses to start while another project's 'tools-config -p' without '-r' has left a revision the part does not have ('Revision is invalid!'). Overwrites build/config/M55_HP_mram_cfg.json and build/images/M55_HP_mram_stub.bin in the SETOOLS tree, then runs app-gen-toc and app-write-mram; stops at the first failing step. Set SW4 to SEUART, set 'alif.setools.root' in your VS Code settings.", "type": "shell", "command": [ "cp './.alif/M55_HP_mram_cfg.json' '${config:alif.setools.root}/build/config/M55_HP_mram_cfg.json' &&", "cp './.alif/M55_HP_mram_stub.bin' '${config:alif.setools.root}/build/images/M55_HP_mram_stub.bin' &&", "cd '${config:alif.setools.root}' &&", + "sed -i.bak -E -e 's|(\"Part#\": *\")[^\"]*|\\1E8 (AE822FA0E5597LS0) - 5.5 MRAM / 9.75 SRAM|' -e 's|(\"Revision\": *\")[^\"]*|\\1A0|' utils/global-cfg.db &&", + "./tools-config -p 'E8 (AE822FA0E5597LS0) - 5.5 MRAM / 9.75 SRAM' -r A0 &&", "./app-gen-toc -f 'build/config/M55_HP_mram_cfg.json' &&", "./app-write-mram -p ${input:discoverCOM}" ], @@ -57,6 +59,8 @@ "cp './.alif/M55_HP_mram_cfg.json' '${config:alif.setools.root}/build/config/M55_HP_mram_cfg.json'; if (-not $?) { exit 1 };", "cp './.alif/M55_HP_mram_stub.bin' '${config:alif.setools.root}/build/images/M55_HP_mram_stub.bin'; if (-not $?) { exit 1 };", "cd '${config:alif.setools.root}'; if (-not $?) { exit 1 };", + "(Get-Content utils/global-cfg.db -Raw) -replace '(\\x22Part#\\x22:\\s*\\x22)[^\\x22]*', '$1E8 (AE822FA0E5597LS0) - 5.5 MRAM / 9.75 SRAM' -replace '(\\x22Revision\\x22:\\s*\\x22)[^\\x22]*', '$1A0' | Set-Content utils/global-cfg.db -NoNewline; if (-not $?) { exit 1 };", + "./tools-config -p 'E8 (AE822FA0E5597LS0) - 5.5 MRAM / 9.75 SRAM' -r A0; if (-not $?) { exit 1 };", "./app-gen-toc -f 'build/config/M55_HP_mram_cfg.json'; if (-not $?) { exit 1 };", "./app-write-mram -p ${input:discoverCOM}" ], @@ -112,7 +116,7 @@ { "id": "setupPythonVersion", "type": "promptString", - "description": "Python version for uv (>=3.10,<3.15), e.g. 3.12 or 3.12.10", + "description": "Python version for uv (>=3.10,<3.14), e.g. 3.12 or 3.12.10", "default": "3.12" } ] diff --git a/README.md b/README.md index c421783c..0e9e8714 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ -# ExecuTorch on Ethos-U85: hackathon guide - -The Arm ExecuTorch example, on its `hackathon` branch with the Alif board -added. One CMSIS solution runs a tiny int8 CNN on the Ethos-U85 of the **Alif Ensemble E8 DevKit** (Cortex-M55 -HP core) and on the **Corstone-320 FVP**; you switch between them by -target-type. The model is exported from PyTorch in three steps: the -CMSIS-Toolbox describes the target, `create_ai_layer.py` turns that into the -AI layer, the toolbox builds the application. This page takes you from an -empty machine to a debug session on the board. Everything about the example -itself is in [documentation/example.md](documentation/example.md). +# pico-faces on Ethos-U85 with ExecuTorch: hackathon guide + +The Arm ExecuTorch example, on its `hackathon` branch: a generative model on +a microcontroller. [pico-faces](https://github.com/cpldcpu/pico-faces) by +cpldcpu, a latent diffusion transformer (2.5M parameters) with a small +decoder, generates 128x128 faces; exported through ExecuTorch, every layer of +it runs on the Ethos-U85 NPU while the Cortex-M drives the sampling loop. One +CMSIS solution runs it on the **Alif Ensemble E8 DevKit** (Cortex-M55 HP core; +the faces appear on the board's LCD, a new one per joystick press) and on the +**Corstone-320 FVP**; you switch between them by target-type. The model is +exported from PyTorch in three steps: the CMSIS-Toolbox describes the target, +`create_ai_layer.py` turns that into the AI layer, the toolbox builds the +application. This page takes you from an empty machine to a debug session on +the board. Everything about the example itself is in +[documentation/example.md](documentation/example.md). ## 1. Host tools @@ -20,7 +25,7 @@ itself is in [documentation/example.md](documentation/example.md). 3. Nothing else by hand: when you open the project, the Arm Tools Environment Manager offers to install the tools pinned in `vcpkg-configuration.json` (CMSIS-Toolbox, Arm Compiler 6, GCC, CMake, Ninja, and on Linux and - Windows the Corstone-320 FVP; macOS runs it in Docker, see step 8). Accept. + Windows the Corstone-320 FVP; macOS runs it in Docker, see step 6). Accept. 4. Optional: the **CMSIS Developer Assistant** extension lets an AI agent (Claude Code or GitHub Copilot Chat) build, flash and debug the board through an MCP server. Install it, install one of the agents, run @@ -29,7 +34,7 @@ itself is in [documentation/example.md](documentation/example.md). ## 2. Alif and SEGGER tools (board only) -1. **Alif SETOOLS** V1.110.000 or later from the +1. **Alif SETOOLS** V1.110.00 or later (V1.112.00 is current) from the [Alif software and tools page](https://alifsemi.com/support/software-tools/ensemble/) (login required). Unpack it; on Linux and macOS make the tools executable and install the Python packages its README lists. Add the root directory @@ -77,12 +82,19 @@ installation (`PyTorch::ExecuTorch`, `AlifSemiconductor::Ensemble`, CMSIS). In the CMSIS view open **Manage Solution**, choose the target-type **DevKit-E8** (or **SSE-320-U85** for the FVP) and click **Apply**. -The repository ships a generated AI layer, so no Python is needed to build. -To change the model, run **Terminal > Run Task > Setup Python virtual -environment** once (several GB of PyTorch, takes a while; the **(uv)** -variant of the task uses uv and can download the Python version it asks -for), edit `model/model.py`, and run the task **Create AI layer** before -building. +The model data is generated rather than committed, so a fresh checkout +needs Python once before the first build: + +1. **Terminal > Run Task > Setup Python virtual environment** (several GB of + PyTorch, takes a while; the **(uv)** variant of the task uses uv and can + download the Python version it asks for). It also downloads the + pico-faces checkpoints (about 60 MB, checked by SHA-256) into + `model/pico_faces/`. +2. **Terminal > Run Task > Create AI layer** exports the model for the NPU + and writes `ai_layer/` (a few minutes). It reads + `cmsis-executorch.cbuild-mlops.yml`, which the CMSIS Solution extension + writes when it loads the solution (click **Build** once if the file is + missing). Run the task again after changing `model/model.py`. ## 5. Prepare the board once @@ -92,8 +104,13 @@ debugger needs that table to point at a debug stub. 1. SW4 to **SEUART**, PRG USB attached. 2. **Terminal > Run Task > Alif: Install M55_HP debug stubs (DevKit-E8, single core configuration)**. Choose COM port discovery (`-d`) the first time; SETOOLS remembers the port. The task + selects the DevKit-E8's part in SETOOLS (`tools-config -p 'E8 + (AE822FA0E5597LS0) ...' -r A0`: the part goes into the table of contents, + and the Secure Enclave does not boot a table built for another part), copies the configuration and stub from `.alif/` into the SETOOLS tree and - runs `app-gen-toc` and `app-write-mram`. + runs `app-gen-toc` and `app-write-mram`. If `app-write-mram` reports a + different revision of the board, answer `y`: only the part number decides + whether the table boots. 3. SW4 to **UART4**. Repeat this after another project has reprogrammed the table. @@ -104,25 +121,66 @@ Repeat this after another project has reprogrammed the table. port, 115200 baud. 2. In the CMSIS view click **Build**, then **Debug** (or **Run**). Keil Studio starts the J-Link GDB server over SWD, loads the image into MRAM and stops - at `main`; continue with F5. The console shows: + at `main`; continue with F5. The console shows the Ethos-U banner, the + program and its two methods, the timings of the boot demo, the CRC of the + image, an ASCII preview of the face and the pass marker: ```text Ethos-U version info: Arch: v2.0.0 MACs/cc: 256 Cmd stream: v1 - ExecuTorch Ethos-U85 example: 8896 byte model - Output: 10 element(s): 0.0079 0.0459 0.0475 -0.0475 0.0791 0.0411 -0.0285 -0.0744 -0.2246 -0.0016 + ExecuTorch pico-faces (m3_long_cfg, a16w8/a8w8): 2796016 byte program + Methods: + dit_step 2 input(s), 1 output(s), 16384 planned byte(s) + decode 1 input(s), 1 output(s), 245760 planned byte(s) + Generating: seed 3, 4 steps, class 1, w 4.0 + Display: 480x800 RGB888 panel started + dit_step: 8 call(s), 68 ms total (8 ms each) + decode: 3 ms + total: 78 ms at 400 MHz (wall clock; not meaningful on the FVP) + dit_step: NPU 27132 kcycles, active 95%, MAC active 39%, 32 MAC/cycle, read 21279 kB on AXI0 + 0 kB on AXI1 + decode: NPU 1303 kcycles, active 50%, MAC active 37%, 83 MAC/cycle, read 434 kB on AXI0 + 0 kB on AXI1 + Image: 128x128x3, CRC32 6b938c66 + |::::::::....... . ..........| + |::::::::........... ..... | + ... Test_result: PASS + Interactive: send "G [k_steps] [class] [w]" (viewer/view_serial.py) or "I" + Joystick: left = one new image, right = start/stop continuous generation + ``` + + A face with guidance takes 78 ms: eight `dit_step` calls of 8.5 ms on the + NPU and a 3 ms decode. The same face appears on the LCD, scaled to + 384 x 384 in the centre of the screen; it is bit for bit the image the FVP + produces. (The CRC depends on the exported program, which can differ + slightly between the machines that run **Create AI layer**.) +3. Press the **SW2 joystick** to the left for a new face (the next seed; class + and guidance follow from it), to the right to generate faces back to back + until you press right again. Each one is reported on the console. +4. Or request faces from the host with pico-faces' viewer (close the Serial + Monitor first, the viewer needs the port; in a clone of + [pico-faces](https://github.com/cpldcpu/pico-faces), + `pip install pyserial pillow`): + + ```bash + python viewer/view_serial.py --port /dev/tty.usbmodemXXXX --seed 3 --steps 4 --class 1 --cfg 4 --show ``` -3. Set a breakpoint after `module.forward(input)` in `src/app_main.cpp` and - inspect the output tensor, or ask the CMSIS Developer Assistant to do it: - "Build for the DevKit-E8, load it, break after the inference and show me - the output logits." + Classes are 0 (female, neutral), 1 (female, smiling), 2 (male, neutral), + 3 (male, smiling) and 4 (unconditional); `--cfg` is the guidance strength + (0 = plain), `--steps` 8, 4, 2 or 1. +5. Set a breakpoint after `generate()` in `src/app_main.cpp` and inspect the + timings in `tm`, or ask the CMSIS Developer Assistant to do it: "Build for + the DevKit-E8, load it, break after the first generate() and show me the + NPU cycles per method." **FVP instead of the board:** choose the **SSE-320-U85** target-type and click -**Run** or **Debug**; the same output appears in the terminal. On macOS the +**Run** or **Debug**; the same output appears in the terminal (a run takes a +few minutes), and the image lands in `out/fvp_image.bin`. +`python3 model/verify_export.py --compare out/fvp_image.bin ` +compares it with the host's rendering of the same seed (see +[documentation/example.md](documentation/example.md)). On macOS the FVP runs in Docker (Docker Desktop must be running; the first run builds the image, about 100 MB). On Windows set `model:` in the csolution's SSE-320-U85 target-set to `FVP_Corstone_SSE-320`. @@ -130,7 +188,8 @@ target-set to `FVP_Corstone_SSE-320`. ## 7. If something does not work - **J-Link connects but never stops at `main`:** either the table of contents - does not point at the debug stub (repeat step 5), or the image cannot boot. + does not point at the debug stub or was built for another part, which the + Secure Enclave skips (repeat step 5), or the image cannot boot. Look before reprogramming: in the debugger, read the vector table at `0x80200000` and the fault registers (CFSR/HFSR); a PC of `0xEFFFFFFE` is a lockup at reset, which an image linked to run from ITCM produces when the @@ -141,10 +200,23 @@ target-set to `FVP_Corstone_SSE-320`. until it is power-cycled. - **No console output:** SW4 is still on `SEUART`, or the port was opened before the switch was moved. Set `UART4` and reopen the port. +- **Every SETOOLS tool prints `Revision is invalid!`:** SETOOLS stores the + selected part and revision in `utils/global-cfg.db`, and a + `tools-config -p` without `-r` (another project switching to an E7 or the + AppKit-E8, for example) keeps a revision the new part does not have. From + then on even `tools-config` refuses to run. The stub task of step 5 writes + the DevKit's part and revision into that file before it calls + `tools-config`, so it recovers by itself; for SETOOLS commands of your own, + set `"Part#"` to `"E8 (AE822FA0E5597LS0) - 5.5 MRAM / 9.75 SRAM"` and + `"Revision"` to `"A0"` there. - **`app-write-mram` gets no answer:** press reset while it waits, check SW4 is on `SEUART`, close any terminal holding the port. -- **"torch is not installed":** run the task **Setup Python virtual - environment** first; it is only needed to regenerate the AI layer. +- **The build cannot find `ai_layer/model_pte.c`:** the model data is not + committed; run the tasks **Setup Python virtual environment** and **Create + AI layer** (step 4). +- **"`.venv` does not exist" or "`model/pico_faces/...` is missing":** run the + task **Setup Python virtual environment** first; it creates the + environment and downloads the checkpoints. - After **Apply**, the extension adds a J-Link entry to `.vscode/launch.json` next to the committed FVP entry. That is expected. diff --git a/RTE/_Debug_DevKit-E8/Pre_Include_Global.h b/RTE/_Debug_DevKit-E8/Pre_Include_Global.h index 78cdd4d3..7e77b2f1 100644 --- a/RTE/_Debug_DevKit-E8/Pre_Include_Global.h +++ b/RTE/_Debug_DevKit-E8/Pre_Include_Global.h @@ -1,6 +1,6 @@ /* * CSOLUTION generated file: DO NOT EDIT! - * Generated by: csolution version 2.14.1+p38-gf512b381 + * Generated by: csolution version 2.14.1+p88-g94cb0c5e * * Project: 'cmsis-executorch.Debug+DevKit-E8' * Target: 'Debug+DevKit-E8' @@ -12,10 +12,10 @@ /* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */ // enabling global pre includes #define ETHOSU_ARCH u85 -/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.5.1 */ #define EXECUTORCH_BUILD_ARM_BAREMETAL 1 #define ET_USE_ETHOS_U_BACKEND 1 -/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.5.1 */ /* ExecuTorch global configuration */ #define C10_USING_CUSTOM_GENERATED_MACROS #define FLATBUFFERS_MAX_ALIGNMENT 1024 diff --git a/RTE/_Debug_DevKit-E8/RTE_Components.h b/RTE/_Debug_DevKit-E8/RTE_Components.h index 161f6fa7..fad541d4 100644 --- a/RTE/_Debug_DevKit-E8/RTE_Components.h +++ b/RTE/_Debug_DevKit-E8/RTE_Components.h @@ -1,6 +1,6 @@ /* * CSOLUTION generated file: DO NOT EDIT! - * Generated by: csolution version 2.14.1+p38-gf512b381 + * Generated by: csolution version 2.14.1+p88-g94cb0c5e * * Project: 'cmsis-executorch.Debug+DevKit-E8' * Target: 'Debug+DevKit-E8' @@ -26,21 +26,31 @@ #define RTE_CMSIS_Compiler_STDOUT_Custom /* CMSIS-Compiler STDOUT: Custom */ /* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */ #define RTE_ETHOS_U_CORE_DRIVER +/* AlifSemiconductor::BSP:External peripherals:ILI9806E LCD panel@2.2.0 */ +#define RTE_Drivers_MIPI_DSI_ILI9806E_PANEL /* Driver ILI9806E LCD panel*/ /* AlifSemiconductor::CMSIS Driver:USART@2.2.0 */ #define RTE_Drivers_USART /* Driver UART */ +/* AlifSemiconductor::Device:SOC Peripherals:CDC@2.2.0 */ +#define RTE_Drivers_CDC200 /* Driver CDC200*/ +/* AlifSemiconductor::Device:SOC Peripherals:GPIO@2.2.0 */ +#define RTE_Drivers_IO /* Driver GPIO */ +/* AlifSemiconductor::Device:SOC Peripherals:MIPI DSI CSI2 DPHY@2.2.0 */ +#define RTE_Drivers_MIPI_DSI_CSI2_DPHY /* Driver DPHY */ +/* AlifSemiconductor::Device:SOC Peripherals:MIPI DSI@2.2.0 */ +#define RTE_Drivers_MIPI_DSI /* Driver MIPI DSI */ /* AlifSemiconductor::Device:SOC Peripherals:PINCONF@2.2.0 */ #define RTE_Drivers_LL_PINCONF /* Driver PinPAD and PinMux */ -/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.5.1 */ #define RTE_ML_EXECUTORCH_OP_QUANTIZED_DEQUANTIZE /* ExecuTorch op_dequantize */ -/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.5.1 */ #define RTE_ML_EXECUTORCH_OP_QUANTIZED_QUANTIZE /* ExecuTorch op_quantize */ -/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.5.1 */ #define RTE_ML_EXECUTORCH_BACKEND_ETHOS_U /* ExecuTorch Ethos-U Backend (Cortex-M host) */ -/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.5.1 */ #define RTE_ML_EXECUTORCH_KERNEL_REGISTRATION /* ExecuTorch Kernel Registration */ -/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.5.1 */ #define RTE_ML_EXECUTORCH_KERNEL_UTILS /* ExecuTorch Kernel Utils */ -/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.5.1 */ #define RTE_ML_EXECUTORCH_RUNTIME /* ExecuTorch Runtime */ diff --git a/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h b/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h index 37e190f9..59d04942 100644 --- a/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h +++ b/RTE/_Debug_SSE-320-U85/Pre_Include_Global.h @@ -1,6 +1,6 @@ /* * CSOLUTION generated file: DO NOT EDIT! - * Generated by: csolution version 2.14.1+p38-gf512b381 + * Generated by: csolution version 2.14.1+p88-g94cb0c5e * * Project: 'cmsis-executorch.Debug+SSE-320-U85' * Target: 'Debug+SSE-320-U85' @@ -12,10 +12,10 @@ /* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */ // enabling global pre includes #define ETHOSU_ARCH u85 -/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.5.1 */ #define EXECUTORCH_BUILD_ARM_BAREMETAL 1 #define ET_USE_ETHOS_U_BACKEND 1 -/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.5.1 */ /* ExecuTorch global configuration */ #define C10_USING_CUSTOM_GENERATED_MACROS #define FLATBUFFERS_MAX_ALIGNMENT 1024 diff --git a/RTE/_Debug_SSE-320-U85/RTE_Components.h b/RTE/_Debug_SSE-320-U85/RTE_Components.h index 571f80b4..46fb28de 100644 --- a/RTE/_Debug_SSE-320-U85/RTE_Components.h +++ b/RTE/_Debug_SSE-320-U85/RTE_Components.h @@ -1,6 +1,6 @@ /* * CSOLUTION generated file: DO NOT EDIT! - * Generated by: csolution version 2.14.1+p38-gf512b381 + * Generated by: csolution version 2.14.1+p88-g94cb0c5e * * Project: 'cmsis-executorch.Debug+SSE-320-U85' * Target: 'Debug+SSE-320-U85' @@ -30,17 +30,17 @@ #define RTE_TIMEOUT 1 /* ARM::Machine Learning:NPU Support:Ethos-U Driver&Generic U85@1.26.2 */ #define RTE_ETHOS_U_CORE_DRIVER -/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized dequantize@1.5.1 */ #define RTE_ML_EXECUTORCH_OP_QUANTIZED_DEQUANTIZE /* ExecuTorch op_dequantize */ -/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch Operators:Quantized quantize@1.5.1 */ #define RTE_ML_EXECUTORCH_OP_QUANTIZED_QUANTIZE /* ExecuTorch op_quantize */ -/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Backend EthosU@1.5.1 */ #define RTE_ML_EXECUTORCH_BACKEND_ETHOS_U /* ExecuTorch Ethos-U Backend (Cortex-M host) */ -/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Kernel Registration@1.5.1 */ #define RTE_ML_EXECUTORCH_KERNEL_REGISTRATION /* ExecuTorch Kernel Registration */ -/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Kernel Utils@1.5.1 */ #define RTE_ML_EXECUTORCH_KERNEL_UTILS /* ExecuTorch Kernel Utils */ -/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.4.1 */ +/* PyTorch::Machine Learning:ExecuTorch:Runtime@1.5.1 */ #define RTE_ML_EXECUTORCH_RUNTIME /* ExecuTorch Runtime */ diff --git a/ai_layer/ai_layer.clayer.yml b/ai_layer/ai_layer.clayer.yml index 1322ac2c..78a4ebb7 100644 --- a/ai_layer/ai_layer.clayer.yml +++ b/ai_layer/ai_layer.clayer.yml @@ -3,13 +3,14 @@ # model/model.py or the csolution's mlops: node. layer: type: AI - description: TinyCNN int8 image classifier for Ethos-U85 + description: pico-faces rectified-flow face generator for Ethos-U85 packs: - - pack: PyTorch::ExecuTorch@1.4.1 + - pack: PyTorch::ExecuTorch@1.5.1 define: - - ET_LOG_ENABLED: 0 + - ET_LOG_ENABLED: 1 + - ET_MIN_LOG_LEVEL: Error add-path: - . @@ -23,7 +24,8 @@ layer: - component: Machine Learning:ExecuTorch Operators:Quantized quantize groups: - - group: TinyCNN + - group: PicoFaces files: - file: ./model_pte.c - file: ./model_pte.h + - file: ./model_params.h diff --git a/ai_layer/model_params.h b/ai_layer/model_params.h new file mode 100644 index 00000000..0f29e3b7 --- /dev/null +++ b/ai_layer/model_params.h @@ -0,0 +1,758 @@ +// Generated by create_ai_layer.py from get_params() in model/model.py -- do not edit. +#pragma once + +#define PF_VARIANT "m3_long_cfg" +#define PF_QUANT "a16w8/a8w8" +#define PF_LATENT_CH 8 +#define PF_LATENT_HW 16 +#define PF_IMG_CH 3 +#define PF_IMG_HW 128 +#define PF_COND_DIM 128 +#define PF_N_CLASSES 4 +#define PF_NULL_CLASS 4 +#define PF_N_COND 5 +#define PF_K_MAX 8 +#define PF_N_CFG_W 3 +#define PF_MACS_DIT_STEP 110862336 +#define PF_MACS_DECODE 108527616 + +static const float pf_schedule[8] = { + 1.0f, 0.875f, 0.75f, 0.625f, 0.5f, 0.375f, 0.25f, 0.125f, +}; + +static const float pf_cfg_w[3] = { + 4.0f, 6.0f, 8.0f, +}; + +static const float pf_cond[5][8][128] = { + { + { + -1.19943476f, 0.103804976f, 0.136562869f, 0.403869957f, 0.112720974f, -1.13884139f, 0.175973892f, 0.219021901f, + -0.366773665f, 0.363391578f, 0.106817655f, 0.169369102f, 0.117557563f, 0.504136026f, -0.793248951f, 0.0114166802f, + -1.42425156f, -1.36040747f, 0.0925844833f, 0.0984721407f, 0.003575827f, 0.0365800671f, -4.30147552f, -0.947152913f, + 0.0721685141f, 0.0133737717f, -1.3378737f, 0.197082728f, 0.130707085f, -0.0264369529f, -0.0763685182f, -2.0532577f, + -0.882606864f, -2.86364627f, 0.120616429f, 0.0352996439f, 0.150541902f, 0.113137662f, 0.434817314f, 0.33688271f, + 0.036307428f, 0.00746208709f, 0.547157764f, 0.131747246f, 0.150908291f, -0.0414705202f, -1.45441914f, 0.0722233504f, + -0.487728715f, -0.148328811f, 0.200003564f, 0.474228591f, 0.0650881529f, 0.290172786f, -0.64867115f, -0.767971516f, + -3.74440336f, 1.27137351f, 0.290956557f, -3.26415229f, -0.636946321f, -3.2702086f, -0.750864208f, 0.0513311028f, + -0.590332985f, -1.8723588f, -2.5208509f, -0.150209546f, -3.35050535f, 0.0272536464f, 0.267508298f, -3.03893185f, + 0.111210436f, -0.646374881f, -3.85108638f, 0.00211119256f, -0.040174637f, -0.0211010128f, -3.079283f, 0.163860261f, + -1.53392673f, 0.0662775487f, -0.0361811444f, 0.0857123062f, -0.0379347652f, -3.78045321f, 1.04540241f, -3.58954859f, + -0.0510729365f, -3.58284926f, -3.40466928f, -3.67499018f, -0.104160056f, 1.0210427f, -0.504254699f, 0.104930371f, + 0.25771746f, -0.655095398f, 0.130081698f, 0.0681628212f, -3.2069695f, 0.18854633f, 0.0887730718f, 0.0722799748f, + -3.62117219f, 0.0174629018f, 0.119293101f, 0.147289991f, -0.115880363f, -3.6998744f, 0.172605276f, 0.294987053f, + 0.0140887611f, -2.49925494f, -3.44129729f, -1.28066111f, 0.169725671f, 0.0412407033f, -3.86365557f, 0.106899887f, + 0.033371266f, 0.0592505038f, -1.15162563f, 0.944659948f, -1.53378415f, -0.227571309f, -0.86935097f, 0.125859171f, + }, + { + -1.99555886f, 0.0684241056f, 0.100649126f, 0.475992501f, 0.228917629f, -1.37176812f, 0.259257257f, 0.24150756f, + -0.80439508f, 0.222720787f, 0.116660021f, 0.118016675f, 0.460731655f, -0.378264725f, -0.657062829f, 0.0638025478f, + -1.85027575f, -1.5713402f, 0.0862455741f, 0.139232665f, 0.0168564692f, -0.0448916107f, -6.3716054f, -1.57054615f, + 0.0924993977f, -0.030783534f, -2.9001689f, 0.168514654f, -2.95282936f, -0.293023467f, -0.0310602318f, -3.84112906f, + -0.854857206f, -5.46087456f, 0.133061111f, 0.125418067f, 0.265927732f, 0.0870344043f, -0.163001329f, 0.193304658f, + 0.102037698f, -0.0882560238f, 0.077583313f, 0.219638482f, 0.218367785f, -0.0167177487f, -1.32055056f, 0.00690719485f, + -1.09532595f, -0.221847981f, 0.101478428f, 0.662873566f, 0.201987758f, 0.254007816f, -0.773212671f, -0.937352061f, + -6.27132416f, 1.85911012f, 0.556967497f, -5.2236886f, -1.10349715f, -5.45006657f, -0.821209133f, 0.230985761f, + -0.127377614f, -3.79835653f, -4.50522184f, 0.0722905695f, -5.82312632f, 0.154258534f, 0.225179195f, -5.30927563f, + 0.115855612f, -0.741827726f, -6.30034256f, 0.112866819f, -0.0277810674f, 0.0687439069f, -4.9469471f, 0.204334453f, + -2.79306984f, 0.18101126f, 0.0533369631f, 0.183402538f, 0.0824884102f, -6.21213531f, 0.0214210153f, -5.32310629f, + -0.0431693643f, -6.10527134f, -5.29036427f, -6.32218313f, 0.0758788511f, 0.966289937f, -0.673357606f, 0.0983957201f, + 0.0474261977f, -1.06772029f, 0.207194924f, -0.0146853663f, -5.3261652f, 0.223614514f, 0.154749453f, 0.062489219f, + -5.85608482f, -0.0157372691f, 0.234259725f, 0.10532096f, -0.0535659641f, -6.68825722f, 0.287957728f, 0.381878972f, + 0.120545641f, -4.04973793f, -5.95863724f, -1.89135909f, 0.287185222f, 0.157872722f, -6.39777565f, 0.206911802f, + -0.128427073f, 0.127161667f, -0.790632725f, -0.539326131f, -3.05735183f, -0.0387249291f, -1.57841849f, 0.0983982086f, + }, + { + -3.4618721f, 0.0482278913f, 0.122715972f, 0.384129316f, 0.259087294f, -1.37658489f, 0.236367911f, 0.226850286f, + -0.692059696f, 0.229423374f, 0.120232381f, 0.152775615f, 0.496889025f, -1.00244665f, -1.33166492f, 0.136823088f, + -1.94460988f, -2.87548971f, 0.0569194481f, 0.157617718f, 0.0928296372f, -0.0738508776f, -7.45765495f, -1.69312513f, + 0.128352463f, -0.0373573937f, -3.53788042f, 0.178976431f, -3.83524513f, -0.318194926f, 0.0127794482f, -4.59734774f, + -1.00658715f, -6.08264256f, 0.0916001797f, 0.151255161f, 0.242025629f, 0.0348282754f, -0.150417358f, 0.211216629f, + 0.146064043f, -0.221797869f, -1.13137221f, 0.238059416f, 0.200034767f, -0.0187397506f, -1.62670279f, -0.0324807465f, + -0.966354489f, -0.143274277f, -0.0260483585f, -0.161828071f, 0.23458533f, 0.269714028f, -0.539936602f, -0.837006092f, + -6.99163198f, 1.32899213f, 0.694981754f, -6.26230001f, -1.57494569f, -6.44478273f, -0.823202491f, 0.233269826f, + -0.531124175f, -4.39740038f, -5.4539876f, 0.125157595f, -6.43100262f, 0.187477574f, 0.231976137f, -6.13673401f, + 0.100797288f, -0.520478845f, -6.63235998f, 0.171127349f, 0.0474647805f, 0.0420683846f, -5.81103373f, 0.215462416f, + -3.30449605f, 0.164081246f, 0.132382929f, 0.19907333f, 0.134623051f, -6.89435577f, -0.674743414f, -6.56840181f, + -0.0877557471f, -6.74697304f, -6.18740416f, -6.75902987f, 0.0597337447f, 0.763522804f, -0.296117544f, 0.176918656f, + -0.0329941437f, -1.40113938f, 0.27137506f, 0.0106427101f, -6.22406578f, 0.245996118f, 0.154608339f, 0.0786916539f, + -6.66034365f, -0.0763522908f, 0.263750911f, 0.0586164743f, -0.0456729382f, -7.03705406f, 0.277822435f, 0.374804527f, + 0.138795897f, -4.77822399f, -6.51749229f, -1.83324873f, 0.305374861f, 0.189941615f, -6.92006302f, 0.210010126f, + -0.119345583f, 0.181783438f, -0.684237421f, -1.79708838f, -3.62195635f, 0.0473786145f, -1.74803412f, 0.128745288f, + }, + { + -4.43103933f, 0.0497533083f, 0.118723474f, 0.189238995f, 0.219314367f, -1.36481953f, 0.224623471f, 0.208785146f, + -0.750304222f, 0.0937742516f, 0.114632912f, 0.144239038f, 0.461228073f, -0.732371986f, -2.143924f, 0.129409358f, + -1.92627406f, -3.64546251f, 0.0632177815f, 0.122609906f, 0.113452829f, -0.0553626269f, -7.46511269f, -1.66763604f, + 0.142062783f, -0.030592205f, -4.11436892f, 0.188348934f, -4.08924675f, -0.338962525f, 0.0136303958f, -5.02959156f, + -1.83651721f, -6.42061281f, 0.0881991535f, 0.152242377f, 0.230995223f, 0.0718223825f, 0.303691536f, 0.223758966f, + 0.123419777f, -0.221035793f, -2.00144434f, 0.240183413f, 0.194626749f, -0.0472359657f, -1.74765968f, -0.192090511f, + -0.685294211f, -0.103514127f, 0.0427646264f, -0.934587359f, 0.197738484f, 0.242766544f, -0.492337883f, -0.872634828f, + -7.07372427f, 0.630035162f, 0.687302828f, -6.3956666f, -2.70697165f, -6.60257387f, -0.534934938f, 0.221232072f, + -1.22330463f, -4.56612206f, -5.51131773f, 0.0789807215f, -6.86306906f, 0.183931708f, 0.225523546f, -6.1312933f, + 0.087571539f, -0.379322201f, -6.98451805f, 0.159819886f, 0.0677453801f, 0.023307316f, -5.91626167f, 0.194071487f, + -3.69744515f, 0.156611517f, 0.134269178f, 0.187789738f, 0.13540639f, -7.13237095f, -0.61547792f, -6.48963308f, + -0.122582555f, -7.11827183f, -6.15311289f, -7.27983379f, 0.00452066539f, 0.675339043f, -0.104098916f, 0.250383496f, + -0.112492055f, -1.08742332f, 0.23350738f, 0.0230017714f, -6.44488716f, 0.232543439f, 0.147785336f, 0.0992466286f, + -6.85661077f, -0.114081293f, 0.23746486f, 0.111620501f, 0.0086557027f, -7.3441143f, 0.26864633f, 0.364137381f, + 0.158557177f, -5.15864182f, -6.67885828f, -1.31017971f, 0.300060242f, 0.17599608f, -7.39324236f, 0.181879878f, + -0.121026091f, 0.178441048f, -0.914869905f, -2.04698539f, -4.09057283f, 0.0788330361f, -1.77483749f, 0.101306915f, + }, + { + -5.44689798f, 0.0595595874f, 0.120956853f, -0.0118453167f, 0.176376909f, -1.36065078f, 0.178258345f, 0.198375791f, + -1.43915021f, 0.0389744714f, 0.134031102f, 0.136931568f, 0.435956657f, -0.466494501f, -3.0444622f, 0.126224965f, + -2.12939835f, -4.11458683f, 0.0464008749f, 0.0703641772f, 0.120268814f, -0.0161253456f, -7.74022818f, -1.71230364f, + 0.156814814f, -0.00390338711f, -4.77792025f, 0.197136939f, -4.5645175f, -0.328228652f, 0.0275843311f, -5.52309418f, + -2.53834677f, -6.72792053f, 0.0908741876f, 0.156003654f, 0.219681844f, 0.0809698775f, 0.69805187f, 0.186283275f, + 0.108827673f, -0.219289333f, -2.50238895f, 0.217111394f, 0.189455748f, -0.0636894032f, -2.08013248f, -0.419716179f, + -0.739735782f, -0.074388288f, 0.117962226f, -1.61689126f, 0.152581945f, 0.21243073f, -0.467258453f, -1.03535509f, + -7.37339449f, 0.212373555f, 0.662085533f, -6.75502014f, -3.64693737f, -6.8510952f, -0.186327279f, 0.212560683f, + -1.98970306f, -5.03756189f, -5.49807358f, 0.0220744647f, -7.15944576f, 0.15792343f, 0.202766925f, -6.70941591f, + 0.0893897116f, -0.169137299f, -7.3245225f, 0.133323908f, 0.0718085542f, 0.00305650383f, -6.1464138f, 0.185669094f, + -3.91457558f, 0.14775379f, 0.115779065f, 0.182741836f, 0.109629072f, -7.42731047f, -0.526621222f, -6.60556555f, + -0.13306576f, -7.37796736f, -6.47754049f, -7.59177446f, -0.016672153f, 0.42757529f, -0.242110789f, 0.237359866f, + -0.051723063f, -0.855400383f, 0.20859164f, 0.0568513423f, -6.78324032f, 0.212122947f, 0.12910217f, 0.0997500643f, + -7.20865583f, -0.0847538114f, 0.187850535f, 0.104698047f, -0.0235786233f, -7.73797846f, 0.232804328f, 0.333292514f, + 0.165128917f, -5.23726559f, -6.98631334f, -1.02766025f, 0.276843578f, 0.161259547f, -7.80658197f, 0.165706798f, + -0.115040064f, 0.171196237f, -1.14108372f, -2.17877913f, -4.5533514f, 0.0921964645f, -1.59430385f, 0.098020345f, + }, + { + -6.47334051f, 0.04722853f, 0.120281309f, -0.183198452f, 0.143033534f, -1.34385085f, 0.136028752f, 0.173195451f, + -1.89196002f, 0.00147723407f, 0.146900594f, 0.116599351f, 0.396771848f, -0.366327524f, -3.76582789f, 0.113651261f, + -2.16953182f, -4.5913825f, 0.0435782f, 0.017204443f, 0.132056832f, -0.00167989358f, -8.16452503f, -1.78822851f, + 0.163618624f, 0.0690874904f, -5.24319077f, 0.191952184f, -5.04169178f, -0.284928679f, 0.0405432023f, -5.98711491f, + -2.90659165f, -7.10129976f, 0.103783861f, 0.153925642f, 0.19380331f, 0.062472403f, 0.882945716f, 0.149691433f, + 0.0935239196f, -0.205419242f, -2.79880977f, 0.190252125f, 0.177321762f, -0.0660816729f, -2.70319939f, -0.651612639f, + -1.04275775f, -0.0670486167f, 0.122649148f, -2.1245563f, 0.129723653f, 0.176540807f, -0.409765422f, -1.25358415f, + -7.68431377f, -0.0664196759f, 0.606220126f, -7.28151798f, -4.15296364f, -7.36442518f, 0.174611464f, 0.202431276f, + -2.58045173f, -5.58458948f, -5.73181534f, -0.0483566336f, -7.30449677f, 0.130530149f, 0.193035051f, -7.50463295f, + 0.0782123432f, 0.151599377f, -7.41026354f, 0.10752897f, 0.0796605796f, -0.015783757f, -6.29147959f, 0.183010355f, + -3.9705472f, 0.13279736f, 0.105640344f, 0.172870427f, 0.0895927474f, -7.75953341f, -0.556498408f, -7.04490948f, + -0.134278163f, -7.57784605f, -6.98857641f, -7.79750729f, -0.0312641971f, 0.225826353f, -0.496102363f, 0.217002079f, + 0.2240908f, -0.930692732f, 0.175992414f, 0.0755559057f, -7.12425232f, 0.186208814f, 0.117016353f, 0.0930702165f, + -7.60982466f, -0.0637253821f, 0.160756335f, 0.0211523343f, -0.0508757755f, -7.95444632f, 0.20172891f, 0.302745283f, + 0.152322426f, -5.17384148f, -7.4058733f, -0.906374574f, 0.251192033f, 0.152534753f, -7.86813688f, 0.13358897f, + -0.0830751732f, 0.155647248f, -1.33326983f, -2.52076316f, -5.00412273f, 0.0545000434f, -1.25209689f, 0.0792894065f, + }, + { + -7.11220551f, 0.0231190808f, 0.0999559984f, -0.308837712f, 0.105417646f, -1.35081482f, 0.112771548f, 0.150126919f, + -2.00195408f, -0.023172617f, 0.138220146f, 0.079194136f, 0.35111168f, -0.303678393f, -4.23709393f, 0.0979934335f, + -1.87719214f, -4.96281195f, 0.0806129351f, -0.0134773301f, 0.158352003f, 0.0125434101f, -8.38632584f, -1.85503352f, + 0.162621021f, 0.139433414f, -5.49525452f, 0.179583997f, -5.23993921f, -0.241104484f, 0.0399851762f, -6.31018019f, + -2.95753956f, -7.37137508f, 0.0900286064f, 0.14959766f, 0.165283725f, 0.0544244424f, 0.830911279f, 0.162775129f, + 0.0666886047f, -0.180822194f, -3.05408716f, 0.17404893f, 0.166787624f, -0.0739734992f, -3.01722264f, -0.846259773f, + -1.4119302f, -0.168266505f, 0.0253320765f, -2.48853993f, 0.104411587f, 0.139572769f, -0.481812f, -1.47076559f, + -7.77005863f, -0.214921728f, 0.557505369f, -7.48346901f, -4.3728857f, -7.5945735f, 0.48653689f, 0.188872546f, + -3.06140137f, -5.90584469f, -5.74680948f, -0.154060915f, -7.39212799f, 0.116560787f, 0.176064923f, -8.09504604f, + 0.0602849647f, 0.540545583f, -7.34685707f, 0.0887776837f, 0.085532181f, -0.0330953263f, -6.16067839f, 0.15209119f, + -3.94840693f, 0.119297937f, 0.0819884762f, 0.151157349f, 0.0747665837f, -7.86550808f, -0.59918642f, -7.16910791f, + -0.117356457f, -7.636343f, -7.27623558f, -7.73033333f, -0.0501420125f, -0.0175455082f, -0.741278291f, 0.181391954f, + 0.553107858f, -1.04615283f, 0.139365301f, 0.0612472631f, -7.20848036f, 0.15220657f, 0.105062447f, 0.090755403f, + -7.70470667f, -0.0367191434f, 0.145092741f, 0.0555568635f, -0.0434380695f, -8.11700916f, 0.179015666f, 0.269899189f, + 0.143191025f, -5.05968285f, -7.5147171f, -0.890652299f, 0.22284694f, 0.139029875f, -7.87520409f, 0.111937746f, + -0.0762246549f, 0.127378225f, -1.49933231f, -2.79920697f, -5.12607479f, 0.00593827479f, -1.07423794f, 0.0534165874f, + }, + { + -7.30585957f, 0.0278555527f, 0.0813550651f, -0.371805161f, 0.0732540563f, -1.32796609f, 0.116804667f, 0.145286545f, + -1.92023933f, 0.0501186699f, 0.116614446f, 0.0668070763f, 0.321639806f, -0.285755932f, -4.71890402f, 0.0994042307f, + -1.27911377f, -5.21679306f, 0.0709667951f, 0.0248568896f, 0.144209787f, 0.0141709931f, -8.29843712f, -1.88745451f, + 0.14290975f, 0.0106571112f, -5.41501904f, 0.156796813f, -5.04763651f, -0.236309111f, 0.0359794125f, -6.18487692f, + -2.86939096f, -7.25175714f, 0.0450980738f, 0.144137695f, 0.14287892f, 0.0289048143f, 0.691064596f, 0.154092804f, + 0.0644845292f, -0.169082761f, -3.28060603f, 0.148448646f, 0.145286709f, -0.074049823f, -2.83811402f, -1.00196743f, + -1.95613861f, -0.316825449f, -0.209380955f, -2.77130651f, 0.0849075317f, 0.118312359f, -0.595203519f, -1.74054754f, + -7.74071503f, -0.292434454f, 0.510697842f, -7.34563732f, -4.3476429f, -7.21745443f, 0.626081705f, 0.182002559f, + -3.57532549f, -5.94299746f, -5.58633661f, -0.141254783f, -7.41364002f, 0.0905366391f, 0.146316767f, -8.08402252f, + 0.0533861704f, 0.911499023f, -6.97779989f, 0.0764704943f, 0.0778400376f, 0.00467542373f, -5.87015343f, 0.131761864f, + -4.03207064f, 0.0894862115f, 0.0612911582f, 0.132193908f, 0.0621086359f, -7.74817371f, -0.688727736f, -7.10170794f, + -0.107042551f, -7.64880133f, -7.13769245f, -7.15157175f, -0.0473169871f, -0.0688858926f, -1.01014686f, 0.181613341f, + 0.836614549f, -1.30179524f, 0.125504151f, 0.0559124462f, -6.89832449f, 0.131997079f, 0.0827169418f, 0.0929087028f, + -7.65031242f, -0.0191436522f, 0.145867437f, 0.298651606f, -0.0435237363f, -7.96481562f, 0.140933692f, 0.247737944f, + 0.126006782f, -5.06443119f, -7.06078863f, -1.05330241f, 0.191917777f, 0.129258245f, -7.92037487f, 0.0927955359f, + -0.082161352f, 0.0966672674f, -1.71957898f, -2.65902042f, -4.7713623f, 0.346030265f, -1.28700089f, 0.0480430238f, + }, + }, + { + { + -0.970057487f, 0.106934749f, 0.137284636f, 0.639338493f, 0.116724491f, -1.14073646f, 0.17654632f, 0.221347108f, + -0.959505677f, 0.265501499f, 0.107110977f, 0.174423099f, 0.125656351f, -0.953386843f, -0.617672741f, 0.0152115971f, + -1.18241429f, -0.480348825f, 0.0942183509f, 0.105888382f, 0.00445184577f, 0.0361720249f, -4.14005089f, 0.52162379f, + 0.0774868578f, 0.0202811174f, -1.38086748f, 0.199038282f, 0.354820102f, -0.019119136f, -0.0733883232f, -1.99654543f, + -1.34970462f, -2.93273544f, 0.124269716f, 0.033734113f, 0.152358979f, 0.114915282f, 0.485829651f, 0.238719806f, + 0.0368359685f, 0.0116432225f, 0.941672504f, 0.138256967f, 0.157054916f, -0.0387999564f, -1.2209326f, -0.67166549f, + -0.481270164f, -0.215884775f, 0.0977888182f, -0.183551729f, 0.0514122844f, 0.292685241f, -0.655484498f, -0.577370763f, + -3.7126317f, 1.23525393f, 0.294019252f, -3.39904332f, -0.868436813f, -3.18306661f, -0.704130352f, 0.0530261397f, + -0.216865748f, -2.10935736f, -2.50115132f, -0.139606252f, -3.50140047f, 0.0300923623f, 0.271384358f, -3.41196084f, + 0.123228371f, -0.723597884f, -4.06128073f, 0.00616619922f, -0.0352302194f, -0.033287216f, -3.06606579f, 0.16995506f, + -1.57645333f, 0.069506675f, -0.03327154f, 0.0904477537f, -0.0334941745f, -3.83852959f, 1.04002249f, -3.70730472f, + -0.0505737998f, -3.54535174f, -3.23903465f, -4.0592351f, -0.102473602f, 1.01290548f, -0.514081717f, 0.0373436138f, + 0.290846765f, -0.778140306f, 0.133102581f, 0.0696858242f, -3.32629633f, 0.191387549f, 0.0914083421f, 0.073279731f, + -3.4794991f, 0.00757962931f, 0.116766416f, 0.156437889f, -0.146546036f, -3.82801127f, 0.192683116f, 0.299627602f, + 0.0131718554f, -2.42581415f, -3.56561136f, -0.95573312f, 0.174044549f, 0.0431587137f, -4.12198544f, 0.104951806f, + 0.0321015753f, 0.0625904575f, -0.777304411f, 0.712126374f, -1.89047527f, -0.179742202f, -0.651878834f, 0.127759814f, + }, + { + -1.76618159f, 0.071553871f, 0.101370893f, 0.711461067f, 0.232921153f, -1.37366319f, 0.2598297f, 0.243832767f, + -1.39712715f, 0.124830723f, 0.116953343f, 0.123070657f, 0.468830436f, -1.83578753f, -0.481486678f, 0.0675974637f, + -1.60843849f, -0.691281557f, 0.0878794417f, 0.146648914f, 0.017732488f, -0.045299653f, -6.21018076f, -0.101769522f, + 0.0978177413f, -0.0238761865f, -2.94316292f, 0.170470208f, -2.72871637f, -0.285705656f, -0.0280800369f, -3.78441691f, + -1.32195497f, -5.52996349f, 0.136714399f, 0.123852536f, 0.267744809f, 0.0888120234f, -0.111988969f, 0.0951417536f, + 0.102566242f, -0.0840748921f, 0.472098053f, 0.226148203f, 0.224514425f, -0.0140471887f, -1.08706403f, -0.73698163f, + -1.08886731f, -0.289403945f, -0.000736325979f, 0.0050932467f, 0.188311905f, 0.256520271f, -0.780026019f, -0.746751308f, + -6.2395525f, 1.82299054f, 0.560030222f, -5.35857916f, -1.33498764f, -5.36292458f, -0.774475276f, 0.232680798f, + 0.246089637f, -4.03535509f, -4.48552227f, 0.0828938633f, -5.97402143f, 0.15709725f, 0.229055256f, -5.68230438f, + 0.127873555f, -0.819050729f, -6.51053715f, 0.116921827f, -0.0228366479f, 0.0565577038f, -4.93373013f, 0.210429251f, + -2.83559656f, 0.184240386f, 0.0562465712f, 0.188137993f, 0.0869290009f, -6.2702117f, 0.0160411298f, -5.44086266f, + -0.0426702276f, -6.06777382f, -5.12472963f, -6.70642805f, 0.0775653049f, 0.958152711f, -0.683184683f, 0.0308089536f, + 0.0805554837f, -1.19076526f, 0.210215807f, -0.0131623633f, -5.44549179f, 0.226455733f, 0.157384723f, 0.0634889752f, + -5.71441174f, -0.0256205425f, 0.231733039f, 0.114468858f, -0.08423163f, -6.81639385f, 0.308035553f, 0.386519521f, + 0.119628735f, -3.9762969f, -6.08295155f, -1.56643116f, 0.291504085f, 0.159790739f, -6.65610552f, 0.204963729f, + -0.129696772f, 0.130501628f, -0.416311473f, -0.771859765f, -3.41404295f, 0.00910417736f, -1.36094642f, 0.100298859f, + }, + { + -3.23249483f, 0.0513576604f, 0.12343774f, 0.619597852f, 0.263090819f, -1.37847996f, 0.236940339f, 0.229175493f, + -1.28479171f, 0.13153331f, 0.120525703f, 0.157829612f, 0.504987836f, -2.45996952f, -1.15608871f, 0.140618011f, + -1.70277262f, -1.99543107f, 0.0585533157f, 0.165033966f, 0.0937056541f, -0.0742589161f, -7.29623032f, -0.224348411f, + 0.133670807f, -0.0304500461f, -3.58087444f, 0.180931985f, -3.61113214f, -0.310877115f, 0.0157596432f, -4.54063559f, + -1.47368491f, -6.15173149f, 0.0952534676f, 0.14968963f, 0.243842706f, 0.0366058946f, -0.0994049981f, 0.113053724f, + 0.146592587f, -0.217616737f, -0.736857474f, 0.244569138f, 0.206181392f, -0.0160691906f, -1.39321625f, -0.776369572f, + -0.959895968f, -0.210830256f, -0.128263116f, -0.81960839f, 0.220909476f, 0.272226483f, -0.546749949f, -0.64640528f, + -6.95986032f, 1.29287255f, 0.698044479f, -6.39719057f, -1.80643618f, -6.35764074f, -0.776468635f, 0.234964862f, + -0.157656938f, -4.63439846f, -5.43428802f, 0.135760888f, -6.58189774f, 0.19031629f, 0.235852197f, -6.50976276f, + 0.112815231f, -0.597701848f, -6.84255457f, 0.175182357f, 0.0524091981f, 0.0298821814f, -5.79781675f, 0.221557215f, + -3.34702277f, 0.167310372f, 0.13529253f, 0.203808784f, 0.139063641f, -6.95243216f, -0.68012327f, -6.68615818f, + -0.0872566104f, -6.70947552f, -6.02176952f, -7.14327478f, 0.0614202023f, 0.755385578f, -0.305944592f, 0.109331891f, + 0.000135140494f, -1.52418435f, 0.274395943f, 0.0121657122f, -6.34339237f, 0.248837352f, 0.157243609f, 0.0796914101f, + -6.51867056f, -0.0862355679f, 0.26122424f, 0.0677643716f, -0.0763386041f, -7.1651907f, 0.297900259f, 0.379445076f, + 0.137878984f, -4.70478296f, -6.6418066f, -1.50832081f, 0.309693754f, 0.191859633f, -7.17839289f, 0.208062053f, + -0.120615274f, 0.185123384f, -0.309916168f, -2.02962208f, -3.97864747f, 0.095207721f, -1.53056192f, 0.130645931f, + }, + { + -4.20166206f, 0.0528830774f, 0.119445242f, 0.424707532f, 0.223317876f, -1.3667146f, 0.2251959f, 0.211110353f, + -1.34303617f, -0.00411581481f, 0.114926234f, 0.149293005f, 0.469326854f, -2.18989491f, -1.96834779f, 0.133204281f, + -1.6844368f, -2.76540375f, 0.0648516491f, 0.130026147f, 0.114328846f, -0.0557706654f, -7.30368805f, -0.198859334f, + 0.147381127f, -0.0236848593f, -4.15736294f, 0.190304488f, -3.86513376f, -0.331644714f, 0.0166105926f, -4.97287941f, + -2.30361509f, -6.48970175f, 0.0918524414f, 0.150676847f, 0.2328123f, 0.0736000016f, 0.354703873f, 0.125596061f, + 0.123948321f, -0.216854662f, -1.60692954f, 0.246693134f, 0.200773373f, -0.044565402f, -1.51417315f, -0.935979366f, + -0.67883569f, -0.171070099f, -0.0594501235f, -1.59236765f, 0.18406263f, 0.245278999f, -0.4991512f, -0.682034016f, + -7.04195261f, 0.593915582f, 0.690365553f, -6.53055716f, -2.93846202f, -6.51543188f, -0.488201082f, 0.222927108f, + -0.849837303f, -4.80312014f, -5.49161816f, 0.0895840153f, -7.01396418f, 0.186770424f, 0.229399607f, -6.50432205f, + 0.0995894819f, -0.456545174f, -7.19471264f, 0.163874894f, 0.0726898015f, 0.0111211129f, -5.9030447f, 0.200166285f, + -3.73997188f, 0.159840643f, 0.137178779f, 0.192525193f, 0.139846981f, -7.19044733f, -0.620857775f, -6.60738945f, + -0.122083418f, -7.08077431f, -5.98747826f, -7.66407871f, 0.00620712014f, 0.667201817f, -0.113925971f, 0.182796717f, + -0.0793627799f, -1.21046829f, 0.236528262f, 0.0245247725f, -6.56421375f, 0.235384658f, 0.150420606f, 0.100246385f, + -6.71493769f, -0.12396457f, 0.234938174f, 0.120768398f, -0.0220099632f, -7.47225094f, 0.288724154f, 0.368777931f, + 0.157640263f, -5.08520126f, -6.80317259f, -0.985251784f, 0.304379106f, 0.177914098f, -7.65157223f, 0.179931805f, + -0.122295782f, 0.181780994f, -0.540548623f, -2.27951908f, -4.44726372f, 0.126662135f, -1.55736542f, 0.103207566f, + }, + { + -5.21752071f, 0.0626893565f, 0.121678621f, 0.223623246f, 0.180380434f, -1.36254585f, 0.178830773f, 0.200700998f, + -2.03188229f, -0.0589155927f, 0.134324431f, 0.141985536f, 0.444055438f, -1.92401743f, -2.86888599f, 0.130019873f, + -1.88756108f, -3.23452806f, 0.0480347425f, 0.0777804181f, 0.121144831f, -0.016533386f, -7.57880354f, -0.243526921f, + 0.162133157f, 0.00300395861f, -4.82091427f, 0.199092492f, -4.34040451f, -0.320910841f, 0.0305645261f, -5.46638203f, + -3.00544453f, -6.79700947f, 0.0945274755f, 0.154438123f, 0.221498922f, 0.0827474967f, 0.749064267f, 0.0881203711f, + 0.109356217f, -0.215108201f, -2.10787416f, 0.223621115f, 0.195602372f, -0.0610188395f, -1.84664607f, -1.16360497f, + -0.733277261f, -0.141944259f, 0.0157474801f, -2.27467155f, 0.138906091f, 0.214943185f, -0.474071771f, -0.844754338f, + -7.34162283f, 0.176253974f, 0.665148258f, -6.8899107f, -3.87842774f, -6.76395321f, -0.139593437f, 0.21425572f, + -1.61623573f, -5.27455997f, -5.478374f, 0.0326777585f, -7.31034088f, 0.160762146f, 0.206642985f, -7.08244514f, + 0.101407647f, -0.246360302f, -7.53471708f, 0.137378916f, 0.0767529756f, -0.00912970025f, -6.13319683f, 0.191763893f, + -3.9571023f, 0.150982916f, 0.118688673f, 0.187477291f, 0.114069663f, -7.48538685f, -0.532001078f, -6.72332191f, + -0.132566616f, -7.34046936f, -6.31190586f, -7.97601938f, -0.0149856973f, 0.419438004f, -0.251937836f, 0.169773102f, + -0.0185937826f, -0.978445292f, 0.211612523f, 0.0583743453f, -6.90256691f, 0.214964181f, 0.131737441f, 0.10074982f, + -7.06698275f, -0.0946370885f, 0.185323849f, 0.113845944f, -0.054244291f, -7.86611509f, 0.252882153f, 0.337933064f, + 0.164212003f, -5.16382456f, -7.11062765f, -0.702732265f, 0.281162441f, 0.163177565f, -8.06491184f, 0.163758725f, + -0.116309755f, 0.174536183f, -0.766762376f, -2.41131282f, -4.91004229f, 0.140025571f, -1.37683177f, 0.0999209955f, + }, + { + -6.24396324f, 0.0503582992f, 0.121003076f, 0.052270107f, 0.147037044f, -1.34574592f, 0.136601195f, 0.175520658f, + -2.4846921f, -0.0964128301f, 0.147193924f, 0.121653333f, 0.404870629f, -1.82385039f, -3.59025168f, 0.117446184f, + -1.92769456f, -3.71132374f, 0.0452120677f, 0.0246206839f, 0.132932842f, -0.00208793394f, -8.0031004f, -0.319451749f, + 0.168936968f, 0.0759948343f, -5.28618479f, 0.193907738f, -4.81757879f, -0.277610868f, 0.043523401f, -5.93040276f, + -3.37368941f, -7.1703887f, 0.107437149f, 0.152360111f, 0.195620388f, 0.0642500222f, 0.933958113f, 0.0515285283f, + 0.0940524638f, -0.201238111f, -2.40429497f, 0.196761847f, 0.183468401f, -0.0634111091f, -2.46971297f, -1.39550149f, + -1.03629911f, -0.134604588f, 0.0204343945f, -2.78233647f, 0.116047785f, 0.179053262f, -0.41657877f, -1.06298327f, + -7.65254211f, -0.102539256f, 0.609282851f, -7.41640854f, -4.38445425f, -7.27728319f, 0.221345305f, 0.204126313f, + -2.20698452f, -5.82158756f, -5.71211576f, -0.0377533399f, -7.45539188f, 0.133368865f, 0.196911111f, -7.87766171f, + 0.0902302861f, 0.0743763894f, -7.62045813f, 0.111583978f, 0.0846050009f, -0.0279699601f, -6.27826262f, 0.189105153f, + -4.01307392f, 0.136026487f, 0.108549953f, 0.177605882f, 0.0940333381f, -7.81760979f, -0.561878324f, -7.16266584f, + -0.133779034f, -7.54034805f, -6.82294178f, -8.1817522f, -0.0295777414f, 0.217689082f, -0.50592941f, 0.149415314f, + 0.257220089f, -1.05373764f, 0.179013297f, 0.0770789087f, -7.24357891f, 0.189050049f, 0.119651623f, 0.0940699726f, + -7.46815157f, -0.0736086592f, 0.158229649f, 0.0303002317f, -0.0815414414f, -8.08258343f, 0.221806735f, 0.307385832f, + 0.151405513f, -5.10040045f, -7.53018761f, -0.581446648f, 0.255510926f, 0.154452771f, -8.12646675f, 0.131640896f, + -0.0843448639f, 0.158987194f, -0.958948553f, -2.75329685f, -5.36081362f, 0.10232915f, -1.03462481f, 0.0811900571f, + }, + { + -6.88282824f, 0.0262488499f, 0.100677766f, -0.0733691603f, 0.109421164f, -1.35270989f, 0.113343984f, 0.152452126f, + -2.59468603f, -0.121062681f, 0.138513476f, 0.0842481181f, 0.359210461f, -1.76120126f, -4.06151772f, 0.101788357f, + -1.63535488f, -4.08275318f, 0.0822468027f, -0.00606108923f, 0.159228027f, 0.0121353697f, -8.2249012f, -0.386256814f, + 0.167939365f, 0.146340758f, -5.53824854f, 0.18153955f, -5.01582623f, -0.233786672f, 0.0429653749f, -6.25346804f, + -3.42463732f, -7.44046402f, 0.0936818942f, 0.148032129f, 0.167100802f, 0.0562020615f, 0.881923676f, 0.0646122172f, + 0.0672171488f, -0.176641062f, -2.65957236f, 0.180558652f, 0.172934249f, -0.0713029355f, -2.78373623f, -1.59014869f, + -1.40547156f, -0.235822484f, -0.0768826753f, -3.1463201f, 0.0907357186f, 0.142085224f, -0.488625318f, -1.28016484f, + -7.73828697f, -0.251041293f, 0.560568094f, -7.61835957f, -4.60437632f, -7.50743151f, 0.533270717f, 0.190567583f, + -2.68793416f, -6.14284277f, -5.72710991f, -0.143457621f, -7.54302311f, 0.119399503f, 0.179940984f, -8.46807575f, + 0.0723029003f, 0.46332258f, -7.55705166f, 0.092832692f, 0.0904766023f, -0.0452815294f, -6.14746141f, 0.158185989f, + -3.99093366f, 0.12252707f, 0.0848980844f, 0.155892804f, 0.0792071745f, -7.92358446f, -0.604566336f, -7.28686428f, + -0.11685732f, -7.59884548f, -7.11060095f, -8.11457825f, -0.0484555587f, -0.0256827772f, -0.751105368f, 0.113805197f, + 0.586237133f, -1.1691978f, 0.142386183f, 0.0627702624f, -7.32780695f, 0.155047789f, 0.107697718f, 0.0917551592f, + -7.56303358f, -0.0466024168f, 0.142566055f, 0.0647047609f, -0.0741037354f, -8.2451458f, 0.199093506f, 0.274539739f, + 0.142274112f, -4.98624229f, -7.63903141f, -0.565724373f, 0.227165818f, 0.140947893f, -8.13353443f, 0.109989665f, + -0.0774943456f, 0.130718172f, -1.12501109f, -3.03174067f, -5.48276567f, 0.0537673831f, -0.856765807f, 0.0553172305f, + }, + { + -7.0764823f, 0.0309853218f, 0.0820768327f, -0.13633661f, 0.0772575736f, -1.32986116f, 0.117377102f, 0.147611752f, + -2.5129714f, -0.0477713943f, 0.116907768f, 0.0718610585f, 0.329738587f, -1.74327874f, -4.54332781f, 0.103199154f, + -1.03727651f, -4.33673429f, 0.0726006627f, 0.0322731286f, 0.145085812f, 0.0137629528f, -8.13701248f, -0.418677807f, + 0.148228094f, 0.0175644569f, -5.45801306f, 0.158752367f, -4.82352352f, -0.2289913f, 0.0389596075f, -6.12816477f, + -3.33648872f, -7.32084608f, 0.0487513654f, 0.142572165f, 0.144695997f, 0.0306824315f, 0.742076993f, 0.0559298918f, + 0.0650130734f, -0.164901629f, -2.88609123f, 0.154958367f, 0.151433334f, -0.0713792592f, -2.60462761f, -1.74585629f, + -1.94967997f, -0.384381413f, -0.311595708f, -3.42908669f, 0.0712316632f, 0.120824814f, -0.602016866f, -1.54994678f, + -7.70894337f, -0.328554034f, 0.513760567f, -7.48052788f, -4.57913351f, -7.13031244f, 0.672815561f, 0.183697596f, + -3.20185828f, -6.17999554f, -5.56663704f, -0.130651489f, -7.56453514f, 0.093375355f, 0.150192827f, -8.45705223f, + 0.0654041022f, 0.834276021f, -7.18799448f, 0.0805255026f, 0.082784459f, -0.00751078036f, -5.85693645f, 0.137856647f, + -4.07459736f, 0.0927153379f, 0.0642007664f, 0.136929363f, 0.0665492266f, -7.8062501f, -0.694107592f, -7.2194643f, + -0.106543414f, -7.61130333f, -6.97205782f, -7.53581667f, -0.0456305295f, -0.0770231634f, -1.01997387f, 0.114026576f, + 0.869743824f, -1.42484021f, 0.128525034f, 0.0574354492f, -7.01765108f, 0.134838298f, 0.0853522122f, 0.0939084589f, + -7.50863934f, -0.0290269256f, 0.143340752f, 0.307799518f, -0.0741894022f, -8.09295273f, 0.161011532f, 0.252378494f, + 0.125089869f, -4.99099016f, -7.18510294f, -0.728374422f, 0.196236655f, 0.131176263f, -8.17870522f, 0.090847455f, + -0.0834310427f, 0.100007221f, -1.34525776f, -2.89155412f, -5.12805319f, 0.393859386f, -1.06952882f, 0.0499436706f, + }, + }, + { + { + -0.309570014f, 0.105184488f, 0.139343709f, 0.485604376f, 0.114031665f, -1.13089025f, 0.16714707f, 0.219971061f, + -0.809345424f, 0.313536912f, 0.107182309f, 0.179740801f, 0.125453815f, 0.40939343f, 0.187293902f, 0.00996591523f, + -0.867729664f, -0.769602895f, 0.0939842463f, 0.105265759f, 0.00350929145f, 0.0354348682f, -3.91510296f, 0.446279526f, + 0.0758070052f, 0.0142814778f, -1.466048f, 0.193561956f, 0.0133079588f, -0.0163000654f, -0.0731628537f, -1.98076415f, + -1.3896209f, -2.81349945f, 0.124927007f, 0.0328078419f, 0.15077205f, 0.119643547f, 0.445483506f, 0.176977918f, + 0.0331835225f, 0.0124410726f, 0.685679078f, 0.136196017f, 0.15228641f, -0.0430670679f, -1.29140806f, 0.32196039f, + -0.929786146f, -0.346694708f, 0.298682094f, 0.19948329f, 0.0531000048f, 0.293747187f, -0.640966475f, 0.121291965f, + -3.56915545f, 1.18261731f, 0.289886594f, -3.12720466f, -0.508294284f, -3.04616022f, -1.17789543f, 0.048351869f, + -1.2528944f, -1.74482214f, -2.17252493f, -0.130504161f, -3.33159161f, 0.0306348242f, 0.268300533f, -2.63963509f, + 0.117705047f, -0.500619829f, -3.79061699f, 0.00450645015f, -0.0343707278f, -0.0323753282f, -2.87098074f, 0.169933468f, + -1.38454461f, 0.0698240399f, -0.0307277814f, 0.0917382464f, -0.0346831419f, -3.66132641f, -0.298110962f, -3.2656846f, + -0.0498418435f, -3.21502852f, -2.82937074f, -3.89544725f, -0.102699332f, 0.993867218f, -0.805137396f, 0.082359314f, + 0.375710934f, -0.691300273f, 0.13126035f, 0.071441792f, -3.10749221f, 0.190944865f, 0.0911523774f, 0.0706074685f, + -3.34118176f, 0.00905294716f, 0.115172371f, 0.151925266f, -0.0625387281f, -3.75174975f, 0.192441553f, 0.297328174f, + 0.012038352f, -2.20729303f, -3.37110591f, -0.671857774f, 0.171535581f, 0.0384173431f, -3.95444775f, 0.105555795f, + 0.0347596705f, 0.065079622f, -0.811144114f, 1.15172267f, -1.84414124f, -0.257445902f, -0.525580704f, 0.130517021f, + }, + { + -1.10569406f, 0.0698036104f, 0.103429973f, 0.55772692f, 0.23022832f, -1.36381698f, 0.250430435f, 0.242456719f, + -1.24696684f, 0.172866136f, 0.117024675f, 0.12838836f, 0.4686279f, -0.473007321f, 0.32348001f, 0.0623517819f, + -1.29375386f, -0.980535626f, 0.087645337f, 0.146026284f, 0.0167899337f, -0.0460368097f, -5.98523283f, -0.177113757f, + 0.0961378887f, -0.0298758261f, -3.02834344f, 0.164993882f, -3.07022858f, -0.282886595f, -0.0278545655f, -3.76863551f, + -1.36187124f, -5.4107275f, 0.137371689f, 0.122926265f, 0.266157895f, 0.0935402885f, -0.152335122f, 0.0333998725f, + 0.0989137962f, -0.0832770392f, 0.216104612f, 0.224087253f, 0.219745934f, -0.0183142964f, -1.15753949f, 0.256644219f, + -1.53738332f, -0.420213878f, 0.200156957f, 0.388128281f, 0.18999961f, 0.257582217f, -0.765507996f, -0.0480885804f, + -6.09607649f, 1.77035391f, 0.555897534f, -5.08674049f, -0.974845111f, -5.22601843f, -1.24824035f, 0.228006527f, + -0.789939046f, -3.67082f, -4.15689611f, 0.0919959545f, -5.80421257f, 0.157639712f, 0.225971445f, -4.90997887f, + 0.122350223f, -0.596072674f, -6.23987341f, 0.115262076f, -0.0219771564f, 0.0574695915f, -4.73864508f, 0.210407659f, + -2.64368796f, 0.184557766f, 0.0587903298f, 0.189428478f, 0.0857400373f, -6.09300852f, -1.32209229f, -4.99924231f, + -0.0419382751f, -5.73745012f, -4.71506596f, -6.54264021f, 0.0773395747f, 0.939114451f, -0.974240363f, 0.0758246481f, + 0.165419653f, -1.10392523f, 0.208373576f, -0.0114064012f, -5.22668791f, 0.226013049f, 0.157128766f, 0.0608167127f, + -5.57609463f, -0.0241472237f, 0.230138987f, 0.109956242f, -0.000224329531f, -6.74013233f, 0.307794005f, 0.384220093f, + 0.118495233f, -3.75777578f, -5.88844585f, -1.2825557f, 0.288995147f, 0.155049369f, -6.48856783f, 0.205567703f, + -0.127038673f, 0.132990792f, -0.450151145f, -0.33226341f, -3.36770892f, -0.0685995221f, -1.23464823f, 0.103056058f, + }, + { + -2.57200742f, 0.0496073961f, 0.12549682f, 0.465863734f, 0.260397971f, -1.36863375f, 0.227541089f, 0.227799445f, + -1.1346314f, 0.179568738f, 0.120597035f, 0.163147315f, 0.504785299f, -1.09718919f, -0.351122081f, 0.135372326f, + -1.38808799f, -2.28468513f, 0.058319211f, 0.164411336f, 0.0927630961f, -0.0749960765f, -7.07128239f, -0.299692661f, + 0.131990939f, -0.0364496857f, -3.66605496f, 0.17545566f, -3.95264435f, -0.308058053f, 0.0159851126f, -4.52485418f, + -1.51360118f, -6.0324955f, 0.0959107578f, 0.148763359f, 0.242255777f, 0.0413341597f, -0.139751151f, 0.0513118431f, + 0.142940134f, -0.216818884f, -0.9928509f, 0.242508188f, 0.201412886f, -0.0203362983f, -1.46369171f, 0.217256278f, + -1.40841198f, -0.341640174f, 0.0726301819f, -0.436573386f, 0.222597182f, 0.273288429f, -0.532231927f, 0.0522574037f, + -6.81638432f, 1.24023592f, 0.693911791f, -6.12535191f, -1.44629371f, -6.2207346f, -1.25023365f, 0.230290592f, + -1.19368565f, -4.26986361f, -5.10566187f, 0.14486298f, -6.41208887f, 0.190858752f, 0.232768387f, -5.73743725f, + 0.1072919f, -0.374723792f, -6.57189083f, 0.173522606f, 0.0532686897f, 0.0307940692f, -5.6027317f, 0.221535623f, + -3.15511417f, 0.167627752f, 0.137836292f, 0.20509927f, 0.137874678f, -6.77522898f, -2.01825666f, -6.24453783f, + -0.0865246579f, -6.37915182f, -5.61210585f, -6.97948694f, 0.0611944646f, 0.736347318f, -0.597000301f, 0.154347599f, + 0.0849993154f, -1.43734431f, 0.272553682f, 0.0139216743f, -6.12458849f, 0.248394668f, 0.156987652f, 0.0770191476f, + -6.38035345f, -0.0847622454f, 0.259630203f, 0.0632517487f, 0.00766869634f, -7.08892918f, 0.297658712f, 0.377145648f, + 0.136745483f, -4.48626184f, -6.44730091f, -1.22444534f, 0.307184786f, 0.187118262f, -7.0108552f, 0.208666027f, + -0.117957182f, 0.187612548f, -0.343755841f, -1.59002566f, -3.93231344f, 0.0175040215f, -1.40426385f, 0.133403122f, + }, + { + -3.54117465f, 0.0511328131f, 0.121504322f, 0.270973414f, 0.220625043f, -1.35686839f, 0.215796649f, 0.209734306f, + -1.19287598f, 0.0439196043f, 0.114997566f, 0.154610723f, 0.469124317f, -0.827114582f, -1.1633811f, 0.127958596f, + -1.36975217f, -3.05465794f, 0.0646175444f, 0.129403532f, 0.113386288f, -0.0565078259f, -7.07874012f, -0.274203569f, + 0.145701274f, -0.0296844989f, -4.24254322f, 0.184828162f, -4.20664597f, -0.328825653f, 0.0168360621f, -4.95709801f, + -2.34353137f, -6.37046576f, 0.0925097317f, 0.149750575f, 0.231225371f, 0.0783282667f, 0.314357728f, 0.0638541803f, + 0.120295875f, -0.216056809f, -1.86292303f, 0.244632185f, 0.196004868f, -0.0488325134f, -1.58464861f, 0.057646513f, + -1.12735164f, -0.301880002f, 0.141443163f, -1.2093327f, 0.185750335f, 0.246340945f, -0.484633207f, 0.0166286528f, + -6.8984766f, 0.541278958f, 0.686232865f, -6.25871849f, -2.57831955f, -6.37852573f, -0.961966157f, 0.218252838f, + -1.88586605f, -4.43858528f, -5.162992f, 0.0986861065f, -6.84415531f, 0.187312886f, 0.226315796f, -5.73199654f, + 0.0940661505f, -0.233567163f, -6.9240489f, 0.162215143f, 0.073549293f, 0.0120330025f, -5.70795965f, 0.200144693f, + -3.54806328f, 0.160158023f, 0.139722541f, 0.193815678f, 0.138658017f, -7.01324415f, -1.95899129f, -6.1657691f, + -0.121351466f, -6.75045061f, -5.57781458f, -7.50029087f, 0.00598138617f, 0.648163557f, -0.404981673f, 0.227812409f, + 0.00550139695f, -1.12362826f, 0.234686032f, 0.0262807347f, -6.34540987f, 0.234941974f, 0.150164649f, 0.0975741223f, + -6.57662058f, -0.122491248f, 0.233344123f, 0.116255783f, 0.0619973391f, -7.39598942f, 0.288482606f, 0.366478503f, + 0.156506762f, -4.86668015f, -6.6086669f, -0.701376438f, 0.301870167f, 0.173172727f, -7.48403454f, 0.180535778f, + -0.11963769f, 0.184270158f, -0.574388325f, -1.83992279f, -4.40092993f, 0.0489584431f, -1.43106723f, 0.105964765f, + }, + { + -4.55703354f, 0.0609390959f, 0.1237377f, 0.0698890984f, 0.1776876f, -1.35269964f, 0.169431522f, 0.199324951f, + -1.88172197f, -0.0108801723f, 0.134395763f, 0.147303253f, 0.443852901f, -0.561237097f, -2.06391931f, 0.124774195f, + -1.57287645f, -3.52378225f, 0.0478006378f, 0.0771577954f, 0.120202273f, -0.0172705445f, -7.35385561f, -0.31887117f, + 0.160453305f, -0.00299568102f, -4.90609455f, 0.193616167f, -4.68191671f, -0.31809178f, 0.0307899974f, -5.45060062f, + -3.0453608f, -6.67777348f, 0.0951847658f, 0.153511852f, 0.219911993f, 0.0874757618f, 0.708718121f, 0.0263784826f, + 0.105703771f, -0.214310348f, -2.36386776f, 0.221560165f, 0.190833867f, -0.0652859509f, -1.91712153f, -0.169979155f, + -1.18179321f, -0.272754192f, 0.21664077f, -1.89163661f, 0.140593797f, 0.216005132f, -0.459553778f, -0.14609161f, + -7.19814682f, 0.123617359f, 0.66101557f, -6.61807203f, -3.51828527f, -6.62704706f, -0.613358498f, 0.20958145f, + -2.6522646f, -4.91002512f, -5.14974785f, 0.0417798534f, -7.14053202f, 0.161304608f, 0.203559175f, -6.31011915f, + 0.0958843231f, -0.0233822688f, -7.26405334f, 0.135719165f, 0.0776124671f, -0.00821781065f, -5.93811178f, 0.191742301f, + -3.7651937f, 0.151300296f, 0.121232435f, 0.188767776f, 0.112880699f, -7.30818367f, -1.87013459f, -6.28170156f, + -0.131834656f, -7.01014614f, -5.90224218f, -7.81223154f, -0.0152114313f, 0.400399774f, -0.542993546f, 0.214788795f, + 0.0662703887f, -0.891605258f, 0.209770292f, 0.0601303056f, -6.68376303f, 0.214521497f, 0.131481469f, 0.098077558f, + -6.92866564f, -0.093163766f, 0.183729798f, 0.109333329f, 0.0297630131f, -7.78985357f, 0.252640605f, 0.335633636f, + 0.163078502f, -4.94530344f, -6.91612196f, -0.418856889f, 0.278653502f, 0.158436194f, -7.89737415f, 0.164362699f, + -0.113651663f, 0.177025348f, -0.800602078f, -1.97171652f, -4.8637085f, 0.0623218715f, -1.25053358f, 0.102678195f, + }, + { + -5.58347607f, 0.0486080348f, 0.123062156f, -0.101464033f, 0.144344211f, -1.33589971f, 0.12720193f, 0.174144611f, + -2.33453178f, -0.0483774096f, 0.147265255f, 0.126971036f, 0.404668093f, -0.46107012f, -2.785285f, 0.112200499f, + -1.61300993f, -4.00057793f, 0.044977963f, 0.0239980631f, 0.131990299f, -0.00282509252f, -7.77815247f, -0.394796014f, + 0.167257115f, 0.0699951947f, -5.37136507f, 0.188431412f, -5.159091f, -0.274791807f, 0.0437488705f, -5.91462135f, + -3.41360569f, -7.05115271f, 0.108094439f, 0.15143384f, 0.194033459f, 0.0689782873f, 0.893611968f, -0.0102133583f, + 0.0904000178f, -0.200440258f, -2.66028857f, 0.194700897f, 0.178699911f, -0.0676782206f, -2.54018831f, -0.401875615f, + -1.48481512f, -0.265414506f, 0.221327692f, -2.39930153f, 0.117735505f, 0.180115208f, -0.402060777f, -0.364320606f, + -7.5090661f, -0.15517588f, 0.605150163f, -7.14456987f, -4.02431154f, -7.14037704f, -0.25241977f, 0.199452043f, + -3.24301314f, -5.45705271f, -5.38348961f, -0.0286512431f, -7.28558302f, 0.133911327f, 0.193827301f, -7.10533619f, + 0.0847069547f, 0.2973544f, -7.34979439f, 0.109924227f, 0.0854644924f, -0.0270580724f, -6.08317757f, 0.189083561f, + -3.82116532f, 0.136343867f, 0.111093715f, 0.178896368f, 0.0928443745f, -7.64040661f, -1.90001178f, -6.72104549f, + -0.133047074f, -7.21002483f, -6.4132781f, -8.01796436f, -0.0298034754f, 0.198650822f, -0.79698509f, 0.194431007f, + 0.342084259f, -0.966897607f, 0.177171066f, 0.0788348764f, -7.02477503f, 0.188607365f, 0.119395658f, 0.0913977101f, + -7.32983446f, -0.0721353367f, 0.156635597f, 0.0257876124f, 0.00246585906f, -8.00632191f, 0.221565172f, 0.305086404f, + 0.150272012f, -4.88187933f, -7.33568192f, -0.297571242f, 0.253001958f, 0.1497114f, -7.95892906f, 0.13224487f, + -0.0816867724f, 0.161476359f, -0.992788255f, -2.31370068f, -5.31447983f, 0.0246254504f, -0.908326685f, 0.0839472562f, + }, + { + -6.22234106f, 0.0244985875f, 0.102736846f, -0.227103308f, 0.106728338f, -1.34286368f, 0.103944726f, 0.151076078f, + -2.44452572f, -0.0730272606f, 0.138584808f, 0.0895658284f, 0.359007925f, -0.39842099f, -3.25655103f, 0.0965426713f, + -1.32067025f, -4.37200737f, 0.0820126981f, -0.00668371003f, 0.158285469f, 0.0113982111f, -7.99995375f, -0.461601079f, + 0.166259512f, 0.140341118f, -5.62342882f, 0.176063225f, -5.35733843f, -0.230967596f, 0.0431908444f, -6.23768663f, + -3.46455359f, -7.32122803f, 0.0943391845f, 0.147105858f, 0.165513873f, 0.0609303266f, 0.84157753f, 0.00287033245f, + 0.0635647029f, -0.175843209f, -2.91556597f, 0.178497702f, 0.168165743f, -0.0755700469f, -2.85421157f, -0.596522748f, + -1.85398757f, -0.366632402f, 0.124010615f, -2.76328516f, 0.092423439f, 0.143147171f, -0.474107325f, -0.581502199f, + -7.59481096f, -0.303677917f, 0.556435406f, -7.3465209f, -4.24423361f, -7.37052536f, 0.0595056713f, 0.185893312f, + -3.72396278f, -5.77830791f, -5.39848375f, -0.13435553f, -7.37321424f, 0.119941965f, 0.176857173f, -7.69574976f, + 0.0667795762f, 0.686300635f, -7.28638792f, 0.091172941f, 0.0913360938f, -0.0443696417f, -5.95237637f, 0.158164397f, + -3.79902506f, 0.122844443f, 0.0874418467f, 0.15718329f, 0.0780182108f, -7.74638128f, -1.94269979f, -6.84524393f, + -0.116125368f, -7.26852179f, -6.70093727f, -7.95079041f, -0.048681289f, -0.044721026f, -1.04216111f, 0.158820897f, + 0.671101332f, -1.08235776f, 0.140543953f, 0.0645262301f, -7.10900307f, 0.154605106f, 0.107441753f, 0.0890828967f, + -7.42471647f, -0.045129098f, 0.140972003f, 0.0601921454f, 0.00990356505f, -8.16888428f, 0.198851943f, 0.272240311f, + 0.14114061f, -4.76772118f, -7.44452572f, -0.281848967f, 0.22465685f, 0.136206523f, -7.96599627f, 0.110593654f, + -0.0748362541f, 0.133207351f, -1.15885067f, -2.59214449f, -5.43643188f, -0.0239363164f, -0.730467677f, 0.0580744334f, + }, + { + -6.41599512f, 0.0292350613f, 0.0841359124f, -0.290070742f, 0.0745647475f, -1.32001495f, 0.107977845f, 0.146235704f, + -2.36281109f, 0.000264026225f, 0.1169791f, 0.0771787614f, 0.329536051f, -0.380498528f, -3.73836112f, 0.0979534686f, + -0.722591877f, -4.62598848f, 0.0723665506f, 0.0316505097f, 0.144143254f, 0.0130257942f, -7.91206503f, -0.494022071f, + 0.146548241f, 0.0115648173f, -5.54319334f, 0.153276041f, -5.16503572f, -0.226172224f, 0.0391850807f, -6.11238337f, + -3.376405f, -7.20161009f, 0.0494086519f, 0.141645893f, 0.143109068f, 0.0354106985f, 0.701730847f, -0.0058119949f, + 0.0613606274f, -0.164103776f, -3.14208484f, 0.152897418f, 0.146664828f, -0.0756463706f, -2.67510295f, -0.752230465f, + -2.39819598f, -0.515191317f, -0.11070241f, -3.04605174f, 0.0729193836f, 0.12188676f, -0.587498844f, -0.851284027f, + -7.56546736f, -0.381190658f, 0.509627879f, -7.20868921f, -4.2189908f, -6.9934063f, 0.199050516f, 0.179023325f, + -4.23788691f, -5.81546068f, -5.23801088f, -0.121549398f, -7.39472628f, 0.0939178169f, 0.147109017f, -7.68472624f, + 0.0598807782f, 1.05725408f, -6.91733074f, 0.0788657516f, 0.0836439505f, -0.00659889076f, -5.66185141f, 0.13783507f, + -3.88268852f, 0.0930327028f, 0.0667445213f, 0.138219848f, 0.065360263f, -7.62904692f, -2.03224111f, -6.77784395f, + -0.105811462f, -7.28098011f, -6.56239414f, -7.37202883f, -0.0458562672f, -0.0960614085f, -1.31102955f, 0.159042269f, + 0.954608023f, -1.33800018f, 0.126682803f, 0.0591914095f, -6.7988472f, 0.134395614f, 0.0850962475f, 0.0912361965f, + -7.37032223f, -0.0275536068f, 0.1417467f, 0.30328688f, 0.00981789827f, -8.01669121f, 0.160769969f, 0.250079066f, + 0.123956375f, -4.77246904f, -6.99059725f, -0.444499046f, 0.193727687f, 0.126434892f, -8.01116657f, 0.0914514437f, + -0.0807729512f, 0.102496386f, -1.37909734f, -2.45195794f, -5.0817194f, 0.316155672f, -0.943230629f, 0.0527008697f, + }, + }, + { + { + 0.386257857f, 0.109255455f, 0.13878724f, 0.479037881f, 0.115733996f, -1.14214098f, 0.182522804f, 0.221017942f, + -0.544417858f, 0.284219176f, 0.106718078f, 0.179916978f, 0.121650934f, -0.768811941f, 0.28284651f, 0.0192163419f, + -0.736350417f, -1.22478175f, 0.0923488438f, 0.0988336131f, 0.00436243135f, 0.0344335288f, -4.19288969f, 0.664527774f, + 0.0756474137f, 0.0630292594f, -1.31573761f, 0.199257091f, 0.0594535172f, -0.0245943647f, -0.0777437836f, -1.99137962f, + -0.95420748f, -2.8085115f, 0.121831246f, 0.0345788673f, 0.151275203f, 0.116813883f, 0.499506652f, 0.194323525f, + 0.0392295681f, 0.0103765735f, 1.01214552f, 0.134187609f, 0.154673025f, -0.0390278399f, -1.35944247f, -0.498814285f, + -0.570130348f, -0.166316584f, 0.200510174f, -0.416683048f, 0.0487599522f, 0.295390606f, -0.794998348f, 0.298102647f, + -3.74433804f, 1.24287236f, 0.298531502f, -3.25652432f, -0.764015555f, -3.26315188f, -1.08756006f, 0.051027596f, + -0.84218365f, -1.92594969f, -2.47334695f, -0.142822206f, -3.47231507f, 0.032722652f, 0.26817283f, -3.48120785f, + 0.119449422f, -0.530785143f, -4.01559448f, 0.00586177129f, -0.0354525559f, -0.0237224177f, -3.05601335f, 0.167921007f, + -1.68585253f, 0.0723798051f, -0.0303358231f, 0.091240257f, -0.0299138054f, -3.77852488f, 0.0896627307f, -3.56682324f, + -0.0493371077f, -3.45758867f, -3.201298f, -3.80099559f, -0.0999710411f, 1.02866733f, -0.764865339f, -0.0501731746f, + 0.332142979f, -0.385770321f, 0.134068951f, 0.0691208541f, -3.2205863f, 0.190926462f, 0.089816466f, 0.0743359029f, + -3.45790052f, 0.0160744563f, 0.113189913f, 0.138400987f, -0.0651933998f, -3.85699081f, 0.179926634f, 0.298815101f, + 0.0134017225f, -2.36439729f, -3.48525476f, -0.665063441f, 0.175885767f, 0.0452454723f, -3.88758111f, 0.10942246f, + 0.0340470523f, 0.0687191114f, -1.10065532f, 0.868130565f, -1.78023696f, -0.177621216f, -0.382934809f, 0.131657586f, + }, + { + -0.409866214f, 0.0738745779f, 0.102873489f, 0.551160455f, 0.231930658f, -1.37506771f, 0.265806168f, 0.2435036f, + -0.982039273f, 0.143548399f, 0.116560444f, 0.128564551f, 0.464825034f, -1.65121269f, 0.419032633f, 0.0716022104f, + -1.16237462f, -1.43571448f, 0.0860099345f, 0.139594138f, 0.0176430736f, -0.0470381528f, -6.26301956f, 0.0411345065f, + 0.0959782973f, 0.0188719518f, -2.87803292f, 0.170689017f, -3.0240829f, -0.291180879f, -0.0324354991f, -3.7792511f, + -0.926457822f, -5.40573931f, 0.134275928f, 0.12469729f, 0.266661048f, 0.0907106251f, -0.0983119756f, 0.0507454835f, + 0.104959838f, -0.085341543f, 0.542571127f, 0.22207883f, 0.222132534f, -0.0142750684f, -1.2255739f, -0.564130425f, + -1.17772746f, -0.239835769f, 0.101985037f, -0.228038073f, 0.185659558f, 0.259225637f, -0.919539869f, 0.128722101f, + -6.27125883f, 1.83060896f, 0.564542472f, -5.21606064f, -1.23056638f, -5.44300985f, -1.15790498f, 0.230682254f, + -0.379228264f, -3.85194731f, -4.45771837f, 0.0796779096f, -5.94493628f, 0.159727529f, 0.225843742f, -5.75155163f, + 0.124094598f, -0.626237988f, -6.4648509f, 0.116617404f, -0.0230589844f, 0.0661225021f, -4.92367744f, 0.208395198f, + -2.94499564f, 0.187113523f, 0.0591822863f, 0.188930482f, 0.09050937f, -6.21020699f, -0.934318662f, -5.30038118f, + -0.0414335355f, -5.98001051f, -5.08699322f, -6.44818878f, 0.0800678656f, 0.973914564f, -0.933968306f, -0.056707833f, + 0.121851712f, -0.798395276f, 0.211182177f, -0.0137273371f, -5.33978176f, 0.225994647f, 0.155792847f, 0.0645451471f, + -5.6928134f, -0.0171257146f, 0.228156537f, 0.0964319482f, -0.0028790012f, -6.84537363f, 0.295279056f, 0.385707021f, + 0.1198586f, -3.91488004f, -6.00259495f, -1.27576149f, 0.293345302f, 0.161877498f, -6.42170095f, 0.209434375f, + -0.127751291f, 0.136630267f, -0.739662409f, -0.615855515f, -3.30380464f, 0.0112251639f, -1.09200227f, 0.104196623f, + }, + { + -1.87617958f, 0.0536783673f, 0.124940336f, 0.45929724f, 0.262100309f, -1.37988448f, 0.242916822f, 0.228846326f, + -0.869703889f, 0.150250986f, 0.120132804f, 0.163323492f, 0.500982404f, -2.27539444f, -0.255569458f, 0.144622758f, + -1.25670874f, -2.73986387f, 0.0566838086f, 0.15797919f, 0.0936162397f, -0.0759974197f, -7.34906912f, -0.0814443827f, + 0.131831363f, 0.0122980932f, -3.51574445f, 0.181150794f, -3.90649867f, -0.316352338f, 0.0114041828f, -4.53546953f, + -1.0781877f, -6.02750731f, 0.0928149968f, 0.150534391f, 0.24275893f, 0.0385044962f, -0.0857280046f, 0.0686574504f, + 0.148986176f, -0.21888338f, -0.666384399f, 0.240499765f, 0.203799501f, -0.0162970703f, -1.53172612f, -0.603518367f, + -1.04875612f, -0.161262065f, -0.0255417451f, -1.05273974f, 0.218257129f, 0.274931848f, -0.6862638f, 0.229068086f, + -6.99156666f, 1.30049098f, 0.702556729f, -6.25467205f, -1.70201492f, -6.43772602f, -1.15989828f, 0.232966319f, + -0.782974839f, -4.45099115f, -5.40648413f, 0.132544935f, -6.55281258f, 0.192946568f, 0.232640684f, -6.57901001f, + 0.109036274f, -0.404889107f, -6.79686832f, 0.174877927f, 0.0521868616f, 0.0394469798f, -5.78776407f, 0.219523162f, + -3.45642185f, 0.17018351f, 0.138228253f, 0.204601273f, 0.142644018f, -6.89242744f, -1.63048303f, -6.54567671f, + -0.0860199183f, -6.62171221f, -5.98403311f, -6.88503551f, 0.0639227554f, 0.77114743f, -0.556728244f, 0.0218151063f, + 0.0414313674f, -1.13181436f, 0.275362313f, 0.0116007393f, -6.23768234f, 0.248376265f, 0.155651733f, 0.080747582f, + -6.49707222f, -0.0777407363f, 0.257647753f, 0.0497274622f, 0.00501402467f, -7.19417048f, 0.285143763f, 0.378632575f, + 0.138108864f, -4.64336586f, -6.56145f, -1.21765113f, 0.311534941f, 0.193946391f, -6.94398832f, 0.212532699f, + -0.1186698f, 0.191252038f, -0.633267105f, -1.87361777f, -3.86840916f, 0.0973287076f, -1.2616179f, 0.134543702f, + }, + { + -2.84534693f, 0.0552037843f, 0.120947838f, 0.264406919f, 0.222327381f, -1.36811912f, 0.231172383f, 0.210781187f, + -0.927948415f, 0.0146018546f, 0.114533335f, 0.154786915f, 0.465321451f, -2.00532007f, -1.06782842f, 0.137209028f, + -1.23837292f, -3.50983667f, 0.062982142f, 0.122971378f, 0.114239432f, -0.057509169f, -7.35652685f, -0.0559553057f, + 0.145541683f, 0.0190632809f, -4.09223318f, 0.190523297f, -4.16050053f, -0.337119937f, 0.0122551313f, -4.96771336f, + -1.90811777f, -6.36547756f, 0.0894139707f, 0.151521608f, 0.231728524f, 0.0754986033f, 0.368380874f, 0.0811997876f, + 0.126341909f, -0.218121305f, -1.53645647f, 0.242623776f, 0.198391482f, -0.0447932854f, -1.65268302f, -0.763128161f, + -0.767695844f, -0.121501908f, 0.0432712398f, -1.82549906f, 0.181410283f, 0.24798435f, -0.63866508f, 0.193439335f, + -7.07365894f, 0.601534069f, 0.694877803f, -6.38803864f, -2.83404064f, -6.59551716f, -0.871630728f, 0.220928565f, + -1.47515523f, -4.61971283f, -5.46381426f, 0.0863680616f, -6.98487902f, 0.189400703f, 0.226188093f, -6.5735693f, + 0.0958105251f, -0.263732433f, -7.14902639f, 0.163570464f, 0.0724674612f, 0.0206859112f, -5.89299202f, 0.198132232f, + -3.84937096f, 0.162713781f, 0.140114501f, 0.193317682f, 0.143427342f, -7.13044262f, -1.57121754f, -6.46690798f, + -0.120846726f, -6.993011f, -5.94974184f, -7.40583944f, 0.00870967656f, 0.682963669f, -0.364709616f, 0.095279932f, + -0.0380665474f, -0.818098307f, 0.237494633f, 0.0239597987f, -6.45850372f, 0.234923571f, 0.14882873f, 0.101302557f, + -6.69333935f, -0.115469739f, 0.231361672f, 0.102731489f, 0.0593426675f, -7.50123072f, 0.275967658f, 0.36796543f, + 0.157870144f, -5.02378464f, -6.72281599f, -0.694582105f, 0.306220323f, 0.180000857f, -7.41716766f, 0.184402451f, + -0.120350309f, 0.187909648f, -0.863899589f, -2.12351489f, -4.33702564f, 0.128783122f, -1.28842127f, 0.10710533f, + }, + { + -3.86120558f, 0.0650100634f, 0.123181224f, 0.0633226261f, 0.179389939f, -1.36395037f, 0.184807241f, 0.200371832f, + -1.61679447f, -0.0401979238f, 0.133931518f, 0.147479445f, 0.440050036f, -1.73944247f, -1.96836662f, 0.13402462f, + -1.44149721f, -3.97896099f, 0.0461652353f, 0.0707256496f, 0.121055417f, -0.0182718858f, -7.63164234f, -0.100622892f, + 0.160293713f, 0.0457521006f, -4.75578451f, 0.199311301f, -4.6357708f, -0.326386064f, 0.0262090657f, -5.46121597f, + -2.6099472f, -6.67278528f, 0.0920890048f, 0.155282885f, 0.220415145f, 0.0846460983f, 0.762741208f, 0.0437240936f, + 0.111749813f, -0.216374844f, -2.0374012f, 0.219551742f, 0.193220481f, -0.0612467229f, -1.98515594f, -0.990753829f, + -0.822137415f, -0.0923760682f, 0.118468843f, -2.50780296f, 0.136253744f, 0.217648536f, -0.613585651f, 0.0307190716f, + -7.37332916f, 0.183872446f, 0.669660509f, -6.74739218f, -3.77400637f, -6.84403849f, -0.523023069f, 0.212257177f, + -2.24155378f, -5.09115267f, -5.45057011f, 0.0294618048f, -7.28125572f, 0.163392425f, 0.203431472f, -7.15169191f, + 0.0976286978f, -0.0535475537f, -7.48903084f, 0.137074485f, 0.0765306354f, 0.000435099006f, -6.12314415f, 0.18972984f, + -4.06650162f, 0.153856054f, 0.121624388f, 0.188269779f, 0.117650032f, -7.42538214f, -1.48236084f, -6.58284044f, + -0.131329924f, -7.25270653f, -6.27416945f, -7.71778011f, -0.0124831405f, 0.435199857f, -0.502721488f, 0.0822563171f, + 0.0227024462f, -0.586075306f, 0.212578893f, 0.0578093715f, -6.79685688f, 0.214503095f, 0.13014555f, 0.101805992f, + -7.04538441f, -0.0861422569f, 0.181747347f, 0.095809035f, 0.0271083415f, -7.89509487f, 0.240125686f, 0.337120563f, + 0.164441884f, -5.10240746f, -7.03027105f, -0.412062585f, 0.283003658f, 0.165264323f, -7.83050728f, 0.168229371f, + -0.114364281f, 0.180664837f, -1.0901134f, -2.25530863f, -4.79980373f, 0.142146558f, -1.10788763f, 0.103818759f, + }, + { + -4.88764811f, 0.052679006f, 0.12250568f, -0.108030513f, 0.146046549f, -1.34715044f, 0.142577663f, 0.175191492f, + -2.06960416f, -0.0776951611f, 0.14680101f, 0.127147228f, 0.400865227f, -1.63927555f, -2.68973231f, 0.121450923f, + -1.48163068f, -4.45575666f, 0.0433425605f, 0.0175659172f, 0.132843435f, -0.00382643379f, -8.05593872f, -0.176547736f, + 0.167097524f, 0.11874298f, -5.22105503f, 0.194126546f, -5.11294556f, -0.283086091f, 0.0391679369f, -5.9252367f, + -2.97819209f, -7.04616451f, 0.104998678f, 0.153204873f, 0.194536611f, 0.0661486238f, 0.947635055f, 0.00713225175f, + 0.0964460596f, -0.202504754f, -2.33382201f, 0.192692488f, 0.18108651f, -0.0636389926f, -2.60822296f, -1.22265029f, + -1.12515926f, -0.085036397f, 0.123155758f, -3.01546788f, 0.113395452f, 0.181758612f, -0.55609268f, -0.187509924f, + -7.68424845f, -0.0949207842f, 0.613795102f, -7.27389002f, -4.28003263f, -7.35736847f, -0.162084341f, 0.20212777f, + -2.83230233f, -5.63818026f, -5.68431187f, -0.0409692936f, -7.42630672f, 0.135999143f, 0.193699598f, -7.94690895f, + 0.0864513293f, 0.267189145f, -7.57477188f, 0.111279555f, 0.0843826607f, -0.0184051618f, -6.26820993f, 0.1870711f, + -4.12247324f, 0.138899624f, 0.111485668f, 0.178398371f, 0.0976137072f, -7.75760508f, -1.51223803f, -7.02218437f, + -0.132542327f, -7.45258522f, -6.78520536f, -7.92351294f, -0.0270751845f, 0.233450919f, -0.756713033f, 0.0618985295f, + 0.298516303f, -0.661367655f, 0.179979667f, 0.0765139386f, -7.13786888f, 0.188588962f, 0.118059739f, 0.0951261446f, + -7.44655323f, -0.0651138276f, 0.154653147f, 0.0122633222f, -0.000188812613f, -8.11156273f, 0.209050253f, 0.306573331f, + 0.151635394f, -5.03898335f, -7.44983101f, -0.290776968f, 0.257352114f, 0.15653953f, -7.89206219f, 0.136111543f, + -0.0823993906f, 0.165115848f, -1.28229952f, -2.59729266f, -5.25057507f, 0.104450136f, -0.76568079f, 0.0850878209f, + }, + { + -5.5265131f, 0.0285695568f, 0.102180362f, -0.233669773f, 0.108430669f, -1.35411441f, 0.11932046f, 0.152122959f, + -2.17959809f, -0.102345012f, 0.138120562f, 0.0897420123f, 0.355205059f, -1.5766263f, -3.16099834f, 0.105793096f, + -1.189291f, -4.82718611f, 0.0803772956f, -0.0131158559f, 0.159138605f, 0.0103968699f, -8.27774048f, -0.243352801f, + 0.166099921f, 0.189088896f, -5.47311878f, 0.181758359f, -5.31119251f, -0.239261895f, 0.0386099108f, -6.24830198f, + -3.02914f, -7.31623983f, 0.0912434235f, 0.148876891f, 0.166017026f, 0.0581006631f, 0.895600677f, 0.0202159435f, + 0.0696107447f, -0.177907705f, -2.58909941f, 0.176489294f, 0.170552358f, -0.0715308189f, -2.92224622f, -1.41729736f, + -1.49433172f, -0.186254293f, 0.0258386899f, -3.37945151f, 0.0880833864f, 0.14479059f, -0.628139198f, -0.404691488f, + -7.76999331f, -0.243422836f, 0.565080345f, -7.47584105f, -4.4999547f, -7.58751678f, 0.149841085f, 0.188569039f, + -3.31325197f, -5.95943546f, -5.69930601f, -0.146673575f, -7.51393795f, 0.122029789f, 0.17672947f, -8.537323f, + 0.0685239434f, 0.656135321f, -7.51136541f, 0.0925282687f, 0.0902542621f, -0.0357167311f, -6.13740873f, 0.156151935f, + -4.10033274f, 0.1254002f, 0.0878337994f, 0.156685293f, 0.0827875435f, -7.86357975f, -1.55492616f, -7.14638281f, + -0.115620628f, -7.51108217f, -7.07286453f, -7.85633898f, -0.0459529981f, -0.00992093422f, -1.00188899f, 0.0262884125f, + 0.627533376f, -0.776827812f, 0.143352553f, 0.0622052923f, -7.22209692f, 0.154586703f, 0.106105834f, 0.0928113312f, + -7.54143524f, -0.0381075889f, 0.138989553f, 0.0466678552f, 0.00724889338f, -8.2741251f, 0.186337024f, 0.273727238f, + 0.142503992f, -4.92482567f, -7.55867481f, -0.275054693f, 0.229007035f, 0.143034652f, -7.89912939f, 0.114460319f, + -0.0755488724f, 0.13684684f, -1.44836199f, -2.87573647f, -5.37252712f, 0.0558883697f, -0.587821782f, 0.0592150018f, + }, + { + -5.72016716f, 0.0333060287f, 0.0835794359f, -0.296637237f, 0.0762670785f, -1.33126569f, 0.123353578f, 0.147282586f, + -2.09788346f, -0.0290537234f, 0.116514869f, 0.0773549527f, 0.325733185f, -1.5587039f, -3.64280844f, 0.107203893f, + -0.59121263f, -5.08116722f, 0.0707311481f, 0.0252183639f, 0.14499639f, 0.0120244529f, -8.18985176f, -0.275773793f, + 0.14638865f, 0.0603125989f, -5.3928833f, 0.158971176f, -5.11888981f, -0.234466523f, 0.0346041471f, -6.12299871f, + -2.9409914f, -7.19662189f, 0.0463128947f, 0.143416926f, 0.143612221f, 0.0325810313f, 0.755753994f, 0.0115336152f, + 0.0674066693f, -0.166168272f, -2.81561828f, 0.150889009f, 0.149051443f, -0.0716071427f, -2.7431376f, -1.5730052f, + -2.03854012f, -0.334813237f, -0.208874345f, -3.66221809f, 0.068579331f, 0.123530172f, -0.741530716f, -0.674473405f, + -7.7406497f, -0.320935577f, 0.518272817f, -7.33800936f, -4.47471189f, -7.21039772f, 0.289385915f, 0.181699052f, + -3.82717609f, -5.99658823f, -5.53883314f, -0.133867443f, -7.53544998f, 0.0960056409f, 0.146981314f, -8.52629948f, + 0.0616251528f, 1.02708876f, -7.14230824f, 0.0802210793f, 0.0825621188f, 0.0020540189f, -5.84688377f, 0.135822594f, + -4.1839962f, 0.095588468f, 0.0671364814f, 0.137721851f, 0.0701295957f, -7.74624538f, -1.64446735f, -7.07898283f, + -0.105306722f, -7.5235405f, -6.9343214f, -7.2775774f, -0.0431279764f, -0.0612613186f, -1.27075756f, 0.0265097916f, + 0.911040068f, -1.03247023f, 0.129491404f, 0.0568704754f, -6.91194105f, 0.134377211f, 0.083760336f, 0.0949646309f, + -7.487041f, -0.0205320977f, 0.139764249f, 0.289762586f, 0.0071632266f, -8.12193203f, 0.14825505f, 0.251565993f, + 0.125319749f, -4.92957306f, -7.10474634f, -0.437704742f, 0.198077872f, 0.133263022f, -7.94430017f, 0.0953181088f, + -0.0814855695f, 0.106135875f, -1.66860867f, -2.73554993f, -5.01781464f, 0.395980358f, -0.800584733f, 0.0538414344f, + }, + }, + { + { + -0.391270518f, 0.105296224f, 0.140426472f, 0.303451568f, 0.115631536f, -1.13921845f, 0.175137043f, 0.212703675f, + -0.475519747f, 0.316723913f, 0.103497103f, 0.180500254f, 0.113338038f, -0.0165741071f, -0.247486621f, 0.0121251047f, + -0.948595166f, -0.58411777f, 0.0922629833f, 0.108204499f, -0.00112906378f, 0.0354491584f, -4.15307426f, 0.577963948f, + 0.074559778f, 0.0157850999f, -1.41190946f, 0.196690142f, 0.054226324f, -0.0258981399f, -0.0781067237f, -2.19235563f, + -1.01363969f, -2.81687188f, 0.122423179f, 0.0322142169f, 0.148873642f, 0.128185555f, 0.434799045f, 0.207223594f, + 0.0368501544f, 0.0108902361f, 0.928495407f, 0.133208588f, 0.150443211f, -0.0445773751f, -1.31232786f, -0.416484207f, + -0.391987741f, -0.238789365f, 0.187523648f, 0.0234347861f, 0.100976162f, 0.286698222f, -0.773893774f, -0.188343018f, + -3.7365272f, 1.31054938f, 0.282559395f, -3.16531086f, -0.525888443f, -3.0952723f, -0.889841735f, 0.0486668125f, + -0.728634119f, -2.02922034f, -2.47783804f, -0.162055165f, -3.51341844f, 0.0256475657f, 0.271357268f, -2.8153255f, + 0.116908848f, -0.651471734f, -4.06807089f, 0.0012537844f, -0.0369890928f, -0.0543270893f, -3.09583712f, 0.164344236f, + -1.62041748f, 0.067076467f, -0.0301050954f, 0.0880530179f, -0.0376613066f, -3.86028862f, 0.588011444f, -3.49444556f, + -0.0495385379f, -3.25643849f, -3.20513034f, -3.97231197f, -0.109654963f, 1.02875912f, -1.10537338f, 0.0771810114f, + 0.337417334f, -0.849968672f, 0.128321245f, 0.0676544234f, -3.13051987f, 0.188004702f, 0.0920639336f, 0.0720468238f, + -3.62880564f, 0.0114301126f, 0.117680371f, 0.165600657f, -0.0791453496f, -3.58485675f, 0.17337741f, 0.301099002f, + 0.0121624507f, -2.53350282f, -3.49553752f, -0.472695857f, 0.172870159f, 0.0394849367f, -3.96561646f, 0.106123425f, + 0.0237914566f, 0.0619569719f, -0.332136065f, 0.770167649f, -1.97900081f, -0.213497549f, -0.770088792f, 0.124490082f, + }, + { + -1.18739462f, 0.0699153543f, 0.104512729f, 0.375574112f, 0.231828198f, -1.37214518f, 0.258420408f, 0.235189334f, + -0.913141131f, 0.176053137f, 0.113339469f, 0.129147813f, 0.456512153f, -0.898974836f, -0.111300528f, 0.0645109713f, + -1.37461936f, -0.795050502f, 0.0859240741f, 0.148965031f, 0.0121515794f, -0.0460225195f, -6.22320366f, -0.0454293266f, + 0.0948906615f, -0.0283722058f, -2.97420478f, 0.168122068f, -3.02930999f, -0.292484671f, -0.0327984393f, -3.98022699f, + -0.985890031f, -5.41410017f, 0.134867862f, 0.12233264f, 0.264259487f, 0.102082305f, -0.163019583f, 0.0636455491f, + 0.102580421f, -0.0848278776f, 0.458920926f, 0.221099824f, 0.21790272f, -0.0198246054f, -1.17845929f, -0.481800377f, + -0.999584913f, -0.31230855f, 0.0889984965f, 0.212079763f, 0.237875775f, 0.250533253f, -0.898435295f, -0.357723564f, + -6.26344824f, 1.89828598f, 0.548570335f, -5.12484694f, -0.99243927f, -5.27513027f, -0.96018666f, 0.228321478f, + -0.265678704f, -3.95521808f, -4.46220922f, 0.0604449436f, -5.98603916f, 0.152652442f, 0.229028195f, -5.08566952f, + 0.121554025f, -0.746924579f, -6.51732683f, 0.112009414f, -0.0245955214f, 0.0355178304f, -4.96350098f, 0.204818428f, + -2.87956071f, 0.181810185f, 0.0594130158f, 0.185743257f, 0.0827618688f, -6.29197073f, -0.435969919f, -5.2280035f, + -0.0416349657f, -5.77886057f, -5.09082556f, -6.61950493f, 0.0703839436f, 0.974006295f, -1.27447641f, 0.0706463531f, + 0.127126068f, -1.26259363f, 0.205434471f, -0.0151937688f, -5.24971533f, 0.223072886f, 0.158040315f, 0.062256068f, + -5.86371851f, -0.0217700601f, 0.232646987f, 0.123631626f, -0.016830951f, -6.57323933f, 0.288729846f, 0.387990922f, + 0.11861933f, -4.08398533f, -6.01287794f, -1.08339381f, 0.290329695f, 0.156116962f, -6.49973631f, 0.206135333f, + -0.138006881f, 0.129868135f, 0.0288569033f, -0.713818431f, -3.50256848f, -0.024651181f, -1.47915626f, 0.0970291197f, + }, + { + -2.65370798f, 0.0497191362f, 0.126579568f, 0.283710927f, 0.261997849f, -1.37696195f, 0.235531062f, 0.22053206f, + -0.800805807f, 0.182755724f, 0.116911829f, 0.163906768f, 0.492669523f, -1.52315676f, -0.785902619f, 0.137531519f, + -1.46895349f, -2.09920001f, 0.0565979443f, 0.167350084f, 0.0881247446f, -0.0749817863f, -7.30925322f, -0.168008208f, + 0.130743727f, -0.0349460654f, -3.6119163f, 0.178583845f, -3.91172576f, -0.31765613f, 0.0110412408f, -4.7364459f, + -1.13761997f, -6.03586817f, 0.0934069306f, 0.148169741f, 0.240357369f, 0.0498761758f, -0.150435612f, 0.0815575197f, + 0.146606773f, -0.218369722f, -0.750034571f, 0.239520758f, 0.199569687f, -0.0218466073f, -1.48461151f, -0.521188319f, + -0.870613515f, -0.233734846f, -0.0385282822f, -0.612621903f, 0.270473361f, 0.266239464f, -0.665159225f, -0.257377565f, + -6.98375607f, 1.368168f, 0.686584592f, -6.16345835f, -1.46388781f, -6.26984644f, -0.962180018f, 0.230605543f, + -0.669425249f, -4.55426168f, -5.41097498f, 0.113311969f, -6.59391546f, 0.185871482f, 0.235825136f, -5.9131279f, + 0.106495701f, -0.525575697f, -6.84934425f, 0.170269951f, 0.0506503247f, 0.00884230714f, -5.8275876f, 0.215946391f, + -3.39098692f, 0.164880171f, 0.138458967f, 0.201414049f, 0.134896502f, -6.97419119f, -1.13213432f, -6.47329903f, + -0.0862213522f, -6.42056227f, -5.98786545f, -7.05635166f, 0.0542388335f, 0.771239161f, -0.897236288f, 0.149169296f, + 0.0467057265f, -1.59601271f, 0.269614577f, 0.0101343077f, -6.14761591f, 0.245454505f, 0.157899201f, 0.0784585029f, + -6.66797733f, -0.0823850781f, 0.262138188f, 0.0769271404f, -0.0089379251f, -6.92203617f, 0.278594553f, 0.380916476f, + 0.136869594f, -4.81247139f, -6.571733f, -1.02528346f, 0.308519363f, 0.188185856f, -7.02202368f, 0.209233657f, + -0.128925398f, 0.184489906f, 0.135252208f, -1.97158062f, -4.067173f, 0.061452359f, -1.648772f, 0.127376199f, + }, + { + -3.62287521f, 0.0512445532f, 0.122587077f, 0.0888206065f, 0.222224921f, -1.36519659f, 0.223786622f, 0.20246692f, + -0.859050274f, 0.0471065938f, 0.11131236f, 0.155370176f, 0.45700857f, -1.25308216f, -1.59816158f, 0.130117789f, + -1.45061767f, -2.86917281f, 0.0628962815f, 0.132342264f, 0.108747937f, -0.0564935356f, -7.31671095f, -0.142519146f, + 0.144454047f, -0.0281808767f, -4.18840456f, 0.187956348f, -4.16572762f, -0.338423729f, 0.0118921893f, -5.16868973f, + -1.96755004f, -6.37383842f, 0.0900059044f, 0.149156958f, 0.229326963f, 0.0868702829f, 0.303673267f, 0.0940998569f, + 0.123962499f, -0.217607647f, -1.6201067f, 0.241644755f, 0.194161668f, -0.0503428206f, -1.60556841f, -0.680798054f, + -0.589553237f, -0.193974689f, 0.0302847028f, -1.38538122f, 0.2336265f, 0.239291981f, -0.617560506f, -0.293006331f, + -7.06584835f, 0.669211149f, 0.678905666f, -6.29682493f, -2.59591365f, -6.42763758f, -0.673912466f, 0.218567789f, + -1.36160564f, -4.72298336f, -5.46830511f, 0.0671350956f, -7.0259819f, 0.182325616f, 0.229372546f, -5.90768719f, + 0.0932699516f, -0.384419054f, -7.20150232f, 0.158962488f, 0.070930928f, -0.00991876051f, -5.93281555f, 0.194555461f, + -3.78393602f, 0.157410443f, 0.140345216f, 0.190130457f, 0.135679841f, -7.21220636f, -1.07286882f, -6.3945303f, + -0.121048152f, -6.79186106f, -5.95357418f, -7.57715559f, -0.000974246301f, 0.683055401f, -0.705217659f, 0.222634122f, + -0.0327921882f, -1.28229666f, 0.231746927f, 0.022493368f, -6.36843729f, 0.232001811f, 0.151076198f, 0.0990134776f, + -6.86424446f, -0.120114081f, 0.235852122f, 0.129931167f, 0.0453907177f, -7.22909641f, 0.269418448f, 0.370249331f, + 0.156630874f, -5.19288969f, -6.73309898f, -0.502214551f, 0.303204715f, 0.174240321f, -7.49520302f, 0.181103408f, + -0.130605906f, 0.181147516f, -0.0953802764f, -2.22147775f, -4.53578949f, 0.0929067805f, -1.67557526f, 0.0999378264f, + }, + { + -4.63873386f, 0.0610508323f, 0.124820456f, -0.112263694f, 0.179287478f, -1.36102784f, 0.177421495f, 0.192057565f, + -1.54789627f, -0.00769318268f, 0.130710557f, 0.148062706f, 0.431737155f, -0.987204611f, -2.4986999f, 0.126933381f, + -1.65374196f, -3.33829713f, 0.0460793711f, 0.0800965354f, 0.115563922f, -0.0172562543f, -7.59182644f, -0.187186718f, + 0.159206077f, -0.00149205886f, -4.85195589f, 0.196744353f, -4.64099836f, -0.327689856f, 0.0258461237f, -5.66219234f, + -2.66937947f, -6.68114614f, 0.0926809385f, 0.152918234f, 0.218013585f, 0.0960177779f, 0.698033631f, 0.0566241592f, + 0.109370396f, -0.215861186f, -2.12105131f, 0.218572736f, 0.188990667f, -0.0667962581f, -1.93804133f, -0.908423722f, + -0.643994808f, -0.164848849f, 0.10548231f, -2.06768513f, 0.188469961f, 0.208956167f, -0.592481077f, -0.455726594f, + -7.36551857f, 0.251549482f, 0.653688371f, -6.65617847f, -3.53587937f, -6.67615891f, -0.325304806f, 0.209896401f, + -2.12800407f, -5.1944232f, -5.45506096f, 0.0102288444f, -7.32235861f, 0.156317338f, 0.206615925f, -6.4858098f, + 0.0950881243f, -0.174234167f, -7.54150677f, 0.13246651f, 0.0749941021f, -0.0301695727f, -6.16296768f, 0.186153069f, + -4.00106621f, 0.148552716f, 0.121855117f, 0.185082555f, 0.109902531f, -7.50714588f, -0.984012127f, -6.51046276f, + -0.131531358f, -7.05155659f, -6.27800179f, -7.88909626f, -0.0221670642f, 0.435291648f, -0.843229532f, 0.209610507f, + 0.0279768035f, -1.05027366f, 0.206831187f, 0.0563429371f, -6.70679045f, 0.211581334f, 0.132393017f, 0.0995169133f, + -7.21628952f, -0.0907865986f, 0.186237797f, 0.123008713f, 0.0131563917f, -7.62296057f, 0.233576462f, 0.339404464f, + 0.163202614f, -5.27151299f, -7.04055405f, -0.219695002f, 0.27998805f, 0.159503788f, -7.90854263f, 0.164930329f, + -0.124619879f, 0.17390269f, -0.32159403f, -2.35327148f, -4.99856806f, 0.106270209f, -1.49504161f, 0.0966512561f, + }, + { + -5.66517639f, 0.0487197749f, 0.124144912f, -0.283616841f, 0.145944089f, -1.34422791f, 0.135191903f, 0.166877225f, + -2.0007062f, -0.04519042f, 0.143580049f, 0.127730489f, 0.392552346f, -0.887037635f, -3.22006559f, 0.114359692f, + -1.69387543f, -3.8150928f, 0.0432566963f, 0.0269368049f, 0.12735194f, -0.00281080231f, -8.01612377f, -0.263111562f, + 0.166009888f, 0.0714988187f, -5.31722641f, 0.191559598f, -5.11817265f, -0.284389883f, 0.0388049968f, -6.12621307f, + -3.03762436f, -7.05452538f, 0.105590612f, 0.150840223f, 0.192135051f, 0.0775203034f, 0.882927477f, 0.0200323183f, + 0.0940666422f, -0.201991096f, -2.41747212f, 0.191713467f, 0.176856697f, -0.0691885278f, -2.56110811f, -1.14032018f, + -0.947016716f, -0.157509178f, 0.110169217f, -2.57535005f, 0.165611655f, 0.173066244f, -0.534988046f, -0.67395556f, + -7.67643785f, -0.0272437334f, 0.597822964f, -7.18267632f, -4.0419054f, -7.18948889f, 0.0356339328f, 0.199766994f, + -2.71875286f, -5.74145079f, -5.68880272f, -0.0602022558f, -7.46740961f, 0.128924057f, 0.196884051f, -7.28102684f, + 0.0839107558f, 0.146502525f, -7.62724781f, 0.106671564f, 0.0828461275f, -0.0490098335f, -6.30803347f, 0.183494329f, + -4.05703783f, 0.133596286f, 0.111716397f, 0.175211146f, 0.089866206f, -7.83936882f, -1.01388943f, -6.94980669f, + -0.132743761f, -7.25143528f, -6.7890377f, -8.09482956f, -0.0367591083f, 0.233542696f, -1.09722114f, 0.189252719f, + 0.303790659f, -1.12556601f, 0.174231961f, 0.0750475079f, -7.04780245f, 0.185667202f, 0.120307215f, 0.0928370655f, + -7.61745834f, -0.0697581694f, 0.159143597f, 0.0394629985f, -0.0141407624f, -7.83942842f, 0.202501029f, 0.308857232f, + 0.150396124f, -5.20808887f, -7.460114f, -0.0984093547f, 0.254336536f, 0.150778994f, -7.97009754f, 0.1328125f, + -0.0926549882f, 0.158353716f, -0.513780236f, -2.69525552f, -5.44933939f, 0.0685737878f, -1.15283477f, 0.0779203176f, + }, + { + -6.30404139f, 0.0246103257f, 0.103819601f, -0.409256101f, 0.108328208f, -1.35119188f, 0.111934699f, 0.143808693f, + -2.11070013f, -0.0698402673f, 0.134899601f, 0.090325281f, 0.346892178f, -0.824388504f, -3.69133162f, 0.0987018645f, + -1.40153575f, -4.18652201f, 0.0802914351f, -0.00374496821f, 0.15364711f, 0.0114125013f, -8.23792458f, -0.329916626f, + 0.165012285f, 0.141844735f, -5.56929016f, 0.179191411f, -5.31642008f, -0.240565673f, 0.0382469706f, -6.44927835f, + -3.08857226f, -7.3246007f, 0.0918353572f, 0.14651224f, 0.163615465f, 0.0694723427f, 0.83089304f, 0.0331160091f, + 0.0672313273f, -0.177394047f, -2.67274952f, 0.175510272f, 0.166322544f, -0.0770803541f, -2.87513137f, -1.33496737f, + -1.31618917f, -0.258727074f, 0.0128521528f, -2.93933368f, 0.140299588f, 0.136098206f, -0.607034624f, -0.891137123f, + -7.76218271f, -0.175745785f, 0.549108207f, -7.38462734f, -4.26182747f, -7.4196372f, 0.347559363f, 0.186208263f, + -3.1997025f, -6.06270599f, -5.70379686f, -0.165906534f, -7.55504084f, 0.114954703f, 0.179913923f, -7.87144041f, + 0.0659833699f, 0.53544873f, -7.56384134f, 0.0879202783f, 0.0887177289f, -0.0663214028f, -6.17723227f, 0.152575165f, + -4.0348978f, 0.120096862f, 0.088064529f, 0.153498068f, 0.0750400424f, -7.94534349f, -1.05657744f, -7.07400513f, + -0.115822062f, -7.30993223f, -7.07669687f, -8.0276556f, -0.0556369238f, -0.00982915983f, -1.34239709f, 0.153642595f, + 0.632807732f, -1.24102616f, 0.137604848f, 0.0607388616f, -7.13203049f, 0.151664943f, 0.108353309f, 0.090522252f, + -7.71234035f, -0.0427519344f, 0.143480003f, 0.0738675296f, -0.0067030564f, -8.00199127f, 0.1797878f, 0.276011139f, + 0.141264722f, -5.09393072f, -7.56895781f, -0.0826870799f, 0.225991428f, 0.137274116f, -7.97716475f, 0.111161284f, + -0.0858044699f, 0.130084693f, -0.67984271f, -2.97369933f, -5.57129145f, 0.0200120211f, -0.974975765f, 0.0520474985f, + }, + { + -6.49769545f, 0.0293467976f, 0.085218668f, -0.47222355f, 0.0761646181f, -1.32834315f, 0.115967818f, 0.138968319f, + -2.0289855f, 0.0034510158f, 0.113293901f, 0.0779382139f, 0.317420304f, -0.806466043f, -4.17314148f, 0.100112662f, + -0.803457379f, -4.44050312f, 0.0706452876f, 0.0345892534f, 0.139504895f, 0.0130400844f, -8.15003586f, -0.362337619f, + 0.145301014f, 0.0130684394f, -5.48905468f, 0.156404227f, -5.12411737f, -0.2357703f, 0.0342412069f, -6.32397509f, + -3.00042367f, -7.20498276f, 0.0469048247f, 0.141052276f, 0.14121066f, 0.0439527109f, 0.691046357f, 0.0244336817f, + 0.0650272518f, -0.165654615f, -2.89926839f, 0.149909988f, 0.144821629f, -0.0771566778f, -2.69602275f, -1.49067509f, + -1.86039758f, -0.407286018f, -0.221860871f, -3.22210026f, 0.120795541f, 0.114837795f, -0.720426142f, -1.16091907f, + -7.73283911f, -0.253258526f, 0.50230068f, -7.24679565f, -4.23658466f, -7.04251814f, 0.487104207f, 0.179338276f, + -3.71362662f, -6.09985876f, -5.54332399f, -0.153100401f, -7.57655287f, 0.0889305547f, 0.150165766f, -7.86041689f, + 0.0590845756f, 0.906402171f, -7.19478416f, 0.0756130889f, 0.0810255855f, -0.0285506546f, -5.88670731f, 0.132245824f, + -4.11856127f, 0.0902851298f, 0.067367211f, 0.134534627f, 0.0623820946f, -7.82800913f, -1.14611864f, -7.00660515f, + -0.105508149f, -7.32239056f, -6.93815374f, -7.44889355f, -0.0528118983f, -0.0611695424f, -1.61126554f, 0.153863981f, + 0.916314423f, -1.49666858f, 0.123743691f, 0.0554040447f, -6.82187462f, 0.131455451f, 0.0860078037f, 0.0926755518f, + -7.65794611f, -0.0251764432f, 0.144254699f, 0.316962272f, -0.00678872317f, -7.84979773f, 0.141705826f, 0.253849894f, + 0.124080472f, -5.09867859f, -7.11502934f, -0.245337158f, 0.195062265f, 0.127502486f, -8.02233601f, 0.0920190737f, + -0.091741167f, 0.0993737355f, -0.900089383f, -2.83351278f, -5.21657896f, 0.360104024f, -1.18773866f, 0.0466739349f, + }, + }, +}; diff --git a/ai_layer/model_pte.c b/ai_layer/model_pte.c deleted file mode 100644 index 5d95da2f..00000000 --- a/ai_layer/model_pte.c +++ /dev/null @@ -1,560 +0,0 @@ -// Generated by create_ai_layer.py -- do not edit. -__attribute__((aligned(16))) const unsigned char model_pte[] = { - 0x24, 0x00, 0x00, 0x00, 0x45, 0x54, 0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, - 0x08, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0x8c, 0x1a, 0x00, 0x00, 0xb0, 0xe1, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe0, 0xdf, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x32, 0xdf, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x50, 0x1a, 0x00, 0x00, - 0x76, 0x65, 0x6c, 0x61, 0x5f, 0x62, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x6d, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x43, 0x4f, 0x50, 0x31, 0x01, 0x00, 0x10, 0x00, 0x18, 0x01, 0x00, 0x20, 0x00, 0x70, 0x00, 0x20, - 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf8, 0x00, - 0x05, 0x01, 0x01, 0x00, 0x0f, 0x01, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, - 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x02, 0x00, 0x0c, 0x01, 0x02, 0x00, - 0x0a, 0x01, 0x0f, 0x00, 0x04, 0x01, 0x0f, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x04, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x09, 0x01, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, - 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x41, 0x19, 0x1f, 0x01, 0x01, 0x00, - 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x01, 0x02, 0x00, 0x11, 0x01, 0x0f, 0x00, 0x13, 0x01, 0x0f, 0x00, 0x1b, 0x01, 0x0f, 0x00, - 0x1c, 0x01, 0x0f, 0x00, 0x1a, 0x01, 0x0f, 0x00, 0x15, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x14, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x16, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x18, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00, - 0x25, 0x01, 0x00, 0x00, 0x26, 0x01, 0x80, 0xff, 0x27, 0x01, 0x7f, 0x00, 0x24, 0x40, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x16, 0x01, 0x0f, 0x00, 0x15, 0x01, 0x0f, 0x00, 0x17, 0x01, 0x0f, 0x00, - 0x24, 0x01, 0x00, 0x01, 0x2f, 0x01, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x30, 0x01, 0x00, 0x00, - 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x01, 0x01, 0x00, 0x31, 0x40, 0x00, 0x00, - 0x00, 0x20, 0x00, 0x00, 0x32, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x32, 0x01, 0x01, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x24, 0x40, 0x22, 0x20, 0x23, 0x72, 0xc9, 0x02, 0x05, 0x01, 0x41, 0x00, - 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x0f, 0x00, 0x0c, 0x01, 0x0f, 0x00, - 0x04, 0x01, 0x02, 0x00, 0x06, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00, 0x14, 0x01, 0x41, 0x00, - 0x10, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x01, 0x0f, 0x00, 0x13, 0x01, 0x07, 0x00, - 0x18, 0x01, 0x80, 0xff, 0x21, 0x01, 0x02, 0x00, 0x20, 0x01, 0x02, 0x00, 0x22, 0x01, 0x04, 0x00, - 0x2e, 0x01, 0x00, 0x00, 0x28, 0x01, 0x01, 0x00, 0x20, 0x40, 0x00, 0x00, 0xa0, 0x20, 0x00, 0x00, - 0x21, 0x40, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x29, 0x01, 0x01, 0x00, 0x22, 0x40, 0x00, 0x00, - 0x00, 0x20, 0x00, 0x00, 0x23, 0x40, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x16, 0x01, 0x07, 0x00, - 0x24, 0x01, 0x00, 0x02, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x10, 0x00, 0x00, 0x04, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, - 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x41, 0x01, 0x10, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x12, 0x01, 0x07, 0x00, 0x11, 0x01, 0x07, 0x00, 0x1b, 0x01, 0x07, 0x00, - 0x1c, 0x01, 0x07, 0x00, 0x1a, 0x01, 0x07, 0x00, 0x15, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - 0x16, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x21, 0x01, 0x01, 0x00, - 0x20, 0x01, 0x01, 0x00, 0x22, 0x01, 0x03, 0x00, 0x24, 0x40, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, - 0x15, 0x01, 0x07, 0x00, 0x24, 0x01, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x24, 0x40, 0x26, 0x20, - 0x9f, 0x71, 0x5e, 0x18, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x07, 0x00, - 0x0c, 0x01, 0x07, 0x00, 0x0a, 0x01, 0x07, 0x00, 0x05, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - 0x06, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x09, 0x01, 0x80, 0xff, 0x00, 0x01, 0x01, 0x00, - 0x01, 0x01, 0x01, 0x00, 0x03, 0x01, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00, 0x14, 0x01, 0x41, 0x00, - 0x10, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x13, 0x01, 0x0f, 0x00, 0x18, 0x01, 0x80, 0xff, - 0x21, 0x01, 0x02, 0x00, 0x20, 0x01, 0x02, 0x00, 0x22, 0x01, 0x04, 0x00, 0x28, 0x01, 0x00, 0x00, - 0x20, 0x40, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x21, 0x40, 0x00, 0x00, 0xc0, 0x05, 0x00, 0x00, - 0x29, 0x01, 0x00, 0x00, 0x22, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x01, 0x0f, 0x00, - 0x09, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, - 0x02, 0x01, 0x00, 0x00, 0x14, 0x01, 0x01, 0x31, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x01, 0x03, 0x00, 0x11, 0x01, 0x03, 0x00, 0x1b, 0x01, 0x0f, 0x00, 0x1c, 0x01, 0x0f, 0x00, - 0x1a, 0x01, 0x03, 0x00, 0x15, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x16, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, - 0x21, 0x01, 0x01, 0x00, 0x20, 0x01, 0x01, 0x00, 0x22, 0x01, 0x03, 0x00, 0x24, 0x40, 0x00, 0x20, - 0x01, 0x00, 0x00, 0x00, 0x16, 0x01, 0x03, 0x00, 0x15, 0x01, 0x03, 0x00, 0x24, 0x01, 0x00, 0x01, - 0x05, 0x00, 0x00, 0x00, 0x24, 0x40, 0x29, 0x20, 0x8d, 0xa0, 0x86, 0x34, 0x05, 0x01, 0x01, 0x00, - 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, - 0x0a, 0x01, 0x00, 0x00, 0x04, 0x01, 0xff, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x09, 0x01, 0x80, 0xff, 0x14, 0x01, 0x01, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x12, 0x01, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x13, 0x01, 0x09, 0x00, 0x1b, 0x01, 0x00, 0x00, - 0x1c, 0x01, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x15, 0x40, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x14, 0x40, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x18, 0x01, 0x4d, 0x00, 0x21, 0x01, 0x00, 0x00, - 0x20, 0x01, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, - 0x21, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x22, 0x40, 0x00, 0x00, 0x60, 0x08, 0x00, 0x00, - 0x16, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x5f, 0x98, 0xff, 0xff, 0x23, 0x72, 0xc9, 0x02, 0x22, 0x00, 0x45, 0xbd, 0xff, 0xff, 0xb9, 0x1d, - 0xcd, 0x5c, 0x27, 0x00, 0x70, 0x6a, 0x00, 0x00, 0xbd, 0x10, 0xa2, 0x59, 0x27, 0x00, 0x08, 0x99, - 0xff, 0xff, 0xf7, 0x1e, 0x8f, 0x2e, 0x26, 0x00, 0x87, 0xf6, 0xff, 0xff, 0x41, 0x6f, 0x31, 0x0b, - 0x24, 0x00, 0xd2, 0x81, 0x00, 0x00, 0x0d, 0xab, 0x03, 0x2d, 0x26, 0x00, 0xa2, 0x32, 0x00, 0x00, - 0xa1, 0xd6, 0x12, 0x15, 0x25, 0x00, 0x40, 0x04, 0x00, 0x00, 0x41, 0x8f, 0x5e, 0x27, 0x26, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xb9, 0x06, 0xdc, 0x01, 0x8c, 0x31, 0xc6, 0x08, 0x30, 0x24, 0xcb, 0x4e, 0x29, 0xf8, 0xda, 0x84, - 0x14, 0x63, 0x65, 0xdf, 0x5b, 0x90, 0x0c, 0x09, 0x40, 0x1a, 0x12, 0xfe, 0x57, 0x6f, 0xd4, 0x87, - 0xdb, 0x08, 0x95, 0xdd, 0x4f, 0xfe, 0xff, 0xa2, 0x93, 0x9c, 0x67, 0xf6, 0xa7, 0xe6, 0x23, 0xff, - 0x8f, 0x31, 0xc6, 0x98, 0x14, 0x63, 0xd9, 0xbf, 0x7d, 0xed, 0x25, 0x23, 0x7e, 0x05, 0x12, 0xfe, - 0xd7, 0x5b, 0x84, 0xdf, 0xc7, 0x69, 0x27, 0x48, 0x4b, 0xfe, 0x3f, 0xb6, 0x4c, 0x97, 0xcf, 0xdb, - 0xe4, 0x0b, 0x08, 0xff, 0x8f, 0x31, 0xc6, 0x98, 0x14, 0x63, 0xe3, 0xfc, 0xb3, 0xa9, 0x6a, 0x3b, - 0x5a, 0x14, 0x12, 0xfe, 0x8f, 0xe5, 0x57, 0x19, 0x2b, 0x5e, 0x4e, 0x18, 0x49, 0xfe, 0x7f, 0x11, - 0xdb, 0x30, 0x29, 0x60, 0xa8, 0x61, 0x28, 0xff, 0x8f, 0x31, 0xc6, 0x98, 0x14, 0x63, 0xe4, 0x28, - 0xa4, 0xf0, 0xed, 0x80, 0x35, 0xf7, 0x12, 0xfe, 0x47, 0xb6, 0x39, 0xf3, 0x08, 0x93, 0x66, 0x1e, - 0x49, 0xfe, 0x3f, 0x99, 0x2b, 0x4d, 0xf6, 0x02, 0x82, 0xed, 0x0e, 0xff, 0x8f, 0x31, 0xc6, 0x98, - 0x14, 0x63, 0x31, 0x28, 0xfa, 0x3e, 0xa0, 0x0d, 0x36, 0xa5, 0x12, 0xfe, 0x07, 0x64, 0xf6, 0x7b, - 0x2d, 0x5c, 0x10, 0xd7, 0x49, 0xfe, 0xbf, 0x30, 0x7f, 0xb6, 0x58, 0x1e, 0x1c, 0xf3, 0x01, 0xff, - 0x8f, 0x31, 0xc6, 0x98, 0x14, 0x63, 0xe7, 0x2d, 0x5d, 0x6e, 0x9b, 0x65, 0xd8, 0x33, 0x12, 0xfe, - 0x07, 0x08, 0x0d, 0xe0, 0x5f, 0x92, 0xfe, 0x05, 0x48, 0xfe, 0x3f, 0xc1, 0x01, 0x0a, 0x5d, 0xca, - 0x7e, 0xdd, 0x3e, 0xff, 0x8f, 0x31, 0xc6, 0x98, 0x14, 0x63, 0x15, 0x72, 0x7d, 0x02, 0xb3, 0x7b, - 0xaa, 0xa5, 0x12, 0xfe, 0xa7, 0x47, 0xe3, 0xe7, 0x13, 0x22, 0x65, 0x15, 0x4a, 0xfe, 0xff, 0xf3, - 0x59, 0x06, 0x2c, 0xfb, 0xd8, 0x99, 0x32, 0xff, 0x8f, 0x31, 0xc6, 0x98, 0x14, 0x63, 0xa4, 0x5f, - 0x33, 0xbe, 0xde, 0xfc, 0x0c, 0xf7, 0x12, 0xfe, 0xe7, 0x86, 0x81, 0xe8, 0xd6, 0x71, 0x7d, 0x06, - 0x48, 0xfe, 0xbf, 0x45, 0x67, 0xbb, 0xbb, 0xed, 0x87, 0xa6, 0x17, 0xff, 0x8f, 0x31, 0xc6, 0x98, - 0x14, 0x63, 0xf4, 0x94, 0xc4, 0xc2, 0x40, 0x9a, 0xbb, 0x12, 0x12, 0xfe, 0xa7, 0x91, 0xa9, 0x17, - 0x37, 0x1f, 0x53, 0x1b, 0x49, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x68, 0x13, 0x00, 0x00, 0x9f, 0x71, 0x5e, 0x18, 0x26, 0x00, 0x01, 0x4b, 0x00, 0x00, 0xd1, 0xbb, - 0x93, 0x18, 0x26, 0x00, 0x80, 0xe5, 0xff, 0xff, 0xef, 0x3d, 0x28, 0x31, 0x27, 0x00, 0xa0, 0x3a, - 0x00, 0x00, 0x19, 0xbb, 0xe0, 0x30, 0x27, 0x00, 0xdd, 0xeb, 0x00, 0x00, 0xc3, 0x94, 0x2d, 0x62, - 0x28, 0x00, 0x35, 0x62, 0xff, 0xff, 0x0b, 0x5b, 0xf4, 0x5f, 0x28, 0x00, 0xb3, 0xf2, 0xff, 0xff, - 0x15, 0xe2, 0x05, 0x30, 0x27, 0x00, 0x06, 0xa5, 0xff, 0xff, 0x2d, 0xdb, 0x45, 0x0c, 0x25, 0x00, - 0xf4, 0xd1, 0x00, 0x00, 0x67, 0xa2, 0xe5, 0x30, 0x27, 0x00, 0xb7, 0xcb, 0x00, 0x00, 0x49, 0x9e, - 0x54, 0x62, 0x28, 0x00, 0x7f, 0xf9, 0xff, 0xff, 0x07, 0x61, 0x1f, 0x62, 0x28, 0x00, 0x0e, 0xad, - 0xff, 0xff, 0x31, 0x8e, 0x72, 0x18, 0x26, 0x00, 0x69, 0x6c, 0xff, 0xff, 0xf9, 0xbd, 0x3e, 0x0c, - 0x25, 0x00, 0xb6, 0xac, 0x00, 0x00, 0xdd, 0x24, 0xf2, 0x60, 0x28, 0x00, 0x85, 0xef, 0xff, 0xff, - 0xbb, 0x83, 0xc8, 0x61, 0x28, 0x00, 0x4b, 0xe3, 0x00, 0x00, 0xa9, 0x82, 0x3d, 0x18, 0x26, 0x00, - 0xc8, 0x23, 0x5c, 0x01, 0x0c, 0x00, 0xdd, 0xe4, 0x51, 0x20, 0x93, 0x44, 0x1d, 0x4a, 0x00, 0xe0, - 0xba, 0xeb, 0x25, 0xc1, 0x9f, 0x95, 0x85, 0x03, 0x00, 0xad, 0xce, 0x82, 0xa8, 0x66, 0x08, 0x0c, - 0x5c, 0xf5, 0xe5, 0xcb, 0xa9, 0xd9, 0xc2, 0xd4, 0x96, 0x00, 0x50, 0xd4, 0xc3, 0xef, 0xd2, 0x75, - 0x73, 0x03, 0x0d, 0x00, 0x13, 0xf2, 0xb3, 0xc9, 0x77, 0x0f, 0xc3, 0xd4, 0x28, 0xf2, 0x64, 0x0d, - 0xa6, 0x58, 0x21, 0xdc, 0xf0, 0x8f, 0x99, 0x6b, 0x8a, 0xa3, 0x25, 0x88, 0x01, 0xf5, 0xff, 0x7c, - 0x9a, 0xd2, 0x92, 0x79, 0x48, 0x39, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4f, 0x00, 0x00, 0x00, - 0x28, 0xa8, 0xae, 0xa0, 0xce, 0xc4, 0xab, 0x29, 0xb4, 0x9c, 0xb2, 0xbf, 0xb3, 0x78, 0xd6, 0xf7, - 0x00, 0x20, 0x9d, 0x54, 0x6c, 0xc7, 0xb9, 0x61, 0xdd, 0x05, 0x00, 0x58, 0xf6, 0x83, 0xf8, 0xa0, - 0x2d, 0x55, 0x83, 0x72, 0xf4, 0xd8, 0xf4, 0xa2, 0x6e, 0xcf, 0xee, 0x00, 0x30, 0x62, 0x2c, 0x47, - 0x7f, 0xbe, 0x07, 0x3d, 0xf8, 0xff, 0x48, 0x85, 0x6d, 0xb1, 0x9c, 0x0c, 0xf0, 0xb5, 0xff, 0x1f, - 0x2a, 0x98, 0x64, 0x78, 0x67, 0xb0, 0x60, 0xf6, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x6a, - 0x83, 0xe2, 0xaf, 0x8c, 0x2d, 0xf9, 0xa5, 0x00, 0x20, 0x7c, 0xb5, 0x21, 0x3f, 0xc5, 0xe7, 0x30, - 0x03, 0x00, 0x88, 0xf9, 0x4d, 0x8f, 0x8c, 0xd5, 0x89, 0x3e, 0xb7, 0xbf, 0x36, 0xf1, 0x0a, 0xc6, - 0xeb, 0x32, 0x00, 0x00, 0xf7, 0x16, 0xc7, 0x23, 0x94, 0xd9, 0xb3, 0x07, 0xf0, 0xc8, 0x25, 0xb0, - 0x14, 0x3a, 0xe1, 0xbf, 0x31, 0xff, 0x3f, 0xbb, 0xe1, 0x5b, 0x13, 0xba, 0x28, 0x54, 0xf2, 0xff, - 0x6b, 0xd3, 0xfd, 0x55, 0x8f, 0xca, 0x50, 0x2c, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xf0, - 0x2c, 0x41, 0xc7, 0x8f, 0xa7, 0x86, 0xb8, 0x07, 0x00, 0x6e, 0x3b, 0xa0, 0x8c, 0x16, 0xbb, 0x36, - 0xd2, 0xa7, 0xc7, 0x90, 0x7d, 0x37, 0xe8, 0x4b, 0x14, 0x00, 0x80, 0x1e, 0x00, 0xf2, 0x59, 0x25, - 0x79, 0x84, 0x0c, 0x40, 0xf4, 0x77, 0x82, 0xb1, 0x60, 0x10, 0xd8, 0x37, 0xfb, 0x45, 0x7e, 0xbf, - 0x20, 0x3f, 0x93, 0x16, 0xf0, 0x8f, 0x6c, 0x92, 0xc4, 0x7f, 0x1f, 0x5a, 0x55, 0xfb, 0xff, 0xb8, - 0x5d, 0x5a, 0x36, 0xcf, 0x54, 0x79, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0x68, 0x20, 0xbe, 0xe2, 0x2c, 0xc6, 0xbf, 0x41, 0x1c, 0xc2, 0x5b, 0x31, 0x2d, 0x5b, 0xf1, 0x80, - 0x00, 0x60, 0xa3, 0x3f, 0xfb, 0x04, 0x6e, 0xb6, 0x17, 0x08, 0x00, 0xbf, 0x7b, 0xf8, 0x95, 0xd6, - 0xf6, 0xce, 0xf0, 0x86, 0xe0, 0x49, 0x16, 0x52, 0x69, 0x41, 0xd0, 0x00, 0x00, 0x7b, 0x4d, 0xbb, - 0xc1, 0x9f, 0x99, 0xe3, 0xf0, 0xff, 0xcb, 0xaf, 0xc3, 0x95, 0x49, 0x67, 0x47, 0x70, 0xff, 0x7f, - 0x7f, 0x29, 0x1c, 0xbf, 0xba, 0x59, 0x25, 0xf6, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0x94, 0x62, 0x97, 0x60, 0xcb, 0xae, 0xe0, 0x00, 0x80, 0x37, 0xe9, 0x3b, 0x74, 0x9e, 0xaa, 0xca, - 0x0f, 0x00, 0x63, 0x6e, 0x78, 0x06, 0x89, 0x4e, 0x58, 0x95, 0xcd, 0x39, 0xf3, 0xf7, 0x7e, 0x66, - 0x60, 0xe1, 0x00, 0x50, 0xa7, 0xd5, 0x0f, 0x45, 0xc2, 0xc8, 0xf5, 0x04, 0xf0, 0x09, 0x29, 0xf1, - 0xa3, 0xed, 0x29, 0xc8, 0xc9, 0xff, 0xff, 0x94, 0xa6, 0xcd, 0x9f, 0xd6, 0x05, 0xa0, 0xf4, 0xff, - 0x02, 0x72, 0x47, 0xe3, 0x28, 0x05, 0x83, 0x27, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x40, - 0xb1, 0xc7, 0x8d, 0x58, 0xc5, 0x5e, 0x0f, 0x0e, 0x00, 0x8f, 0x10, 0x8f, 0xe8, 0x6b, 0xc1, 0x65, - 0xd4, 0x50, 0x1b, 0xd1, 0x20, 0xeb, 0x54, 0x92, 0x43, 0x00, 0xf0, 0x03, 0xed, 0xe2, 0xd7, 0x14, - 0x5d, 0xa2, 0x0e, 0x00, 0x7f, 0x22, 0xe2, 0x3f, 0xa7, 0xb3, 0xbe, 0x25, 0xe2, 0x4c, 0x3a, 0xdc, - 0x0a, 0x3c, 0xb1, 0x47, 0xf0, 0xef, 0x6b, 0x29, 0x35, 0xcf, 0xf0, 0x2a, 0x2d, 0xf5, 0xff, 0x2b, - 0x7b, 0x4a, 0xde, 0xd2, 0x39, 0x38, 0x56, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0x1e, 0xd1, 0xc2, 0x9f, 0x7a, 0xca, 0x8f, 0x52, 0x8d, 0x77, 0x27, 0x0c, 0x5a, 0x7c, 0x48, 0x0c, - 0x20, 0x80, 0x88, 0x45, 0xc8, 0x27, 0x9b, 0x94, 0x51, 0x0a, 0x00, 0xab, 0x15, 0xa3, 0xa7, 0xe5, - 0xe5, 0xdb, 0x15, 0xdc, 0x29, 0x67, 0xf6, 0xd9, 0xc9, 0x2d, 0xad, 0x00, 0x60, 0x46, 0xb2, 0x47, - 0x24, 0x6f, 0x6d, 0x47, 0xfd, 0xff, 0xe5, 0xdd, 0xe1, 0x2a, 0x27, 0x1e, 0xbe, 0x85, 0xff, 0xbf, - 0x0a, 0x5d, 0x5a, 0xd5, 0x09, 0xb3, 0x75, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xd9, - 0x39, 0x46, 0xb1, 0xe1, 0xce, 0xfc, 0xc3, 0x00, 0x80, 0x6e, 0xbd, 0x39, 0x5b, 0xae, 0x80, 0x27, - 0x02, 0x00, 0xf9, 0x19, 0xb3, 0x7e, 0x9c, 0x4c, 0x7b, 0x5b, 0x0a, 0x35, 0xee, 0x11, 0x3e, 0xeb, - 0x61, 0x19, 0x00, 0x00, 0xd2, 0x3e, 0x7f, 0x59, 0xa7, 0x21, 0xb5, 0x01, 0xf4, 0x9f, 0x79, 0xa4, - 0xae, 0xcf, 0x1f, 0xd8, 0x16, 0xff, 0x4f, 0xae, 0xa8, 0xb9, 0x02, 0xc7, 0xf1, 0xf0, 0xf5, 0xff, - 0x1e, 0x3d, 0x6e, 0x11, 0xfd, 0xe9, 0x8d, 0x4a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x04, - 0x1b, 0xb7, 0x5e, 0x9a, 0x63, 0xcb, 0xe6, 0x36, 0x00, 0x90, 0x24, 0xb9, 0xaa, 0x1d, 0xbf, 0x10, - 0x09, 0xaa, 0xcd, 0x83, 0xf1, 0x90, 0x7e, 0x7c, 0xff, 0x0e, 0x00, 0xe6, 0x8b, 0xf1, 0xf7, 0x93, - 0x3d, 0xc6, 0xa8, 0x00, 0x90, 0xf6, 0x2e, 0x31, 0x06, 0x07, 0x09, 0x0d, 0x6d, 0xd7, 0x54, 0xcd, - 0xba, 0x7b, 0xd2, 0x1d, 0xfa, 0xff, 0xf6, 0x60, 0x5f, 0x6d, 0x77, 0x72, 0x3a, 0x65, 0xff, 0xcf, - 0xd6, 0xad, 0xcf, 0x70, 0x0b, 0x2e, 0x9d, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0xe0, 0x56, 0x6c, 0x7a, 0x62, 0x99, 0xcf, 0x9f, 0x4f, 0xf1, 0xd2, 0x13, 0x31, 0xc9, 0x82, 0x87, - 0x0b, 0x00, 0x68, 0x19, 0x60, 0x9f, 0x01, 0xd9, 0x1e, 0x23, 0x00, 0x30, 0x14, 0x58, 0x1d, 0x55, - 0xb5, 0x09, 0x7e, 0x35, 0xda, 0xbf, 0x71, 0xa4, 0x05, 0xdf, 0xae, 0x0d, 0xf0, 0xa2, 0xd4, 0x5e, - 0x70, 0x4e, 0xb3, 0xbe, 0x3e, 0xff, 0x3f, 0x47, 0xb3, 0xac, 0x7c, 0xa5, 0x8f, 0x50, 0xf5, 0xff, - 0xba, 0x02, 0x6d, 0x07, 0xec, 0x40, 0xf8, 0x80, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0xe2, 0x5b, 0x1d, 0x0f, 0xd8, 0x4f, 0x6a, 0x03, 0x00, 0xc4, 0x15, 0x45, 0x93, 0x66, 0xc3, 0x24, - 0xdf, 0x00, 0x40, 0xcf, 0xb7, 0xbb, 0x95, 0xc1, 0x74, 0x3b, 0xba, 0xa7, 0x27, 0x10, 0xa8, 0xdc, - 0xf2, 0x4d, 0x08, 0x00, 0xf6, 0x6c, 0x80, 0x7e, 0xed, 0x72, 0x18, 0x1f, 0xf0, 0xcf, 0x3a, 0xd4, - 0x56, 0x27, 0xfa, 0x8e, 0xe6, 0xf0, 0xff, 0xf4, 0x8c, 0xcc, 0xe4, 0x28, 0x49, 0x18, 0xa4, 0xff, - 0x8f, 0xd0, 0x3e, 0x42, 0xcf, 0x48, 0x07, 0xcb, 0xf3, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0xe9, 0xf9, 0x36, 0x6b, 0x52, 0x99, 0x80, 0xa5, 0x00, 0xa0, 0x3a, 0x0d, 0xf8, 0x89, 0xac, 0x59, - 0x0f, 0xd6, 0xd1, 0x4f, 0xc6, 0x89, 0x5c, 0x9f, 0x32, 0x0c, 0x00, 0xc0, 0xc5, 0x71, 0xbb, 0x4f, - 0xf9, 0x70, 0xe6, 0x00, 0x10, 0xe7, 0x03, 0xa9, 0x73, 0x0b, 0x26, 0x95, 0x48, 0xa2, 0x3a, 0xa2, - 0xc0, 0xfb, 0x25, 0x17, 0xf4, 0xff, 0x17, 0x86, 0xf5, 0xb1, 0x88, 0x33, 0x2d, 0x30, 0xff, 0x6f, - 0xfa, 0x06, 0x6c, 0xb8, 0x86, 0x6a, 0xd2, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0x00, - 0xa0, 0xff, 0x0e, 0xb6, 0xe8, 0x4b, 0x21, 0x12, 0x15, 0x80, 0x02, 0xe9, 0x3a, 0x8d, 0x06, 0x3d, - 0x0a, 0x00, 0xf8, 0x1b, 0x65, 0x31, 0x75, 0x61, 0x62, 0x00, 0x00, 0xa0, 0x93, 0x8d, 0x76, 0xeb, - 0x6c, 0xc9, 0x96, 0xfa, 0x09, 0x48, 0x96, 0xee, 0x97, 0xdb, 0xde, 0x01, 0xf0, 0xac, 0xcb, 0xe5, - 0xb5, 0x1d, 0x57, 0x5d, 0xee, 0xff, 0x1f, 0x2a, 0x3a, 0x4b, 0xd4, 0x04, 0x0a, 0xa4, 0xf0, 0xff, - 0x75, 0x13, 0x24, 0x92, 0xb0, 0xe6, 0x58, 0x7a, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xe0, - 0x64, 0xfb, 0x0e, 0xe2, 0xf1, 0x98, 0xe8, 0x02, 0x00, 0xc5, 0x2a, 0x48, 0x55, 0x3d, 0xae, 0x17, - 0x69, 0x00, 0x10, 0xca, 0x45, 0x5a, 0x21, 0xf0, 0xfd, 0x7a, 0x31, 0x6f, 0x8c, 0x7d, 0x7b, 0xb6, - 0xf0, 0x53, 0x01, 0x00, 0x7e, 0xad, 0xc7, 0x8d, 0xe3, 0xd8, 0xd4, 0xbd, 0xf0, 0x1f, 0xab, 0x7c, - 0xdb, 0x8f, 0x1a, 0x08, 0x2e, 0xfe, 0xff, 0xf2, 0x91, 0xfc, 0x6c, 0x25, 0x5f, 0x0c, 0xae, 0xff, - 0x8f, 0x69, 0x53, 0x99, 0x1c, 0xfe, 0xe1, 0xad, 0xf9, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0xf3, 0xa5, 0x99, 0x0a, 0x36, 0xde, 0xbf, 0x0c, 0x00, 0x50, 0xf3, 0x3a, 0x49, 0x12, 0x0a, 0x6a, - 0xfd, 0x3e, 0x2f, 0x5b, 0xa6, 0x1b, 0x76, 0x1e, 0xdc, 0x0f, 0x00, 0x7c, 0xde, 0x28, 0x02, 0xa3, - 0xd2, 0xee, 0x8d, 0x00, 0xc0, 0xbb, 0x09, 0x3a, 0x47, 0xec, 0x56, 0x03, 0xf4, 0x44, 0x80, 0xaf, - 0x13, 0xf4, 0xc5, 0x8d, 0xfe, 0xff, 0x54, 0x2b, 0x93, 0x63, 0x2b, 0xd5, 0x5e, 0x6b, 0xff, 0xaf, - 0x0f, 0xf2, 0x80, 0xf1, 0x50, 0xac, 0x05, 0xfa, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0xa0, 0x3a, 0x36, 0x56, 0x0b, 0xdb, 0x5f, 0xe1, 0x22, 0x19, 0xe1, 0x36, 0x81, 0xfe, 0x72, 0x25, - 0x06, 0x00, 0x3b, 0x6e, 0x3b, 0xab, 0x54, 0xa9, 0x90, 0x11, 0x00, 0x90, 0x9d, 0x12, 0x40, 0x14, - 0x7d, 0x90, 0x8e, 0x46, 0x02, 0xdd, 0x2d, 0xbd, 0x0f, 0xb4, 0x24, 0x08, 0xf0, 0x33, 0x82, 0x2f, - 0x0f, 0x8b, 0x42, 0xd1, 0x6f, 0xff, 0x6f, 0x08, 0xc0, 0x22, 0x1d, 0x60, 0xe6, 0x76, 0xf8, 0xff, - 0x32, 0x2f, 0x65, 0xea, 0x52, 0x5d, 0xdd, 0xe5, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0xae, 0xf2, 0x4c, 0xc3, 0x84, 0x58, 0x15, 0x05, 0x00, 0xb1, 0x8c, 0x11, 0x51, 0x41, 0x6f, 0x8b, - 0xba, 0x00, 0xe0, 0x5c, 0x41, 0x40, 0xb7, 0xee, 0xe0, 0xe2, 0xa6, 0x89, 0x96, 0x66, 0xf0, 0xd2, - 0x9a, 0xfa, 0x0d, 0x00, 0x35, 0xfc, 0xd3, 0x31, 0x9c, 0xfc, 0xea, 0x65, 0xf0, 0xbf, 0xcb, 0x9c, - 0xc0, 0xb2, 0x44, 0x04, 0x71, 0xf5, 0xff, 0x78, 0x2a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xf0, - 0xbb, 0xf7, 0xfe, 0xff, 0x8d, 0xa0, 0x86, 0x34, 0x29, 0x00, 0xd8, 0x70, 0x01, 0x00, 0xa7, 0x3f, - 0x36, 0x34, 0x29, 0x00, 0x56, 0x05, 0x01, 0x00, 0x81, 0x58, 0xbc, 0x68, 0x2a, 0x00, 0x31, 0xf9, - 0xfe, 0xff, 0xcf, 0xea, 0xef, 0x68, 0x2a, 0x00, 0xcb, 0xa8, 0x00, 0x00, 0x49, 0x3e, 0x33, 0x34, - 0x29, 0x00, 0x2c, 0x9e, 0x00, 0x00, 0xcd, 0x98, 0x1a, 0x69, 0x2a, 0x00, 0x94, 0xeb, 0xfe, 0xff, - 0x1d, 0x76, 0x82, 0x34, 0x29, 0x00, 0x8d, 0xf0, 0xfe, 0xff, 0x4b, 0x61, 0xbe, 0x68, 0x2a, 0x00, - 0x4c, 0xc0, 0xfe, 0xff, 0x5b, 0xfb, 0xc7, 0x68, 0x2a, 0x00, 0x15, 0xac, 0x00, 0x00, 0xeb, 0x67, - 0x2e, 0x1a, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x88, 0x4f, 0x5c, 0x01, 0x0c, 0x00, 0x8e, 0xdc, 0xc8, 0xa5, 0x48, 0x69, 0x7e, 0x15, 0x00, 0xf0, - 0x35, 0x0a, 0xc6, 0x72, 0x7b, 0x6a, 0x07, 0x0c, 0x00, 0x1e, 0x81, 0x15, 0x1f, 0xef, 0xaa, 0x19, - 0xdb, 0xb8, 0x07, 0x73, 0x2a, 0xd3, 0x43, 0x44, 0x0f, 0x00, 0x10, 0xe8, 0x95, 0xed, 0x47, 0x51, - 0xdd, 0xef, 0x04, 0x10, 0x4e, 0x42, 0xcd, 0x3e, 0x9a, 0x1c, 0x17, 0xc0, 0xbe, 0x10, 0x12, 0xd7, - 0xbe, 0x25, 0x69, 0xc9, 0x00, 0x60, 0x5d, 0x03, 0xd0, 0xd6, 0x63, 0x54, 0x4f, 0x01, 0x00, 0xaf, - 0xe3, 0x5e, 0x3a, 0x14, 0xaa, 0x69, 0x12, 0x7f, 0x7a, 0x2a, 0xdf, 0xa5, 0x36, 0x9c, 0xc8, 0x00, - 0x90, 0x88, 0xa5, 0xaf, 0x80, 0x44, 0xe6, 0x9b, 0x01, 0x00, 0x9a, 0xc1, 0x7f, 0xf4, 0x30, 0x29, - 0x82, 0x76, 0x33, 0xe1, 0x93, 0x1a, 0x08, 0x40, 0x2c, 0xdd, 0x00, 0x30, 0x17, 0xa7, 0x5d, 0x8f, - 0x51, 0x7f, 0x00, 0x0d, 0x00, 0x76, 0x98, 0x18, 0xab, 0x66, 0x1c, 0x54, 0xc0, 0xb6, 0x6a, 0xbc, - 0x4f, 0x38, 0x0a, 0xaf, 0x58, 0x00, 0xb0, 0x28, 0x93, 0x03, 0xcb, 0xad, 0xad, 0x10, 0x05, 0x00, - 0x7a, 0x9d, 0xf2, 0xb4, 0x2e, 0x70, 0x58, 0x4d, 0x8f, 0x13, 0xc6, 0x14, 0xb0, 0x01, 0x4c, 0x98, - 0x00, 0x90, 0x92, 0x96, 0xa2, 0xf5, 0x21, 0xf9, 0xdc, 0x80, 0x08, 0x84, 0xf6, 0xd4, 0xe6, 0x55, - 0x57, 0x1b, 0x5c, 0x3b, 0xf4, 0x81, 0x40, 0x82, 0x3f, 0x77, 0xce, 0x00, 0x40, 0x75, 0x46, 0x71, - 0x53, 0x11, 0x96, 0xf5, 0x03, 0x00, 0xe1, 0xb3, 0x17, 0x25, 0xe7, 0x94, 0xfd, 0x95, 0x2c, 0x00, - 0xc7, 0x6f, 0x75, 0x95, 0x01, 0x42, 0x00, 0x90, 0x43, 0x1f, 0x36, 0x9e, 0x67, 0x40, 0x10, 0x0f, - 0x00, 0xdd, 0x14, 0x7c, 0xaa, 0x0a, 0x7b, 0xc0, 0x20, 0x32, 0x52, 0x3b, 0x65, 0x48, 0x4a, 0x66, - 0x15, 0x00, 0x52, 0x1a, 0x4c, 0x3f, 0x14, 0x82, 0xfd, 0xa9, 0x06, 0x01, 0x8e, 0x7c, 0x46, 0xc7, - 0x8c, 0xc9, 0xe4, 0xaa, 0x00, 0x70, 0x26, 0x73, 0x4a, 0x83, 0x28, 0x4d, 0x1d, 0x1b, 0x75, 0xc7, - 0xb2, 0x6f, 0xc1, 0x6a, 0x76, 0x07, 0x00, 0xe9, 0xcd, 0x02, 0xed, 0xd0, 0x71, 0xb3, 0x1b, 0x00, - 0xb0, 0x5a, 0x08, 0xa8, 0x86, 0xb6, 0xfd, 0x5d, 0x2c, 0x10, 0x1a, 0x35, 0x94, 0x06, 0xc2, 0x53, - 0x01, 0x00, 0xab, 0x25, 0x8f, 0x9b, 0x7d, 0x36, 0x6a, 0x65, 0x00, 0xd0, 0x85, 0x3c, 0x78, 0xfc, - 0xf0, 0xa1, 0x6b, 0xab, 0x3a, 0x77, 0x4c, 0x5a, 0xe8, 0x92, 0x8f, 0x07, 0x00, 0xaa, 0xbc, 0x12, - 0x42, 0x36, 0x92, 0x84, 0x87, 0x80, 0xe0, 0xaa, 0x0a, 0xc2, 0x5a, 0x96, 0x78, 0x58, 0x11, 0xff, - 0x8d, 0xff, 0x6b, 0x87, 0x0b, 0xf7, 0x08, 0x04, 0xc6, 0xf6, 0x25, 0x62, 0x3a, 0xea, 0xe4, 0xa1, - 0x00, 0x80, 0x42, 0x78, 0x8a, 0x4c, 0x96, 0xad, 0xbb, 0xdf, 0xd0, 0x7b, 0x22, 0x6f, 0x23, 0xf2, - 0xe1, 0x09, 0x00, 0x7d, 0xed, 0x5a, 0xe3, 0xfd, 0x7f, 0xd5, 0x63, 0x00, 0xf0, 0x49, 0x49, 0x85, - 0xea, 0xd8, 0x8d, 0x51, 0x95, 0xd7, 0xbd, 0x69, 0x64, 0xc5, 0x0e, 0xa8, 0x05, 0x00, 0x07, 0x79, - 0x2d, 0x43, 0xdf, 0x2b, 0x4c, 0x67, 0x00, 0x90, 0xa0, 0x83, 0xde, 0xbf, 0x01, 0xd3, 0xad, 0x17, - 0xc6, 0xfd, 0x34, 0x16, 0xaa, 0x7b, 0xf4, 0x05, 0x00, 0xaa, 0x9d, 0xd9, 0x18, 0x7b, 0xb6, 0xc7, - 0xd2, 0x00, 0x80, 0xc9, 0xaa, 0xa3, 0x21, 0xd2, 0xc9, 0x4b, 0x78, 0x1c, 0x08, 0xff, 0x02, 0x4d, - 0xf0, 0x91, 0x04, 0x00, 0xb5, 0xb0, 0x23, 0x26, 0x9e, 0xaa, 0x86, 0x2b, 0x00, 0x10, 0xce, 0xba, - 0x65, 0x96, 0x17, 0x40, 0x29, 0x97, 0x13, 0xd0, 0x6f, 0xcb, 0xcc, 0x3a, 0x25, 0x08, 0x00, 0xed, - 0x5b, 0xc9, 0x66, 0xb9, 0x35, 0x70, 0xbe, 0x00, 0x80, 0x83, 0x9e, 0x30, 0xeb, 0x7c, 0x55, 0x37, - 0x9b, 0xc7, 0x9d, 0xb4, 0x99, 0x2a, 0xdd, 0xf7, 0x01, 0x00, 0x27, 0xed, 0x41, 0xa8, 0x7d, 0xec, - 0x7f, 0xe7, 0x00, 0xe0, 0xb6, 0xec, 0xda, 0x74, 0x8f, 0x2e, 0x90, 0x7a, 0x3c, 0xef, 0x34, 0xf0, - 0xe9, 0x46, 0x2e, 0x09, 0x00, 0x23, 0x03, 0x8d, 0x74, 0xaa, 0x56, 0xf5, 0xb5, 0x00, 0x80, 0x6a, - 0xab, 0xdd, 0xb5, 0x87, 0x82, 0xc8, 0x53, 0xb3, 0xf1, 0x77, 0x9f, 0x0e, 0x36, 0xd7, 0xf6, 0xff, - 0x93, 0xa1, 0xd8, 0xa5, 0xf5, 0x65, 0x30, 0x5c, 0xff, 0x0f, 0xc6, 0xdc, 0x43, 0x8a, 0x5c, 0xcc, - 0xfb, 0xf2, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0xff, 0xf2, 0x93, 0x61, 0x71, 0x7b, 0xd5, 0x32, - 0x49, 0xff, 0x5f, 0x0f, 0x78, 0xef, 0x82, 0xb5, 0x05, 0x3c, 0xfd, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xff, 0x34, 0x99, 0xd9, 0xd6, 0xb9, 0xa2, 0xd5, 0xfc, 0xff, 0x15, 0xf0, 0xf8, 0x7e, 0x97, 0xdb, - 0xcf, 0x3f, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x8e, 0x49, 0x81, 0x37, 0x6b, 0x06, 0xb7, 0x2e, - 0xff, 0x8f, 0xee, 0xd4, 0x41, 0x6e, 0x7c, 0xd7, 0x16, 0xf0, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xff, 0x32, 0xd2, 0x71, 0x4c, 0xef, 0xd4, 0xfd, 0xe3, 0xff, 0x3f, 0x1e, 0x00, 0xe6, 0xda, 0xc2, - 0x07, 0x02, 0xf6, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x6f, 0xb6, 0x0e, 0x32, 0xc4, 0xf3, 0x50, 0xb0, - 0xf5, 0xff, 0x97, 0x43, 0x71, 0x6c, 0xa3, 0x84, 0xa3, 0xac, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0x64, 0xd5, 0x7e, 0x83, 0x33, 0x7a, 0x3f, 0x3f, 0xff, 0x9f, 0xda, 0x03, 0x39, 0x45, 0xa3, 0x84, - 0x21, 0xf3, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0xff, 0x05, 0x3d, 0x33, 0x59, 0x68, 0x73, 0x1d, - 0x49, 0xff, 0xdf, 0xfe, 0xf5, 0xe6, 0xe8, 0x62, 0xed, 0xc5, 0xfc, 0xff, 0x0f, 0x00, 0x00, 0x04, - 0xd0, 0x7c, 0x3c, 0x79, 0x3e, 0x43, 0x16, 0xdc, 0x05, 0x00, 0x2a, 0xcf, 0xcc, 0xbb, 0xf8, 0x63, - 0x71, 0x21, 0x99, 0x27, 0x4f, 0x13, 0xdd, 0xe9, 0x6c, 0x1b, 0x00, 0x50, 0xdd, 0x4d, 0xee, 0x2a, - 0x71, 0x1d, 0xd4, 0x0e, 0x00, 0x20, 0xa1, 0x76, 0xc5, 0xae, 0x73, 0x63, 0xd5, 0x23, 0x9a, 0xba, - 0x49, 0x95, 0xef, 0x60, 0x80, 0x00, 0x90, 0x72, 0x2d, 0x69, 0xc8, 0x5e, 0xa5, 0x00, 0x03, 0x00, - 0xcc, 0x75, 0x43, 0x00, 0x2f, 0x30, 0xe2, 0x97, 0x7a, 0xda, 0x21, 0x7f, 0xc3, 0x95, 0x48, 0x38, - 0x00, 0xa0, 0xf4, 0xd2, 0x2f, 0xab, 0xef, 0x87, 0x4f, 0x04, 0x00, 0x99, 0x05, 0xe9, 0x42, 0x78, - 0xf6, 0xcf, 0x1f, 0x56, 0x43, 0x35, 0x4f, 0x76, 0x88, 0x36, 0x1f, 0x00, 0xd0, 0x73, 0x51, 0x47, - 0x0b, 0xf9, 0x9d, 0x41, 0x05, 0x00, 0x2b, 0xf7, 0xa3, 0x0d, 0x72, 0x55, 0xa3, 0xb1, 0xfc, 0xe8, - 0x83, 0xd7, 0xe5, 0x79, 0xd4, 0x54, 0x00, 0xe0, 0xd4, 0x6c, 0x02, 0xad, 0xaa, 0x8d, 0x58, 0x01, - 0x00, 0x88, 0x25, 0x82, 0xe3, 0x42, 0xf6, 0xfb, 0x83, 0x13, 0xc9, 0xdd, 0xaf, 0x3f, 0x92, 0x69, - 0x09, 0x00, 0x80, 0x88, 0x5e, 0xab, 0x86, 0x86, 0xf9, 0x4a, 0x0b, 0x00, 0x19, 0x03, 0xbd, 0xf6, - 0x6d, 0x92, 0x8d, 0x00, 0xf4, 0x3b, 0x9a, 0x0d, 0x61, 0x9a, 0xe1, 0xd4, 0x08, 0x20, 0xae, 0xf5, - 0xdc, 0x79, 0x2d, 0x72, 0x46, 0x0f, 0x00, 0xba, 0x02, 0x9d, 0xf6, 0xca, 0x6c, 0x89, 0x40, 0x69, - 0xd8, 0x7e, 0xbb, 0xdc, 0x03, 0x0b, 0xa2, 0x00, 0x70, 0x72, 0xa8, 0x09, 0x81, 0xd0, 0xe4, 0x3b, - 0x00, 0x00, 0xa5, 0x05, 0xf4, 0xca, 0xc0, 0x19, 0x02, 0x6f, 0x9d, 0x30, 0xd6, 0xa9, 0x86, 0x1d, - 0x50, 0x54, 0x00, 0x90, 0x2e, 0x17, 0x2c, 0x12, 0x03, 0x49, 0xcf, 0x0f, 0x00, 0x45, 0x62, 0xfc, - 0x4a, 0x62, 0x5c, 0x67, 0x27, 0xdf, 0xe3, 0xc5, 0x69, 0x26, 0x77, 0x09, 0xc6, 0x00, 0x60, 0x61, - 0x8a, 0x46, 0xf8, 0xbd, 0x68, 0x59, 0x04, 0x00, 0x43, 0x0a, 0x7b, 0xed, 0x83, 0x31, 0xc0, 0x8c, - 0x2c, 0x24, 0x82, 0x9c, 0x67, 0x1b, 0x6e, 0x8a, 0x00, 0x30, 0x75, 0x86, 0xcd, 0xb0, 0x4a, 0x8d, - 0x62, 0x0c, 0x00, 0x44, 0xd3, 0x4d, 0x30, 0xd7, 0x28, 0x89, 0x38, 0x07, 0x7d, 0x5c, 0x79, 0x66, - 0x6d, 0xaa, 0x3c, 0x00, 0x70, 0x88, 0xbd, 0x86, 0x93, 0xe4, 0x27, 0xd0, 0x01, 0x00, 0xe8, 0x73, - 0xee, 0xc3, 0xd3, 0x50, 0x93, 0x33, 0xdd, 0xe6, 0x05, 0xcc, 0xc5, 0x56, 0x5f, 0xba, 0x00, 0x90, - 0x99, 0x90, 0x74, 0xb8, 0x72, 0xc5, 0xb8, 0x07, 0x00, 0xec, 0xad, 0x74, 0xb7, 0x50, 0xb0, 0x1c, - 0x86, 0xdd, 0x2e, 0x9a, 0x23, 0x8f, 0xca, 0x74, 0x31, 0x00, 0xa0, 0xd5, 0x3c, 0x2a, 0xd7, 0xef, - 0x5a, 0x0c, 0x0c, 0x00, 0x9a, 0x00, 0x74, 0xe7, 0xcc, 0x41, 0x5a, 0x8a, 0x43, 0x95, 0x60, 0x8f, - 0xcf, 0x60, 0x97, 0x80, 0x00, 0xd0, 0x68, 0x4c, 0xef, 0x40, 0xc3, 0x6e, 0x99, 0x06, 0x00, 0x35, - 0x5d, 0x8a, 0x2f, 0xbe, 0x38, 0x27, 0x7d, 0x26, 0x8f, 0x4f, 0xc8, 0x7d, 0x0f, 0xf2, 0x19, 0x00, - 0x00, 0x7a, 0xbb, 0x9a, 0xbd, 0x49, 0x30, 0x67, 0x04, 0x00, 0x3a, 0x04, 0x90, 0x37, 0x95, 0x60, - 0x42, 0x03, 0x32, 0x62, 0x9d, 0x20, 0x6c, 0xda, 0xbf, 0x2b, 0x00, 0x50, 0x75, 0x27, 0x9a, 0xb8, - 0xe2, 0x87, 0xf1, 0x05, 0x00, 0x3f, 0x10, 0x57, 0x5b, 0x3f, 0xb0, 0x4a, 0xdd, 0x07, 0xe5, 0xc0, - 0x2d, 0x01, 0x9b, 0x89, 0x91, 0x00, 0x70, 0x9e, 0xb4, 0xfe, 0x22, 0xf0, 0xb0, 0x67, 0x05, 0x00, - 0x81, 0x99, 0x59, 0x4f, 0x6e, 0x37, 0xd6, 0x36, 0x06, 0xd2, 0x6d, 0x45, 0xa6, 0x3f, 0xf2, 0x5e, - 0x00, 0xb0, 0x11, 0x32, 0x21, 0x34, 0xdc, 0xee, 0xf5, 0x07, 0x00, 0x48, 0x07, 0xab, 0xd7, 0xe3, - 0xb1, 0x6b, 0xd5, 0xfb, 0x7b, 0xe8, 0xb3, 0x8a, 0xb4, 0x69, 0x79, 0x00, 0xf0, 0x4a, 0x00, 0x55, - 0x20, 0x49, 0x8d, 0x04, 0x02, 0x02, 0xd0, 0x7f, 0x16, 0xe4, 0xe2, 0x31, 0xec, 0xd9, 0x78, 0x39, - 0x9e, 0x0e, 0xe5, 0x04, 0x49, 0x8c, 0xf0, 0x2f, 0xe6, 0xa5, 0x5a, 0xb8, 0x73, 0x90, 0x13, 0xf9, - 0xff, 0x39, 0x0c, 0xbf, 0x28, 0xb0, 0x30, 0xcc, 0xcb, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0x1f, 0xb9, 0x93, 0x9d, 0xcc, 0xaa, 0x20, 0x4f, 0xf7, 0xff, 0xc9, 0x63, 0x3d, 0x4a, 0x07, 0xcc, - 0xcd, 0x29, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x07, 0x59, 0xd5, 0x88, 0x1f, 0xf6, 0x1c, 0xa1, - 0xff, 0xdf, 0x35, 0x5d, 0xc4, 0x07, 0x30, 0x3f, 0xbc, 0xf8, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xdf, - 0x10, 0x8b, 0x4b, 0x6c, 0xfe, 0xb9, 0xad, 0xf5, 0xff, 0xdf, 0xcb, 0x97, 0xf0, 0x47, 0x69, 0xb4, - 0xb0, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x5f, 0xa6, 0x81, 0x47, 0xdb, 0xce, 0xbf, 0x2a, - 0xfa, 0xff, 0x29, 0x30, 0x3a, 0x46, 0xa2, 0x0c, 0x3f, 0x66, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0x9d, 0xc3, 0xab, 0x94, 0x7d, 0x92, 0x18, 0x0f, 0xff, 0x5f, 0x9c, 0x03, 0x3f, 0xfb, 0xb9, 0xcf, - 0x44, 0xf6, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0x7f, 0xf3, 0x74, 0xd7, 0xa8, 0xde, 0xe0, 0xd0, 0xfe, - 0xff, 0x81, 0xe8, 0x68, 0x86, 0x2a, 0xd7, 0x6d, 0xed, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xaf, 0xd0, 0x3f, 0x00, 0x1a, 0xa9, 0xe8, 0xea, 0xf8, 0xff, 0x3d, 0x63, 0x9e, 0xdc, 0xe7, 0x35, - 0x8b, 0x4f, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x5c, 0xca, 0xf2, 0x26, 0x08, 0x71, 0x65, 0x01, - 0x3b, 0x74, 0x60, 0x87, 0xa6, 0x0f, 0x8b, 0x15, 0x00, 0x40, 0x24, 0x22, 0x0a, 0xe2, 0x53, 0x11, - 0xfb, 0x0d, 0x00, 0x12, 0x94, 0x77, 0x18, 0x03, 0x76, 0x6f, 0xb5, 0x26, 0x23, 0x0a, 0x6c, 0x68, - 0x4f, 0x52, 0x92, 0x00, 0x70, 0xc5, 0xc2, 0x9c, 0x27, 0x31, 0xa6, 0x65, 0x0e, 0x00, 0x06, 0xd7, - 0x45, 0x4a, 0x86, 0xfd, 0x17, 0x5b, 0xd8, 0x25, 0xd6, 0x00, 0x1b, 0x93, 0x8e, 0x93, 0x00, 0x30, - 0x54, 0x5d, 0xcf, 0x08, 0xa1, 0xfc, 0x53, 0x00, 0x00, 0xb0, 0xf8, 0x7a, 0x2a, 0xa4, 0x69, 0x24, - 0x5c, 0xac, 0xf1, 0x69, 0xe3, 0x65, 0x9a, 0xb1, 0x0a, 0x00, 0xc0, 0x4e, 0xdb, 0x5f, 0x02, 0x30, - 0x05, 0xa1, 0x03, 0x00, 0x04, 0x30, 0x65, 0xce, 0x19, 0x1f, 0xc3, 0x84, 0xca, 0x89, 0x5e, 0x4c, - 0x62, 0x8a, 0xc4, 0xe3, 0x00, 0x40, 0xa9, 0xcf, 0xf3, 0xa2, 0xb8, 0x34, 0x05, 0x05, 0x00, 0x6e, - 0x09, 0xf0, 0xaf, 0x60, 0xe2, 0xca, 0x84, 0x64, 0x41, 0x86, 0x78, 0x56, 0x07, 0x85, 0x49, 0x00, - 0x50, 0xca, 0x66, 0x71, 0x95, 0x51, 0x10, 0x3f, 0x0e, 0x00, 0x92, 0x95, 0xb8, 0x4e, 0x3e, 0xc8, - 0xa7, 0xb1, 0xa8, 0x51, 0x0f, 0x4a, 0x9d, 0x14, 0xe3, 0xb7, 0x00, 0x60, 0x70, 0xf2, 0x47, 0x58, - 0x81, 0xc5, 0x0e, 0x0c, 0x00, 0xdd, 0x63, 0x86, 0x91, 0x30, 0x6d, 0xbb, 0xb3, 0x76, 0xc0, 0x08, - 0xf7, 0xf9, 0x39, 0x2b, 0xc8, 0x00, 0x20, 0xd0, 0xbb, 0xc8, 0xd5, 0xc6, 0x43, 0x02, 0x00, 0x00, - 0xb9, 0x3a, 0xbb, 0x7a, 0x97, 0x53, 0xd8, 0xe2, 0xc3, 0x5c, 0x60, 0x60, 0x21, 0x3e, 0xeb, 0xb8, - 0x00, 0x00, 0x95, 0xca, 0x53, 0x54, 0xb6, 0x61, 0x59, 0x09, 0x00, 0x48, 0x07, 0x8a, 0xa8, 0x42, - 0x06, 0xd4, 0x73, 0x7d, 0x33, 0x15, 0x7f, 0x3d, 0x07, 0x30, 0x7a, 0x00, 0xe0, 0x64, 0xdb, 0x81, - 0x08, 0xf5, 0x84, 0x98, 0x0a, 0x00, 0x42, 0x8b, 0xce, 0x41, 0x35, 0xdf, 0xf8, 0x54, 0x17, 0x3e, - 0x91, 0xd5, 0xbf, 0xd5, 0x79, 0xc8, 0x00, 0x30, 0x0b, 0xe8, 0x27, 0xeb, 0xa3, 0xe8, 0x63, 0x0f, - 0x00, 0xf3, 0x5f, 0xab, 0x7b, 0x30, 0x9a, 0x77, 0xdc, 0xda, 0x1b, 0xe1, 0x0f, 0x0e, 0xa8, 0xdb, - 0x64, 0x00, 0xa0, 0xb9, 0xd5, 0xef, 0xbc, 0x23, 0x03, 0xa7, 0x0e, 0x00, 0xba, 0x18, 0x0f, 0x40, - 0x09, 0x21, 0xb3, 0xe6, 0x4b, 0xbe, 0x9e, 0x09, 0xd1, 0x12, 0xe3, 0x10, 0x00, 0x80, 0x82, 0xd8, - 0xad, 0x32, 0x42, 0x29, 0x03, 0x03, 0x00, 0x6b, 0xf7, 0x88, 0x8f, 0x29, 0xd5, 0xc8, 0x30, 0x54, - 0xc7, 0x43, 0x16, 0x1c, 0x59, 0xbf, 0xa4, 0x00, 0x20, 0xdd, 0xa7, 0x12, 0x48, 0x53, 0x67, 0x80, - 0x04, 0x00, 0x42, 0x8f, 0x9d, 0xe3, 0x01, 0x4c, 0x47, 0xa2, 0xf8, 0x9e, 0x51, 0xb2, 0x7a, 0x16, - 0x56, 0xcd, 0x00, 0x70, 0x0d, 0xd0, 0xd4, 0xb7, 0x8e, 0x9a, 0x07, 0x04, 0x00, 0x65, 0xc2, 0xe5, - 0x43, 0x26, 0x16, 0x73, 0x4a, 0x64, 0x7e, 0x57, 0x50, 0x31, 0x42, 0x8f, 0x68, 0x00, 0xb0, 0xfe, - 0xc8, 0x1b, 0x98, 0x76, 0xd4, 0x1f, 0x04, 0x00, 0x60, 0xda, 0xe5, 0x78, 0xf1, 0xf3, 0x63, 0xa9, - 0xd3, 0xca, 0x1f, 0x07, 0x7d, 0xb0, 0x11, 0xf6, 0x00, 0xf0, 0x52, 0x8a, 0x72, 0x14, 0x42, 0xdf, - 0xdc, 0x06, 0x00, 0xfd, 0xaa, 0xfa, 0x94, 0x50, 0x3a, 0x09, 0x87, 0x67, 0x40, 0xb7, 0x0e, 0x03, - 0x54, 0xce, 0x4f, 0x00, 0x90, 0x6c, 0x6a, 0x64, 0x5a, 0x59, 0xb9, 0x99, 0x0c, 0x00, 0xcf, 0x9f, - 0x6b, 0x24, 0x37, 0x91, 0x6a, 0xa5, 0x18, 0x66, 0x11, 0x2c, 0xc1, 0x70, 0x9f, 0x21, 0x00, 0x60, - 0x14, 0x4c, 0x6c, 0xee, 0xe0, 0x1c, 0x79, 0x06, 0x00, 0xc7, 0xd5, 0xe0, 0xc3, 0x23, 0x49, 0x07, - 0xdc, 0x20, 0x5d, 0xb8, 0x8f, 0x06, 0x38, 0xf4, 0x81, 0x00, 0x50, 0x55, 0x9e, 0x01, 0x41, 0x14, - 0x2b, 0x56, 0x0a, 0x00, 0x6c, 0xbe, 0x0e, 0x80, 0x5f, 0x30, 0x65, 0x10, 0x54, 0x91, 0x17, 0x2b, - 0xe6, 0x5b, 0x00, 0x4f, 0x00, 0xc0, 0x19, 0xdd, 0x35, 0xea, 0xc1, 0x50, 0xd7, 0x0c, 0xf0, 0x9d, - 0x54, 0x45, 0xf8, 0xb9, 0xd9, 0x51, 0xc3, 0xff, 0xbf, 0xcf, 0x37, 0xac, 0x27, 0x82, 0xa8, 0xbf, - 0xfd, 0xff, 0xd6, 0x42, 0xe7, 0xdc, 0x45, 0x69, 0xbf, 0xde, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xfd, 0xc1, 0x96, 0x06, 0x8f, 0x35, 0xc5, 0xd7, 0xff, 0x6f, 0x93, 0x6f, 0xd6, 0x83, 0xd5, 0x13, - 0xde, 0xfd, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0x5f, 0x03, 0xac, 0xe4, 0x32, 0x64, 0x6e, 0x55, 0xf0, - 0xff, 0x48, 0x67, 0xaf, 0x1e, 0xa1, 0x9d, 0x00, 0x54, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0x2f, 0x60, 0x42, 0x25, 0x0c, 0xe9, 0x51, 0x1f, 0xf5, 0xff, 0xa9, 0xc4, 0xb6, 0x3a, 0xce, 0x78, - 0x64, 0x4b, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0xac, 0x46, 0xbe, 0x27, 0xcf, 0x30, 0xef, 0x19, - 0xff, 0x9f, 0x92, 0x69, 0xf4, 0x94, 0x17, 0x60, 0xc6, 0xfc, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0x0f, - 0xc6, 0x85, 0x7c, 0x05, 0x0e, 0x98, 0x0a, 0xfd, 0xff, 0xa0, 0xf0, 0x2d, 0x52, 0x09, 0xd1, 0xa8, - 0xb4, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0xff, 0x2c, 0xc8, 0x3e, 0x5b, 0xb1, 0x29, 0x17, - 0xfb, 0xff, 0x5b, 0x71, 0xf7, 0x08, 0x28, 0x11, 0x3d, 0x24, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0x86, 0x49, 0xf1, 0x88, 0x8e, 0x2d, 0x5e, 0x1f, 0xff, 0x1f, 0x4d, 0x46, 0xba, 0x62, 0xc7, 0x35, - 0x57, 0xf6, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x10, 0xa2, 0x11, 0xbb, 0x4c, 0x86, 0xc4, 0x25, 0x04, - 0x00, 0xc3, 0xe2, 0xfd, 0x18, 0x15, 0x3b, 0x70, 0xe9, 0x0a, 0x41, 0xa4, 0x54, 0x64, 0x98, 0x1d, - 0xd1, 0x00, 0xf0, 0x0c, 0x92, 0x2f, 0xb4, 0x1e, 0x8b, 0x54, 0x07, 0x00, 0x9a, 0xef, 0x5f, 0x95, - 0x95, 0x99, 0xf4, 0x9b, 0x67, 0xbf, 0xba, 0xad, 0x68, 0x5d, 0x0d, 0xfa, 0x00, 0x10, 0x36, 0x02, - 0x24, 0x0d, 0xcc, 0x51, 0x16, 0x01, 0x00, 0xfc, 0x00, 0xc9, 0x11, 0x7c, 0x51, 0xf8, 0x67, 0x58, - 0x67, 0x7c, 0x0d, 0xbc, 0x8c, 0xac, 0xd6, 0x00, 0x00, 0x92, 0xc5, 0xeb, 0xc9, 0x52, 0x74, 0xf3, - 0x00, 0x00, 0xca, 0xcd, 0xaf, 0x10, 0x63, 0x0c, 0x35, 0xf3, 0xcc, 0xd0, 0x85, 0x34, 0x80, 0x50, - 0xef, 0x2a, 0x01, 0x10, 0x53, 0x9d, 0x62, 0x76, 0xcc, 0x2f, 0xb2, 0x0d, 0x00, 0x2a, 0x1c, 0xf5, - 0x32, 0xf0, 0x5f, 0x52, 0x9e, 0x05, 0x8a, 0xce, 0x87, 0x37, 0x85, 0xd0, 0x84, 0x00, 0xb0, 0xa2, - 0x90, 0xa1, 0x83, 0x20, 0x2d, 0x88, 0x00, 0x00, 0xda, 0x95, 0xf0, 0xf8, 0xf2, 0x08, 0xe2, 0x1a, - 0x31, 0xd7, 0x87, 0xb6, 0x07, 0xde, 0x1d, 0xd7, 0x00, 0x30, 0x87, 0x7e, 0xf3, 0x06, 0x17, 0x12, - 0x8f, 0x0b, 0x00, 0xdf, 0x2f, 0x0e, 0x53, 0x5d, 0x47, 0x32, 0x6a, 0x12, 0x42, 0xfc, 0x57, 0xac, - 0x48, 0x01, 0x33, 0x00, 0x20, 0x14, 0x87, 0xaf, 0x4d, 0x57, 0x8e, 0x43, 0x0b, 0x00, 0x31, 0x26, - 0x50, 0x05, 0xc4, 0x99, 0x2e, 0x04, 0x0e, 0x5f, 0x40, 0xcd, 0x4a, 0x77, 0x87, 0x0e, 0x00, 0xe0, - 0xe4, 0x67, 0x86, 0xfd, 0xf3, 0x20, 0x76, 0x0a, 0x00, 0x11, 0x47, 0x84, 0xce, 0x9e, 0x1e, 0xdf, - 0xd2, 0x8e, 0xdc, 0x6e, 0x55, 0x53, 0xdd, 0x0c, 0x1d, 0x00, 0xe0, 0x3d, 0x45, 0xac, 0x10, 0x87, - 0xfd, 0xcd, 0x03, 0x00, 0x8e, 0x27, 0x8a, 0x06, 0xc0, 0x5d, 0xed, 0x91, 0xf6, 0xe9, 0xf1, 0x11, - 0x4e, 0x24, 0xf9, 0xd1, 0x80, 0xb1, 0x8f, 0x80, 0x18, 0x42, 0x8e, 0xb8, 0x51, 0x0c, 0x00, 0xdb, - 0xb3, 0xea, 0x53, 0x54, 0x46, 0x95, 0x16, 0x00, 0x10, 0xbc, 0x34, 0xc6, 0x3b, 0x9e, 0xc5, 0x71, - 0x4d, 0x74, 0xca, 0xa6, 0x17, 0x03, 0x53, 0xdf, 0x07, 0x00, 0x5c, 0x0b, 0x79, 0x3b, 0x1d, 0x00, - 0x29, 0x4a, 0x00, 0x00, 0x6d, 0xa2, 0x45, 0x10, 0xea, 0xab, 0x64, 0xed, 0x43, 0xc8, 0xd9, 0xbf, - 0x0e, 0x50, 0x05, 0x0b, 0x00, 0x48, 0xdb, 0xe5, 0x2d, 0x32, 0xe4, 0x80, 0x07, 0x00, 0x40, 0x7f, - 0xc9, 0xc5, 0xab, 0xe8, 0x0a, 0xe6, 0x68, 0xba, 0xf3, 0xe1, 0x5d, 0x5e, 0x99, 0xef, 0x03, 0x00, - 0xc3, 0x50, 0xb1, 0xcc, 0xd9, 0xd4, 0x74, 0xa7, 0x00, 0x20, 0xbf, 0xbd, 0xda, 0x0d, 0x3a, 0x93, - 0x4f, 0xaf, 0x77, 0x99, 0xcb, 0x69, 0x43, 0x47, 0xe8, 0x0e, 0x00, 0xe9, 0x56, 0x0b, 0x28, 0x3b, - 0x32, 0xb1, 0x06, 0x00, 0x10, 0xe5, 0x90, 0xcc, 0xda, 0xf2, 0xe6, 0x14, 0x83, 0xe7, 0xd3, 0x06, - 0xda, 0xf9, 0x0e, 0xf5, 0x25, 0x00, 0x7f, 0x30, 0xb7, 0xb4, 0xa5, 0xc5, 0x8c, 0xd0, 0x00, 0x40, - 0x8c, 0x32, 0x1a, 0x48, 0xa2, 0xee, 0x3e, 0x91, 0x3f, 0xd5, 0xb7, 0xc0, 0x92, 0x14, 0xdf, 0x01, - 0x00, 0xc0, 0xc2, 0xf4, 0x00, 0x99, 0x90, 0x35, 0x10, 0x00, 0x30, 0x78, 0xdd, 0x12, 0x86, 0x89, - 0xfe, 0x8c, 0xe8, 0xaa, 0xa2, 0x99, 0xae, 0x66, 0x7a, 0x4e, 0x0e, 0x00, 0xbb, 0x31, 0xcb, 0x88, - 0x97, 0x1b, 0x20, 0x93, 0x00, 0x80, 0x18, 0x4b, 0x5e, 0x11, 0x45, 0x4d, 0xe4, 0x92, 0x27, 0xef, - 0x9a, 0x7e, 0x3b, 0xaf, 0x1c, 0x00, 0x00, 0x75, 0x54, 0x61, 0x35, 0x60, 0x7e, 0x70, 0x48, 0x00, - 0x70, 0x16, 0xe0, 0x47, 0xe2, 0x33, 0x6c, 0xd6, 0x9f, 0x36, 0x94, 0xba, 0x71, 0x8b, 0x0f, 0x10, - 0x06, 0x00, 0x62, 0xe1, 0x02, 0xc2, 0x06, 0x3f, 0x11, 0x50, 0x00, 0xe0, 0xd3, 0x20, 0xf8, 0x04, - 0xfe, 0x16, 0x52, 0x5a, 0x67, 0xcf, 0xa3, 0xc1, 0x95, 0xcf, 0xa0, 0x07, 0x00, 0xa1, 0x3c, 0x4d, - 0xd2, 0x11, 0x7e, 0xb2, 0xbd, 0x00, 0x90, 0xba, 0xaf, 0xab, 0xd0, 0xb2, 0x78, 0x39, 0x52, 0x79, - 0x8a, 0x2d, 0x7c, 0x83, 0xa0, 0x12, 0xf3, 0xff, 0x3d, 0x73, 0x21, 0x2d, 0x9b, 0x92, 0xd6, 0xad, - 0xff, 0xef, 0x56, 0x17, 0xe9, 0xb7, 0x94, 0xe7, 0x6d, 0xf2, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xff, 0xb6, 0xe6, 0x19, 0xec, 0x1e, 0xf3, 0xe8, 0x09, 0xff, 0xff, 0x57, 0x38, 0x42, 0xa2, 0xfe, - 0xba, 0x24, 0xf2, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x1f, 0x21, 0x84, 0x2d, 0xa0, 0x68, 0x1a, 0x46, - 0xf1, 0xff, 0xa3, 0xd2, 0x84, 0x67, 0x12, 0xe4, 0x8f, 0xab, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xc3, 0x96, 0x85, 0xa8, 0x3e, 0x9c, 0xb3, 0x07, 0xff, 0x2f, 0x98, 0x90, 0xbe, 0xa7, 0x7a, 0xe9, - 0x7c, 0xf7, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0xff, 0x26, 0x20, 0x03, 0x7a, 0xbd, 0x33, 0x72, - 0x7c, 0xff, 0xaf, 0x1e, 0x53, 0xc0, 0x69, 0xfd, 0xe9, 0x28, 0xf4, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0x3f, 0x10, 0xab, 0xd3, 0x97, 0xf2, 0xa7, 0x0b, 0xf0, 0xff, 0x66, 0x8a, 0x4a, 0xb8, 0xd9, 0xeb, - 0xf9, 0x38, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x49, 0x4f, 0x2e, 0x1a, 0x11, 0xd7, 0x28, 0x37, - 0xff, 0x5f, 0xcd, 0xce, 0x1e, 0x14, 0x4a, 0xb1, 0x98, 0xf4, 0xff, 0xff, 0x0f, 0x00, 0xf0, 0xff, - 0xff, 0x84, 0x38, 0xff, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x73, 0x63, 0x72, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x76, 0x65, 0x6c, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, - 0x28, 0x00, 0x24, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, - 0x08, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x68, 0x02, 0x00, 0x00, 0xf8, 0x02, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00, 0xd4, 0x03, 0x00, 0x00, - 0xd8, 0x03, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0xac, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x32, 0xfd, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, - 0x10, 0x02, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, - 0xd0, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0xf8, 0xf9, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x65, 0x74, 0x68, 0x6f, 0x73, 0x2d, 0x75, 0x38, 0x35, 0x2d, 0x32, 0x35, - 0x36, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, - 0x24, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x46, 0x61, 0x6c, 0x73, 0x65, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x70, 0x72, 0x65, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x5f, 0x69, 0x6f, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x5c, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x70, 0x75, - 0x74, 0x5f, 0x72, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x61, 0x72, - 0x6f, 0x75, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x8c, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x76, 0x65, 0x6c, 0x61, 0x0d, 0x00, 0x00, 0x00, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x00, 0x00, 0x00, - 0xb4, 0xfa, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, - 0x2d, 0x2d, 0x61, 0x63, 0x63, 0x65, 0x6c, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2d, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x3d, 0x65, 0x74, 0x68, 0x6f, 0x73, 0x2d, 0x75, 0x38, 0x35, 0x2d, 0x32, - 0x35, 0x36, 0x20, 0x2d, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x3d, 0x2f, 0x55, 0x73, 0x65, - 0x72, 0x73, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x65, 0x72, 0x30, 0x31, 0x2f, 0x44, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x2f, 0x43, 0x4d, 0x53, - 0x49, 0x53, 0x2d, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x63, 0x68, 0x2d, 0x6d, 0x6c, - 0x6f, 0x70, 0x73, 0x2f, 0x2e, 0x63, 0x6d, 0x73, 0x69, 0x73, 0x2f, 0x65, 0x6e, 0x73, 0x65, 0x6d, - 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x6c, 0x61, 0x2e, 0x69, 0x6e, 0x69, 0x20, 0x2d, 0x2d, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x3d, 0x72, 0x61, 0x77, - 0x20, 0x2d, 0x2d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x3d, 0x45, 0x74, 0x68, 0x6f, 0x73, 0x5f, 0x55, 0x38, 0x35, 0x5f, 0x53, 0x52, 0x41, 0x4d, 0x5f, - 0x4d, 0x52, 0x41, 0x4d, 0x20, 0x2d, 0x2d, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x2d, 0x6d, 0x6f, - 0x64, 0x65, 0x3d, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x53, 0x72, 0x61, 0x6d, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x66, 0x6c, 0x61, 0x67, - 0x73, 0x00, 0x00, 0x00, 0xa8, 0xfb, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x54, 0x4f, 0x53, 0x41, 0x2d, 0x31, 0x2e, 0x30, 0x2b, 0x49, 0x4e, 0x54, - 0x2b, 0x69, 0x6e, 0x74, 0x31, 0x36, 0x2b, 0x69, 0x6e, 0x74, 0x34, 0x2b, 0x63, 0x66, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x74, 0x6f, 0x73, 0x61, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x00, 0x00, 0x00, - 0xc8, 0xfc, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x45, 0x74, 0x68, 0x6f, 0x73, 0x55, 0x42, 0x61, - 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x71, 0x75, 0x61, 0x6e, - 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x65, 0x64, - 0x3a, 0x3a, 0x64, 0x65, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x7a, 0x65, 0x5f, 0x70, 0x65, 0x72, - 0x5f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x00, 0x4c, 0xfc, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x00, 0x29, 0x00, 0x00, 0x00, - 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x63, 0x6f, 0x6d, 0x70, - 0x6f, 0x73, 0x65, 0x64, 0x3a, 0x3a, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x7a, 0x65, 0x5f, 0x70, - 0x65, 0x72, 0x5f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0xa0, 0xfd, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd0, 0xfc, 0xff, 0xff, - 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0xe0, 0xfd, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xe0, 0xff, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x00, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x28, 0x02, 0x00, 0x00, - 0xc4, 0x01, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x00, 0x84, 0x01, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, - 0x48, 0x01, 0x00, 0x00, 0x2c, 0x01, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, - 0x88, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xac, 0xfe, 0xff, 0xff, 0xa8, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x02, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xc0, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1a, 0xfe, 0xff, 0xff, - 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x32, 0xfe, 0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xf0, 0xfe, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x4a, 0xfe, 0xff, 0xff, - 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x62, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, 0x31, 0xeb, 0x59, 0x3f, - 0x00, 0x00, 0x00, 0x00, 0x24, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x02, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x06, 0x92, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x60, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x3e, 0xff, 0xff, 0xff, - 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xa4, 0xfe, 0xff, 0xff, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0xa0, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xfa, 0xfe, 0xff, 0xff, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x12, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xd0, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x2a, 0xff, 0xff, 0xff, - 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xec, 0xff, 0xff, 0xff, - 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x0c, 0x00, 0x0b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x70, 0x3f, - 0x00, 0x00, 0x00, 0x00, 0xa8, 0xff, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, - 0x14, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x78, 0xff, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, - 0x09, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, - 0x12, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, - 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x00, 0x06, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, - 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x39, 0x00, 0x00, 0x00, 0x5b, 0x31, 0x2c, 0x20, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, - 0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, - 0x3a, 0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x20, 0x22, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, - 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x22, 0x3a, 0x20, 0x5b, 0x5d, 0x7d, 0x5d, 0x00, 0x00, 0x00, - 0xfe, 0x00, 0x00, 0x00, 0x5b, 0x31, 0x2c, 0x20, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, - 0x20, 0x22, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x73, 0x2e, 0x74, 0x75, 0x70, 0x6c, 0x65, - 0x22, 0x2c, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x6e, - 0x75, 0x6c, 0x6c, 0x22, 0x2c, 0x20, 0x22, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x5f, - 0x73, 0x70, 0x65, 0x63, 0x22, 0x3a, 0x20, 0x5b, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, - 0x20, 0x22, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x73, 0x2e, 0x74, 0x75, 0x70, 0x6c, 0x65, - 0x22, 0x2c, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x6e, - 0x75, 0x6c, 0x6c, 0x22, 0x2c, 0x20, 0x22, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x5f, - 0x73, 0x70, 0x65, 0x63, 0x22, 0x3a, 0x20, 0x5b, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, - 0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, - 0x3a, 0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x20, 0x22, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, - 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x22, 0x3a, 0x20, 0x5b, 0x5d, 0x7d, 0x5d, 0x7d, 0x2c, 0x20, - 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, - 0x6e, 0x73, 0x2e, 0x64, 0x69, 0x63, 0x74, 0x22, 0x2c, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x5b, 0x5d, 0x22, 0x2c, 0x20, 0x22, 0x63, 0x68, 0x69, 0x6c, - 0x64, 0x72, 0x65, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x22, 0x3a, 0x20, 0x5b, 0x5d, 0x7d, 0x5d, - 0x7d, 0x5d, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x00 -}; -const unsigned long model_pte_size = sizeof(model_pte); diff --git a/board/Corstone-320/Board-U85.clayer.yml b/board/Corstone-320/Board-U85.clayer.yml index 3ba21104..7a6e914e 100644 --- a/board/Corstone-320/Board-U85.clayer.yml +++ b/board/Corstone-320/Board-U85.clayer.yml @@ -22,6 +22,10 @@ layer: - CMSIS_target_header: \"Corstone-320.h\" - ETHOSU85 - ARM_MODEL_USE_PMU_COUNTERS + # The runner saves the demo image and its measurements through semihosting + # (board_save_file in retarget_stdio.c) into this directory, relative to the + # model's working directory (the workspace): out/fvp_image.bin, out/fvp_result.txt + - APP_RESULT_DIR: \"out\" packs: - pack: ARM::CMSIS @@ -55,4 +59,7 @@ layer: - file: ./retarget_stdio.c linker: + # The linker scripts are the Device:Startup configuration files in + # RTE/Device/SSE-320-FVP/; they place the ExecuTorch program + # (.rodata.model, ~2.8 MB) in DDR (ROM2), not in the 2 MB FPGA SRAM. - regions: ./regions_SSE-320.h diff --git a/board/Corstone-320/RTE/Device/SSE-320-FVP/ac6_linker_script.sct.src b/board/Corstone-320/RTE/Device/SSE-320-FVP/ac6_linker_script.sct.src index 7820e1f1..4cb13732 100644 --- a/board/Corstone-320/RTE/Device/SSE-320-FVP/ac6_linker_script.sct.src +++ b/board/Corstone-320/RTE/Device/SSE-320-FVP/ac6_linker_script.sct.src @@ -16,6 +16,14 @@ * limitations under the License. */ +/* Changed for the CMSIS-Executorch example (the gcc_ and clang_ scripts next + * to this one have the same changes): + * - The exported ExecuTorch program (ai_layer/model_pte.c, section + * .rodata.model, ~2.8 MB) goes to the DDR region ROM2 instead of the 2 MB + * FPGA SRAM that holds the code; the Ethos-U85 reads its weights from there. + * - RAM is a load region of its own (see LR_RAM0), and RW_RAM0 is NOCOMPRESS. + */ + /* ---------------------------------------------------------------------------- Stack seal size definition *----------------------------------------------------------------------------*/ @@ -42,13 +50,29 @@ LR_ROM0 __ROM0_BASE __ROM0_SIZE { *(Veneer$$CMSE) } #endif +} + +/* + * RAM gets its own load region rather than living inside LR_ROM0: armlink emits + * one PT_LOAD per load region and sizes it to span every execution region it + * contains. With the ExecuTorch pools (8 MB of ZI in app_main.cpp) inside + * LR_ROM0 the ELF segment based at ROM0 would claim ~9 MB, and the FVP refuses + * the image ("Failed to write bytes at address range [0x12200000..]", past the + * end of the 2 MB FPGA SRAM). The RW data is loaded straight into DDR. + */ +LR_RAM0 __RAM0_BASE __RAM0_SIZE { RW_NOINIT __RAM0_BASE UNINIT (__RAM0_SIZE - __HEAP_SIZE - __STACK_SIZE - __STACKSEAL_SIZE) { *.o(.bss.noinit) *.o(.bss.noinit.*) } - RW_RAM0 AlignExpr(+0, 8) (__RAM0_SIZE - __HEAP_SIZE - __STACK_SIZE - __STACKSEAL_SIZE - AlignExpr(ImageLength(RW_NOINIT), 8)) { + ; NOCOMPRESS: this load region has the same base as the execution region, so + ; the image loader places the initial RW data in place. If armlink compressed + ; it, SystemInit() (which runs before the C library decompresses the region) + ; would write SystemCoreClock into the compressed blob and corrupt the data + ; that follows it (seen: the ExecuTorch PAL function table held 25000000). + RW_RAM0 AlignExpr(+0, 8) NOCOMPRESS (__RAM0_SIZE - __HEAP_SIZE - __STACK_SIZE - __STACKSEAL_SIZE - AlignExpr(ImageLength(RW_NOINIT), 8)) { *(+RW +ZI) } @@ -64,24 +88,6 @@ LR_ROM0 __ROM0_BASE __ROM0_SIZE { STACKSEAL +0 EMPTY __STACKSEAL_SIZE { ; Reserve empty region for stack seal immediately after stack } #endif - -#if __RAM1_SIZE > 0 - RW_RAM1 __RAM1_BASE __RAM1_SIZE { - .ANY (+RW +ZI) - } -#endif - -#if __RAM2_SIZE > 0 - RW_RAM2 __RAM2_BASE __RAM2_SIZE { - .ANY (+RW +ZI) - } -#endif - -#if __RAM3_SIZE > 0 - RW_RAM3 __RAM3_BASE __RAM3_SIZE { - .ANY (+RW +ZI) - } -#endif } #if __ROM1_SIZE > 0 @@ -95,6 +101,7 @@ LR_ROM1 __ROM1_BASE __ROM1_SIZE { #if __ROM2_SIZE > 0 LR_ROM2 __ROM2_BASE __ROM2_SIZE { ER_ROM2 +0 __ROM2_SIZE { + *(.rodata.model) ; the ExecuTorch program; a named selector outranks the +RO of ER_ROM0 .ANY (+RO +XO) } } diff --git a/board/Corstone-320/RTE/Device/SSE-320-FVP/clang_linker_script.ld.src b/board/Corstone-320/RTE/Device/SSE-320-FVP/clang_linker_script.ld.src index e7ec9d02..b80f1962 100644 --- a/board/Corstone-320/RTE/Device/SSE-320-FVP/clang_linker_script.ld.src +++ b/board/Corstone-320/RTE/Device/SSE-320-FVP/clang_linker_script.ld.src @@ -33,6 +33,10 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* Changed for the CMSIS-Executorch example: the section .rodata.model (the + * ExecuTorch program) goes to ROM2 in a program header of its own, see + * SECTIONS. */ + /* ---------------------------------------------------------------------------- Stack seal size definition *----------------------------------------------------------------------------*/ @@ -75,6 +79,7 @@ ENTRY(Reset_Handler) PHDRS { text PT_LOAD; + model PT_LOAD; ram PT_LOAD; ram_init PT_LOAD; tls PT_TLS; @@ -82,6 +87,16 @@ PHDRS SECTIONS { +#if __ROM2_SIZE > 0 + /* The ExecuTorch program (ai_layer/model_pte.c, ~2.8 MB) goes to the DDR + region ROM2 instead of the 2 MB FPGA SRAM that holds the code. It comes + before .text, whose .rodata pattern would otherwise take it, and before + .init, so that lld keeps the text segment contiguous in the file. */ + .rodata.model : ALIGN(16) { + *(.rodata.model) + } >ROM2 AT>ROM2 :model +#endif + .init : { KEEP (*(.vectors)) diff --git a/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src b/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src index 7cd986d8..694e7571 100644 --- a/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src +++ b/board/Corstone-320/RTE/Device/SSE-320-FVP/gcc_linker_script.ld.src @@ -16,6 +16,9 @@ * limitations under the License. */ +/* Changed for the CMSIS-Executorch example: the section .rodata.model (the + * ExecuTorch program) goes to ROM2, see SECTIONS. */ + /* ---------------------------------------------------------------------------- Stack seal size definition *----------------------------------------------------------------------------*/ @@ -89,6 +92,16 @@ ENTRY(Reset_Handler) SECTIONS { +#if __ROM2_SIZE > 0 + /* The ExecuTorch program (ai_layer/model_pte.c, ~2.8 MB) goes to the DDR + region ROM2 instead of the 2 MB FPGA SRAM that holds the code. It comes + before .text, whose .rodata pattern would otherwise take it. */ + .rodata.model : ALIGN(16) + { + *(.rodata.model) + } > ROM2 +#endif + .text : { KEEP(*(.vectors)) diff --git a/board/Corstone-320/main.c b/board/Corstone-320/main.c index 3949a17f..ddafabd5 100644 --- a/board/Corstone-320/main.c +++ b/board/Corstone-320/main.c @@ -39,5 +39,8 @@ int main (void) { osKernelInitialize(); #endif - return (app_main()); + /* End the simulation: the FVP stops on the semihosting exit call (or + --simlimit), not on the EOT character the application prints. */ + stdio_exit(app_main()); + return 0; } diff --git a/board/Corstone-320/main.h b/board/Corstone-320/main.h index 6e2048ee..3fcd7064 100644 --- a/board/Corstone-320/main.h +++ b/board/Corstone-320/main.h @@ -26,6 +26,7 @@ extern "C" { /* Prototypes */ extern int app_main (void); extern int stdio_init (void); +extern void stdio_exit (int status); #if defined(ETHOSU_ARCH) extern void ethos_setup (void); diff --git a/board/Corstone-320/regions_SSE-320.h b/board/Corstone-320/regions_SSE-320.h index fa441955..e15850de 100644 --- a/board/Corstone-320/regions_SSE-320.h +++ b/board/Corstone-320/regions_SSE-320.h @@ -118,7 +118,7 @@ // keeps its method table and planned buffers on the heap. // Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> // Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> -#define __STACK_SIZE 0x00001000 +#define __STACK_SIZE 0x00008000 #define __HEAP_SIZE 0x00018000 // diff --git a/board/Corstone-320/retarget_stdio.c b/board/Corstone-320/retarget_stdio.c index 8efb957b..c93c0507 100644 --- a/board/Corstone-320/retarget_stdio.c +++ b/board/Corstone-320/retarget_stdio.c @@ -24,11 +24,24 @@ * semihosting (BKPT 0xAB), which the FVP serves directly on its stdout -- * no UART model, base address, or driver involved. Requires * mps4_board.subsystem.cpu0.semihosting-enable=1 (see fvp_config.txt). + * The same channel ends the simulation (stdio_exit) and writes files on the + * host (board_save_file). *---------------------------------------------------------------------------*/ +#include +#include + /* Semihosting operation numbers (Arm semihosting specification). */ +#define SYS_OPEN 0x01 +#define SYS_CLOSE 0x02 #define SYS_WRITEC 0x03 +#define SYS_WRITE 0x05 #define SYS_READC 0x07 +#define SYS_EXIT 0x18 + +/* SYS_EXIT reason codes */ +#define ADP_Stopped_RunTimeErrorUnknown 0x20023 +#define ADP_Stopped_ApplicationExit 0x20026 static int semihosting_call (int op, void *param) { register int r0 __asm__("r0") = op; @@ -79,3 +92,41 @@ int stdout_putchar (int ch) { int stdin_getchar (void) { return semihosting_call(SYS_READC, 0); } + +/** + End the simulation through semihosting SYS_EXIT (the FVP stops). + + \param[in] status Exit status of the application: 0 reports a normal exit, + anything else a run-time error +*/ +void stdio_exit (int status) { + /* On AArch32, R1 holds the reason code itself, not a parameter block. */ + uintptr_t reason = (status == 0) ? ADP_Stopped_ApplicationExit : ADP_Stopped_RunTimeErrorUnknown; + (void)semihosting_call(SYS_EXIT, (void *)reason); + for (;;) { __asm__ volatile ("wfi"); } +} + +/** + Save a buffer to a file on the simulation host through semihosting + (SYS_OPEN "wb", SYS_WRITE, SYS_CLOSE). Relative paths resolve against the + model's working directory, i.e. the workspace when the CMSIS extension + starts the FVP. Used by the runner to hand the generated image and the + measurements to the host for checking (APP_RESULT_DIR). + + \param[in] path File to create + \param[in] data Bytes to write + \param[in] n Number of bytes + \return 0 on success, -1 on failure +*/ +int board_save_file (const char *path, const void *data, size_t n) { + size_t len = 0U; + while (path[len] != '\0') { len++; } + uintptr_t open_args[3] = { (uintptr_t)path, 5U /* "wb" */, len }; + int fd = semihosting_call(SYS_OPEN, open_args); + if (fd < 0) { return -1; } + uintptr_t write_args[3] = { (uintptr_t)fd, (uintptr_t)data, n }; + int not_written = semihosting_call(SYS_WRITE, write_args); + uintptr_t close_args[1] = { (uintptr_t)fd }; + (void)semihosting_call(SYS_CLOSE, close_args); + return (not_written == 0) ? 0 : -1; +} diff --git a/board/DevKit-E8/Board-U85.clayer.yml b/board/DevKit-E8/Board-U85.clayer.yml index 5f0cb96f..d130aaf9 100644 --- a/board/DevKit-E8/Board-U85.clayer.yml +++ b/board/DevKit-E8/Board-U85.clayer.yml @@ -3,14 +3,15 @@ # # Alif Ensemble E8 DevKit board bring-up for the Ethos-U85 NPU, running on the # Cortex-M55 high-performance (HP) core. Derived from the pack's -# Boards/DevKit-e8/Layers/M55_HP/Board_HP-U85.clayer.yml and trimmed to what a -# headless ExecuTorch inference runner needs: device startup, the Secure -# Enclave services (clocks, pin mux), a UART-backed stdout on the PRG USB -# connector, and the Ethos-U driver. Camera, display, Ethernet, USB, VIO and -# the vStream drivers are omitted. +# Boards/DevKit-e8/Layers/M55_HP/Board_HP-U85.clayer.yml and trimmed to what an +# ExecuTorch face generator needs: device startup, the Secure Enclave services +# (clocks, pin mux), a UART-backed stdout on the PRG USB connector, the Ethos-U +# driver, and the MIPI-DSI display (CDC200 + ILI9806E panel) that shows the +# generated images. Camera, Ethernet, USB, VIO and the vStream drivers are +# omitted. layer: type: Board - description: DevKit-E8 board setup for headless Ethos-U85 inference on M55_HP + description: DevKit-E8 board setup for Ethos-U85 inference on M55_HP with the LCD for-board: Alif Semiconductor::DevKit-E8 for-device: Alif Semiconductor::AE822FA0E5597LS0:M55_HP device: :M55_HP @@ -30,14 +31,23 @@ layer: - CMSIS_target_header: \"DevKit-E8.h\" - ETHOSU85 # The runner's memory pools (src/app_main.cpp) do not fit the 1 MB DTCM - # that the Alif linker scripts use for .bss. Place them in the bulk SRAM - # at 0x02000000 (NPU-accessible; SRAM0+SRAM1 combined, 8 MB, with the AC6 - # script, SRAM0 alone, 4 MB, with the GNU one) via the section named here, and - # size them for that region; the linker scripts in this layer route the - # section there. + # that the Alif linker scripts use for .bss. Place them in the bulk SRAM0 + # (4 MB at 0x02000000, NPU-accessible) via the section named here; the + # linker scripts in this layer route the section there, above the A32 boot + # stub and next to the 1.15 MB LCD frame buffer. The temp pool holds the + # Ethos-U scratch of the decoder. - APP_POOL_SECTION: \".bss.ai_pool\" - APP_METHOD_POOL_SIZE: 0x100000 - - APP_TEMP_POOL_SIZE: 0x200000 + - APP_TEMP_POOL_SIZE: 0x180000 + # After the boot demo the runner keeps serving pico-faces' serial protocol + # on the console, for pico-faces' viewer/view_serial.py (APP_INTERACTIVE), + # and generates new faces on the SW2 joystick: left, one image; right, + # start or stop back-to-back generation (APP_BUTTONS, buttons.c). + - APP_INTERACTIVE + - APP_BUTTONS + # Every generated image is shown on the LCD (board_display_image(), + # display.c). + - APP_DISPLAY packs: - pack: AlifSemiconductor::Ensemble@^2.2.0-0 @@ -68,6 +78,14 @@ layer: - component: Device:SOC Peripherals:MHU - component: Device:SOC Peripherals:PINCONF + # Display: CDC200 controller, 2-lane MIPI DSI + DPHY, the ILI9806E panel of + # the DevKit (480 x 800), GPIO for the panel reset and backlight + - component: Device:SOC Peripherals:CDC + - component: Device:SOC Peripherals:MIPI DSI + - component: Device:SOC Peripherals:MIPI DSI CSI2 DPHY + - component: Device:SOC Peripherals:GPIO + - component: BSP:External peripherals:ILI9806E LCD panel + - component: Machine Learning:NPU Support:Ethos-U Driver&Generic U85 - component: Services:Retarget IO:STDERR @@ -84,6 +102,8 @@ layer: - file: ./DevKit-E8.h - file: ./ethos_setup.c - file: ./retarget_stdio.c + - file: ./display.c + - file: ./buttons.c - group: Ethos Interface files: diff --git a/board/DevKit-E8/README.md b/board/DevKit-E8/README.md index 70913e79..dc7377ac 100644 --- a/board/DevKit-E8/README.md +++ b/board/DevKit-E8/README.md @@ -4,32 +4,67 @@ Device: `Alif Semiconductor::AE822FA0E5597LS0:M55_HP` (Cortex-M55 high-performan core, 400 MHz) with the Ethos-U85 (256 MACs) of the Ensemble E8. Derived from the Ensemble pack's `Boards/DevKit-e8/Layers/M55_HP/Board_HP-U85.clayer.yml` -and trimmed to what a headless inference runner needs. Camera, display, -Ethernet, USB, VIO and vStream drivers are left out. The target-set in the -csolution debugs it through the on-board J-Link (`J-Link Server`, SWD at -4 MHz, `start-pname: M55_HP`). +and trimmed to what the face generator needs: the console, the NPU, the SW2 +joystick and the 4.3" MIPI-DSI display (ILI9806E, 480 x 800) on connector +J21, driven by the CDC200 display controller. Camera, Ethernet, USB, VIO and +the vStream drivers are left out. The target-set in the csolution debugs it +through the on-board J-Link (`J-Link Server`, SWD at 4 MHz, +`start-pname: M55_HP`). | File | Purpose | |------|---------| -| `Board-U85.clayer.yml` | Layer: startup, SE services, UART4 stdio, Ethos-U85 driver, memory placement | -| `main.c` | Pin/GPIO config, SE services, clocks, stdio, NPU init, then `app_main()` | +| `Board-U85.clayer.yml` | Layer: startup, SE services, UART4 stdio, Ethos-U85 driver, display, memory placement | +| `main.c` | Cache invalidation, pin/GPIO config, SE services, clocks, MIPI DPHY power-up, stdio, NPU init, then `app_main()` | +| `display.c` | `board_display_image()`: brings up CDC200 + DSI + panel on first use and shows an RGB image scaled and centred in the RGB888 frame buffer | +| `buttons.c` | `board_buttons()`: SW2 joystick presses since the last call; `board_console_poll()`: a console read that does not block | | `retarget_stdio.c` | `stdio_init()` for UART4 through the CMSIS USART driver (115200 8N1) | | `ethos_setup.c` | Ethos-U85 driver init at `NPU_HG_BASE`, IRQ 366, prints the NPU banner | | `ethosu_cb_dcache.c` | D-cache clean/invalidate hooks for NPU buffers outside the TCMs | -| `linker_ac6_mram.sct.src`, `linker_gnu_mram.ld.src` | Pack linker scripts plus a 32 kB stack and the `.bss.ai_pool` section in bulk SRAM | +| `linker_ac6_mram.sct.src`, `linker_gnu_mram.ld.src` | Pack linker scripts plus a 32 kB stack and the `.bss.ai_pool` section in bulk SRAM, above the A32 boot stub | | `RTE/` | Configuration files carried with the layer (see below) | +The layer's `define:` node sets what `src/app_main.cpp` does on this board: +the pool sizes and their section (`APP_METHOD_POOL_SIZE`, +`APP_TEMP_POOL_SIZE`, `APP_POOL_SECTION`), `APP_DISPLAY` (show every image +on the LCD, 128x128 scaled 3x to 384x384 and centred on black), +`APP_INTERACTIVE` (serve image requests on the console after the boot demo, +see STDIO) and `APP_BUTTONS` (SW2 left: one new image; right: start or stop +generating back to back). + ## Memory layout | Region | Address | Used for | |--------|---------|----------| -| MRAM (HP application region) | `0x80200000`, 2 MB | Constants, the embedded `.pte` model; with AC6 also the code, which executes in place. With GCC and Clang the startup code executes from MRAM and copies the rest of the code to ITCM (`linker_gnu_mram.ld.src`) | -| DTCM (SRAM3) | `0x20000000` (core alias; `0x50800000` global), 1 MB | `.data`/`.bss`, 96 kB heap, 32 kB stack | -| SRAM0/SRAM1 (bulk) | `0x02000000`, 8 MB combined (`SRAM0_SRAM1_COMBINED` in `app_mem_regions.h`; the GNU script uses SRAM0 alone, 4 MB) | `.bss.ai_pool`: the runner's 1 MB method pool and 2 MB temp pool (NPU scratch) | +| MRAM (HP application region) | `0x80200000`, 3.4375 MB | Constants and the embedded 2.8 MB `.pte` model; with AC6 also the code, which executes in place. With GCC and Clang the startup code executes from MRAM and copies the rest of the code to ITCM (`linker_gnu_mram.ld.src`) | +| MRAM (user region) | `0x80570000`, 32 kB | Unused; keeps the pack's MPU setup well-formed | +| DTCM (SRAM3) | `0x20000000` (core alias; `0x50800000` global), 1 MB | `.data`/`.bss` (latents, image buffer), 96 kB heap, 32 kB stack | +| SRAM0 (bulk) | `0x02010000`, 4 MB less 64 kB | `.bss.ai_pool`: the runner's 1 MB method pool and 1.5 MB temp pool (NPU scratch); `.bss.lcd_frame_buf`: the 1.15 MB RGB888 frame buffer | + +The numbers are set in `RTE/Device/AE822FA0E5597LS0_M55_HP/app_mem_regions.h`. +The pack's default splits the 5.5 MB of application MRAM into 2 MB for the +HE core, 2 MB for the HP core and a 1.5 MB user region; the model does not +fit 2 MB, and this layer runs only the HP core, so its code region extends +over the user area up to `0x80570000`. The HP vector table stays at +`0x80200000`, where the debug stub's ATOC entry boots from, and the ATOC +package at the top of MRAM is not touched. The A32 boot stub the ATOC loads +to the start of SRAM0 is the reason the pools start 64 kB in +(`APP_SRAM_POOL_OFFSET`). -The pool sizes and section come from the `define:` node of the layer -(`APP_METHOD_POOL_SIZE`, `APP_TEMP_POOL_SIZE`, `APP_POOL_SECTION`) and are -consumed by `src/app_main.cpp`. +SRAM1 follows SRAM0 at `0x02400000` (the ATOC device configuration stitches +the two banks together; the device pack's `0x08000000` is not mapped then). +The M55_HP and the debugger can use it, but the Ethos-U85 could not run with +its weights there (`ethosu_invoke` returned -1), so the layer keeps +`SRAM0_SRAM1_COMBINED 0` and places nothing the NPU reads in SRAM1. + +> [!Warning] +> Do not change the Secure Enclave run profile from this core. On the +> DevKit-E8, `SERVICES_set_run_cfg` calls that assign `memory_blocks` (the +> pack display demo's `MRAM_MASK | SRAM0_MASK`, for example) returned success +> and then left MRAM or SRAM0 unusable: MRAM reads `0xFF`, writes to +> `0x02xxxxxx` read back zero, and neither a debugger reset nor a power cycle +> recovers it. Recovery is SETOOLS over the SE UART (SW4 = SEUART): +> `maintenance`, Hard Maintenance mode, then the "Alif: Install M55_HP debug +> stubs" task and a new flash. ## RTE configuration @@ -37,7 +72,8 @@ consumed by `src/app_main.cpp`. matters: Alif's stdio retarget refuses to build unless UART4 is in polling mode (`RTE_UART4_BLOCKING_MODE_ENABLE 1` in `RTE_Device.h`), and the Conductor-generated `pins.h`/`board_defs.h` differ from the pack defaults. The -files are the pack's own DevKit-E8 layer configuration. +files are the pack's own DevKit-E8 layer configuration, with the memory +regions above changed in `app_mem_regions.h`. ## STDIO @@ -46,6 +82,14 @@ console; SW4 in position **SEUART** (the default) routes the same USB serial port to the Secure Enclave for SETOOLS. The on-board J-Link sits behind the same connector. +With `APP_INTERACTIVE` the console carries binary image frames too, in +pico-faces' serial protocol: the host sends `G [steps] [class] [w]`, +the runner answers with the timing as text and a 49 kB RGB frame, which +takes about 4.3 s at 115200 baud. pico-faces' `viewer/view_serial.py` works +unchanged; close the Serial Monitor first, the viewer needs the port. A +faster console (`UART_BAUDRATE: 921600` in the layer's `define:` node, 0.5 s +per frame) needs the same rate in the viewer's `serial.Serial()` call. + ## Before the first debug session Program the debug stubs into the device's ATOC with Alif SETOOLS once: diff --git a/board/DevKit-E8/RTE/Device/AE822FA0E5597LS0_M55_HP/app_mem_regions.h b/board/DevKit-E8/RTE/Device/AE822FA0E5597LS0_M55_HP/app_mem_regions.h index f43bbb33..7e54e76c 100644 --- a/board/DevKit-E8/RTE/Device/AE822FA0E5597LS0_M55_HP/app_mem_regions.h +++ b/board/DevKit-E8/RTE/Device/AE822FA0E5597LS0_M55_HP/app_mem_regions.h @@ -30,7 +30,7 @@ // RTSS HP Region size [bytes] <0x0-0x00580000:8> // Defines size of RTSS HP application memory region. // Default: 0x00200000 -#define APP_MRAM_HP_SIZE 0x00200000 +#define APP_MRAM_HP_SIZE 0x00370000 // 3.4375 MB: HP code region extended over the former MRAM_USER area for the ~2.8 MB model // // MRAM User Configuration @@ -38,11 +38,11 @@ // MRAM User Region // User Base address <0x80000000-0x8057FFFF:8> // Defines base address of MRAM user region -#define APP_MRAM_USER_BASE 0x80400000 +#define APP_MRAM_USER_BASE 0x80570000 // was 0x80400000; the HP code region ends here // User Region size [bytes] <0x0-0x00580000:8> // Defines size of MRAM user region -#define APP_MRAM_USER_SIZE 0x00180000 // 1.5 MB +#define APP_MRAM_USER_SIZE 0x00008000 // 32 kB, ends at 0x80578000 below the ATOC package (~0x8057C000); non-zero keeps mpu.c's regions well-formed // Application executable MRAM region (before USER area) #define APP_CODE_MRAM_SIZE (APP_MRAM_USER_BASE - APP_MRAM_HP_BASE) @@ -52,7 +52,7 @@ // ======================= // Combine SRAM0 & SRAM1 // Combines SRAM0 and SRAM1 into single memory region -#define SRAM0_SRAM1_COMBINED 1 +#define SRAM0_SRAM1_COMBINED 0 // SRAM1 follows SRAM0, but the Ethos-U85 cannot reach it: keep NPU buffers in SRAM0 // SRAM // Base address <0x02000000-0x027FFFFF:8> // Defines base address of SRAM memory region. @@ -65,6 +65,10 @@ // No zero initialize // Excludes SRAM0 region from zero initialization. #define APP_SRAM_NOINIT 1 +// Offset of the application pools <0x0-0x00400000:8> +// The ATOC loads the A32 boot stub (a32_stub_0.bin) to 0x02000000; the +// ExecuTorch pools (.bss.ai_pool) and the LCD frame buffer start above it. +#define APP_SRAM_POOL_OFFSET 0x00010000 // // SRAM0 diff --git a/board/DevKit-E8/buttons.c b/board/DevKit-E8/buttons.c new file mode 100644 index 00000000..6096ec52 --- /dev/null +++ b/board/DevKit-E8/buttons.c @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------- + * Copyright (c) 2026 Arm Limited (or its affiliates). All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * DevKit-E8 user input for the runner: the SW2 joystick and a non-blocking + * console read. + * + * SW2 is a five-way switch on LPGPIO (port 15) pins 0-4, active low with the + * pad pull-ups (schematic 220-00319-B sheet 12, "Use Internal GPIO Pullups"). + * The direction names follow the pack's own VIO driver for this board + * (Boards/DevKit-e8/Drivers/vio_DevKit-E8.c): A = left, B = up, C = down, + * D = right, CENTER = select. The Conductor GPIO configuration of the layer + * (RTE/BSP/.../gpios.h, applied by board_gpios_config()) makes the pins + * inputs with debounce and falling-edge interrupt detection; the interrupt + * is never enabled in the NVIC, so the LPGPIO interrupt status register works + * as a press latch: a press is recorded even while the CPU is busy in an + * inference, and board_buttons() collects and clears it. + *---------------------------------------------------------------------------*/ + +#include + +#include "RTE_Components.h" +#include CMSIS_device_header + +#include "board_defs.h" + +#define BUTTON_LEFT (1u << 0) +#define BUTTON_RIGHT (1u << 1) +#define BUTTON_UP (1u << 2) +#define BUTTON_DOWN (1u << 3) +#define BUTTON_SELECT (1u << 4) + +#define LPGPIO ((volatile GPIO_Type *) LPGPIO_BASE) + +static unsigned map_pins(uint32_t pins) +{ + unsigned m = 0u; + if (pins & (1u << BOARD_JOY_SW_A_GPIO_PIN)) m |= BUTTON_LEFT; + if (pins & (1u << BOARD_JOY_SW_D_GPIO_PIN)) m |= BUTTON_RIGHT; + if (pins & (1u << BOARD_JOY_SW_B_GPIO_PIN)) m |= BUTTON_UP; + if (pins & (1u << BOARD_JOY_SW_C_GPIO_PIN)) m |= BUTTON_DOWN; + if (pins & (1u << BOARD_JOY_SW_CENTER_GPIO_PIN)) m |= BUTTON_SELECT; + return m; +} + +/* + Return the SW2 directions pressed since the previous call as a bit mask + (BUTTON_LEFT, BUTTON_RIGHT, BUTTON_UP, BUTTON_DOWN, BUTTON_SELECT) and + clear them. +*/ +unsigned board_buttons(void) +{ + uint32_t pressed = LPGPIO->GPIO_INTSTATUS & 0x1Fu; + if (pressed) { + LPGPIO->GPIO_PORTA_EOI = pressed; + } + return map_pins(pressed); +} + +/* + Return the SW2 directions currently held down (same bits), for the debugger + and for tests of the wiring. +*/ +unsigned board_buttons_held(void) +{ + return map_pins(~LPGPIO->GPIO_EXT_PORTA & 0x1Fu); +} + +/* + Return the next console character, or -1 when none is waiting. The pack's + stdin_getchar() blocks until a character arrives, which would stop the + runner from polling the joystick; this reads the console UART's receive + register directly (UART4 on the PRG USB connector, set up by stdio_init()). +*/ +int board_console_poll(void) +{ + volatile UART_Type *uart = (volatile UART_Type *) UART4_BASE; + if (uart->UART_LSR & 0x01u) { /* LSR[0]: receiver data ready */ + return (int) (uart->UART_RBR & 0xFFu); + } + return -1; +} diff --git a/board/DevKit-E8/display.c b/board/DevKit-E8/display.c new file mode 100644 index 00000000..1e5f4803 --- /dev/null +++ b/board/DevKit-E8/display.c @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------- + * Copyright (c) 2026 Arm Limited (or its affiliates). All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * DevKit-E8 display: shows an RGB image on the 4.3" MIPI-DSI panel (ILI9806E, + * 480 x 800 portrait) through the CDC200 display controller. The application + * calls board_display_image() with an 8-bit RGB image (HWC); the image is + * scaled up by the largest integer factor that fits the panel, centred on a + * black background and written into the RGB888 frame buffer in bulk SRAM. + * The display is brought up on the first call (CDC200 + DSI + panel), as in + * the pack's Boards/Templates/Baremetal/demo_cdc200.c. + *---------------------------------------------------------------------------*/ + +#include +#include +#include + +#include "RTE_Components.h" +#include CMSIS_device_header +#include "RTE_Device.h" + +#include "Driver_CDC200.h" + +#define LCD_WIDTH RTE_PANEL_HACTIVE_TIME /* 480 */ +#define LCD_HEIGHT RTE_PANEL_VACTIVE_LINE /* 800 */ + +#if RTE_CDC200_PIXEL_FORMAT != 1 +#error "display.c expects RTE_CDC200_PIXEL_FORMAT 1 (RGB888) in RTE_Device.h" +#endif +#define LCD_BPP 3 + +extern ARM_DRIVER_CDC200 Driver_CDC200; +static ARM_DRIVER_CDC200 *CDCdrv = &Driver_CDC200; + +/* RGB888 frame buffer, 480 x 800 x 3 = 1152000 bytes, in bulk SRAM (NPU and + display DMA accessible); the layer's linker scripts route the section. */ +static uint8_t lcd_frame[LCD_HEIGHT][LCD_WIDTH][LCD_BPP] + __attribute__((section(".bss.lcd_frame_buf"), aligned(32))); + +static int display_ready = 0; + +static void display_callback(uint32_t event) +{ + if (event & ARM_CDC_DSI_ERROR_EVENT) { + printf("Display: DSI error event\n"); + } +} + +/* Bring up the display controller, DSI link and panel; returns 0 on success. */ +static int display_init(void) +{ + int32_t ret; + + memset(lcd_frame, 0, sizeof(lcd_frame)); + SCB_CleanDCache_by_Addr((uint32_t *) lcd_frame, sizeof(lcd_frame)); + + ret = CDCdrv->Initialize(display_callback); + if (ret != ARM_DRIVER_OK) { + printf("Display: CDC200 Initialize failed (%d)\n", (int) ret); + return -1; + } + ret = CDCdrv->PowerControl(ARM_POWER_FULL); + if (ret != ARM_DRIVER_OK) { + printf("Display: CDC200 PowerControl failed (%d)\n", (int) ret); + return -1; + } + ret = CDCdrv->Control(CDC200_CONFIGURE_DISPLAY, (uint32_t) lcd_frame); + if (ret != ARM_DRIVER_OK) { + printf("Display: CDC200 configure failed (%d)\n", (int) ret); + return -1; + } + ret = CDCdrv->Start(); + if (ret != ARM_DRIVER_OK) { + printf("Display: CDC200 Start failed (%d)\n", (int) ret); + return -1; + } + printf("Display: %ux%u RGB888 panel started\n", (unsigned) LCD_WIDTH, (unsigned) LCD_HEIGHT); + return 0; +} + +/* + Show an 8-bit RGB (HWC, or gray when channels == 1) image on the panel. + + \param[in] rgb Pixel data, height x width x channels bytes + \param[in] width Image width in pixels + \param[in] height Image height in pixels + \param[in] channels 1 (gray) or 3 (RGB) + \return 0 on success, -1 when the display is not available +*/ +int board_display_image(const uint8_t *rgb, int width, int height, int channels) +{ + if (!display_ready) { + if (display_init() != 0) { + return -1; + } + display_ready = 1; + } + if (width <= 0 || height <= 0 || (channels != 1 && channels != 3)) { + return -1; + } + + /* Largest integer scale that fits, centred. */ + int scale = LCD_WIDTH / width; + if (LCD_HEIGHT / height < scale) { + scale = LCD_HEIGHT / height; + } + if (scale < 1) { + scale = 1; + } + const int out_w = width * scale, out_h = height * scale; + const int x0 = (LCD_WIDTH - out_w) / 2, y0 = (LCD_HEIGHT - out_h) / 2; + + for (int y = 0; y < out_h && y0 + y < LCD_HEIGHT; y++) { + const uint8_t *row = rgb + (size_t)(y / scale) * width * channels; + uint8_t *dst = &lcd_frame[y0 + y][x0][0]; + for (int x = 0; x < out_w && x0 + x < LCD_WIDTH; x++) { + const uint8_t *px = row + (x / scale) * channels; + /* CDC200 RGB888: little-endian 0x00RRGGBB in memory, i.e. B, G, R */ + dst[0] = channels == 3 ? px[2] : px[0]; + dst[1] = channels == 3 ? px[1] : px[0]; + dst[2] = px[0]; + dst += LCD_BPP; + } + } + /* The frame buffer is read by the display controller's DMA: push the + cached lines out (bulk SRAM is write-through, but be explicit). */ + SCB_CleanDCache_by_Addr((uint32_t *) &lcd_frame[y0][0][0], (int32_t)(out_h * LCD_WIDTH * LCD_BPP)); + return 0; +} diff --git a/board/DevKit-E8/linker_ac6_mram.sct.src b/board/DevKit-E8/linker_ac6_mram.sct.src index 91d55874..6c7984ff 100644 --- a/board/DevKit-E8/linker_ac6_mram.sct.src +++ b/board/DevKit-E8/linker_ac6_mram.sct.src @@ -5,7 +5,8 @@ /* Copied from the Ensemble pack (Device/core/rtss_hp/linker/linker_ac6_mram.sct) for the CMSIS-Executorch DevKit-E8 layer: a larger main stack, and the - .bss.ai_pool section (the ExecuTorch memory pools of the runner) in bulk SRAM. */ + .bss.ai_pool section (the ExecuTorch memory pools of the runner) in bulk SRAM, + above the A32 boot stub the ATOC loads to the start of SRAM0. */ #define __ROM_BASE APP_MRAM_HP_BASE #define __ROM_SIZE APP_CODE_MRAM_SIZE @@ -91,7 +92,7 @@ LR_ROM __ROM_BASE NOCOMPRESS __ROM_SIZE { ; load region size_region } #if (SRAM0_SRAM1_COMBINED == 1) - RW_SRAM APP_SRAM_BASE APP_SRAM_SIZE { ; Update sections as needed + RW_SRAM (APP_SRAM_BASE + APP_SRAM_POOL_OFFSET) (APP_SRAM_SIZE - APP_SRAM_POOL_OFFSET) { ; above the A32 boot stub * (.bss.ai_pool) ; ExecuTorch method/temp pools (src/app_main.cpp) * (.bss.lcd_crop_and_interpolate_buf) ; LCD crop and interpolate image processing buffer. * (.bss.lcd_frame_buf) ; LCD frame Buffer. @@ -99,7 +100,7 @@ LR_ROM __ROM_BASE NOCOMPRESS __ROM_SIZE { ; load region size_region * (.bss.camera_frame_bayer_to_rgb_buf) ; (Optional) Camera Frame Buffer for Bayer to RGB Conversion. } #else - RW_SRAM0 APP_SRAM0_BASE APP_SRAM0_SIZE { ; Update sections as needed + RW_SRAM0 (APP_SRAM0_BASE + APP_SRAM_POOL_OFFSET) (APP_SRAM0_SIZE - APP_SRAM_POOL_OFFSET) { ; above the A32 boot stub * (.bss.ai_pool) ; ExecuTorch method/temp pools (src/app_main.cpp) * (.bss.lcd_crop_and_interpolate_buf) ; LCD crop and interpolate image processing buffer. * (.bss.lcd_frame_buf) ; LCD frame Buffer. diff --git a/board/DevKit-E8/linker_gnu_mram.ld.src b/board/DevKit-E8/linker_gnu_mram.ld.src index 4f9b99de..53a2b230 100644 --- a/board/DevKit-E8/linker_gnu_mram.ld.src +++ b/board/DevKit-E8/linker_gnu_mram.ld.src @@ -28,7 +28,8 @@ */ /* Copied from the Ensemble pack (Device/core/rtss_hp/linker/linker_gnu_mram.ld.src) for the CMSIS-Executorch DevKit-E8 layer: a larger main stack, and the - .bss.ai_pool section (the runner's ExecuTorch memory pools) in bulk SRAM. */ + .bss.ai_pool section (the runner's ExecuTorch memory pools) in bulk SRAM, + above the A32 boot stub the ATOC loads to the start of SRAM0. */ /* app_mem_regions.h (the layer's RTE copy) is force-included by the CMSIS-Toolbox through the layer's `linker: regions:` entry, which also makes the toolbox preprocess this .src file with the project defines. */ @@ -51,7 +52,8 @@ MEMORY ITCM (rwx) : ORIGIN = APP_ITCM_BASE , LENGTH = APP_HP_ITCM_SIZE DTCM (rwx) : ORIGIN = APP_DTCM_BASE , LENGTH = APP_HP_DTCM_SIZE #if __HAS_BULK_SRAM - SRAM0 (rwx) : ORIGIN = APP_SRAM0_BASE, LENGTH = APP_SRAM0_SIZE + /* Above the A32 boot stub the ATOC loads to the start of SRAM0 */ + SRAM0 (rwx) : ORIGIN = APP_SRAM0_BASE + APP_SRAM_POOL_OFFSET, LENGTH = APP_SRAM0_SIZE - APP_SRAM_POOL_OFFSET SRAM1 (rwx) : ORIGIN = APP_SRAM1_BASE, LENGTH = APP_SRAM1_SIZE #endif MRAM (rx) : ORIGIN = __ROM_BASE, LENGTH = __ROM_SIZE diff --git a/board/DevKit-E8/main.c b/board/DevKit-E8/main.c index d2a402e7..239e8a2f 100644 --- a/board/DevKit-E8/main.c +++ b/board/DevKit-E8/main.c @@ -15,9 +15,9 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * DevKit-E8 (M55_HP) board bring-up for a headless Ethos-U85 runner. Derived - * from the pack's Boards/DevKit-e8/Layers/M55_HP/main.c without the MIPI, - * USB, Ethernet and VIO initialisation. + * DevKit-E8 (M55_HP) board bring-up for the Ethos-U85 face generator. Derived + * from the pack's Boards/DevKit-e8/Layers/M55_HP/main.c without the USB, + * Ethernet and VIO initialisation; the MIPI DPHY is powered for the display. *---------------------------------------------------------------------------*/ #include "RTE_Components.h" @@ -28,8 +28,33 @@ #include "se_services_port.h" +/* VBAT PWR_CTRL fields: power and isolation of the MIPI DPHYs and their PLL */ +#define VBAT_PWR_CTRL_TX_DPHY_PWR_MASK (1U << 0) +#define VBAT_PWR_CTRL_TX_DPHY_ISO (1U << 1) +#define VBAT_PWR_CTRL_RX_DPHY_PWR_MASK (1U << 4) +#define VBAT_PWR_CTRL_RX_DPHY_ISO (1U << 5) +#define VBAT_PWR_CTRL_DPHY_PLL_PWR_MASK (1U << 8) +#define VBAT_PWR_CTRL_DPHY_PLL_ISO (1U << 9) +#define VBAT_PWR_CTRL_DPHY_VPH_1P8_PWR_BYP_EN (1U << 12) + +/* Power up the MIPI DPHYs (the display's DSI link needs the TX DPHY and PLL) */ +static void vbat_init(void) +{ + VBAT->PWR_CTRL &= ~(VBAT_PWR_CTRL_TX_DPHY_PWR_MASK | VBAT_PWR_CTRL_RX_DPHY_PWR_MASK | + VBAT_PWR_CTRL_DPHY_PLL_PWR_MASK | VBAT_PWR_CTRL_DPHY_VPH_1P8_PWR_BYP_EN); + VBAT->PWR_CTRL &= ~(VBAT_PWR_CTRL_TX_DPHY_ISO | VBAT_PWR_CTRL_RX_DPHY_ISO | + VBAT_PWR_CTRL_DPHY_PLL_ISO); +} + int main(void) { + /* The pack's startup enables the caches without invalidating them, but + after a flash update and a warm reset from the debugger the data cache + can still hold lines of the previous image, and the runtime then reads + a corrupted program. Drop them before anything uses the model. */ + SCB_CleanInvalidateDCache(); + SCB_InvalidateICache(); + /* Apply the Conductor pin configuration (includes the UART4 console pins) */ board_pins_config(); @@ -39,9 +64,13 @@ int main(void) /* Bring up the Secure Enclave services (MHU link to the SE) */ se_services_port_init(); - /* Request the clocks the SE has to enable for this core */ + /* Request the clocks the SE has to enable for this core (HFOSC and + 100 MHz also feed the display controller and the DSI link) */ board_clocks_config(CLKEN_HFOSC_MASK | CLKEN_CLK_100M_MASK); + /* Power up the MIPI DPHY for the display */ + vbat_init(); + /* Initialize STDIO (UART4 on the PRG USB connector) */ stdio_init(); diff --git a/cmsis-executorch.cproject.yml b/cmsis-executorch.cproject.yml index 5128d15f..b5e02d31 100644 --- a/cmsis-executorch.cproject.yml +++ b/cmsis-executorch.cproject.yml @@ -1,13 +1,14 @@ # Copyright 2026 Arm Limited and/or its affiliates. # SPDX-License-Identifier: Apache-2.0 # -# The application: a headless runner that loads the embedded .pte and runs one -# inference on the Ethos-U85. The AI layer (generated by create_ai_layer.py) -# brings the ExecuTorch runtime, the operator components and the model data; -# the Board layer brings the board bring-up. +# The application: a headless face generator that loads the embedded .pte, +# runs pico-faces' sampler around its two Ethos-U85 methods and, on the +# DevKit, serves image requests on the console. The AI layer (generated by +# create_ai_layer.py) brings the ExecuTorch runtime, the operator components +# and the model data; the Board layer brings the board bring-up. project: packs: - - pack: PyTorch::ExecuTorch@1.4.1 + - pack: PyTorch::ExecuTorch@1.5.1 - pack: ARM::CMSIS misc: diff --git a/cmsis-executorch.csolution.yml b/cmsis-executorch.csolution.yml index 01d5558d..61726af9 100644 --- a/cmsis-executorch.csolution.yml +++ b/cmsis-executorch.csolution.yml @@ -1,8 +1,9 @@ # Copyright 2026 Arm Limited and/or its affiliates. # SPDX-License-Identifier: Apache-2.0 # -# A minimal ExecuTorch-on-Ethos-U85 example for the Corstone-320 (SSE-320) FVP -# and the Alif Ensemble E8 DevKit (target-type DevKit-E8). Three steps build it: +# pico-faces (a latent diffusion transformer that generates 128x128 faces) on +# the Ethos-U85 with ExecuTorch, for the Corstone-320 (SSE-320) FVP and the +# Alif Ensemble E8 DevKit (target-type DevKit-E8). Three steps build it: # # cbuild setup cmsis-executorch.csolution.yml --active SSE-320-U85 # python create_ai_layer.py cmsis-executorch.cbuild-mlops.yml @@ -12,7 +13,7 @@ # create_ai_layer.py reads the NPU and Vela settings from it, exports the model # from PyTorch in the project's venv and writes the AI layer. solution: - created-for: CMSIS-Toolbox@2.14.1 + created-for: CMSIS-Toolbox@2.15.0 cdefault: compiler: AC6 @@ -20,7 +21,7 @@ solution: # Pinned exactly: the pack's C++ runtime and the Python exporter pinned in # requirements.txt must be the same ExecuTorch version, or the .pte fails # to load. `cbuild setup --packs` fetches it from the public index. - - pack: PyTorch::ExecuTorch@1.4.1 + - pack: PyTorch::ExecuTorch@1.5.1 - pack: ARM::CMSIS - pack: ARM::CMSIS-NN - pack: ARM::CMSIS-Compiler @@ -81,17 +82,6 @@ solution: clock: 4000000 start-pname: M55_HP - misc: - # CMSIS-Toolbox 2.14.1 passes -mfpu=fpv5-sp-d16 to Clang for the Cortex-M85 - # and M55, a single-precision FPU on cores with a double-precision one and MVE; - # LLVM crashes on some kernels with that combination. The later option - # wins, so name the M85's FPU explicitly (newer toolboxes leave it to -mcpu). - - for-compiler: CLANG - C-CPP: - - -mfpu=fp-armv8-fullfp16-d16 - ASM: - - -mfpu=fp-armv8-fullfp16-d16 - build-types: - type: Debug debug: on @@ -105,7 +95,7 @@ solution: # create_ai_layer.py picks it up on its next run. # https://open-cmsis-pack.github.io/cmsis-toolbox/build-overview/#mlops-information mlops: - description: TinyCNN int8 image classifier for Ethos-U85 + description: pico-faces rectified-flow face generator for Ethos-U85 npu: type: Ethos-U85 # explicit: the Ensemble pack lists an Ethos-U55 first vela: @@ -117,8 +107,8 @@ solution: memory: Shared_Sram model: clayer: $AI-Layer$ - name: TinyCNN + name: PicoFaces hardware: - target: DevKit-E8 # named explicitly: the released 2.14.1 detects no hardware target + target: DevKit-E8 # the target-type of the board simulator: target: SSE-320-U85 diff --git a/create_ai_layer.py b/create_ai_layer.py index d231470c..6ba336c7 100644 --- a/create_ai_layer.py +++ b/create_ai_layer.py @@ -21,23 +21,67 @@ backend and the operator components the exported program actually uses model_pte.c / .h the ExecuTorch program as a C array + model_params.h constants of the model for the application (optional) model.pte the program itself, for inspection +model/model.py describes the model through a small contract: + + get_model(), get_calibration_inputs() one method, "forward" (the simple case) + get_methods() several methods, each with its module, + example inputs, quantization and + calibration data (see Method below) + get_params() constants written to model_params.h + +Every method is quantized, calibrated on its data and delegated to the Ethos-U. +The script prints, per method, how many Ethos-U delegates the graph has and +which operators remain on the CPU. Set AI_LAYER_STRICT=1 to fail when a method +is not a single delegate, AI_LAYER_VERBOSE=1 for the backend's partitioning +diagnostics, AI_LAYER_DUMP= to keep the TOSA and Vela artefacts, and +AI_LAYER_VELA_FLAGS for extra Vela options (e.g. --verbose-performance). + The script runs itself in the solution's .venv (see setup_venv.py) when it is -started with an interpreter that has no torch. +started with another interpreter. """ from __future__ import annotations +import logging +import operator import os import re import subprocess import sys +import time +from collections import Counter +from dataclasses import dataclass from pathlib import Path +from typing import Any, Callable, Iterable HERE = Path(__file__).resolve().parent PACK = "PyTorch::ExecuTorch" SYMBOL = "model_pte" +PARAMS_HEADER = "model_params.h" +# The float <-> integer boundary of a fully delegated method stays on the CPU. +BOUNDARY_OPS = { + "quantized_decomposed::quantize_per_tensor", + "quantized_decomposed::dequantize_per_tensor", +} + + +@dataclass(frozen=True) +class Method: + """One method of the exported program (model.get_methods() returns these). + + calibration yields input tuples for the quantizer's observers; without it + the example inputs are used. quantization is "a8w8" (int8 activations and + weights) or "a16w8" (int16 activations, int8 weights). + """ + + name: str + module: Any + example_inputs: tuple + calibration: Callable[[], Iterable[tuple]] | None = None + quantization: str = "a8w8" def run_in_venv() -> None: @@ -72,27 +116,27 @@ def pack_root() -> Path: def executorch_version(mlops_file: Path) -> str: - """The ExecuTorch pack version cbuild setup resolved, from .cbuild-pack.yml. + """The ExecuTorch pack version cbuild setup resolved for the csolution. - The file is a lock file that keeps earlier resolutions, so an unversioned - selector can still point at an older pack; the entry selected by the - csolution's exact pin (PyTorch::ExecuTorch@) is the one in use. + .cbuild-pack.yml is a lock file that keeps earlier resolutions: + after a pack update, an AI layer generated for the previous version still + selects that one. The entry selected by the csolution's own pack entry + (PyTorch::ExecuTorch@) is the one in use. """ import yaml + solution = mlops_file.with_name(mlops_file.name.replace(".cbuild-mlops.yml", ".csolution.yml")) + wanted = [ + entry["pack"] + for entry in yaml.safe_load(solution.read_text())["solution"].get("packs", []) + if entry["pack"].partition("@")[0] == PACK + ] pack_file = mlops_file.with_name(mlops_file.name.replace(".cbuild-mlops.yml", ".cbuild-pack.yml")) - fallback = None for entry in yaml.safe_load(pack_file.read_text())["cbuild-pack"]["resolved-packs"]: name, _, version = entry["resolved-pack"].partition("@") - selectors = entry.get("selected-by-pack", []) - if name != PACK or not selectors: - continue - if f"{PACK}@{version}" in selectors: + if name == PACK and set(wanted) & set(entry.get("selected-by-pack", [])): return version - fallback = fallback or version - if fallback: - return fallback - sys.exit(f"{pack_file}: {PACK} is not among the resolved packs") + sys.exit(f"{pack_file}: no {PACK} resolved for {wanted or 'the csolution'}; run cbuild setup first") def executorch_pack(version: str) -> Path: @@ -126,45 +170,191 @@ def option(name: str) -> str | None: # in the .pte. A path relative to the working directory keeps the # program identical between checkouts; Vela resolves it from there. kwargs["config_ini"] = os.path.relpath(mlops_dir / vela["ini"]) + if flags := os.environ.get("AI_LAYER_VELA_FLAGS", "").split(): + kwargs["extra_flags"] = flags print(f"[ai_layer] Vela: {kwargs}") - return EthosUCompileSpec(**kwargs) + spec = EthosUCompileSpec(**kwargs) + if dump := os.environ.get("AI_LAYER_DUMP"): + spec.dump_intermediate_artifacts_to(dump) + return spec -def export_model(spec) -> bytes: - """Quantize model/model.py, delegate it to the Ethos-U and return the .pte.""" - import torch - from executorch.backends.arm.ethosu import EthosUPartitioner - from executorch.backends.arm.quantizer import EthosUQuantizer, get_symmetric_quantization_config - from executorch.exir import EdgeCompileConfig, ExecutorchBackendConfig, to_edge_transform_and_lower - from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e +def compile_spec_from_file(mlops_file: str | Path): + """compile_spec() for a *.cbuild-mlops.yml (used by model/verify_export.py).""" + import yaml + + path = Path(mlops_file).resolve() + return compile_spec(yaml.safe_load(path.read_text())["cbuild-mlops"], path.parent) + +def default_compile_spec(): + """An Ethos-U85-256 spec with Vela's built-in system config, for host checks + that run without a *.cbuild-mlops.yml (the quantizer only needs the NPU).""" + return compile_spec( + {"npu": {"type": "Ethos-U85", "macs": 256}, + "vela": {"options": "--system-config Ethos_U85_SYS_DRAM_Mid --memory-mode Shared_Sram"}}, + HERE, + ) + + +# ---------------------------------------------------------------------------- +# The model contract + + +def load_model_module(): sys.path.insert(0, str(HERE / "model")) - from model import get_calibration_inputs, get_model + import model + + return model + + +def methods(model) -> list[Method]: + """model.get_methods(), or "forward" from get_model() and get_calibration_inputs().""" + if hasattr(model, "get_methods"): + return list(model.get_methods()) + samples = model.get_calibration_inputs() + return [Method("forward", model.get_model(), (samples[0],), lambda: [(s,) for s in samples])] + + +def quant_config(kind: str): + from executorch.backends.arm.quantizer import ( + get_symmetric_a16w8_quantization_config, + get_symmetric_quantization_config, + ) + + if kind == "a8w8": + return get_symmetric_quantization_config(is_per_channel=True) + if kind == "a16w8": + return get_symmetric_a16w8_quantization_config(is_per_channel=True) + sys.exit(f"unknown quantization {kind!r}: expected a8w8 or a16w8") + + +def strip_guards_fn(gm) -> None: + """Drop the dead `_guards_fn` call_module that torch.export emits for some + models; the Arm annotation passes iterate over every module unconditionally.""" + changed = False + for node in list(gm.graph.nodes): + if node.op == "call_module" and str(node.target) == "_guards_fn": + gm.graph.erase_node(node) + changed = True + if changed: + gm.graph.eliminate_dead_code() + gm.recompile() + + +def calibrate(prepared, method: Method) -> None: + """Run the observers over the method's calibration data (or its example inputs).""" + import torch + + batches = method.calibration() if method.calibration else [method.example_inputs] + count, t0 = 0, time.time() + with torch.no_grad(): + for inputs in batches: + prepared(*inputs) + count += 1 + print(f"[ai_layer] {method.name}: calibrated on {count} input set(s) in {time.time() - t0:.1f} s") - model, samples = get_model(), get_calibration_inputs() - example = (samples[0],) - graph = torch.export.export(model, example).module() +def quantize_method(method: Method, spec): + """The method's module with quantize/dequantize nodes (the fake-quant graph).""" + import torch + from executorch.backends.arm.quantizer import EthosUQuantizer + from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e + + graph = torch.export.export(method.module, method.example_inputs).module() + strip_guards_fn(graph) # Quantize the whole graph so the partitioner can move every node into the - # Ethos-U delegate; only a float<->int8 boundary stays on the CPU. + # Ethos-U delegate; only the float <-> integer boundary stays on the CPU. quantizer = EthosUQuantizer(spec) - quantizer.set_global(get_symmetric_quantization_config(is_per_channel=True)) + quantizer.set_global(quant_config(method.quantization)) prepared = prepare_pt2e(graph, quantizer) - with torch.no_grad(): - for sample in samples: - prepared(sample) # calibrate - quantized = convert_pt2e(prepared) + calibrate(prepared, method) + return convert_pt2e(prepared) + + +# ---------------------------------------------------------------------------- +# Export + + +def op_name(target) -> str | None: + """`aten::mul.Tensor` for an edge or aten op object; None for graph plumbing.""" + if target is operator.getitem: + return None + name = getattr(target, "name", None) + if callable(name): + try: + return name() + except Exception: + pass + text = str(target) + if found := re.search(r"schema = ([a-z_0-9]+::[\w.]+)", text): + return found.group(1) + if found := re.match(r"^([a-z_0-9]+::[\w.]+)$", text): + return found.group(1) + return None + + +def count_ops(graph_module) -> tuple[int, Counter]: + delegates, cpu_ops = 0, Counter() + for node in graph_module.graph.nodes: + if node.op != "call_function": + continue + if "executorch_call_delegate" in str(node.target): + delegates += 1 + elif name := op_name(node.target): + cpu_ops[name] += 1 + return delegates, cpu_ops + + +def report_partitioning(edge, name: str) -> set[str]: + """Print the delegate count and CPU operators of one method; return the CPU ops.""" + delegates, cpu_ops = count_ops(edge.exported_program(name).graph_module) + ops = ", ".join(f"{op} x{n}" if n > 1 else op for op, n in cpu_ops.most_common()) or "none" + print(f"[ai_layer] {name}: {delegates} Ethos-U delegate(s); CPU operators: {ops}") + stray = {re.sub(r"\.\w+$", "", op) for op in cpu_ops} - BOUNDARY_OPS + if delegates != 1 or stray: + message = ( + f"[ai_layer] warning: {name} is not a single Ethos-U delegate " + f"({delegates} delegate(s), CPU operators beyond the quantize/dequantize " + f"boundary: {sorted(stray) or 'none'}); set AI_LAYER_VERBOSE=1 for the reasons" + ) + if os.environ.get("AI_LAYER_STRICT"): + sys.exit(message) + print(message, file=sys.stderr) + return set(cpu_ops) + + +def export_model(spec, model) -> tuple[bytes, set[str]]: + """Quantize, delegate and serialize every method; return the .pte and the CPU operators.""" + import torch + from executorch.backends.arm.ethosu import EthosUPartitioner + from executorch.exir import EdgeCompileConfig, ExecutorchBackendConfig, to_edge_transform_and_lower + + programs = {} + for method in methods(model): + print(f"[ai_layer] {method.name}: quantization {method.quantization}") + programs[method.name] = torch.export.export(quantize_method(method, spec), method.example_inputs) edge = to_edge_transform_and_lower( - torch.export.export(quantized, example), - partitioner=[EthosUPartitioner(spec)], + programs, + partitioner={name: [EthosUPartitioner(spec)] for name in programs}, compile_config=EdgeCompileConfig(_check_ir_validity=False), ) + cpu_ops: set[str] = set() + for name in programs: + cpu_ops |= report_partitioning(edge, name) + program = edge.to_executorch(ExecutorchBackendConfig(extract_delegate_segments=False)) - return bytes(program.buffer) + for name in programs: # operators to_executorch adds (copies, for example) need components too + cpu_ops |= set(count_ops(program.exported_program(name).graph_module)[1]) + return bytes(program.buffer), cpu_ops + +# ---------------------------------------------------------------------------- +# The generated layer -def components(pte: bytes, pack: Path) -> tuple[list[str], list[str]]: + +def components(pte: bytes, pack: Path, cpu_ops: set[str]) -> tuple[list[str], list[str]]: """Runtime, kernel utils and registration, backend, plus one operator component per operator the .pte uses. The pack's "Extension Tensor" is not selected: its tensor_ptr_maker.cpp @@ -175,9 +365,14 @@ def components(pte: bytes, pack: Path) -> tuple[list[str], list[str]]: available = set(re.findall(r'Csub="([^"]+)"', pdsc.read_text())) family = {"aten": "Portable", "quantized_decomposed": "Quantized", "cortex_m": "Cortex-M"} + found = {(ns.decode(), op.decode()) for ns, op in re.findall(rb"(aten|quantized_decomposed|cortex_m)::(\w+)", pte)} + for name in cpu_ops: + ns, _, op = name.partition("::") + if ns in family: + found.add((ns, op.split(".")[0])) + selected, unknown = set(), [] - for ns, op in sorted(set(re.findall(rb"(aten|quantized_decomposed|cortex_m)::(\w+)", pte))): - ns, op = ns.decode(), op.decode() + for ns, op in sorted(found): candidates = [ f"{family[ns]} {op}", f"{family[ns]} {re.sub(r'_(per_tensor|per_channel|byte|copy)$', '', op)}", @@ -193,10 +388,14 @@ def components(pte: bytes, pack: Path) -> tuple[list[str], list[str]]: def c_array(pte: bytes) -> str: + """The program as a C array in the section .rodata.model, so a board layer's + linker script can give it a memory of its own (the Corstone-320 layer puts + it in DDR: the 2 MB FPGA SRAM that holds the code is too small); scripts + that only know .rodata* or +RO still pick it up.""" rows = [", ".join(f"0x{b:02x}" for b in pte[i : i + 16]) for i in range(0, len(pte), 16)] return ( "// Generated by create_ai_layer.py -- do not edit.\n" - f"__attribute__((aligned(16))) const unsigned char {SYMBOL}[] = {{\n " + f'__attribute__((aligned(16), section(".rodata.model"))) const unsigned char {SYMBOL}[] = {{\n ' + ",\n ".join(rows) + f"\n}};\nconst unsigned long {SYMBOL}_size = sizeof({SYMBOL});\n" ) @@ -215,7 +414,50 @@ def c_array(pte: bytes) -> str: """ -def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Path, version: str) -> str: +def c_number(value: float) -> str: + text = f"{float(value):.9g}" + if not any(ch in text for ch in ".en"): # e, inf, nan + text += ".0" + return text + "f" + + +def c_initializer(values, indent: str = " ") -> list[str]: + """Lines of a nested brace initializer for a float array of any rank.""" + if values.ndim == 1: + items = [c_number(v) for v in values.tolist()] + return [indent + ", ".join(items[i : i + 8]) + "," for i in range(0, len(items), 8)] + lines = [] + for row in values: + lines += [indent + "{", *c_initializer(row, indent + " "), indent + "},"] + return lines + + +def params_header(params: dict[str, Any]) -> str: + """model_params.h from model.get_params(), names as given: strings, integers + and floats become #defines, sequences and tensors of numbers `static const + float` arrays of the same shape.""" + import numpy as np + + lines = [ + "// Generated by create_ai_layer.py from get_params() in model/model.py -- do not edit.", + "#pragma once", + "", + ] + for name, value in params.items(): + if isinstance(value, str): + lines.append(f'#define {name} "{value}"') + elif isinstance(value, (bool, int)): + lines.append(f"#define {name} {int(value)}") + elif isinstance(value, float): + lines.append(f"#define {name} {c_number(value)}") + else: + array = np.asarray(value.detach().cpu() if hasattr(value, "detach") else value, dtype=np.float32) + dims = "".join(f"[{d}]" for d in array.shape) + lines += ["", f"static const float {name}{dims} = {{", *c_initializer(array), "};"] + return "\n".join(lines) + "\n" + + +def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Path, version: str, files: list[str]) -> str: lines = [ f"# Generated by create_ai_layer.py from {mlops_file.name} -- do not edit.", f"# Re-run `python create_ai_layer.py {mlops_file.name}` after changing", @@ -228,7 +470,8 @@ def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Pa f" - pack: {PACK}@{version}", "", " define:", - " - ET_LOG_ENABLED: 0", + " - ET_LOG_ENABLED: 1", # the runner prints the runtime's error messages, + " - ET_MIN_LOG_LEVEL: Error", # but not its progress notes "", " add-path:", " - .", @@ -240,8 +483,7 @@ def clayer(mlops: dict, runtime: list[str], operators: list[str], mlops_file: Pa " groups:", f" - group: {mlops['model'].get('name', 'Model')}", " files:", - f" - file: ./{SYMBOL}.c", - f" - file: ./{SYMBOL}.h", + *[f" - file: ./{name}" for name in files], "", ] return "\n".join(lines) @@ -254,20 +496,29 @@ def main() -> None: import yaml + if os.environ.get("AI_LAYER_VERBOSE"): + logging.basicConfig(level=logging.INFO) + logging.getLogger("executorch.backends.arm").setLevel(logging.INFO) + mlops_file = Path(sys.argv[1]).resolve() mlops = yaml.safe_load(mlops_file.read_text())["cbuild-mlops"] layer_file = mlops_file.parent / mlops["model"]["clayer"] layer_dir = layer_file.parent - pte = export_model(compile_spec(mlops, mlops_file.parent)) + model = load_model_module() + pte, cpu_ops = export_model(compile_spec(mlops, mlops_file.parent), model) version = executorch_version(mlops_file) - runtime, operators = components(pte, executorch_pack(version)) + runtime, operators = components(pte, executorch_pack(version), cpu_ops) layer_dir.mkdir(parents=True, exist_ok=True) + files = [f"{SYMBOL}.c", f"{SYMBOL}.h"] (layer_dir / "model.pte").write_bytes(pte) (layer_dir / f"{SYMBOL}.c").write_text(c_array(pte), newline="\n") (layer_dir / f"{SYMBOL}.h").write_text(HEADER, newline="\n") - layer_file.write_text(clayer(mlops, runtime, operators, mlops_file, version), newline="\n") + if hasattr(model, "get_params"): + (layer_dir / PARAMS_HEADER).write_text(params_header(model.get_params()), newline="\n") + files.append(PARAMS_HEADER) + layer_file.write_text(clayer(mlops, runtime, operators, mlops_file, version, files), newline="\n") print(f"[ai_layer] {len(pte)} byte program, operators: {operators}") print(f"[ai_layer] wrote {layer_file}") diff --git a/documentation/example.md b/documentation/example.md index 085a6ef0..31c38d46 100644 --- a/documentation/example.md +++ b/documentation/example.md @@ -1,10 +1,17 @@ -# ExecuTorch on Ethos-U +# ExecuTorch on Ethos-U: pico-faces This example shows how to deploy and run an [ExecuTorch](https://github.com/pytorch/executorch) model on an Arm Ethos-U NPU. +The model is [pico-faces](https://github.com/cpldcpu/pico-faces) by cpldcpu: a +latent rectified-flow diffusion transformer (DiT, 128 x 8 layers, 2.5M +parameters) plus a small convolutional decoder that generate 128x128 RGB faces +in five classes (gender x smile, plus unconditional). Upstream runs it through a +hand-written int8 engine on a Raspberry Pi Pico 2; here its float checkpoints +are exported through the ExecuTorch Arm backend and every layer runs on the +Ethos-U85, while the Cortex-M drives the sampling loop. The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/) provides the source code components to build the ExecuTorch runtime, required operators, and Ethos-U backend. -The build process uses the [CMSIS-Toolbox 2.14.1](https://open-cmsis-pack.github.io/cmsis-toolbox/) or higher. +The build process uses the [CMSIS-Toolbox 2.15.0](https://open-cmsis-pack.github.io/cmsis-toolbox/) or higher. This example application targets the Arm Corstone-320 reference platform with an Ethos-U85 NPU, simulated on the Arm FVP, and the @@ -24,6 +31,15 @@ the first debug session, is the [getting-started README](../README.md). ## What the example demonstrates +- Exporting a generative model with two methods (`dit_step` and `decode`) into + one ExecuTorch program, quantized to 16-bit activations and 8-bit weights + (the decoder to 8-bit activations) and calibrated on real sampling + trajectories, with both methods fully delegated to the Ethos-U85. +- A runner (`src/app_main.cpp`) that loads both methods once and runs the + rectified-flow Euler sampler with classifier-free guidance around them; on + the DevKit it shows every face on the board's LCD, generates new ones on the + joystick, and serves pico-faces' serial protocol so the upstream viewer + displays them on the host too. - Exporting an ExecuTorch model for Ethos-U from a Python virtual environment. - The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/) links only the required and operator components that the ML model needs. - Managing the NPU and Vela configuration in the CMSIS solution project rather than duplicating it in the Python exporter. @@ -42,13 +58,13 @@ the first debug session, is the [getting-started README](../README.md). The pack [`PyTorch::ExecuTorch`](https://www.keil.arm.com/packs/executorch-pytorch/) can be optionally installed manually with: ```bash -cpackget add PyTorch::ExecuTorch@1.4.1 +cpackget add PyTorch::ExecuTorch@1.5.1 ``` > [!Note] > The pack and Python exporter versions must match, as the generated `.pte` -> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.4.1`. -> The matching wheel is `executorch==1.4.1` from PyPI, pinned in +> format is consumed by the runtime supplied in `PyTorch::ExecuTorch@1.5.1`. +> The matching wheel is `executorch==1.5.1` from PyPI, pinned in > `requirements.txt`. ## Quick start @@ -59,15 +75,17 @@ The example can be built and run entirely in Keil Studio for VS Code. 2. Clone or download this repository, then open its folder in VS Code. 3. Before using the example for the first time, select **Terminal > Run Task > Setup Python virtual environment**. Wait for the task to create the `.venv` - environment and install the packages required to export the model. (The - **(uv)** variant of the task uses [uv](https://docs.astral.sh/uv/) instead - of pip and can download the Python version it asks for.) + environment, install the packages required to export the model and + download the pico-faces checkpoints (about 60 MB) into `model/pico_faces/`. + (The **(uv)** variant of the task uses [uv](https://docs.astral.sh/uv/) + instead of pip and can download the Python version it asks for.) 4. Select **Terminal > Run Task > Create AI layer**. This exports the model for the NPU of the active target and writes the `ai_layer/` directory. It reads `cmsis-executorch.cbuild-mlops.yml`, which the extension writes when the solution is loaded or built (`cbuild setup` on the command line). The - repository ships a generated layer, so this step is only needed after - changing the model or the target. + model data (`ai_layer/model_pte.c`, 2.8 MB of program as a 14 MB C source) + is generated, not committed, so this step is needed once per checkout and + again after changing the model. 5. Use the CMSIS action buttons to build the application, then select **Run** or **Debug**. Keil Studio starts the Corstone-320 FVP automatically. On macOS, where Arm ships no FVP build, `.vscode/fvp.sh` runs the model in Docker: @@ -80,19 +98,63 @@ For the Alif Ensemble E8 DevKit, choose the `DevKit-E8` target-type in **Manage Solution** and follow the [getting-started README](../README.md) for the one-time board preparation (SETOOLS, switches, J-Link). -A successful run prints the Ethos-U configuration, output logits, and a pass -result: +A successful run prints the Ethos-U configuration, the loaded methods, the +timings of the boot demo (seed 3, 4 steps, class 1, guidance 4), the CRC-32 +of the image, a coarse ASCII preview and a pass result. On the Corstone-320 +FVP: ```text Ethos-U version info: Arch: v2.0.0 MACs/cc: 256 Cmd stream: v1 -ExecuTorch Ethos-U85 example: 8896 byte model -Output: 10 element(s): 0.0079 0.0459 0.0475 -0.0475 0.0791 0.0411 -0.0285 -0.0744 -0.2246 -0.0016 +ExecuTorch pico-faces (m3_long_cfg, a16w8/a8w8): 2796016 byte program +Methods: + dit_step 2 input(s), 1 output(s), 16384 planned byte(s) + decode 1 input(s), 1 output(s), 245760 planned byte(s) +Generating: seed 3, 4 steps, class 1, w 4.0 + dit_step: 8 call(s), 835 ms total (104 ms each) + decode: 41 ms + total: 953 ms at 25 MHz (wall clock; not meaningful on the FVP) + dit_step: NPU 20040 kcycles, active 99%, MAC active 52%, 44 MAC/cycle, read 21279 kB on AXI0 + 0 kB on AXI1 + decode: NPU 617 kcycles, active 99%, MAC active 78%, 175 MAC/cycle, read 434 kB on AXI0 + 0 kB on AXI1 +Image: 128x128x3, CRC32 6b938c66 + |::::::::....... . ..........| + |::::::::........... ..... | + |:.:::::::...:---:::.. .... | + |:.::...:..:-==+++=-:............| + ... +Result files in out: written Test_result: PASS ``` +The CRC identifies the exported program's image: AC6, GCC and Clang builds, +the FVP and the DevKit-E8 all produce the same image from the same program. +The program itself can differ in a few quantization parameters from one host +to the next, because the calibration runs in floating point: the export +behind this listing (macOS) gives CRC 6b938c66, CI's export on Linux gives +3f8a2b1e. Either way the image matches the host's fake-quant rendering of the +same seed at about 48.5 dB PSNR, and the float model at 44 dB (`--compare`, +see step 5 below). On the DevKit-E8 +the face takes 78 ms at 400 MHz: 8.5 ms per `dit_step` (27.1 M NPU cycles for +the eight calls, NPU active 95%, MAC array active 39%) and 3 ms for `decode` +(1.3 M cycles). + +The two `NPU` lines come from the Ethos-U performance monitoring unit, read +by the runner around every method call: NPU clock cycles, the share of them +the NPU and its MAC array were busy, the average MACs per cycle derived from +the model's MAC count (`PF_MACS_*` in `model_params.h`), and the data read on +the NPU's two AXI ports. The DiT keeps the NPU busy but its MAC array only +40 to 50 percent of the time: most of the rest is the elementwise work of the +16-bit graph. Vela's per-operator estimate (`AI_LAYER_VELA_FLAGS=--verbose-performance`) +attributes the largest share to the RESCALE operators around every 16-bit +add and multiply, followed by the multiplies themselves, the softmax +max-reduction and transposes; the convolutions and matrix products are about +a quarter. The weights, 2.6 MB per `dit_step` pass, are streamed from DDR +on the FVP and from MRAM on the DevKit concurrently with that work. The +FVP's NPU cycle counts are its performance model's estimates; the DevKit's +come from the silicon. + ### Command-line build The same workflow from the VS Code Terminal (or any shell with the tools from @@ -113,9 +175,14 @@ On Windows: .\setup_venv.bat ``` -The setup script creates `.venv/` and installs the packages required to -quantize and export the model. It is safe to run again; use `--recreate` when -you want a completely new environment. The wrappers use `python3` (`python` on +The setup script creates `.venv/`, installs the packages required to +quantize and export the model, and downloads the three pico-faces checkpoint +files from a pinned upstream commit into `model/pico_faces/m3_long_cfg/`, +verifying their SHA-256. It is safe to run again; use `--recreate` when you +want a completely new environment, `--skip-download` to leave the checkpoints +alone, or `--download-only` to fetch just the checkpoints. With +`PICO_FACES_DIR` pointing at a local pico-faces clone, its `checkpoints/` +directory is used instead of the download. The wrappers use `python3` (`python` on Windows); point them at another interpreter with `PYTHON=python3.12 ./setup_venv.sh`. With [uv](https://docs.astral.sh/uv/getting-started/installation/) on `PATH`, @@ -131,9 +198,15 @@ version of an existing environment. #### 1. Generate the MLOps information ```bash +touch ai_layer/model_pte.c # fresh checkout only, see below cbuild setup cmsis-executorch.csolution.yml --active SSE-320-U85 --packs ``` +The committed AI layer lists `ai_layer/model_pte.c`, which only step 2 +generates, and csolution refuses a layer whose files are missing; on a fresh +checkout an empty placeholder stands in until `create_ai_layer.py` writes the +real file. + This resolves the packs and the active target and writes `cmsis-executorch.cbuild-mlops.yml`: the processor, NPU and Vela settings of the target, and the location of the AI layer. (`--packs` @@ -149,10 +222,15 @@ python3 create_ai_layer.py cmsis-executorch.cbuild-mlops.yml ``` This is the MLOps step. The script reads the NPU and Vela settings from the -file, quantizes and exports `model/model.py` for that NPU, and writes the -complete AI layer into `ai_layer/`: the component selection and the model as a -C array. It runs itself in `.venv` when started with another interpreter (use -`python` on Windows). +file, quantizes and exports the two methods of `model/model.py` for that NPU +(calibrating each on data the model provides), and writes the complete AI +layer into `ai_layer/`: the component selection, the model as a C array and +`model_params.h` with the sampling schedule and the conditioning table. It +prints, per method, the number of Ethos-U delegates and the operators left on +the CPU; for this model both methods are one delegate each, with only the +quantize/dequantize boundary on the CPU. It runs itself in `.venv` when +started with another interpreter (use `python` on Windows) and takes a few +minutes (calibration plus Vela). #### 3. Build the application @@ -176,7 +254,49 @@ out/cmsis-executorch/SSE-320-U85/Debug/cmsis-executorch.axf `.vscode/fvp.sh` is the model command the Run and Debug buttons use too; on Linux and Windows `FVP_Corstone_SSE-320` can be called directly with the same -arguments. +arguments. The application generates one face (seed 3, 4 steps, class 1, +guidance 4), prints the timings, the CRC-32 of the image and a coarse ASCII +preview, writes the image and the measurements to `out/fvp_image.bin` and +`out/fvp_result.txt` through semihosting, and ends the simulation. A run +takes a few minutes. + +#### 5. Check the result on the host + +```bash +python3 model/verify_export.py --stage fakequant --seed 3 --class 1 --w 4 --k 4 --out out/pf_fakequant.png +python3 model/verify_export.py --compare out/fvp_image.bin out/pf_fakequant.png +``` + +The host stages use the firmware's noise generator, so the same seed, class, +guidance and step count give the same face as on the FVP and the board. +`--stage float` samples with the float model, `--stage fakequant` with the +quantized graphs the export produces (what the NPU is expected to compute); +`--compare` prints the PSNR between two images (PNGs, or the raw frame the +FVP writes), and `--min-psnr` turns it into a check, as in CI. `--stage tosa` +executes both methods through the TOSA reference model, the integer +semantics the Ethos-U implements, and reports the deviation from the +fake-quant graphs: the check that catches 16-bit lowering problems before a +build. `--check-upstream ` verifies the re-implementation in +`model/model.py` against the original pico-faces modules. + +#### 6. Generate faces on the DevKit-E8 + +On the board the runner keeps going after the boot demo. Every face appears +on the LCD; the SW2 joystick generates new ones (left: one image; right: +start or stop generating back to back). The runner also serves image +requests on the UART console in the serial protocol of pico-faces' firmware, +so pico-faces' viewer works unchanged (in a clone of the upstream repository, +`pip install pyserial pillow`): + +```bash +python viewer/view_serial.py --port /dev/tty.usbmodemXXXX --seed 3 --steps 4 --class 1 --cfg 4 --show +``` + +Close the Serial Monitor first: the viewer needs the port. `--class` is 0 +(female, neutral), 1 (female, smiling), 2 (male, neutral), 3 (male, smiling) +or 4 (unconditional); `--cfg` is the guidance strength (0 = plain); +`--steps` is 8, 4, 2 or 1. A frame takes about 4 s to transfer at 115200 +baud, much longer than its generation. ## How model generation works @@ -192,7 +312,7 @@ mlops: memory: Shared_Sram model: clayer: $AI-Layer$ - name: TinyCNN + name: PicoFaces ``` `cbuild setup --active DevKit-E8` resolves it into @@ -206,8 +326,39 @@ target configuration is never duplicated in Python. The script then writes: - `ai_layer/ai_layer.clayer.yml`: the CMSIS components required by the model. - `ai_layer/model_pte.c` and `model_pte.h`: the ExecuTorch program embedded as a C array. +- `ai_layer/model_params.h`: the constants the runner needs (latent and image + shapes, the schedule, the conditioning vectors per class and step). - `ai_layer/model.pte`: the program itself, for inspection. +`model/model.py` re-implements the pico-faces modules with the operators the +Ethos-U delegate supports (RMSNorm from primitives, attention on rank-3 +tensors, patchify as a strided convolution) and loads the upstream weights by +name. Two details of the graph matter for the 16-bit path and were found with +the TOSA reference model (the host fake-quant reference does not model them): +reductions (the mean in RMSNorm, the softmax row sums) are matrix products +with a constant ones vector instead of `aten.mean`/`aten.sum`, whose +`REDUCE_SUM` has a documented 16-bit issue on the Ethos-U85; and the output +projection stays a Linear with its bias, followed by a transposed convolution +with fixed 0/1 weights for the unpatchify, because a bias add after the +transposed convolution fuses into a RESCALE whose shift is too small for +16-bit accumulators (the reference model reports "value should stay within +[-262144, 262143]", and the NPU output saturates). The conditioning tower +(timestep embedding, class embedding) is evaluated at export time for the 8 +schedule points and 5 classes, so the program takes the conditioning vector as +an input and the firmware needs no float operators on the CPU. + +| Method | Runs on | Work | +|--------|---------|------| +| `dit_step(z, c) -> v` | Ethos-U85 | 111 MMAC per call; 2 calls per step with guidance | +| `decode(z) -> img` | Ethos-U85 | 109 MMAC | +| Euler update, guidance blend, noise, RGB conversion | Cortex-M | a few thousand float operations | + +The DiT runs at `a16w8` (16-bit activations, 8-bit weights): it reproduces +the float model closely, whereas int8 activations (`PICO_FACES_QUANT=a8w8`) +give visibly degraded faces because the DiT's residual stream needs more than +8 bits. The decoder runs at `a8w8` (`PICO_FACES_DECODE_QUANT`): its +convolutions do not need the 16-bit path and run about three times as fast. + See [the MLOps flow](mlops-flow.md) for a detailed walkthrough. ## Component selection @@ -221,15 +372,18 @@ changes the operator set needs nothing more than re-running steps 2 and 3. ## Adapting the example -To use a different model, replace or modify `model/model.py` and update the -model name or input handling as required: `INPUT_SHAPE` and -`get_model(input_shape)` define the input, and -`get_calibration_inputs(input_shape, calibration_samples)` returns the samples -the quantizer is calibrated with; give it representative data for a trained -model. The runner in `src/app_main.cpp` builds its input tensor with the same -fixed shape and prints the output as ten floats, so a model with another input -shape or output needs matching changes there. Then re-run `create_ai_layer.py` -and build. +To use a different model, replace or modify `model/model.py`. A model with +one method needs only `get_model()` and `get_calibration_inputs()` (the +samples the quantizer is calibrated with; give it representative data for a +trained model). Several methods, their calibration data and quantization, +and constants for the firmware come through `get_methods()` and +`get_params()` (see [the MLOps flow](mlops-flow.md)). The runner in +`src/app_main.cpp` is written for pico-faces' two methods, so another model +needs a runner of its own; `EmbeddedModule` (`src/arm_embedded_module.hpp`) +loads and executes methods by name. Then re-run `create_ai_layer.py` and +build. `PICO_FACES_VARIANT=m3_decD_deep_full` exports pico-faces' larger +model (12 layers, 3.7 MB of weights), which does not fit the DevKit's MRAM +region without further memory work. To target another Ethos-U configuration, update the target and `mlops:` settings in the CMSIS solution and re-run all three steps. The generated Vela @@ -247,16 +401,18 @@ together. More information is available in |------|---------| | `cmsis-executorch.csolution.yml` | Solution, target, and MLOps configuration | | `cmsis-executorch.cproject.yml` | Application project: sources plus the Board and AI layers | -| `model/model.py` | Example TinyCNN model | +| `model/model.py` | pico-faces re-expressed as the ExecuTorch methods `dit_step` and `decode` | +| `model/verify_export.py` | Host checks: float and fake-quant sampling to PNG, TOSA reference model, upstream comparison, PSNR | +| `model/pico_faces/` | Downloaded checkpoints (not committed) | | `create_ai_layer.py` | Exports the model for the target and writes the AI layer | -| `ai_layer/` | Generated: component selection and the embedded model data | -| `setup_venv.py` (`.sh` / `.bat`) | Creates the Python environment for the export | +| `ai_layer/` | Generated: component selection, `model_params.h` and the embedded model data (`model_pte.c` not committed) | +| `setup_venv.py` (`.sh` / `.bat`) | Creates the Python environment for the export and downloads the checkpoints | | `.vscode.d/tasks.json` | The VS Code tasks (venv setup, Create AI layer, Alif debug stubs) merged by the CMSIS Solution extension | | `.vscode/fvp.sh`, `.vscode/fvp.Dockerfile` | The FVP model command used by Run and Debug; runs the model in Docker on macOS | -| `board/Corstone-320/` | Corstone-320 platform support and FVP configuration | -| `board/DevKit-E8/` | Alif Ensemble E8 DevKit board layer (M55_HP core, Ethos-U85, UART console) | +| `board/Corstone-320/` | Corstone-320 platform support and FVP configuration; the linker scripts in its `RTE/` put the model in DDR | +| `board/DevKit-E8/` | Alif Ensemble E8 DevKit board layer (M55_HP core, Ethos-U85, UART console, LCD, joystick) | | `.alif/` | SETOOLS configuration and debug stubs for the DevKit-E8 (from the Ensemble pack) | -| `src/app_main.cpp` | Loads the model, runs inference, and prints the result; pool sizes overridable with `APP_METHOD_POOL_SIZE`, `APP_TEMP_POOL_SIZE`, `APP_POOL_SECTION` | +| `src/app_main.cpp` | Loads the two methods and runs the sampler; board features through `APP_DISPLAY`, `APP_INTERACTIVE`, `APP_BUTTONS`; pool sizes overridable with `APP_METHOD_POOL_SIZE`, `APP_TEMP_POOL_SIZE`, `APP_POOL_SECTION` | | `src/arm_embedded_module.*` | `EmbeddedModule`: ExecuTorch's `Module` class without the POSIX file loading (BSD-3-Clause, `src/LICENSE-ExecuTorch`) | | `board/DevKit-E8/README.md` | The DevKit-E8 layer, its memory map and RTE configuration | | `documentation/mlops-flow.md` | The MLOps flow in detail | @@ -276,10 +432,13 @@ together. More information is available in The example code is licensed under Apache-2.0; see `LICENSE`. ExecuTorch and `src/arm_embedded_module.*`, which is derived from it, use a BSD-3-Clause -license; see `src/LICENSE-ExecuTorch`. +license; see `src/LICENSE-ExecuTorch`. pico-faces, whose modules +`model/model.py` re-implements and whose checkpoints `setup_venv.py` +downloads, is MIT-licensed; see `model/LICENSE-pico-faces`. ## References +- [pico-faces](https://github.com/cpldcpu/pico-faces) - [PyTorch ExecuTorch CMSIS Pack](https://www.keil.arm.com/packs/executorch-pytorch/) - [ExecuTorch](https://github.com/pytorch/executorch) - [ExecuTorch Arm Ethos-U backend](https://docs.pytorch.org/executorch/main/backends-arm-ethos-u.html) diff --git a/documentation/mlops-flow.md b/documentation/mlops-flow.md index c131db4c..40574b45 100644 --- a/documentation/mlops-flow.md +++ b/documentation/mlops-flow.md @@ -10,11 +10,13 @@ propagate that information. flowchart TD A["cmsis-executorch.csolution.yml
mlops: node"] -->|"1. cbuild setup --active <target>"| B["cmsis-executorch.cbuild-mlops.yml
npu, vela.options, model.clayer"] B -->|"2. create_ai_layer.py"| C["EthosUCompileSpec
quantize, delegate, Vela"] - D["model/model.py
TinyCNN"] --> C + D["model/model.py
pico-faces: dit_step, decode"] --> C C --> E["ai_layer/model_pte.c
the program as a C array"] C --> F["ai_layer/ai_layer.clayer.yml
component selection"] + C --> P["ai_layer/model_params.h
schedule, conditioning table"] E --> G["3. cbuild --active <target>"] F --> G + P --> G G --> H["cmsis-executorch.axf"] ``` @@ -25,7 +27,7 @@ The csolution is the only place where the NPU is described: ```yaml solution: mlops: - description: TinyCNN int8 image classifier for Ethos-U85 + description: pico-faces rectified-flow face generator for Ethos-U85 npu: type: Ethos-U85 vela: @@ -33,10 +35,9 @@ solution: memory: Shared_Sram # memory-mode from the Vela config model: clayer: $AI-Layer$ - name: TinyCNN + name: PicoFaces hardware: - target: DevKit-E8 # [@] of the board; - # named explicitly, 2.14.1 detects none + target: DevKit-E8 # [@] of the board simulator: target: SSE-320-U85 # [@] of the FVP ``` @@ -46,8 +47,8 @@ resolves it and writes `cmsis-executorch.cbuild-mlops.yml`: ```yaml cbuild-mlops: - generated-by: csolution version 2.14.1+p38-gf512b381 - description: TinyCNN int8 image classifier for Ethos-U85 + generated-by: csolution version 2.15.1+p3-gf46d68bf + description: pico-faces rectified-flow face generator for Ethos-U85 processor: type: Cortex-M55 npu: @@ -58,7 +59,7 @@ cbuild-mlops: options: --accelerator-config ethos-u85-256 --system-config Ethos_U85_SRAM_MRAM --memory-mode Shared_Sram model: clayer: ai_layer/ai_layer.clayer.yml - name: TinyCNN + name: PicoFaces hardware: active: DevKit-E8 cbuild-run: out/cmsis-executorch+DevKit-E8.cbuild-run.yml @@ -85,8 +86,11 @@ This is the hand-over point to the MLOps side: everything a model-export pipeline needs to know about the target is in this one file, and nothing in it is specific to this example's Python code. -`cbuild setup` writes the file even when the AI layer does not exist yet, so -the flow also works on a checkout without a generated layer. +`cbuild setup` writes the file even when the AI layer does not exist yet. +Here the clayer and its headers are committed but the 14 MB `model_pte.c` is +not, and csolution refuses a layer whose files are missing: on a fresh +checkout, `touch ai_layer/model_pte.c` first (CI does the same); +`create_ai_layer.py` replaces the placeholder. ## 2. `create_ai_layer.py` turns `*.cbuild-mlops.yml` into the AI layer @@ -96,24 +100,42 @@ for an MLOps system. It reads the file and: 1. builds ExecuTorch's `EthosUCompileSpec` from `npu:` and `vela:` -- the accelerator (`ethos-u85-256`), system config and memory mode come from there, so the Python code contains no NPU configuration; -2. exports `model/model.py`: quantizes it, delegates the whole graph to the - Ethos-U and compiles it with Vela; +2. exports `model/model.py`. The model describes itself through a small + contract: `get_methods()` lists the methods of the program (here + `dit_step` and `decode`, each an `nn.Module` with example inputs, a + calibration data generator and its quantization, `a16w8` or `a8w8`), and + `get_params()` returns constants for the application. A model with only + `get_model()` and `get_calibration_inputs()` is exported as the single + method `forward`. Every method is calibrated on its data (pico-faces: + latents from real sampling trajectories), delegated to the Ethos-U as a + whole and compiled with Vela; the script prints the delegate count and the + operators left on the CPU per method (`AI_LAYER_STRICT=1` turns a partially + delegated method into an error); 3. reads the operators the resulting program still calls on the CPU and looks up the matching components in the `PyTorch::ExecuTorch` pack (the version - `cbuild setup` resolved, from `*.cbuild-pack.yml`); + `cbuild setup` resolved for the csolution's pack entry, from + `*.cbuild-pack.yml`); 4. writes the layer into the directory of `model.clayer`: | File | Content | |------|---------| | `ai_layer/ai_layer.clayer.yml` | runtime, kernel utilities and registration, Ethos-U backend and the operator components the program uses | -| `ai_layer/model_pte.c` / `.h` | the program as a 16-byte-aligned C array, `model_pte` / `model_pte_size` | -| `ai_layer/model.pte` | the program itself, for inspection (not committed) | - -The clayer and the C array are committed, so a checkout builds without -Python. For a fully delegated model the component list is short: `Runtime`, -`Kernel Utils`, `Kernel Registration`, `Backend EthosU`, and the int8 boundary -`Quantized quantize` / `Quantized dequantize` operators. Everything else in -the pack is never compiled, let alone linked. +| `ai_layer/model_pte.c` / `.h` | the program as a 16-byte-aligned C array in the section `.rodata.model`, `model_pte` / `model_pte_size` | +| `ai_layer/model_params.h` | the constants from `get_params()`: latent and image shapes, the sampling schedule, the guidance strengths and the conditioning table `pf_cond[class][step][128]` | +| `ai_layer/model.pte` | the program itself, for inspection | + +The clayer and the headers are committed; `model_pte.c` (a 14 MB C source for +the 2.8 MB program) and `model.pte` are not, so a fresh checkout runs step 2 +once before it builds. For a fully delegated model the component list is +short: `Runtime`, `Kernel Utils`, `Kernel Registration`, `Backend EthosU`, +and the boundary `Quantized quantize` / `Quantized dequantize` operators +(they handle 8- and 16-bit activations). Everything else in the pack is never +compiled, let alone linked. + +The program holds two methods; the application loads both once and runs them +in a loop (`src/app_main.cpp`): the Euler sampler and the classifier-free +guidance blend are a few thousand float operations on the Cortex-M, +everything else is NPU work. ## 3. `cbuild` builds the application @@ -128,7 +150,13 @@ step 3. ## Changing the model or the target -- **Another model:** edit `model/model.py`, run steps 2 and 3. +- **Another model:** edit `model/model.py` (`get_methods()`, or `get_model()` + and `get_calibration_inputs()` for a single method), adapt the runner in + `src/app_main.cpp`, run steps 2 and 3. +- **Another precision:** `PICO_FACES_QUANT=a8w8 python create_ai_layer.py ...` + exports the DiT with 8-bit activations (smaller SRAM footprint, visibly + worse faces); `model/verify_export.py --stage fakequant` shows the effect on + the host before a build. - **Another NPU configuration:** edit the `mlops:` node (and add the matching `target-types:` entry and board layer), run steps 1 to 3: diff --git a/documentation/pack-provenance.md b/documentation/pack-provenance.md index 6fb19ab9..a5164711 100644 --- a/documentation/pack-provenance.md +++ b/documentation/pack-provenance.md @@ -11,14 +11,14 @@ The pack is published as an asset of the matching ExecuTorch GitHub release, which is also what its `.pdsc` declares as its download location: ```xml -https://github.com/pytorch/executorch/releases/download/v1.4.1/ +https://github.com/pytorch/executorch/releases/download/v1.5.1/ ``` So the normal acquisition routes work, and `cbuild setup ... --packs` takes care of it on a fresh clone. To install it by hand: ```bash -cpackget add PyTorch::ExecuTorch@1.4.1 +cpackget add PyTorch::ExecuTorch@1.5.1 ``` The version is pinned exactly, in `cmsis-executorch.csolution.yml` and @@ -26,18 +26,20 @@ The version is pinned exactly, in `cmsis-executorch.csolution.yml` and ```yaml packs: - - pack: PyTorch::ExecuTorch@1.4.1 + - pack: PyTorch::ExecuTorch@1.5.1 ``` -Both must agree. The pin is exact rather than a `@^1.4.1` range because the +Both must agree. The pin is exact rather than a `@^1.5.1` range because the pack's C++ runtime and the Python exporter have to be the *same* ExecuTorch version — see [Moving to a new ExecuTorch version](#moving-to-a-new-executorch-version). `create_ai_layer.py` reads the installed pack's `.pdsc` out of the pack root (`$CMSIS_PACK_ROOT`, or cpackget's default) to find out which operator -components exist. It reads the version `cbuild setup` resolved, taken from -`cmsis-executorch.cbuild-pack.yml`, so a pack root holding several -ExecuTorch versions cannot make it read the wrong one. +components exist. It reads the version `cbuild setup` resolved for the +csolution's `PyTorch::ExecuTorch` entry, taken from +`cmsis-executorch.cbuild-pack.yml`, so neither a pack root holding several +ExecuTorch versions nor an older resolution that the lock file keeps for the +previous AI layer can make it read the wrong one. ## What is in the pack @@ -63,7 +65,7 @@ ExecuTorch tree at `backends/arm/cmsis_pack/scripts/build_pack.sh`. ```bash git clone https://github.com/pytorch/executorch.git cd executorch -git checkout release/1.4 # or the tag matching your target version +git checkout release/1.5 # or the tag matching your target version git submodule update --init --recursive ``` @@ -111,7 +113,7 @@ them); the pack lands in the output directory: backends/arm/cmsis_pack/scripts/build_pack.sh \ --executorch-root "$PWD" \ --build-dir cmake-out-arm \ - --version 1.4.1-local \ + --version 1.5.1-local \ --output-dir pack-output ``` @@ -120,7 +122,7 @@ backends/arm/cmsis_pack/scripts/build_pack.sh \ Before trusting a freshly built pack, check the parts that go missing quietly: ```bash -VERSION=1.4.1-local +VERSION=1.5.1-local PACK=pack-output/PyTorch.ExecuTorch.$VERSION set -e @@ -158,7 +160,8 @@ in order: 2. **Update both pins** — `PyTorch::ExecuTorch@` in the csolution and in the cproject. 3. **Update the Python pins**: `executorch`, `torch` and `torchao` in - `requirements.txt`, following the new release's `install_requirements.py`; + `requirements.txt`, following the new release's `torch_pin.py` and + `install_requirements.py`; `requirements-arm-tosa.txt` (the TOSA serializer and its flatbuffers pin) only changes when the Arm backend's `requirements-arm-tosa.txt` does. 4. **Rebuild the venv, the AI layer and the project:** @@ -173,6 +176,6 @@ in order: A changed operator set simply shows up in the regenerated `ai_layer/ai_layer.clayer.yml`. -Finally, update the version wherever it appears in prose: the README -(Prerequisites and the pack-version note), the csolution header comment and -this page. +Finally, update the version wherever it appears in prose: +`documentation/example.md` (Prerequisites and the pack-version note), +`requirements.txt` and this page. diff --git a/model/LICENSE-pico-faces b/model/LICENSE-pico-faces new file mode 100644 index 00000000..97938b63 --- /dev/null +++ b/model/LICENSE-pico-faces @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Tim B. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/model/model.py b/model/model.py index d9d54a64..ca2ef2e8 100644 --- a/model/model.py +++ b/model/model.py @@ -1,57 +1,576 @@ # Copyright 2026 Arm Limited and/or its affiliates. # SPDX-License-Identifier: Apache-2.0 -"""The example model: a tiny CNN classifier, quantized to int8 for Ethos-U85 by create_ai_layer.py. +# +# The network modules re-implement pico-faces by Tim B. (cpldcpu), MIT license, +# see model/LICENSE-pico-faces. +"""The example model: pico-faces, a latent rectified-flow face generator, for Ethos-U85. -Small enough to export in seconds and fully partition onto the NPU, but real -enough to exercise conv / relu / pool / linear through Vela. Input is a single -16x16 RGB image (NCHW); output is a 10-class logit vector. +pico-faces (https://github.com/cpldcpu/pico-faces) generates 128x128 RGB faces +with a small diffusion transformer (DiT) on an 8x16x16 latent and a +convolutional VAE decoder. Upstream runs it through a hand-written int8 engine +on an RP2350; here the float checkpoints are re-expressed as ExecuTorch methods +and delegated to the Ethos-U85: + + dit_step(z (1,8,16,16), c (1,128)) -> v (1,8,16,16) one velocity evaluation + decode(z (1,8,16,16)) -> img (1,3,128,128) in [-1, 1] + +The conditioning vector c = t_mlp(timestep_embedding(t)) + y_emb(y) depends only +on the schedule step and the class, so it is evaluated at export into a table +(pf_cond in model_params.h) and the firmware feeds it as an input; the Euler +loop and the classifier-free guidance blend run in C on the Cortex-M. + +Contract with create_ai_layer.py: + get_methods() the methods of the exported program + get_params() constants for ai_layer/model_params.h + +Environment knobs: PICO_FACES_VARIANT (m3_long_cfg | m3_decD_deep_full), +PICO_FACES_QUANT (a16w8 | a8w8 for dit_step; a8w8 shows visible artefacts), +PICO_FACES_DECODE_QUANT (a8w8 | a16w8 for decode), PICO_FACES_CALIB_SEEDS +(calibration trajectories, default 16), PICO_FACES_DIR (a local pico-faces +clone instead of the checkpoints setup_venv.py downloads into model/pico_faces/). """ +from __future__ import annotations + +import functools +import math +import os +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Callable, Iterable + +import numpy as np import torch +import torch.nn.functional as F from torch import nn -INPUT_SHAPE = (1, 3, 16, 16) +HERE = Path(__file__).resolve().parent + +VARIANT = os.environ.get("PICO_FACES_VARIANT", "m3_long_cfg") +# The DiT's residual stream needs more than 8 bits; the decoder's convolutions +# do not, and run about twice as fast with 8-bit activations. +QUANTIZATION = os.environ.get("PICO_FACES_QUANT", "a16w8") +DECODE_QUANTIZATION = os.environ.get("PICO_FACES_DECODE_QUANT", "a8w8") +CALIB_SEEDS = int(os.environ.get("PICO_FACES_CALIB_SEEDS", "16")) + +# The sampling schedule of the exported models (models//export.yaml +# upstream): K_MAX Euler steps from t=1 (noise) towards t=0; k_steps in +# {8, 4, 2, 1} strides through it. +SCHEDULE = [1.0, 0.875, 0.75, 0.625, 0.5, 0.375, 0.25, 0.125] +CFG_W = [4.0, 6.0, 8.0] # guidance strengths upstream bakes tables for +N_CLASSES = 4 # 0 f/neutral, 1 f/smile, 2 m/neutral, 3 m/smile +NULL_CLASS = N_CLASSES # unconditional (also the negative of the CFG blend) +EPS = torch.finfo(torch.float32).eps # nn.RMSNorm(eps=None) upstream + + +# ---------------------------------------------------------------------------- +# Checkpoints + + +@dataclass(frozen=True) +class Checkpoint: + dit: dict[str, torch.Tensor] + dit_cfg: dict + dec: dict[str, torch.Tensor] + dec_plan: list[tuple[int, int]] + img_ch: int + latent_mean: torch.Tensor + latent_std: torch.Tensor + +def checkpoint_dir() -> Path: + if clone := os.environ.get("PICO_FACES_DIR"): + return Path(clone) / "checkpoints" / VARIANT + return HERE / "pico_faces" / VARIANT + + +@functools.lru_cache(maxsize=None) +def load_checkpoint() -> Checkpoint: + sys.path.insert(0, str(HERE.parent)) + from setup_venv import PICO_FACES_FILES, sha256 # stdlib only + + folder = checkpoint_dir() + expected = PICO_FACES_FILES[VARIANT] + for name in expected: + if not (folder / name).is_file(): + sys.exit( + f"{folder / name} is missing. Run ./setup_venv.sh (or `python3 " + "setup_venv.py --download-only`) to download the pico-faces " + "checkpoints, or point PICO_FACES_DIR at a pico-faces clone." + ) + + dit_ckpt = torch.load(folder / "dit_qat.pt", map_location="cpu", weights_only=True) + dit = {k.replace("_orig_mod.", ""): v.float() for k, v in dit_ckpt["ema"].items()} + dit_cfg = dit_ckpt["cfg"] + if dit_cfg.get("arch") != "dit" or dit_cfg.get("act") != "relu2": + sys.exit(f"unsupported pico-faces DiT config: arch={dit_cfg.get('arch')} act={dit_cfg.get('act')}") + + # vae_final.pt pickles a numpy scalar and needs weights_only=False: only + # load it after the hash check, so an unexpected file is never unpickled. + vae_file = folder / "vae_final.pt" + if (actual := sha256(vae_file)) != expected["vae_final.pt"]: + sys.exit(f"{vae_file}: SHA-256 {actual} does not match the pinned checkpoint") + vae_ckpt = torch.load(vae_file, map_location="cpu", weights_only=False) + dec = {k[len("decoder."):]: v.float() for k, v in vae_ckpt["model"].items() if k.startswith("decoder.")} + vae_cfg = vae_ckpt["cfg"] + + stats = np.load(folder / "latent_stats.npz") + return Checkpoint( + dit=dit, + dit_cfg=dit_cfg, + dec=dec, + dec_plan=[tuple(int(x) for x in p) for p in vae_cfg["dec_plan"]], + img_ch=int(vae_cfg.get("img_ch", 1)), + latent_mean=torch.from_numpy(stats["mean"].astype(np.float32)), + latent_std=torch.from_numpy(stats["std"].astype(np.float32)), + ) + + +# ---------------------------------------------------------------------------- +# Building blocks, written with the operators the Ethos-U delegate supports + + +class RMSNorm(nn.Module): + """RMS normalisation over the last dimension (no mean subtraction). + + aten.rms_norm has no Ethos-U lowering, so it is spelled out in primitives: + mul, a mean, add, rsqrt (a table on the NPU) and mul. The mean is a matrix + product with a constant 1/dim vector rather than aten.mean: aten.mean lowers + to a TOSA REDUCE_SUM, for which the ExecuTorch Arm backend documents a + 16-bit Ethos-U85 issue (silent zeros in its softmax tests); the product is + a MATMUL with an int48 accumulator that is exact at both precisions. + """ -class TinyCNN(nn.Module): - def __init__(self, input_shape: tuple[int, ...] = INPUT_SHAPE, num_classes: int = 10) -> None: + def __init__(self, dim: int, weight: bool) -> None: super().__init__() - _, channels, height, width = input_shape - self.features = nn.Sequential( - nn.Conv2d(channels, 8, kernel_size=3, padding=1), - nn.ReLU(), - nn.MaxPool2d(2), # 16x16 -> 8x8 - nn.Conv2d(8, 16, kernel_size=3, padding=1), - nn.ReLU(), - nn.MaxPool2d(2), # 8x8 -> 4x4 - ) - self.classifier = nn.Linear(16 * (height // 4) * (width // 4), num_classes) + self.weight = nn.Parameter(torch.ones(dim)) if weight else None + self.register_buffer("mean_weight", torch.full((1, dim), 1.0 / dim)) def forward(self, x: torch.Tensor) -> torch.Tensor: - x = self.features(x) - x = torch.flatten(x, 1) - return self.classifier(x) + ms = F.linear(x * x, self.mean_weight) # (..., 1): mean of the squares + y = x * torch.rsqrt(ms + EPS) + return y * self.weight if self.weight is not None else y + + +class Attention(nn.Module): + """Multi-head self-attention over the 64 tokens on rank-3 tensors. + + Vela accepts non-elementwise operators up to rank 4 with a batch of 1, so + the heads become the batch dimension of rank-3 bmm operands. The softmax + scale 1/sqrt(hd) is folded into q_norm.weight at load time. The softmax is + written out with its row sums as a matrix product with a ones vector (see + RMSNorm for why aten.mean / REDUCE_SUM is avoided). The max subtraction (a + REDUCE_MAX and a SUB, about 10% of the NPU cycles) is not needed in float, + because the qk-RMSNorm bounds the scores, but without it rows whose maximum + lies far below the calibrated range of the exp table lose all precision and + the images fall apart. `model/verify_export.py --stage tosa` checks the + lowered graphs against the TOSA reference model. + """ + + def __init__(self, dim: int, heads: int, tokens: int) -> None: + super().__init__() + self.dim, self.heads, self.hd, self.tokens = dim, heads, dim // heads, tokens + self.qkv = nn.Linear(dim, 3 * dim) + self.q_norm = RMSNorm(self.hd, weight=True) + self.k_norm = RMSNorm(self.hd, weight=True) + self.proj = nn.Linear(dim, dim) + self.register_buffer("ones", torch.ones(heads, tokens, 1)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: # (1, N, dim) + n, h, hd = self.tokens, self.heads, self.hd + q, k, v = self.qkv(x).split(self.dim, dim=-1) + q = self.q_norm(q.reshape(n, h, hd)).permute(1, 0, 2) # (H, N, hd) + k = self.k_norm(k.reshape(n, h, hd)).permute(1, 2, 0) # (H, hd, N) + v = v.reshape(n, h, hd).permute(1, 0, 2) # (H, N, hd) + s = torch.bmm(q, k) # (H, N, N) + e = torch.exp(s - s.amax(-1, keepdim=True)) + att = e * torch.reciprocal(torch.bmm(e, self.ones)) # softmax rows + out = torch.bmm(att, v).permute(1, 0, 2).reshape(1, n, self.dim) + return self.proj(out) + + +class DiTBlock(nn.Module): + """One adaLN-zero transformer block. + + The upstream `mod` (SiLU + Linear(dim, 6*dim), then chunk) is split into + six Linear(dim, dim) so every modulation vector gets its own activation + scale when quantized; the "1 + s" of the scale vectors is folded into the + biases of the two scale Linears. The relu2 activation is relu followed by + a multiplication (not pow, which would become a table). + """ + + def __init__(self, dim: int, heads: int, mlp_ratio: int, tokens: int) -> None: + super().__init__() + self.norm1 = RMSNorm(dim, weight=False) + self.attn = Attention(dim, heads, tokens) + self.norm2 = RMSNorm(dim, weight=False) + self.fc1 = nn.Linear(dim, mlp_ratio * dim) + self.fc2 = nn.Linear(mlp_ratio * dim, dim) + self.mods = nn.ModuleList([nn.Linear(dim, dim) for _ in range(6)]) # s1 b1 g1 s2 b2 g2 + + def forward(self, x: torch.Tensor, a: torch.Tensor) -> torch.Tensor: # a = silu(c), (1, dim) + s1, b1, g1, s2, b2, g2 = (m(a).reshape(1, 1, -1) for m in self.mods) + x = x + g1 * self.attn(self.norm1(x) * s1 + b1) + h = F.relu(self.fc1(self.norm2(x) * s2 + b2)) + h = h * h + return x + g2 * self.fc2(h) + + +class DiTStep(nn.Module): + """dit_step(z, c) -> v: the DiT velocity for one Euler step. + + z: normalised latent (1, z_ch, z_hw, z_hw); c: conditioning vector (1, dim). + Patchify is a strided Conv2d with the upstream Linear weights re-laid (the + (c, py, px) feature order of upstream's patchify is exactly the flattened + conv kernel layout). Unpatchify keeps the upstream output Linear (with its + bias) and follows it with a strided ConvTranspose2d whose fixed 0/1 weights + are a depth-to-space shuffle; an extra bias add after the transposed conv + would fuse into a RESCALE whose shift is too small for the 16-bit path. + """ + + def __init__(self, cfg: dict) -> None: + super().__init__() + d = cfg["dit"] + self.dim, self.depth, self.heads = int(d["dim"]), int(d["depth"]), int(d["heads"]) + self.patch, self.z_ch, self.z_hw = int(d["patch"]), int(cfg.get("latent_ch", 4)), int(cfg.get("latent_hw", 16)) + self.grid = self.z_hw // self.patch + self.tokens = self.grid * self.grid + pdim = self.z_ch * self.patch * self.patch + + self.embed = nn.Conv2d(self.z_ch, self.dim, self.patch, stride=self.patch) + self.register_buffer("pos", torch.zeros(1, self.tokens, self.dim)) + self.blocks = nn.ModuleList( + [DiTBlock(self.dim, self.heads, int(d["mlp_ratio"]), self.tokens) for _ in range(self.depth)] + ) + self.final_norm = RMSNorm(self.dim, weight=False) + self.final_mods = nn.ModuleList([nn.Linear(self.dim, self.dim) for _ in range(2)]) # s b + self.final = nn.Linear(self.dim, pdim) + self.unshuffle = nn.ConvTranspose2d(pdim, self.z_ch, self.patch, stride=self.patch, bias=False) + shuffle = torch.zeros(pdim, self.z_ch, self.patch, self.patch) + for c in range(self.z_ch): + for py in range(self.patch): + for px in range(self.patch): + shuffle[c * self.patch * self.patch + py * self.patch + px, c, py, px] = 1.0 + with torch.no_grad(): + self.unshuffle.weight.copy_(shuffle) + self.unshuffle.weight.requires_grad_(False) + self._pdim = pdim + + def forward(self, z: torch.Tensor, c: torch.Tensor) -> torch.Tensor: + x = self.embed(z).reshape(1, self.dim, self.tokens).permute(0, 2, 1) + self.pos + a = F.silu(c) + for blk in self.blocks: + x = blk(x, a) + s, b = (m(a).reshape(1, 1, -1) for m in self.final_mods) + x = self.final(self.final_norm(x) * s + b) # (1, tokens, pdim) + x = x.permute(0, 2, 1).reshape(1, self._pdim, self.grid, self.grid) + return self.unshuffle(x) + @torch.no_grad() + def load_state(self, sd: dict[str, torch.Tensor]) -> "DiTStep": + dim, p, C = self.dim, self.patch, self.z_ch + self.embed.weight.copy_(sd["embed.weight"].reshape(dim, C, p, p)) + self.embed.bias.copy_(sd["embed.bias"]) + self.pos.copy_(sd["pos"]) + for i, blk in enumerate(self.blocks): + pre = f"blocks.{i}." + blk.attn.qkv.weight.copy_(sd[pre + "attn.qkv.weight"]) + blk.attn.qkv.bias.copy_(sd[pre + "attn.qkv.bias"]) + blk.attn.q_norm.weight.copy_(sd[pre + "attn.q_norm.weight"] * self.blocks[0].attn.hd ** -0.5) + blk.attn.k_norm.weight.copy_(sd[pre + "attn.k_norm.weight"]) + blk.attn.proj.weight.copy_(sd[pre + "attn.proj.weight"]) + blk.attn.proj.bias.copy_(sd[pre + "attn.proj.bias"]) + blk.fc1.weight.copy_(sd[pre + "mlp.0.weight"]) + blk.fc1.bias.copy_(sd[pre + "mlp.0.bias"]) + blk.fc2.weight.copy_(sd[pre + "mlp.2.weight"]) + blk.fc2.bias.copy_(sd[pre + "mlp.2.bias"]) + w, b = sd[pre + "mod.1.weight"], sd[pre + "mod.1.bias"] + for j, m in enumerate(blk.mods): + m.weight.copy_(w[j * dim:(j + 1) * dim]) + m.bias.copy_(b[j * dim:(j + 1) * dim]) + blk.mods[0].bias.add_(1.0) # 1 + s1 + blk.mods[3].bias.add_(1.0) # 1 + s2 + w, b = sd["final_mod.1.weight"], sd["final_mod.1.bias"] + for j, m in enumerate(self.final_mods): + m.weight.copy_(w[j * dim:(j + 1) * dim]) + m.bias.copy_(b[j * dim:(j + 1) * dim]) + self.final_mods[0].bias.add_(1.0) # 1 + s + self.final.weight.copy_(sd["final.weight"]) + self.final.bias.copy_(sd["final.bias"]) + return self -def get_model(input_shape: tuple[int, ...] = INPUT_SHAPE) -> nn.Module: - # Fixed seed: the example uses untrained (random) weights, and a fixed seed - # keeps the exported program identical from one export to the next (the - # C array also embeds the Vela options, so the same working directory is - # part of that guarantee). - torch.manual_seed(0) - return TinyCNN(input_shape).eval() + @classmethod + def from_checkpoint(cls) -> "DiTStep": + ck = load_checkpoint() + return cls(ck.dit_cfg).load_state(ck.dit).eval() -def get_calibration_inputs( - input_shape: tuple[int, ...] = INPUT_SHAPE, calibration_samples: int = 2 -) -> list[torch.Tensor]: - """Float samples for quantization calibration; the first one is also the export example. +class Cond(nn.Module): + """c = t_mlp(timestep_embedding(t)) + y_emb(y): float, evaluated on the host.""" - Fixed values keep the quantization parameters, and with them the output - logits in the README, the same from one export to the next. Replace them - with representative, preprocessed data when calibrating a trained model. + def __init__(self, cfg: dict) -> None: + super().__init__() + dim, t_dim = int(cfg["dit"]["dim"]), int(cfg["t_embed_dim"]) + half = t_dim // 2 + freqs = torch.exp(-math.log(10000.0) * torch.arange(half, dtype=torch.float32) / half) + self.register_buffer("freqs1000", freqs * 1000.0) # t in [0, 1] scaled like DDPM + self.t_mlp = nn.Sequential(nn.Linear(t_dim, dim), nn.SiLU(), nn.Linear(dim, dim)) + self.y_emb = nn.Embedding(int(cfg.get("n_classes", 0)) + 1, dim) + + def forward(self, t: torch.Tensor, y: torch.Tensor) -> torch.Tensor: # t (B,) float, y (B,) int64 + args = t.float()[:, None] * self.freqs1000[None] + emb = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + return self.t_mlp(emb) + self.y_emb(y) + + @torch.no_grad() + def cond_table(self) -> torch.Tensor: + """(n_classes + 1, K_MAX, dim): c for every class at every schedule point.""" + n = self.y_emb.num_embeddings + t = torch.tensor(SCHEDULE, dtype=torch.float32) + return torch.stack([self(t, torch.full((len(SCHEDULE),), y, dtype=torch.int64)) for y in range(n)]) + + @torch.no_grad() + def load_state(self, sd: dict[str, torch.Tensor]) -> "Cond": + self.t_mlp[0].weight.copy_(sd["t_mlp.0.weight"]) + self.t_mlp[0].bias.copy_(sd["t_mlp.0.bias"]) + self.t_mlp[2].weight.copy_(sd["t_mlp.2.weight"]) + self.t_mlp[2].bias.copy_(sd["t_mlp.2.bias"]) + self.y_emb.weight.copy_(sd["y_emb.weight"]) + return self + + @classmethod + def from_checkpoint(cls) -> "Cond": + ck = load_checkpoint() + return cls(ck.dit_cfg).load_state(ck.dit).eval() + + +class Decoder(nn.Module): + """decode(z_norm) -> img in [-1, 1]: the VAE decoder. + + A chain of Conv3x3 (+ folded BatchNorm) + ReLU with nearest 2x upsampling + before the layers the plan flags, and a linear output conv. The latent + de-normalisation z = z_norm * std + mean is folded into the first conv. """ - samples = [torch.full(input_shape, 0.5), torch.full(input_shape, -0.5)][:calibration_samples] - for i in range(len(samples), calibration_samples): - samples.append(torch.randn(input_shape, generator=torch.Generator().manual_seed(i))) - return samples + + def __init__(self, plan: list[tuple[int, int]], z_ch: int, img_ch: int) -> None: + super().__init__() + self.up_before: set[int] = set() + body, c_in = [], z_ch + for i, (c_out, up) in enumerate(plan): + if up: + self.up_before.add(i) + body.append(nn.Conv2d(c_in, c_out, 3, padding=1)) + c_in = c_out + self.body = nn.ModuleList(body) + self.out = nn.Conv2d(c_in, img_ch, 3, padding=1) + + def forward(self, z: torch.Tensor) -> torch.Tensor: + h = z + for i, conv in enumerate(self.body): + if i in self.up_before: + h = F.interpolate(h, scale_factor=2.0, mode="nearest") + h = F.relu(conv(h)) + return torch.clamp(self.out(h), -1.0, 1.0) + + @torch.no_grad() + def load_state(self, sd: dict[str, torch.Tensor], mean: torch.Tensor, std: torch.Tensor) -> "Decoder": + for i, conv in enumerate(self.body): + w, b = sd[f"body.{i}.0.weight"], sd[f"body.{i}.0.bias"] + gamma, beta = sd[f"body.{i}.1.weight"], sd[f"body.{i}.1.bias"] + mu, var = sd[f"body.{i}.1.running_mean"], sd[f"body.{i}.1.running_var"] + f = gamma / torch.sqrt(var + 1e-5) + w = w * f[:, None, None, None] + b = (b - mu) * f + beta + if i == 0: # z_real = z_norm * std + mean, folded into the first conv + b = b + (w * mean[None, :, None, None]).sum(dim=(1, 2, 3)) + w = w * std[None, :, None, None] + conv.weight.copy_(w) + conv.bias.copy_(b) + self.out.weight.copy_(sd["out.weight"]) + self.out.bias.copy_(sd["out.bias"]) + return self + + @classmethod + def from_checkpoint(cls) -> "Decoder": + ck = load_checkpoint() + z_ch = int(ck.dit_cfg.get("latent_ch", 4)) + return cls(ck.dec_plan, z_ch, ck.img_ch).load_state(ck.dec, ck.latent_mean, ck.latent_std).eval() + + +# ---------------------------------------------------------------------------- +# Sampling (host reference) and calibration data + + +def schedule_steps(k_steps: int) -> list[tuple[int, float]]: + """[(k, dt)] for k_steps Euler steps striding the K_MAX schedule.""" + stride = len(SCHEDULE) // k_steps + if stride * k_steps != len(SCHEDULE): + raise ValueError(f"k_steps must divide {len(SCHEDULE)}") + ks = list(range(0, len(SCHEDULE), stride)) + ts = [SCHEDULE[k] for k in ks] + [0.0] + return [(k, t - t_next) for k, t, t_next in zip(ks, ts[:-1], ts[1:])] + + +@torch.no_grad() +def euler_sample( + dit: Callable[[torch.Tensor, torch.Tensor], torch.Tensor], + cond: torch.Tensor, + z: torch.Tensor, + y: int, + w: float | None = None, + k_steps: int = 4, + tap: Callable[[torch.Tensor, torch.Tensor], None] | None = None, +) -> torch.Tensor: + """Rectified-flow Euler sampling from noise z (1, C, H, W) to the final latent. + + cond: the (n_cond, K_MAX, dim) table of Cond.cond_table(). w: classifier-free + guidance strength (None or 0 = plain). tap(z, c) sees every dit input pair. + """ + guided = w is not None and w > 0 and y != NULL_CLASS + for k, dt in schedule_steps(k_steps): + c = cond[y, k][None] + if tap: + tap(z, c) + v = dit(z, c) + if guided: + c_null = cond[NULL_CLASS, k][None] + if tap: + tap(z, c_null) + v_null = dit(z, c_null) + v = v_null + w * (v - v_null) + z = z - dt * v + return z + + +def noise(seed: int, z_ch: int, z_hw: int) -> torch.Tensor: + """The firmware's starting noise for a seed: PCG32 (XSH-RR) and a sum of twelve + 12-bit uniforms per value (CLT-12), in CHW order, as src/app_main.cpp computes + it, so the host reference and the board sample from the same latent. The + generator is pico-faces' engine/src/prng.c; upstream fills in token order.""" + mask, mult, inc = (1 << 64) - 1, 6364136223846793005, 1442695040888963407 + state = inc + state = ((state + seed) * mult + inc) & mask + values = np.empty(z_ch * z_hw * z_hw, dtype=np.float32) + for i in range(values.size): + acc = 0 + for _ in range(12): + x, count = state, state >> 59 + state = (x * mult + inc) & mask + x ^= x >> 18 + out = (x >> 27) & 0xFFFFFFFF + acc += (((out >> count) | (out << ((32 - count) & 31))) & 0xFFFFFFFF) >> 20 + values[i] = (acc - 24576) / 4096.0 + return torch.from_numpy(values).reshape(1, z_ch, z_hw, z_hw) + + +@torch.no_grad() +def to_image(img: torch.Tensor) -> np.ndarray: + """(1, C, H, W) in [-1, 1] -> uint8 HWC, the same mapping the firmware uses.""" + x = ((img.clamp(-1, 1) + 1.0) * 127.5).round().clamp(0, 255).to(torch.uint8) + return x[0].permute(1, 2, 0).contiguous().numpy() + + +@functools.lru_cache(maxsize=None) +def _trajectories() -> tuple[list[tuple[torch.Tensor, torch.Tensor]], list[tuple[torch.Tensor]]]: + """Calibration inputs from float sampling runs (as upstream quant/calibrate.py). + + CALIB_SEEDS trajectories at the full K_MAX schedule; classes cycle 0..3, + every second run is guided with w=8 so the hotter guided activations are + covered too (both passes of each guided step are tapped). + """ + dit, cond = DiTStep.from_checkpoint(), Cond.from_checkpoint().cond_table() + dit_inputs: list[tuple[torch.Tensor, torch.Tensor]] = [] + dec_inputs: list[tuple[torch.Tensor]] = [] + for seed in range(CALIB_SEEDS): + z = noise(777 + seed, dit.z_ch, dit.z_hw) + y, w = seed % N_CLASSES, (max(CFG_W) if seed % 2 else None) + z0 = euler_sample(dit, cond, z, y, w, k_steps=len(SCHEDULE), tap=lambda z, c: dit_inputs.append((z.clone(), c.clone()))) + dec_inputs.append((z0,)) + return dit_inputs, dec_inputs + + +def calib_dit_step() -> Iterable[tuple[torch.Tensor, ...]]: + return _trajectories()[0] + + +def calib_decode() -> Iterable[tuple[torch.Tensor, ...]]: + return _trajectories()[1] + + +# ---------------------------------------------------------------------------- +# The contract with create_ai_layer.py + + +@dataclass(frozen=True) +class Method: + """One method of the program; the fields create_ai_layer.Method has.""" + + name: str + module: nn.Module + example_inputs: tuple[torch.Tensor, ...] + calibration: Callable[[], Iterable[tuple[torch.Tensor, ...]]] | None = None + quantization: str = "a8w8" + + +def quantization(kind: str, variable: str) -> str: + if kind not in ("a8w8", "a16w8"): + sys.exit(f"{variable}={kind!r}: expected a8w8 or a16w8") + return kind + + +def example_inputs() -> tuple[torch.Tensor, torch.Tensor]: + """(z, c) for dit_step; z alone is decode's input.""" + ck = load_checkpoint() + z_ch, z_hw = int(ck.dit_cfg.get("latent_ch", 4)), int(ck.dit_cfg.get("latent_hw", 16)) + return noise(0, z_ch, z_hw), Cond.from_checkpoint().cond_table()[NULL_CLASS, 0][None] + + +def get_methods() -> list[Method]: + z, c = example_inputs() + return [ + Method("dit_step", DiTStep.from_checkpoint(), (z, c), calib_dit_step, + quantization(QUANTIZATION, "PICO_FACES_QUANT")), + Method("decode", Decoder.from_checkpoint(), (z,), calib_decode, + quantization(DECODE_QUANTIZATION, "PICO_FACES_DECODE_QUANT")), + ] + + +def macs(module: nn.Module, inputs: tuple[torch.Tensor, ...]) -> int: + """Multiply-accumulates of the convolutions and matrix products of one call.""" + from torch.utils.flop_counter import FlopCounterMode + + with FlopCounterMode(display=False) as counter, torch.no_grad(): + module(*inputs) + return counter.get_total_flops() // 2 + + +def get_params() -> dict: + """Constants the firmware needs; create_ai_layer.py writes them to model_params.h.""" + ck = load_checkpoint() + dit, dec = DiTStep.from_checkpoint(), Decoder.from_checkpoint() + cond = Cond.from_checkpoint().cond_table() # (N_CLASSES + 1, K_MAX, dim) + z, c = example_inputs() + return { + "PF_VARIANT": VARIANT, + "PF_QUANT": f"{QUANTIZATION}/{DECODE_QUANTIZATION}", # dit_step / decode + "PF_LATENT_CH": dit.z_ch, + "PF_LATENT_HW": dit.z_hw, + "PF_IMG_CH": ck.img_ch, + "PF_IMG_HW": dit.z_hw << len(dec.up_before), + "PF_COND_DIM": cond.shape[2], + "PF_N_CLASSES": N_CLASSES, + "PF_NULL_CLASS": NULL_CLASS, # unconditional; also the negative of the guidance blend + "PF_N_COND": cond.shape[0], + "PF_K_MAX": len(SCHEDULE), + "PF_N_CFG_W": len(CFG_W), + "PF_MACS_DIT_STEP": macs(dit, (z, c)), + "PF_MACS_DECODE": macs(dec, (z,)), + # t of every schedule step; a run of k steps uses every (PF_K_MAX / k)-th + # entry and integrates from t to the next used entry (0 after the last). + "pf_schedule": SCHEDULE, + # Guidance strengths pico-faces' firmware cycles through (any w > 0 works here). + "pf_cfg_w": CFG_W, + # c(t_k, y) = t_mlp(timestep_embedding(t_k)) + y_emb(y), indexed [class][step]. + "pf_cond": cond, + } diff --git a/model/verify_export.py b/model/verify_export.py new file mode 100644 index 00000000..755050bc --- /dev/null +++ b/model/verify_export.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +# Copyright 2026 Arm Limited and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 +"""Host-side checks for the pico-faces export in model.py. + + python model/verify_export.py --stage float --seed 3 --class 1 --w 4 --k 4 --out out/pf_float.png + python model/verify_export.py --stage fakequant --seed 3 --class 1 --w 4 --k 4 --out out/pf_fakequant.png + python model/verify_export.py --stage tosa + python model/verify_export.py --check-upstream /path/to/pico-faces + python model/verify_export.py --compare out/fvp_image.bin out/pf_fakequant.png + +--stage float samples with the float modules of model.py: the reference. +--stage fakequant samples with the quantize/dequantize graphs create_ai_layer.py + produces (convert_pt2e), i.e. what the NPU is expected to compute. +--stage tosa lowers both methods with the TOSA backend and executes one + input each through the TOSA reference model: the integer + semantics the Ethos-U implements (tables, rescales, int48 + accumulators), which the fake-quant graphs do not model. A + large deviation from fakequant here means the NPU output will + be wrong too; run it before flashing a changed 16-bit graph. +--check-upstream compares model.py's re-implementation (folded scales, conv + patchify, BatchNorm and latent folds) against the original + pico-faces modules from a clone of the upstream repository. +--compare PSNR between two images: PNGs, or the raw 128x128 RGB frame + the FVP run writes to out/fvp_image.bin. + +The host stages use the firmware's noise generator, so the same seed, class, +guidance and step count give the image the board and the FVP produce (up to +the rounding differences between the fake-quant graphs and the NPU). + +Runs itself in the solution's .venv, like create_ai_layer.py. +""" + +from __future__ import annotations + +import argparse +import math +import os +import subprocess +import sys +import time +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parent + + +def run_in_venv(required: bool) -> None: + """Re-run under the project's .venv unless this interpreter already is it. + + Only --compare works without it (numpy and pillow are enough).""" + venv = ROOT / ".venv" + if Path(sys.prefix).resolve() == venv.resolve(): + return + python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + if not python.is_file(): + if not required: + return + sys.exit(f"{venv} does not exist. Create it first: ./setup_venv.sh (Linux/macOS) or setup_venv.bat (Windows)") + sys.exit(subprocess.run([str(python), __file__, *sys.argv[1:]]).returncode) + + +def save_png(img_u8, path: Path) -> None: + from PIL import Image + + path.parent.mkdir(parents=True, exist_ok=True) + Image.fromarray(img_u8, "RGB" if img_u8.shape[-1] == 3 else "L").save(path) + print(f"wrote {path}") + + +def stage_modules(stage: str, mlops_file: str | None): + """(dit_step callable, decode callable) for the requested stage.""" + import model as M + + if stage == "float": + return M.DiTStep.from_checkpoint(), M.Decoder.from_checkpoint() + + sys.path.insert(0, str(ROOT)) + import create_ai_layer as C + + spec = C.compile_spec_from_file(mlops_file) if mlops_file else C.default_compile_spec() + mods = {m.name: C.quantize_method(m, spec) for m in M.get_methods()} + return mods["dit_step"], mods["decode"] + + +def sample(args) -> None: + import model as M + + dit, dec = stage_modules(args.stage, args.mlops) + cond = M.Cond.from_checkpoint().cond_table() + z = M.noise(args.seed, *M.example_inputs()[0].shape[1:3]) + t0 = time.time() + z0 = M.euler_sample(dit, cond, z, args.cls, args.w, args.k) + img = M.to_image(dec(z0)) + print(f"{args.stage}: seed {args.seed} class {args.cls} w {args.w} k {args.k}: {time.time() - t0:.1f} s") + save_png(img, Path(args.out)) + + +def check_tosa(args) -> None: + """Run both methods through the TOSA reference model and compare with fake-quant and float.""" + import torch + from executorch.backends.arm.quantizer import TOSAQuantizer + from executorch.backends.arm.test.runner_utils import TosaReferenceModelDispatch + from executorch.backends.arm.tosa.compile_spec import TosaCompileSpec + from executorch.backends.arm.tosa.partitioner import TOSAPartitioner + from executorch.exir import EdgeCompileConfig, to_edge_transform_and_lower + from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e + + import model as M + + sys.path.insert(0, str(ROOT)) + import create_ai_layer as C + + z = M.noise(args.seed, *M.example_inputs()[0].shape[1:3]) + cond = M.Cond.from_checkpoint().cond_table() + floats = {"dit_step": M.DiTStep.from_checkpoint(), "decode": M.Decoder.from_checkpoint()} + worst = 0.0 + for m in M.get_methods(): + spec = TosaCompileSpec("TOSA-1.0+INT+int16" if m.quantization == "a16w8" else "TOSA-1.0+INT") + cfg = C.quant_config(m.quantization) + graph = torch.export.export(m.module, m.example_inputs).module() + C.strip_guards_fn(graph) + quantizer = TOSAQuantizer(spec) + quantizer.set_global(cfg) + prepared = prepare_pt2e(graph, quantizer) + C.calibrate(prepared, m) + converted = convert_pt2e(prepared) + edge = to_edge_transform_and_lower( + torch.export.export(converted, m.example_inputs), + partitioner=[TOSAPartitioner(spec)], + compile_config=EdgeCompileConfig(_check_ir_validity=False), + ) + program = edge.to_executorch() + inputs = (z, cond[args.cls, 0][None]) if m.name == "dit_step" else (z,) + with torch.no_grad(): + fq = converted(*inputs) + fl = floats[m.name](*inputs) + with TosaReferenceModelDispatch(): + ref = program.exported_program().module()(*inputs) + ref = ref[0] if isinstance(ref, (list, tuple)) else ref + d_fq, d_fl = (ref - fq).abs().max().item(), (ref - fl).abs().max().item() + worst = max(worst, d_fq / max(fl.abs().max().item(), 1e-6)) + print(f" {m.name}: refmodel-vs-fakequant max {d_fq:.4f}, refmodel-vs-float max {d_fl:.4f}, " + f"fakequant-vs-float max {(fq - fl).abs().max().item():.4f}, |float| max {fl.abs().max().item():.2f}") + ok = worst <= 0.05 + print(f"tosa: worst relative deviation from fakequant {worst:.3f} -> {'OK' if ok else 'MISMATCH'}") + sys.exit(0 if ok else 1) + + +def check_upstream(clone: str) -> None: + import torch + + import model as M + + sys.path.insert(0, clone) + from train.common.sincos import timestep_embedding + from train.dit.model import build_model + from train.vae.model import build_vae + + ck = M.load_checkpoint() + up = build_model(ck.dit_cfg).eval() + up.load_state_dict(ck.dit) + vae_ckpt = torch.load(M.checkpoint_dir() / "vae_final.pt", map_location="cpu", weights_only=False) + up_vae = build_vae(vae_ckpt["cfg"]).eval() + up_vae.load_state_dict(vae_ckpt["model"]) + + ours, cond, dec = M.DiTStep.from_checkpoint(), M.Cond.from_checkpoint(), M.Decoder.from_checkpoint() + g = torch.Generator().manual_seed(1) + worst = 0.0 + with torch.no_grad(): + for i in range(8): + z = torch.randn(1, ours.z_ch, ours.z_hw, ours.z_hw, generator=g) * (1.0 + i / 4) + t = torch.rand(1, generator=g) + y = torch.tensor([i % (M.N_CLASSES + 1)]) + c_up = up.t_mlp(timestep_embedding(t, up.t_dim)) + up.y_emb(y) + c_ours = cond(t, y) + d_c = (c_up - c_ours).abs().max().item() + v_up = up(z, t, y) + v_ours = ours(z, c_ours) + d_v = (v_up - v_ours).abs().max().item() / max(v_up.abs().max().item(), 1e-6) + worst = max(worst, d_c, d_v) + print(f" sample {i}: |dc| {d_c:.2e} rel |dv| {d_v:.2e}") + z = torch.randn(1, ours.z_ch, ours.z_hw, ours.z_hw, generator=g) + img_up = up_vae.decoder(z * ck.latent_std[None, :, None, None] + ck.latent_mean[None, :, None, None]).clamp(-1, 1) + img_ours = dec(z) + d_img = (img_up - img_ours).abs().max().item() + worst = max(worst, d_img) + print(f" decoder: |dimg| {d_img:.2e} (max pixel step 7.8e-3)") + # The DiT and conditioning must match to float precision; the decoder folds the + # latent mean into the zero-padded first conv (as upstream does), which shifts + # border pixels by about one 8-bit step. + ok = worst <= 2e-2 + print(f"check-upstream: worst deviation {worst:.2e} -> {'OK' if ok else 'MISMATCH'}") + sys.exit(0 if ok else 1) + + +def load_image(path: str): + """An image as float HWC RGB: a PNG, or a raw 128x128x3 frame (.bin).""" + import numpy as np + from PIL import Image + + if path.endswith(".bin"): + return np.fromfile(path, dtype=np.uint8).reshape(128, 128, 3).astype(np.float64) + return np.asarray(Image.open(path).convert("RGB"), dtype=np.float64) + + +def compare(a: str, b: str, min_psnr: float | None) -> None: + import numpy as np + + x, y = load_image(a), load_image(b) + if x.shape != y.shape: + sys.exit(f"shape mismatch: {x.shape} vs {y.shape}") + mse = ((x - y) ** 2).mean() + psnr = math.inf if mse == 0 else 10 * math.log10(255.0**2 / mse) + print(f"PSNR {psnr:.2f} dB, max |diff| {int(np.abs(x - y).max())}, mean |diff| {np.abs(x - y).mean():.2f}") + if min_psnr is not None and psnr < min_psnr: + sys.exit(f"PSNR below {min_psnr} dB") + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--stage", choices=["float", "fakequant", "tosa"], default="float") + ap.add_argument("--seed", type=int, default=3) + ap.add_argument("--class", dest="cls", type=int, default=1, help="0..3, or 4 = unconditional") + ap.add_argument("--w", type=float, default=4.0, help="guidance strength, 0 = plain") + ap.add_argument("--k", type=int, default=4, help="Euler steps: 8, 4, 2 or 1") + ap.add_argument("--out", default="out/pf.png") + ap.add_argument("--mlops", help="*.cbuild-mlops.yml to take the Vela options from (fakequant)") + ap.add_argument("--check-upstream", metavar="CLONE", help="path to a pico-faces clone") + ap.add_argument("--compare", nargs=2, metavar=("A", "B"), help="two images: .png, or a raw 128x128 RGB .bin") + ap.add_argument("--min-psnr", type=float, help="with --compare: fail below this PSNR (dB)") + args = ap.parse_args() + run_in_venv(required=not args.compare) + + sys.path.insert(0, str(HERE)) + if args.check_upstream: + check_upstream(args.check_upstream) + elif args.compare: + compare(*args.compare, args.min_psnr) + elif args.stage == "tosa": + check_tosa(args) + else: + sample(args) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index 1fd6d11f..06cecade 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,13 +3,23 @@ # # executorch, torch and torchao must match: a .pte exported by one ExecuTorch # version only loads in the runtime of the same version, and that runtime is -# the PyTorch::ExecuTorch@1.4.1 pack pinned in the csolution. ExecuTorch 1.4.1 -# targets torch 2.13.0 and torchao 0.18.0 (install_requirements.py on the -# release/1.4 branch); prebuilt wheels exist for Linux, macOS and Windows, -# Python 3.10 to 3.14 (the export venv stays below 3.14: see requirements-arm-tosa.txt). -executorch==1.4.1 -torch==2.13.0 +# the PyTorch::ExecuTorch@1.5.1 pack pinned in the csolution. ExecuTorch 1.5.1 +# targets torch 2.14.0 and torchao 0.18.0 (torch_pin.py and +# install_requirements.py of the v1.5.1 tag); prebuilt wheels exist for Linux, +# macOS and Windows, Python 3.10 to 3.14 (the export venv stays below 3.14: see +# requirements-arm-tosa.txt). +executorch==1.5.1 +torch==2.14.0 torchao==0.18.0 # Vela, the Ethos-U compiler. Same pin as executorch's `ethos_u` extra. ethos-u-vela==5.1.0 + +# Used directly by create_ai_layer.py and model/model.py (the cbuild-mlops.yml +# reader, the checkpoint and calibration code). executorch pulls both in as +# well; listing them keeps the dependency explicit. +numpy +pyyaml + +# Host-side checks only: model/verify_export.py reads and writes PNGs. +pillow diff --git a/setup_venv.bat b/setup_venv.bat index 73225e0c..c89e3180 100644 --- a/setup_venv.bat +++ b/setup_venv.bat @@ -9,7 +9,7 @@ REM and setup_venv.sh differ. With --uv, uv supplies the interpreter, e.g. REM setup_venv.bat --uv --python 3.12 setlocal set "SETUP_USE_UV=" -set "SETUP_UV_PYTHON=>=3.10,<3.15" +set "SETUP_UV_PYTHON=>=3.10,<3.14" REM SHIFT only changes numbered arguments; %* still forwards the original list. :scan_args if "%~1"=="" goto launch diff --git a/setup_venv.py b/setup_venv.py index 4e987584..3650b862 100755 --- a/setup_venv.py +++ b/setup_venv.py @@ -6,22 +6,45 @@ # # Runs on Linux, macOS and Windows. The thin wrappers setup_venv.sh and # setup_venv.bat just delegate here; everything OS-specific lives in this file. -"""Create (or repair) the .venv used to export the model.""" +"""Create (or repair) the .venv used to export the model, and fetch the model checkpoints.""" from __future__ import annotations import argparse +import hashlib import os import re import shutil import subprocess import sys +import urllib.error +import urllib.request import venv from pathlib import Path HERE = Path(__file__).resolve().parent VENV_DIR = HERE / ".venv" +# The pico-faces checkpoints model/model.py loads (https://github.com/cpldcpu/pico-faces, +# MIT license, see model/LICENSE-pico-faces). They are downloaded from a pinned +# upstream commit and verified by SHA-256 into model/pico_faces//, or read +# from $PICO_FACES_DIR/checkpoints// when that variable points at a clone. +PICO_FACES_REPO = "cpldcpu/pico-faces" +PICO_FACES_COMMIT = "ee15d9d183d8428efaeb3078edf47669cfc23994" # 2026-09-12, adds the LICENSE +PICO_FACES_DIR = HERE / "model" / "pico_faces" +PICO_FACES_FILES: dict[str, dict[str, str]] = { + "m3_long_cfg": { + "dit_qat.pt": "d645192896db72e71905d65d465e9c08384502336f9d02811d476743efac651a", + "vae_final.pt": "273d0d1b1784dec8d122642fd6dc028fc1427763f4b0fcbfdb86853621e46646", + "latent_stats.npz": "3cb07560a18a3e7a4b448e99c4ff00cf531cf51fe8d0ce2e97ab00f2b4113470", + }, + "m3_decD_deep_full": { + "dit_qat.pt": "430ca1ef03c52008e46e477ecdbbcf47d61b8588ebee1556e955bfe52c1905e3", + "vae_final.pt": "3dcd8e09a8717f5011373c77a64e18bd83cfa493a7a6cda6a79d56d7034ef3db", + "latent_stats.npz": "3cb07560a18a3e7a4b448e99c4ff00cf531cf51fe8d0ce2e97ab00f2b4113470", + }, +} + # ExecuTorch 1.4 declares requires-python = ">=3.10,<3.15" in its pyproject.toml, # but the tosa-tools 2026.5.0 it pins for the Arm backend (see # requirements-arm-tosa.txt) only ships wheels up to CPython 3.13, so on 3.14 @@ -137,6 +160,62 @@ def smoke_test(python: Path) -> None: ) +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def download(url: str, dest: Path) -> None: + """Fetch url into dest with urllib; fall back to curl when the interpreter has + no usable root certificates (the python.org macOS build until its + "Install Certificates" step has been run).""" + try: + with urllib.request.urlopen(url) as response, dest.open("wb") as out: + shutil.copyfileobj(response, out) + return + except urllib.error.URLError as exc: + if "CERTIFICATE_VERIFY_FAILED" not in str(exc) or shutil.which("curl") is None: + raise + print(" (urllib has no root certificates; using curl)", flush=True) + subprocess.run(["curl", "-fsSL", "--retry", "3", "-o", str(dest), url], check=True) + + +def fetch_checkpoints(variant: str) -> None: + """Download the pico-faces checkpoints of `variant` into model/pico_faces//. + + Files already present with the expected SHA-256 are kept; a download with + another hash fails the setup. + """ + if os.environ.get("PICO_FACES_DIR"): + print(f"PICO_FACES_DIR is set ({os.environ['PICO_FACES_DIR']}); not downloading checkpoints.") + return + + target = PICO_FACES_DIR / variant + target.mkdir(parents=True, exist_ok=True) + base = f"https://raw.githubusercontent.com/{PICO_FACES_REPO}/{PICO_FACES_COMMIT}/checkpoints/{variant}/" + for name, expected in PICO_FACES_FILES[variant].items(): + path = target / name + if path.is_file() and sha256(path) == expected: + print(f"checkpoint ok: {path.relative_to(HERE)}") + continue + url = base + name + print(f"downloading {url}", flush=True) + tmp = path.with_suffix(path.suffix + ".part") + download(url, tmp) + actual = sha256(tmp) + if actual != expected: + tmp.unlink(missing_ok=True) + sys.exit( + f"error: {name} from {url} has SHA-256 {actual}, expected {expected}.\n" + "The pinned upstream file changed or the download was corrupted." + ) + tmp.replace(path) + print(f"checkpoint ok: {path.relative_to(HERE)} ({path.stat().st_size / 1e6:.1f} MB)") + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( @@ -158,8 +237,28 @@ def main() -> int: action="store_true", help="delete and rebuild .venv even if it looks usable", ) + parser.add_argument( + "--variant", + default=os.environ.get("PICO_FACES_VARIANT", "m3_long_cfg"), + choices=sorted(PICO_FACES_FILES), + help="pico-faces model variant whose checkpoints to download (default: %(default)s)", + ) + parser.add_argument( + "--skip-download", + action="store_true", + help="do not download the pico-faces checkpoints", + ) + parser.add_argument( + "--download-only", + action="store_true", + help="only download the checkpoints; leave the venv alone", + ) args = parser.parse_args() + if args.download_only: + fetch_checkpoints(args.variant) + return 0 + if args.python and not args.uv: parser.error("--python requires --uv") uv = shutil.which("uv") if args.uv else None @@ -215,6 +314,9 @@ def main() -> int: smoke_test(python) + if not args.skip_download: + fetch_checkpoints(args.variant) + print() print(f"venv ready: {VENV_DIR}") print("create_ai_layer.py runs itself with this interpreter:") diff --git a/src/app_main.cpp b/src/app_main.cpp index 54d1e758..b5b16f59 100644 --- a/src/app_main.cpp +++ b/src/app_main.cpp @@ -1,29 +1,59 @@ // Copyright 2026 Arm Limited and/or its affiliates. // SPDX-License-Identifier: Apache-2.0 // -// Headless ExecuTorch runner for the Ethos-U85 example. Loads the .pte -// embedded by the AI layer (model_pte.h, see create_ai_layer.py), runs one -// inference on the NPU, and prints the output logits. Built entirely from the -// PyTorch::ExecuTorch pack's runtime + operator components; the board layer -// provides main(), stdout and the Ethos-U driver init and then calls app_main. +// pico-faces on Ethos-U85: a face generator built on the ExecuTorch runtime. +// The AI layer (create_ai_layer.py) embeds one program with two methods +// delegated to the NPU: +// +// dit_step(z, c) -> v the diffusion transformer's velocity for one step +// decode(z) -> img the latent decoder, 128x128 RGB in [-1, 1] +// +// and model_params.h with the sampling schedule and the conditioning vectors +// c(t_k, class). This file runs the rectified-flow Euler loop (with optional +// classifier-free guidance) around those methods, converts the image to 8-bit +// RGB and prints a CRC and an ASCII preview. With APP_INTERACTIVE (the +// DevKit-E8 board layer) it then serves pico-faces' serial protocol, so that +// pico-faces' viewer (viewer/view_serial.py) can request and display images: +// +// host -> "G [k_steps] [class] [w]\n" "I\n" prints an info line +// dev -> "RFI2" u32 seed u16 w u16 h u16 ch u16 class | w*h*ch bytes (HWC) +// | u32 crc32 | u32 gen_ms +// +// The board layer provides main(), stdout/stdin and the Ethos-U driver init and +// then calls app_main(). // // EmbeddedModule (arm_embedded_module.hpp) manages program loading, method // memory and execution, like ExecuTorch's Module class does on POSIX hosts. #include +#include #include #include +#include +#include +#include #include +#include + +#include "RTE_Components.h" +#include CMSIS_device_header #include #include #include #include +#include #include #include "arm_embedded_module.hpp" +#include "model_params.h" #include "model_pte.h" +#if defined(ETHOSU_ARCH) +#include "ethosu_driver.h" +#include "pmu_ethosu.h" +#endif + using arm::embedded::EmbeddedModule; using executorch::aten::DimOrderType; using executorch::aten::ScalarType; @@ -31,8 +61,34 @@ using executorch::aten::SizesType; using executorch::aten::Tensor; using executorch::aten::TensorImpl; using executorch::extension::BufferDataLoader; +using executorch::runtime::Error; using executorch::runtime::EValue; using executorch::runtime::MemoryAllocator; +using executorch::runtime::MethodMeta; +using executorch::runtime::Result; +using executorch::runtime::Span; +using executorch::runtime::TensorInfo; + +extern "C" int stdout_putchar(int ch); // CMSIS-Compiler retarget hooks +extern "C" int stdin_getchar(void); +#ifdef APP_BUTTONS +// Board layer (board/DevKit-E8/buttons.c): SW2 joystick presses since the last +// call (bit mask), and a console read that does not block (-1 when nothing is +// waiting). +extern "C" unsigned board_buttons(void); +extern "C" int board_console_poll(void); +constexpr unsigned kButtonLeft = 1u << 0; +constexpr unsigned kButtonRight = 1u << 1; +#endif +#ifdef APP_DISPLAY +// Board layer (board/DevKit-E8/display.c): show an 8-bit RGB image on the +// board's display. Returns 0 on success. +extern "C" int board_display_image(const uint8_t* rgb, int width, int height, int channels); +#endif +#ifdef APP_RESULT_DIR +// Board layer (board/Corstone-320/retarget_stdio.c): write a file on the host. +extern "C" int board_save_file(const char* path, const void* data, size_t n); +#endif namespace { @@ -52,62 +108,604 @@ namespace { #define APP_POOL_ATTRIBUTES #endif +// The boot demo: seed 3, 4 steps, class 1 (female, smiling), guidance 4. +#ifndef APP_DEMO_SEED +#define APP_DEMO_SEED 3 +#endif +#ifndef APP_DEMO_STEPS +#define APP_DEMO_STEPS 4 +#endif +#ifndef APP_DEMO_CLASS +#define APP_DEMO_CLASS 1 +#endif +#ifndef APP_DEMO_W +#define APP_DEMO_W 4.0f +#endif + constexpr size_t kMethodPoolSize = APP_METHOD_POOL_SIZE; constexpr size_t kTempPoolSize = APP_TEMP_POOL_SIZE; +constexpr size_t kLatent = PF_LATENT_CH * PF_LATENT_HW * PF_LATENT_HW; +constexpr size_t kImage = PF_IMG_HW * PF_IMG_HW * PF_IMG_CH; +constexpr const char* kDitStep = "dit_step"; +constexpr const char* kDecode = "decode"; alignas(16) uint8_t g_method_pool[kMethodPoolSize] APP_POOL_ATTRIBUTES; alignas(16) uint8_t g_temp_pool[kTempPoolSize] APP_POOL_ATTRIBUTES; +// Generation state; static so the 32 kB main stack stays small. +float g_z[kLatent]; +float g_v[kLatent]; +float g_vn[kLatent]; +float g_c[PF_COND_DIM]; +uint8_t g_img[kImage]; + +// The method inputs wrap g_z and g_c; EmbeddedModule copies them into the +// method's own input buffers on every call. +std::array g_latent_sizes{1, PF_LATENT_CH, PF_LATENT_HW, PF_LATENT_HW}; +std::array g_latent_order{0, 1, 2, 3}; +std::array g_cond_sizes{1, PF_COND_DIM}; +std::array g_cond_order{0, 1}; +TensorImpl g_z_impl(ScalarType::Float, g_latent_sizes.size(), g_latent_sizes.data(), g_z, g_latent_order.data()); +TensorImpl g_c_impl(ScalarType::Float, g_cond_sizes.size(), g_cond_sizes.data(), g_c, g_cond_order.data()); + +// Per-method NPU counters from the Ethos-U PMU: cycles the NPU was clocked +// (CCNT), cycles it was active, cycles its MAC array was active, and data beats +// read on the two AXI ports (on the Ensemble E8 all traffic, weights from MRAM +// included, arrives on port 0). +struct NpuCounters { + uint64_t cycles; + uint64_t active; + uint64_t mac_active; + uint64_t axi0_read_beats; + uint64_t axi1_read_beats; +}; + +struct Timing { + uint32_t dit_ms; + uint32_t dit_calls; + uint32_t decode_ms; + uint32_t total_ms; + NpuCounters dit_npu; + NpuCounters decode_npu; +}; + +// --------------------------------------------------------------------------- +// Ethos-U PMU: the driver instance belongs to the board layer; the driver's +// reserve/release API hands it out between inferences. + +#if defined(ETHOSU_ARCH) +constexpr uint32_t kPmuCounters = ETHOSU_PMU_CCNT_Msk | ETHOSU_PMU_CNT1_Msk | ETHOSU_PMU_CNT2_Msk | + ETHOSU_PMU_CNT3_Msk | ETHOSU_PMU_CNT4_Msk; + +void npu_pmu_start() { + ethosu_driver* drv = ethosu_reserve_driver(); + ETHOSU_PMU_Enable(drv); + ETHOSU_PMU_Set_EVTYPER(drv, 0, ETHOSU_PMU_NPU_ACTIVE); + ETHOSU_PMU_Set_EVTYPER(drv, 1, ETHOSU_PMU_MAC_ACTIVE); + ETHOSU_PMU_Set_EVTYPER(drv, 2, ETHOSU_PMU_EXT0_RD_DATA_BEAT_RECEIVED); + ETHOSU_PMU_Set_EVTYPER(drv, 3, ETHOSU_PMU_EXT1_RD_DATA_BEAT_RECEIVED); + ETHOSU_PMU_CYCCNT_Reset(drv); + ETHOSU_PMU_EVCNTR_ALL_Reset(drv); + ETHOSU_PMU_CNTR_Enable(drv, kPmuCounters); + ethosu_release_driver(drv); +} + +void npu_pmu_stop(NpuCounters& c) { + ethosu_driver* drv = ethosu_reserve_driver(); + ETHOSU_PMU_CNTR_Disable(drv, kPmuCounters); + c.cycles += ETHOSU_PMU_Get_CCNTR(drv); + c.active += ETHOSU_PMU_Get_EVCNTR(drv, 0); + c.mac_active += ETHOSU_PMU_Get_EVCNTR(drv, 1); + c.axi0_read_beats += ETHOSU_PMU_Get_EVCNTR(drv, 2); + c.axi1_read_beats += ETHOSU_PMU_Get_EVCNTR(drv, 3); + ethosu_release_driver(drv); +} +#else +void npu_pmu_start() {} +void npu_pmu_stop(NpuCounters&) {} +#endif + +// --------------------------------------------------------------------------- +// Millisecond tick from SysTick, for per-phase timing (the DWT cycle counter +// does not count on every device). Not meaningful on the FVP. + +volatile uint32_t g_ms_ticks; + +void ticks_init() { + SysTick_Config(SystemCoreClock / 1000u); +} + +inline uint32_t ms_now() { return g_ms_ticks; } + +// --------------------------------------------------------------------------- +// PCG32 + CLT-12 gaussian, as pico-faces' engine/src/prng.c. The noise is +// filled in CHW order (upstream fills in token order), so a seed gives other +// faces than on the Pico, but the same as model/verify_export.py on the host. + +uint64_t g_pcg_state; + +void pcg32_seed(uint64_t seed) { + const uint64_t mult = 6364136223846793005ULL, inc = 1442695040888963407ULL; + g_pcg_state = 0; + g_pcg_state = g_pcg_state * mult + inc; + g_pcg_state += seed; + g_pcg_state = g_pcg_state * mult + inc; +} + +uint32_t pcg32_next() { + const uint64_t mult = 6364136223846793005ULL, inc = 1442695040888963407ULL; + uint64_t x = g_pcg_state; + unsigned count = static_cast(x >> 59); + g_pcg_state = x * mult + inc; + x ^= x >> 18; + uint32_t out = static_cast(x >> 27); + return (out >> count) | (out << ((32 - count) & 31)); +} + +float gauss() { + int32_t acc = 0; + for (int j = 0; j < 12; ++j) { + acc += static_cast(pcg32_next() >> 20); + } + return static_cast(acc - 24576) / 4096.0f; +} + +// --------------------------------------------------------------------------- +// CRC-32 (IEEE, reflected), the same as zlib / Python's binascii.crc32. + +uint32_t g_crc_table[256]; + +void crc32_init() { + for (uint32_t i = 0; i < 256; ++i) { + uint32_t c = i; + for (int k = 0; k < 8; ++k) { + c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1); + } + g_crc_table[i] = c; + } +} + +uint32_t crc32(const uint8_t* data, size_t n) { + uint32_t c = 0xFFFFFFFFu; + for (size_t i = 0; i < n; ++i) { + c = g_crc_table[(c ^ data[i]) & 0xFFu] ^ (c >> 8); + } + return c ^ 0xFFFFFFFFu; +} + +// --------------------------------------------------------------------------- +// Methods + +bool check_shape(const TensorInfo& info, const char* what, std::initializer_list expect) { + Span sizes = info.sizes(); + bool ok = sizes.size() == expect.size(); + size_t i = 0; + for (int32_t d : expect) { + ok = ok && sizes[i++] == d; + } + if (!ok) { + printf("%s: unexpected shape [", what); + for (size_t j = 0; j < sizes.size(); ++j) { + printf("%s%d", j ? "," : "", static_cast(sizes[j])); + } + printf("], model_params.h says ["); + i = 0; + for (int32_t d : expect) { + printf("%s%d", i++ ? "," : "", static_cast(d)); + } + printf("]\n"); + } + return ok; +} + +// Loads a method up front (EmbeddedModule would do it on the first call) and +// prints what it needs. +bool load_method(EmbeddedModule& module, const char* name) { + Result meta = module.method_meta(name); + if (!meta.ok()) { + printf("method %s not found in the program (err=%u)\n", name, static_cast(meta.error())); + return false; + } + Error err = module.load_method(name); + if (err != Error::Ok) { + printf("load_method(%s) failed (err=%u)\n", name, static_cast(err)); + return false; + } + size_t planned_bytes = 0; + for (size_t i = 0; i < meta->num_memory_planned_buffers(); ++i) { + planned_bytes += static_cast(meta->memory_planned_buffer_size(i).get()); + } + printf(" %-9s %u input(s), %u output(s), %u planned byte(s)\n", name, + static_cast(meta->num_inputs()), static_cast(meta->num_outputs()), + static_cast(planned_bytes)); + return true; +} + +// Executes a method; returns its first output as floats, or nullptr on failure. +// The data stays valid until the method runs again. +const float* run(EmbeddedModule& module, const char* name, const std::vector& inputs) { + Result> outputs = module.execute(name, inputs); + if (!outputs.ok()) { + printf("%s: execute failed (err=%u)\n", name, static_cast(outputs.error())); + return nullptr; + } + return outputs->at(0).toTensor().const_data_ptr(); +} + +// v = dit_step(g_z, g_c) +bool dit_step(EmbeddedModule& module, float* v, Timing& tm) { + uint32_t t0 = ms_now(); + npu_pmu_start(); + const float* out = run(module, kDitStep, {EValue(Tensor(&g_z_impl)), EValue(Tensor(&g_c_impl))}); + if (out == nullptr) { + return false; + } + npu_pmu_stop(tm.dit_npu); + tm.dit_ms += ms_now() - t0; + tm.dit_calls++; + memcpy(v, out, kLatent * sizeof(float)); + return true; +} + +// img (HWC uint8) = decode(g_z) +bool decode(EmbeddedModule& module, uint8_t* img, Timing& tm) { + uint32_t t0 = ms_now(); + npu_pmu_start(); + const float* out = run(module, kDecode, {EValue(Tensor(&g_z_impl))}); // (1, C, H, W) in [-1, 1] + if (out == nullptr) { + return false; + } + npu_pmu_stop(tm.decode_npu); + tm.decode_ms += ms_now() - t0; + const size_t plane = PF_IMG_HW * PF_IMG_HW; + for (size_t p = 0; p < plane; ++p) { + for (size_t ch = 0; ch < PF_IMG_CH; ++ch) { + float x = out[ch * plane + p]; + x = x < -1.0f ? -1.0f : (x > 1.0f ? 1.0f : x); + img[p * PF_IMG_CH + ch] = static_cast(lrintf((x + 1.0f) * 127.5f)); + } + } + return true; +} + +// The rectified-flow Euler sampler: z1 ~ N(0, 1) at t = 1 down to t = 0, then +// decode. k_steps (8, 4, 2 or 1) strides through the PF_K_MAX schedule points. +bool generate(EmbeddedModule& module, uint32_t seed, int k_steps, int cls, float w, uint8_t* img, Timing& tm) { + memset(&tm, 0, sizeof(tm)); + uint32_t t_start = ms_now(); + if (k_steps != 1 && k_steps != 2 && k_steps != 4 && k_steps != 8) { + k_steps = 4; + } + if (cls < 0 || cls >= PF_N_COND) { + cls = PF_NULL_CLASS; + } + const bool guided = cls != PF_NULL_CLASS && w > 0.0f; + const int stride = PF_K_MAX / k_steps; + + pcg32_seed(seed); + for (size_t i = 0; i < kLatent; ++i) { + g_z[i] = gauss(); + } + + for (int i = 0; i < k_steps; ++i) { + const int k = i * stride; + const float t = pf_schedule[k]; + const float t_next = (i + 1 < k_steps) ? pf_schedule[k + stride] : 0.0f; + const float dt = t - t_next; + + memcpy(g_c, pf_cond[cls][k], sizeof(g_c)); + if (!dit_step(module, g_v, tm)) { + return false; + } + if (guided) { + memcpy(g_c, pf_cond[PF_NULL_CLASS][k], sizeof(g_c)); + if (!dit_step(module, g_vn, tm)) { + return false; + } + for (size_t j = 0; j < kLatent; ++j) { // v = v_null + w * (v_cond - v_null) + g_v[j] = g_vn[j] + w * (g_v[j] - g_vn[j]); + } + } + for (size_t j = 0; j < kLatent; ++j) { + g_z[j] -= dt * g_v[j]; + } + } + + if (!decode(module, img, tm)) { + return false; + } + tm.total_ms = ms_now() - t_start; +#ifdef APP_DISPLAY + board_display_image(img, PF_IMG_HW, PF_IMG_HW, PF_IMG_CH); +#endif + return true; +} + +// 32x16 characters from the luminance of 4x8 pixel blocks. +void ascii_preview(const uint8_t* img) { + static const char ramp[] = " .:-=+*#%@"; + const int cols = 32, rows = 16; + const int bw = PF_IMG_HW / cols, bh = PF_IMG_HW / rows; + for (int r = 0; r < rows; ++r) { + char line[cols + 1]; + for (int c = 0; c < cols; ++c) { + uint32_t sum = 0; + for (int y = r * bh; y < (r + 1) * bh; ++y) { + for (int x = c * bw; x < (c + 1) * bw; ++x) { + const uint8_t* px = img + (y * PF_IMG_HW + x) * PF_IMG_CH; + sum += PF_IMG_CH == 3 ? (299u * px[0] + 587u * px[1] + 114u * px[2]) / 1000u : px[0]; + } + } + unsigned lum = sum / (bw * bh); + line[c] = ramp[(lum * (sizeof(ramp) - 2)) / 255u]; + } + line[cols] = 0; + printf(" |%s|\n", line); + } +} + +void print_npu(const char* name, const NpuCounters& c, uint32_t calls, uint64_t macs_per_call) { + if (c.cycles == 0) { + return; + } + const uint64_t macs = macs_per_call * calls; + // 16-byte AXI beats on the Ethos-U85 + const unsigned kb0 = static_cast(c.axi0_read_beats * 16 / 1024); + const unsigned kb1 = static_cast(c.axi1_read_beats * 16 / 1024); + printf(" %-9s NPU %u kcycles, active %u%%, MAC active %u%%, %u MAC/cycle, " + "read %u kB on AXI0 + %u kB on AXI1\n", + name, static_cast(c.cycles / 1000), static_cast(c.active * 100 / c.cycles), + static_cast(c.mac_active * 100 / c.cycles), static_cast(macs / c.cycles), kb0, kb1); +} + +void print_timing(const Timing& tm) { + printf(" dit_step: %u call(s), %u ms total (%u ms each)\n", static_cast(tm.dit_calls), + static_cast(tm.dit_ms), + static_cast(tm.dit_calls ? tm.dit_ms / tm.dit_calls : 0)); + printf(" decode: %u ms\n", static_cast(tm.decode_ms)); + printf(" total: %u ms at %u MHz (wall clock; not meaningful on the FVP)\n", + static_cast(tm.total_ms), static_cast(SystemCoreClock / 1000000u)); + print_npu("dit_step:", tm.dit_npu, tm.dit_calls, PF_MACS_DIT_STEP); + print_npu("decode:", tm.decode_npu, 1, PF_MACS_DECODE); +} + +#if defined(APP_INTERACTIVE) || defined(APP_BUTTONS) +// --------------------------------------------------------------------------- +// After the boot demo (board layers that define APP_INTERACTIVE or APP_BUTTONS) + +// Raw output: the image frames bypass stdio's text handling. +void uart_write(const void* data, size_t n) { + fflush(stdout); + const uint8_t* p = static_cast(data); + for (size_t i = 0; i < n; ++i) { + stdout_putchar(p[i]); + } +} + +void put_u32(uint32_t v) { + uint8_t b[4] = {static_cast(v), static_cast(v >> 8), + static_cast(v >> 16), static_cast(v >> 24)}; + uart_write(b, 4); +} + +void put_u16(uint16_t v) { + uint8_t b[2] = {static_cast(v), static_cast(v >> 8)}; + uart_write(b, 2); +} + +void send_frame(uint32_t seed, int cls, const uint8_t* img, uint32_t gen_ms) { + uart_write("RFI2", 4); + put_u32(seed); + put_u16(PF_IMG_HW); + put_u16(PF_IMG_HW); + put_u16(PF_IMG_CH); + put_u16(static_cast(cls)); + uart_write(img, kImage); + put_u32(crc32(img, kImage)); + put_u32(gen_ms); +} + +// pico-faces' firmware conventions: class defaults to seed % PF_N_COND, and +// without class and w the guidance cycles through {plain, pf_cfg_w...}. +float default_w(uint32_t seed) { + const int w_idx = static_cast(seed % (PF_N_CFG_W + 1)) - 1; + return w_idx >= 0 ? pf_cfg_w[w_idx] : 0.0f; +} + +#ifdef APP_BUTTONS +// Every joystick press picks the next seed, with class and guidance as for a +// bare "G ". +uint32_t g_next_seed = APP_DEMO_SEED + 1; +uint32_t g_button_images = 0; +bool g_continuous = false; + +void generate_next(EmbeddedModule& module) { + const uint32_t seed = g_next_seed++; + const int cls = static_cast(seed % PF_N_COND); + const float w = default_w(seed); + Timing tm; + if (!generate(module, seed, APP_DEMO_STEPS, cls, w, g_img, tm)) { + printf("ERR generate\n"); + return; + } + g_button_images++; + printf("Image %u: seed %u, class %d, w %.1f, %u ms%s\n", static_cast(g_button_images), + static_cast(seed), cls, static_cast(w), static_cast(tm.total_ms), + g_continuous ? " (continuous)" : ""); +} +#endif + +// A console character without blocking when the board layer offers a poll +// (the retarget's stdin_getchar() waits for one, which would stall the joystick). +int console_getchar() { +#ifdef APP_BUTTONS + return board_console_poll(); +#else + return stdin_getchar(); +#endif +} + +// "G [k_steps] [class] [w]": generate and send one frame. +void serve_request(EmbeddedModule& module, const char* line) { + char *e1, *e2, *e3, *e4; + uint32_t seed = static_cast(strtoul(line + 1, &e1, 0)); + int k_steps = static_cast(strtol(e1, &e2, 0)); + if (!k_steps) { + k_steps = 4; + } + long cv = strtol(e2, &e3, 0); + int cls = (e3 != e2) ? static_cast(cv) : static_cast(seed % PF_N_COND); + float w = 0.0f; + long wv = strtol(e3, &e4, 0); + if (e4 != e3) { + w = static_cast(wv); + } else if (e3 == e2) { + w = default_w(seed); + } + Timing tm; + if (!generate(module, seed, k_steps, cls, w, g_img, tm)) { + printf("ERR generate\n"); + return; + } + if (cls < 0 || cls >= PF_N_COND) { + cls = PF_NULL_CLASS; + } + // Timing as text before the binary frame; the viewer syncs on the magic. + printf("Generated: seed %u, %u steps, class %u, w %.1f\n", static_cast(seed), + static_cast(k_steps), static_cast(cls), static_cast(w)); + print_timing(tm); + send_frame(seed, cls, g_img, tm.total_ms); +} + +// Main loop after the boot demo: the serial protocol (APP_INTERACTIVE) and the +// SW2 joystick (APP_BUTTONS: left = one new image, right = start or stop +// continuous generation). +[[noreturn]] void serve(EmbeddedModule& module) { +#ifdef APP_INTERACTIVE + printf("Interactive: send \"G [k_steps] [class] [w]\" (viewer/view_serial.py) or \"I\"\n"); +#endif +#ifdef APP_BUTTONS + printf("Joystick: left = one new image, right = start/stop continuous generation\n"); + (void)board_buttons(); // drop presses latched during boot +#endif + fflush(stdout); + char line[64]; + int n = 0; + for (;;) { +#ifdef APP_BUTTONS + const unsigned pressed = board_buttons(); + if (pressed & kButtonRight) { + g_continuous = !g_continuous; + printf("Continuous generation %s\n", g_continuous ? "started" : "stopped"); + } + if ((pressed & kButtonLeft) || g_continuous) { + generate_next(module); + } +#endif + int ch = console_getchar(); + if (ch < 0) { + continue; + } + if (ch != '\n' && ch != '\r') { + if (n < static_cast(sizeof line) - 1) { + line[n++] = static_cast(ch); + } + continue; + } + line[n] = 0; + n = 0; +#ifdef APP_INTERACTIVE + if (line[0] == 'G') { + serve_request(module, line); + } else if (line[0] == 'I') { + printf("pico-faces-executorch %s %s K=%u latent=%ux%ux%u img=%ux%ux%u cond=%u pte=%lu\n", + PF_VARIANT, PF_QUANT, static_cast(PF_K_MAX), PF_LATENT_CH, PF_LATENT_HW, + PF_LATENT_HW, PF_IMG_HW, PF_IMG_HW, PF_IMG_CH, static_cast(PF_N_COND), + model_pte_size); + fflush(stdout); + } +#endif + } +} +#endif + } // namespace +extern "C" void SysTick_Handler(void) { g_ms_ticks++; } + +// ExecuTorch's log sink (the runtime's default prints nothing): with +// ET_LOG_ENABLED the runtime's error messages reach the console, e.g. why +// load_method failed. +extern "C" void et_pal_emit_log_message(et_timestamp_t, et_pal_log_level_t level, const char* filename, + const char*, size_t line, const char* message, size_t) { + printf("[ET %c] %s:%u %s\n", static_cast(level), filename, static_cast(line), message); +} + extern "C" int app_main(void) { executorch::runtime::runtime_init(); + ticks_init(); + crc32_init(); - printf("ExecuTorch Ethos-U85 example: %lu byte model\n", model_pte_size); + printf("ExecuTorch pico-faces (%s, %s): %lu byte program\n", PF_VARIANT, PF_QUANT, model_pte_size); - EmbeddedModule module( + static EmbeddedModule module( model_pte, model_pte_size, std::make_unique(model_pte, model_pte_size), std::make_unique(kMethodPoolSize, g_method_pool), std::make_unique(kTempPoolSize, g_temp_pool)); - // TinyCNN takes one 16x16 RGB image in NCHW order (see model/model.py), - // filled here with a deterministic ramp. The tensor only wraps the buffer; - // no tensor extension (and its std::random_device) is needed for that. - alignas(16) static float input_data[3 * 16 * 16]; - for (size_t i = 0; i < sizeof(input_data) / sizeof(input_data[0]); ++i) { - input_data[i] = static_cast(i % 32) / 32.0f - 0.5f; - } - std::array sizes{1, 3, 16, 16}; - std::array dim_order{0, 1, 2, 3}; - TensorImpl input_impl(ScalarType::Float, sizes.size(), sizes.data(), input_data, dim_order.data()); - Tensor input(&input_impl); - - auto outputs = module.forward(input); - if (!outputs.ok()) { - printf("forward failed (err=%u)\n", static_cast(outputs.error())); - return 1; - } - if (outputs->empty()) { - printf("forward returned no outputs\n"); + printf("Methods:\n"); + if (!load_method(module, kDitStep) || !load_method(module, kDecode)) { return 1; } - - const EValue& out = outputs->front(); - if (out.isTensor()) { - Tensor t = out.toTensor(); - printf("Output: %u element(s):", static_cast(t.numel())); - if (t.scalar_type() == ScalarType::Float) { - const float* p = t.const_data_ptr(); - for (size_t i = 0; i < t.numel(); ++i) { - printf(" %.4f", p[i]); - } + { + MethodMeta dit = module.method_meta(kDitStep).get(); + MethodMeta dec = module.method_meta(kDecode).get(); + if (!check_shape(dit.input_tensor_meta(0).get(), "dit_step input z", {1, PF_LATENT_CH, PF_LATENT_HW, PF_LATENT_HW}) || + !check_shape(dit.input_tensor_meta(1).get(), "dit_step input c", {1, PF_COND_DIM}) || + !check_shape(dit.output_tensor_meta(0).get(), "dit_step output v", {1, PF_LATENT_CH, PF_LATENT_HW, PF_LATENT_HW}) || + !check_shape(dec.input_tensor_meta(0).get(), "decode input z", {1, PF_LATENT_CH, PF_LATENT_HW, PF_LATENT_HW}) || + !check_shape(dec.output_tensor_meta(0).get(), "decode output img", {1, PF_IMG_CH, PF_IMG_HW, PF_IMG_HW})) { + return 1; } - printf("\n"); } + // Boot demo: one image with fixed parameters. The CRC identifies the image: + // the same program gives the same CRC on every run and on the FVP. + Timing tm; + printf("Generating: seed %u, %u steps, class %u, w %.1f\n", static_cast(APP_DEMO_SEED), + static_cast(APP_DEMO_STEPS), static_cast(APP_DEMO_CLASS), + static_cast(APP_DEMO_W)); + fflush(stdout); + if (!generate(module, APP_DEMO_SEED, APP_DEMO_STEPS, APP_DEMO_CLASS, APP_DEMO_W, g_img, tm)) { + return 1; + } + print_timing(tm); + printf("Image: %ux%ux%u, CRC32 %08x\n", PF_IMG_HW, PF_IMG_HW, PF_IMG_CH, + static_cast(crc32(g_img, kImage))); + ascii_preview(g_img); +#ifdef APP_RESULT_DIR + // Simulation: hand the image and the measurements to the host through + // semihosting (model/verify_export.py --compare reads the image). + { + static char text[256]; + int n = snprintf(text, sizeof(text), + "seed %u steps %u class %u w %.1f crc32 %08x dit_cycles %u decode_cycles %u\n", + static_cast(APP_DEMO_SEED), static_cast(APP_DEMO_STEPS), + static_cast(APP_DEMO_CLASS), static_cast(APP_DEMO_W), + static_cast(crc32(g_img, kImage)), static_cast(tm.dit_npu.cycles), + static_cast(tm.decode_npu.cycles)); + bool ok = board_save_file(APP_RESULT_DIR "/fvp_image.bin", g_img, kImage) == 0 && + board_save_file(APP_RESULT_DIR "/fvp_result.txt", text, n > 0 ? static_cast(n) : 0) == 0; + printf("Result files in " APP_RESULT_DIR ": %s\n", ok ? "written" : "FAILED"); + } +#endif printf("Test_result: PASS\n"); - printf("\x04"); // EOT: ends the FVP run (semihosting exit); a board just sees a 0x04 + printf("\x04"); // EOT: the end marker for a console log fflush(stdout); + +#if defined(APP_INTERACTIVE) || defined(APP_BUTTONS) + serve(module); +#endif return 0; } diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index 68cb2129..45b31104 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -7,10 +7,10 @@ } ], "requires": { - "arm:tools/open-cmsis-pack/cmsis-toolbox": "2.14.1", - "arm:compilers/arm/arm-none-eabi-gcc": "14.3.1", - "arm:compilers/arm/llvm-embedded": "22.1.0", - "arm:tools/kitware/cmake": "4.2.1", + "arm:tools/open-cmsis-pack/cmsis-toolbox": "2.15.0", + "arm:compilers/arm/arm-none-eabi-gcc": "15.3.1", + "arm:compilers/arm/llvm-embedded": "23.1.0", + "arm:tools/kitware/cmake": "4.3.3", "arm:tools/ninja-build/ninja": "1.13.2", "arm:models/arm/avh-fvp": "11.32.23", "arm:compilers/arm/armclang": "6.24.0"