Skip to content

ci: add linux-x64-cpu (GGML_NATIVE=true) package #56

ci: add linux-x64-cpu (GGML_NATIVE=true) package

ci: add linux-x64-cpu (GGML_NATIVE=true) package #56

Workflow file for this run

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