ci: add linux-x64-cpu (GGML_NATIVE=true) package #56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ci | |
| # Multi-platform build + oudep packaging for GAME-ggml CLI. | |
| # | |
| # Matrix: | |
| # linux-x64-vulkan — Ubuntu, Vulkan backend | |
| # linux-x64-cuda — Ubuntu 22.04, CUDA backend (CUDA 12.6) | |
| # macos-arm64-metal — Apple Silicon, Metal backend | |
| # macos-x64-metal — Intel Mac, Metal backend (cross-compiled on ARM runner) | |
| # windows-x64-vulkan — Windows, Vulkan backend | |
| # windows-x64-cuda — Windows Server 2022, CUDA backend (CUDA 12.6) | |
| # | |
| # On tag push, artifacts are bundled into a GitHub Release. | |
| on: | |
| push: | |
| branches: [main, master, 'feature/**'] | |
| tags: ['v*'] | |
| pull_request: {} | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| prepare-model: | |
| name: prepare medium GGUF | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| cache-dependency-path: scripts/requirements.txt | |
| - name: Install conversion dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r scripts/requirements.txt | |
| - name: Download and verify medium checkpoint | |
| run: | | |
| curl -fL --retry 5 --retry-all-errors \ | |
| -o GAME-1.0-medium.zip \ | |
| https://github.com/openvpi/GAME/releases/download/v1.0.0/GAME-1.0-medium.zip | |
| echo "8c5b3e531e2905b935e664e2f533921cd637243770fab5282413bdb5051ca60c GAME-1.0-medium.zip" \ | |
| | sha256sum --check --strict | |
| unzip -q GAME-1.0-medium.zip | |
| - name: Convert checkpoint and prepare backend config | |
| run: | | |
| mkdir -p model | |
| # Full-precision baseline GGUF (F32). Shipped in the "-full" | |
| # package variants; kept also as an in-CI size sanity reference | |
| # for the quantized check below. | |
| python scripts/convert_pt_to_gguf.py \ | |
| --model-dir GAME-1.0-medium \ | |
| -o model/game_medium.gguf | |
| # Quantized Q8_0 GGUF (recommended for CPU: ~3.4x smaller weights, | |
| # note-level near-lossless; on GPU backends the warm-path difference | |
| # vs F32 is small, so prefer the -full pack for GPU unless RAM-bound). | |
| # Hard constraints enforced by the converter: | |
| # depthwise convs -> F16, norms/scales/biases -> F32. | |
| # Shipped in the "-q8" package variants only. | |
| python scripts/convert_pt_to_gguf.py \ | |
| --model-dir GAME-1.0-medium \ | |
| -o model/game_medium_q8.gguf \ | |
| --quant-config scripts/quant_all_q8.json | |
| cat > model/config.json <<'EOF' | |
| { | |
| "samplerate": 44100, | |
| "timestep": 0.01, | |
| "languages": { | |
| "en": 1, | |
| "ja": 2, | |
| "yue": 3, | |
| "zh": 4 | |
| }, | |
| "loop": true | |
| } | |
| EOF | |
| - name: Verify model files | |
| run: | | |
| test -s model/game_medium.gguf | |
| test -s model/game_medium_q8.gguf | |
| # sanity: the quantized GGUF must be (much) smaller than FP32 | |
| test "$(stat -c%s model/game_medium_q8.gguf)" -lt "$(( $(stat -c%s model/game_medium.gguf) / 2 ))" | |
| python -m json.tool model/config.json >/dev/null | |
| ls -lh model/ | |
| - name: Upload model artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: game-ggml-medium-model | |
| path: model | |
| if-no-files-found: error | |
| retention-days: 7 | |
| build: | |
| name: ${{ matrix.name }} | |
| needs: prepare-model | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| name: linux-x64-vulkan | |
| backend: vulkan | |
| cmake_extra: "-DGAME_GGML_VULKAN=ON" | |
| build_jobs: 4 | |
| pkg_ext: "" | |
| lib_glob: "libggml*.so*" | |
| - os: ubuntu-latest | |
| name: linux-x64-cpu | |
| backend: cpu | |
| # CPU-only package built with -march=native for the target simd | |
| # (ggml then uses AVX2/AVX-512 etc. via GGML_NATIVE). | |
| cmake_extra: "-DGGML_NATIVE=ON" | |
| build_jobs: 4 | |
| pkg_ext: "" | |
| lib_glob: "libggml*.so*" | |
| - os: ubuntu-22.04 | |
| name: linux-x64-cuda | |
| backend: cuda | |
| cmake_extra: '-DGAME_GGML_CUDA=ON -DGGML_NATIVE=OFF -DCMAKE_CUDA_ARCHITECTURES=75' | |
| build_jobs: 2 | |
| pkg_ext: "" | |
| lib_glob: "libggml*.so*" | |
| - os: macos-14 | |
| name: macos-arm64-metal | |
| backend: metal | |
| cmake_extra: "-DGAME_GGML_METAL=ON -DCMAKE_OSX_ARCHITECTURES=arm64" | |
| build_jobs: 4 | |
| pkg_ext: "" | |
| lib_glob: "libggml*.dylib" | |
| - os: macos-14 | |
| name: macos-x64-metal | |
| backend: metal | |
| cmake_extra: "-DGAME_GGML_METAL=ON -DCMAKE_OSX_ARCHITECTURES=x86_64" | |
| build_jobs: 4 | |
| pkg_ext: "" | |
| lib_glob: "libggml*.dylib" | |
| - os: windows-latest | |
| name: windows-x64-vulkan | |
| backend: vulkan | |
| cmake_extra: "-DGAME_GGML_VULKAN=ON" | |
| build_jobs: 4 | |
| pkg_ext: ".exe" | |
| lib_glob: "ggml*.dll" | |
| - os: windows-2022 | |
| name: windows-x64-cuda | |
| backend: cuda | |
| cmake_extra: '-DGAME_GGML_CUDA=ON -DGGML_NATIVE=OFF -DCMAKE_CUDA_ARCHITECTURES=75' | |
| build_jobs: 2 | |
| pkg_ext: ".exe" | |
| lib_glob: "ggml*.dll" | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Vulkan SDK | |
| if: matrix.backend == 'vulkan' | |
| uses: humbletim/install-vulkan-sdk@30ba978f977e81b72d091fc8888feb1fb26f9aff | |
| with: | |
| version: 1.4.309.0 | |
| cache: true | |
| - name: Verify Vulkan shader compiler | |
| if: matrix.backend == 'vulkan' | |
| run: glslc --version | |
| - name: Install CUDA Toolkit (Linux) | |
| if: matrix.backend == 'cuda' && runner.os == 'Linux' | |
| uses: Jimver/cuda-toolkit@1a3c14e26833ccf292b268f9a790fb47dea7b2da # v0.2.28 | |
| with: | |
| cuda: "12.6.3" | |
| method: network | |
| log-file-suffix: "${{ matrix.name }}.txt" | |
| - name: Install CUDA Toolkit (Windows) | |
| if: matrix.backend == 'cuda' && runner.os == 'Windows' | |
| uses: Jimver/cuda-toolkit@b8bf9c6c28f8a92fbb04dcfcaee872e60c57462d # v0.2.36 | |
| with: | |
| cuda: "12.6.3" | |
| method: network | |
| sub-packages: '["nvcc", "cudart", "cublas", "cublas_dev", "visual_studio_integration"]' | |
| log-file-suffix: "${{ matrix.name }}.txt" | |
| - name: Verify CUDA compiler | |
| if: matrix.backend == 'cuda' | |
| run: nvcc --version | |
| - name: Disable native CPU for cross-compile (macOS x86_64 only) | |
| if: matrix.name == 'macos-x64-metal' | |
| run: echo "GGML_NATIVE=OFF" >> "$GITHUB_ENV" | |
| - name: Cache CMake FetchContent | |
| uses: actions/cache@v4 | |
| with: | |
| path: build/_deps | |
| # FetchContent stores generator-specific subbuild state here, so do not | |
| # share it between VS 2022/2026 runners or between different backends. | |
| # v2: this repo was renamed (game_ggml_cli -> game.cpp), changing the | |
| # runner workspace path; old cache entries embed the pre-rename | |
| # absolute path and must not be restored. | |
| key: ${{ matrix.name }}-deps-v2-${{ hashFiles('cmake/Dependencies.cmake') }} | |
| restore-keys: | | |
| ${{ matrix.name }}-deps-v2- | |
| - name: Configure | |
| run: | | |
| cmake -S . -B build \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DGAME_GGML_BUILD_CLI=ON \ | |
| -DGAME_GGML_BUILD_TESTS=OFF \ | |
| ${GGML_NATIVE:+-DGGML_NATIVE=$GGML_NATIVE} \ | |
| ${{ matrix.cmake_extra }} | |
| - name: Build | |
| # ggml-cuda expands many template instances. Unbounded parallel builds can | |
| # exhaust the memory of GitHub-hosted runners and terminate nvcc. | |
| run: cmake --build build --parallel ${{ matrix.build_jobs }} --config Release | |
| - name: Verify binary | |
| run: | | |
| set -euo pipefail | |
| ls -la build/bin/ | |
| # The CUDA CLI links ggml-cuda.dll directly (see src/backend.cpp), so | |
| # startup requires the Toolkit runtime DLLs. Git Bash does not always | |
| # forward its PATH to the Windows loader, so we dump the PE import | |
| # tables and run through PowerShell with a native Windows PATH. | |
| if [ "${{ matrix.backend }}" = "cuda" ] && [ "${{ runner.os }}" = "Windows" ]; then | |
| echo "== CUDA_PATH=$CUDA_PATH ==" | |
| CUDA_BIN="$(cygpath -u "$CUDA_PATH")/bin" | |
| echo "== CUDA Toolkit bin (first 60 entries) ==" | |
| ls -1 "$CUDA_BIN" 2>/dev/null | head -60 || echo "(cannot list $CUDA_BIN)" | |
| echo "== required runtime DLLs ==" | |
| for dll in cudart64_12.dll cublas64_12.dll cublasLt64_12.dll; do | |
| if [ -f "$CUDA_BIN/$dll" ]; then echo "OK $dll"; else echo "MISSING $dll"; fi | |
| done | |
| if python -m pip install --quiet pefile; then | |
| echo "== PE import tables ==" | |
| python scripts/dump_pe_imports.py build/bin/game_ggml_cli.exe build/bin/ggml-cuda.dll | |
| else | |
| echo "(pefile unavailable, skipping import dump)" | |
| fi | |
| fi | |
| # On Windows, ggml-cuda.dll imports nvcuda.dll (the NVIDIA driver | |
| # library) at load time. GitHub-hosted runners have no NVIDIA driver, | |
| # so the process cannot start there even for --version. When the | |
| # driver is present we run the real startup smoke test; otherwise the | |
| # PE dependency checks above are the verification and we report why. | |
| if [ "${{ runner.os }}" = "Windows" ]; then | |
| if [ -f "/c/Windows/System32/nvcuda.dll" ]; then | |
| powershell -NoProfile -ExecutionPolicy Bypass -Command ' | |
| $env:PATH = "$env:CUDA_PATH\bin;" + $env:PATH | |
| $exe = Join-Path (Get-Location) "build/bin/game_ggml_cli${{ matrix.pkg_ext }}" | |
| & $exe --version | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| ' | |
| else | |
| echo "no NVIDIA driver on this runner (nvcuda.dll not found); startup smoke test requires a GPU machine" | |
| fi | |
| else | |
| build/bin/game_ggml_cli${{ matrix.pkg_ext }} --version | |
| fi | |
| - name: Download medium GGUF | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: game-ggml-medium-model | |
| path: model | |
| - name: Package .oudep (full + quantized variants) | |
| run: | | |
| set -euo pipefail | |
| BIN="game_ggml_cli${{ matrix.pkg_ext }}" | |
| # Two sibling packages per platform, identical binaries/config but | |
| # different model weights: | |
| # game_ggml-<name> -> game_medium.gguf = F32 (full precision) | |
| # game_ggml-<name>-q8 -> game_medium.gguf = Q8_0 (quantized) | |
| # OpenUtau's GameGgmlBackend loads the largest *.gguf in a single | |
| # package dir, so each variant must contain exactly one model file. | |
| PKG_FULL="game_ggml-${{ matrix.name }}" | |
| PKG_Q8="${PKG_FULL}-q8" | |
| for pkg in "$PKG_FULL" "$PKG_Q8"; do mkdir -p "$pkg"; done | |
| # CLI binary | |
| cp "build/bin/$BIN" "$PKG_FULL/" | |
| # Shared libraries | |
| find build/bin -maxdepth 1 -name "${{ matrix.lib_glob }}" -exec cp {} "$PKG_FULL/" \; | |
| compgen -G "$PKG_FULL/${{ matrix.lib_glob }}" >/dev/null | |
| # macOS: include the pre-compiled Metal shader cache (one per package). | |
| if [ "${{ runner.os }}" = "macOS" ]; then | |
| METALLIB=$(find build -name "default.metallib" -type f -print -quit) | |
| test -n "$METALLIB" | |
| cp "$METALLIB" "$PKG_FULL/" | |
| fi | |
| # Quantized variant: clone binaries/libs/metallib, swap the weights. | |
| # `cp` (not `cat >`) keeps the executable bit; `cat` would produce a | |
| # mode-0644 file and `test -x` would fail on Linux/macOS runners. | |
| cp "$PKG_FULL/$BIN" "$PKG_Q8/$BIN" | |
| find build/bin -maxdepth 1 -name "${{ matrix.lib_glob }}" -exec cp {} "$PKG_Q8/" \; | |
| if [ "${{ runner.os }}" = "macOS" ]; then cp "$PKG_FULL/default.metallib" "$PKG_Q8/"; fi | |
| # Model weights (each package its own) + OpenUtau backend config. | |
| cp model/game_medium.gguf "$PKG_FULL/game_medium.gguf" | |
| cp model/game_medium_q8.gguf "$PKG_Q8/game_medium.gguf" | |
| cp model/config.json "$PKG_FULL/" | |
| cp model/config.json "$PKG_Q8/" | |
| # oudep manifests | |
| cat > "$PKG_FULL/oudep.yaml" <<EOF | |
| id: game-ggml-medium | |
| version: 0.1.0 | |
| description: GAME GGML inference engine (${{ matrix.name }}) + full FP32 medium GGUF weights | |
| entrypoints: | |
| - loader: Executable | |
| path: $BIN | |
| EOF | |
| cat > "$PKG_Q8/oudep.yaml" <<EOF | |
| id: game-ggml-medium-q8 | |
| # distinct id, and 0.2.0 records the weight change vs the 0.1.0 F32 | |
| # build so version-caching hosts detect the update. | |
| version: 0.2.0 | |
| description: GAME GGML inference engine (${{ matrix.name }}) + Q8_0 quantized medium GGUF weights | |
| entrypoints: | |
| - loader: Executable | |
| path: $BIN | |
| EOF | |
| # OpenUtau expects all of these files at the package root. | |
| echo "== verifying $PKG_FULL and $PKG_Q8 ==" | |
| for pkg in "$PKG_FULL" "$PKG_Q8"; do | |
| test -x "$pkg/$BIN" || { echo "NOT EXECUTABLE: $pkg/$BIN"; ls -l "$pkg"; exit 1; } | |
| test -s "$pkg/game_medium.gguf" | |
| test -s "$pkg/config.json" | |
| test -s "$pkg/oudep.yaml" | |
| done | |
| grep -Fx "id: game-ggml-medium" "$PKG_FULL/oudep.yaml" | |
| grep -Fx "id: game-ggml-medium-q8" "$PKG_Q8/oudep.yaml" | |
| grep -Fx " path: $BIN" "$PKG_FULL/oudep.yaml" | |
| grep -Fx " path: $BIN" "$PKG_Q8/oudep.yaml" | |
| ls -lh "$PKG_FULL/" "$PKG_Q8/" | |
| echo "PKG_FULL=$PKG_FULL" >> "$GITHUB_ENV" | |
| echo "PKG_Q8=$PKG_Q8" >> "$GITHUB_ENV" | |
| - name: Upload build artifact (full) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.PKG_FULL }} | |
| path: ${{ env.PKG_FULL }} | |
| if-no-files-found: error | |
| retention-days: 30 | |
| - name: Upload build artifact (quantized) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.PKG_Q8 }} | |
| path: ${{ env.PKG_Q8 }} | |
| if-no-files-found: error | |
| retention-days: 30 | |
| release: | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download CI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: game_ggml-* | |
| path: artifacts | |
| - name: Re-package all artifacts as .oudep | |
| run: | | |
| set -euo pipefail | |
| for dir in artifacts/*/; do | |
| name=$(basename "$dir") | |
| test -s "$dir/oudep.yaml" | |
| test -s "$dir/game_medium.gguf" | |
| test -s "$dir/config.json" | |
| ( | |
| cd "$dir" | |
| zip -r "../../${name}.oudep" . | |
| ) | |
| done | |
| # PackageManager requires oudep.yaml at the archive root. | |
| # Each artifact name is unique (full vs -q8), so it is also the | |
| # release asset name and therefore unambiguous. | |
| for package in *.oudep; do | |
| test "$(unzip -Z1 "$package" | grep -c '^oudep.yaml$')" -eq 1 | |
| test "$(unzip -Z1 "$package" | grep -c '^game_medium.gguf$')" -eq 1 | |
| test "$(unzip -Z1 "$package" | grep -c '^config.json$')" -eq 1 | |
| done | |
| # both the full (F32) and the quantized (Q8_0) variant are produced. | |
| full_count=$(ls *.oudep | grep -vc -- '-q8\.oudep$') || true | |
| q8_count=$(ls *.oudep | grep -c -- '-q8\.oudep$') || true | |
| echo "full variants: $full_count q8 variants: $q8_count" | |
| test "$full_count" -eq 7 | |
| test "$q8_count" -eq 7 | |
| ls -lh *.oudep | |
| - name: Publish release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: "*.oudep" | |
| generate_release_notes: true | |
| fail_on_unmatched_files: true |