mesh-llm/.github/workflows/ci.yml
Nick DiZazzo 265afa90bb
fix(ci): harden cache writes and readiness cleanup (#1123)
* fix(ci): isolate high-fanout compiler cache writes

* fix(ci): use service signal for readiness cleanup
2026-07-30 11:57:18 -04:00

1634 lines
68 KiB
YAML

name: CI
on:
workflow_dispatch:
inputs:
use_depot:
description: Route eligible non-GPU jobs to Depot for rollout benchmarking.
required: false
default: false
type: boolean
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
CACHE_NAMESPACE: mesh-llm
MODEL_URL: https://huggingface.co/unsloth/SmolLM2-135M-Instruct-GGUF/resolve/9e6855bc4be717fca1ef21360a1db4b29d5c559a/SmolLM2-135M-Instruct-Q8_0.gguf
MODEL_FILE: SmolLM2-135M-Instruct-Q8_0.gguf
CARGO_INCREMENTAL: "0"
CARGO_NET_RETRY: "10"
CARGO_HTTP_MULTIPLEXING: "false"
SCCACHE_GHA_ENABLED: "true"
LLAMA_STAGE_BUILD_DIR: .deps/llama.cpp/build-stage-abi-static
permissions:
contents: read
packages: read
jobs:
changes:
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
outputs:
rust: ${{ steps.filter.outputs.rust }}
ui: ${{ steps.filter.outputs.ui }}
benchmarks: ${{ steps.filter.outputs.benchmarks }}
docker: ${{ steps.filter.outputs.docker }}
windows_cpu: ${{ steps.compute.outputs.windows_cpu_build_required }}
windows_gpu: ${{ steps.compute.outputs.windows_gpu_build_required }}
docs: ${{ steps.filter.outputs.docs }}
affected_crates: ${{ steps.compute.outputs.affected_crates }}
test_crates: ${{ steps.compute.outputs.test_crates }}
batches_json: ${{ steps.compute.outputs.batches_json }}
test_batches_json: ${{ steps.compute.outputs.test_batches_json }}
linux_test_groups_json: ${{ steps.compute.outputs.linux_test_groups_json }}
all_rust: ${{ steps.compute.outputs.all_rust }}
docs_only: ${{ steps.compute.outputs.docs_only }}
rust_changed: ${{ steps.compute.outputs.rust_changed }}
backend_changed: ${{ steps.compute.outputs.backend_changed }}
inference_artifact_required: ${{ steps.compute.outputs.inference_artifact_required }}
backend_recipe_changed: ${{ steps.compute.outputs.backend_recipe_changed }}
sdk_smoke_required: ${{ steps.compute.outputs.sdk_smoke_required }}
runner_contract_required: ${{ steps.compute.outputs.runner_contract_required }}
ui_dist_cache_key: ${{ steps.ui_key.outputs.ui_dist_cache_key }}
linux_inference_artifact_required: ${{ github.event_name == 'workflow_dispatch' || steps.compute.outputs.inference_artifact_required == 'true' }}
macos_inference_artifact_required: ${{ github.event_name == 'workflow_dispatch' || steps.compute.outputs.inference_artifact_required == 'true' || steps.filter.outputs.benchmarks == 'true' }}
depot_enabled: ${{ steps.runners.outputs.depot_enabled }}
allow_depot_remote_cache: ${{ steps.runners.outputs.allow_depot_remote_cache }}
runner: ${{ steps.runners.outputs.runner }}
runner_4: ${{ steps.runners.outputs.runner_4 }}
runner_8: ${{ steps.runners.outputs.runner_8 }}
runner_16: ${{ steps.runners.outputs.runner_16 }}
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
fetch-depth: 0
- name: Select trusted Linux runners
id: runners
uses: ./.github/actions/select-ci-runners
with:
event_name: ${{ github.event_name }}
ref: ${{ github.ref }}
depot_main_enabled: ${{ vars.DEPOT_RUNNERS_ENABLED == 'true' }}
manual_use_depot: ${{ inputs.use_depot == true }}
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
filters: |
rust:
- 'crates/**'
- 'tools/xtask/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'Justfile'
- 'scripts/**'
- 'third_party/llama.cpp/**'
- '.github/cache-version.txt'
- '.github/workflows/ci.yml'
- '.github/workflows/smoke.yml'
- '.github/actions/compute-changes/action.yml'
- '.github/actions/prepare-windows-host-input/**'
ui:
- 'crates/mesh-llm-ui/**'
benchmarks:
- 'crates/skippy-bench/**'
- 'crates/llama-spec-bench/**'
- 'crates/mesh-llm-gpu-bench/**'
- '.github/workflows/ci.yml'
docker:
- '.dockerignore'
- 'docker/**'
- 'fly/Dockerfile'
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/docker.yml'
docs:
- 'docs/**'
- '**.md'
- '!crates/**'
- '!third_party/**'
- id: compute
uses: ./.github/actions/compute-changes
with:
event_name: ${{ github.event_name }}
base_sha: ${{ github.event.before || '' }}
head_sha: ${{ github.sha }}
- name: Compute UI dist cache key
id: ui_key
run: |
HASH=$(git ls-files -s crates/mesh-llm-ui .github/cache-version.txt | git hash-object --stdin)
echo "ui_dist_cache_key=${CACHE_NAMESPACE}-ui-dist-${HASH}" >> "$GITHUB_OUTPUT"
arc_runner_image_contract:
needs: changes
if: ${{ github.ref == 'refs/heads/main' && (github.event_name == 'workflow_dispatch' || needs.changes.outputs.runner_contract_required == 'true') }}
name: Runner image / ARC ${{ matrix.architecture }}
runs-on: ${{ matrix.runner }}
permissions:
contents: read
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- architecture: amd64
runner: mesh-llm-amd64
machine: x86_64
- architecture: arm64
runner: mesh-llm-arm64
machine: aarch64
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt ARC environment
env:
EXPECTED_MACHINE: ${{ matrix.machine }}
run: |
test "$(uname -m)" = "$EXPECTED_MACHINE"
verify-runner-image self-hosted
cargo check -p mesh-llm-config
linux_host_input:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' }}
name: Linux immutable release host
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
env:
MESH_LLM_SKIP_UI: "1"
MESH_LLM_REQUIRE_SCCACHE: "1"
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- name: Build UI
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.ui == 'true' }}
working-directory: crates/mesh-llm-ui
run: pnpm i --frozen-lockfile && pnpm run build
- name: Test UI
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.ui == 'true' }}
working-directory: crates/mesh-llm-ui
run: pnpm test
- name: Save UI dist cache
if: ${{ github.ref == 'refs/heads/main' && needs.changes.outputs.ui == 'true' }}
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: crates/mesh-llm-ui/dist
key: ${{ needs.changes.outputs.ui_dist_cache_key }}
- name: Configure Linux Rust linker
run: |
mkdir -p .cargo
cat > .cargo/config.toml <<'EOF'
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
EOF
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
continue-on-error: true
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: linux-release-host
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Check release-target repo consistency
run: cargo run -p xtask -- repo-consistency release-targets
- name: Check embedded client dependency purity
run: |
cargo tree -p mesh-llm-client --prefix=none --no-dedupe > /tmp/mesh-client-deps.txt
FORBIDDEN="keyring|rpassword|hf-hub|dirs|clap|include_dir|rmcp|keyring-core"
if grep -E "^[[:space:]│├└─-]*($FORBIDDEN)([[:space:]]|$)" /tmp/mesh-client-deps.txt; then
echo "ERROR: Forbidden dependency found in mesh-client"
grep -E "^[[:space:]│├└─-]*($FORBIDDEN)([[:space:]]|$)" /tmp/mesh-client-deps.txt
exit 1
fi
- name: Prepare UI placeholder
run: |
mkdir -p crates/mesh-llm-ui/dist
test -f crates/mesh-llm-ui/dist/index.html ||
printf '<html></html>' > crates/mesh-llm-ui/dist/index.html
- name: Prepare backend-neutral release host
uses: ./.github/actions/prepare-host-input
with:
profile: release
output_dir: host-input
skip_ui: "true"
- name: Upload immutable host input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-host-input
path: host-input/
if-no-files-found: error
retention-days: 1
- name: Capture sccache evidence
if: ${{ !cancelled() }}
uses: ./.github/actions/capture-sccache-stats
with:
artifact_name: sccache-main-linux-host-${{ github.run_attempt }}
linux_cpu_runtime_input:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' }}
name: Linux CPU runtime
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
env:
LLAMA_STAGE_BACKEND: cpu
LLAMA_STAGE_BUILD_DIR: .deps/llama.cpp/build-stage-abi-dynamic-cpu
MESH_LLM_REQUIRE_SCCACHE: "1"
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Resolve CPU native toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
pinned_epoch: mesh-llm-cuda-runner-sha256-8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- name: Prepare CPU native runtime
uses: ./.github/actions/prepare-native-runtime-input
with:
backend: cpu
target: x86_64-unknown-linux-gnu
output_dir: runtime-input
- name: Upload immutable CPU runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-cpu-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
- name: Capture sccache evidence
if: ${{ !cancelled() }}
uses: ./.github/actions/capture-sccache-stats
with:
artifact_name: sccache-main-linux-cpu-runtime-${{ github.run_attempt }}
linux_cpu_artifact:
needs: [changes, linux_host_input, linux_cpu_runtime_input]
if: ${{ needs.linux_host_input.result == 'success' && needs.linux_cpu_runtime_input.result == 'success' }}
name: Linux CPU product
runs-on: ${{ needs.changes.outputs.runner_4 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Download immutable host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-host-input
path: host-input
- name: Download immutable CPU runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-cpu-runtime-input
path: runtime-input
- name: Compose CPU product from producer inputs
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: ci-product
backend: cpu
- name: Upload Linux composed product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-inference-binaries
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1
linux_static_abi_input:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' && (needs.changes.outputs.test_batches_json != '[]' || needs.changes.outputs.linux_test_groups_json != '[]' || needs.changes.outputs.sdk_smoke_required == 'true') }}
name: Linux static ABI input
permissions:
contents: read
packages: read
uses: ./.github/workflows/static-abi-artifact.yml
with:
backend: cpu
target: x86_64-unknown-linux-gnu
artifact_name: ci-linux-static-abi-input
runner_size: '8'
retention_days: 1
rust_crate_tests:
needs: [changes, linux_static_abi_input]
if: ${{ needs.linux_static_abi_input.result == 'success' && needs.changes.outputs.test_batches_json != '[]' && needs.changes.outputs.docs_only != 'true' }}
name: Rust crate tests (${{ matrix.batch.idx }})
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
batch: ${{ fromJson(needs.changes.outputs.test_batches_json) }}
env:
LLAMA_STAGE_BACKEND: cpu
LLAMA_STAGE_BUILD_DIR: .deps/llama.cpp/build-stage-abi-static
MESH_LLM_SKIP_UI: "1"
RUSTFLAGS: "-C link-arg=-fuse-ld=lld"
# Four concurrent crate shards caused 94% of cold-main GHA compiler-cache
# write errors. Their per-shard Cargo target caches own persistent reuse;
# keep sccache writable and job-local on GitHub-hosted runners instead.
# Authorized Depot jobs still select disk,webdav before this GHA opt-out.
SCCACHE_GHA_ENABLED: "false"
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Resolve static ABI toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
pinned_epoch: mesh-llm-cuda-runner-sha256-8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
continue-on-error: true
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: main-rust-crate-tests-${{ matrix.batch.idx }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Prepare UI placeholder
run: mkdir -p crates/mesh-llm-ui/dist && printf '<html></html>' > crates/mesh-llm-ui/dist/index.html
- name: Prepare patched llama.cpp source checkout
if: ${{ contains(matrix.batch.crates, 'skippy-quantize') }}
run: scripts/prepare-llama.sh pinned
- name: Download immutable static ABI input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-static-abi-input
path: ${{ runner.temp }}/static-abi-input
- name: Restore immutable static ABI input
run: scripts/restore-static-abi-input.sh "$RUNNER_TEMP/static-abi-input" "$LLAMA_STAGE_BUILD_DIR" x86_64-unknown-linux-gnu cpu
- name: Run crate tests
env:
TEST_CRATES: ${{ toJson(matrix.batch.crates) }}
run: |
mapfile -t crates < <(jq -r '.[]' <<<"$TEST_CRATES")
for crate in "${crates[@]}"; do
echo "::group::cargo test -p $crate"
cargo test -p "$crate"
echo "::endgroup::"
done
- name: Capture sccache evidence
if: ${{ !cancelled() }}
uses: ./.github/actions/capture-sccache-stats
with:
artifact_name: sccache-main-rust-crate-tests-${{ matrix.batch.idx }}-${{ github.run_attempt }}
linux_test_groups:
needs: [changes, linux_static_abi_input]
if: ${{ needs.linux_static_abi_input.result == 'success' && needs.changes.outputs.docs_only != 'true' && needs.changes.outputs.linux_test_groups_json != '[]' }}
name: Linux tests (${{ matrix.group }})
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.changes.outputs.linux_test_groups_json) }}
env:
AFFECTED: ${{ needs.changes.outputs.affected_crates }}
ALL_RUST: ${{ needs.changes.outputs.all_rust }}
LLAMA_STAGE_BACKEND: cpu
LLAMA_STAGE_BUILD_DIR: .deps/llama.cpp/build-stage-abi-static
MESH_LLM_SKIP_UI: "1"
MESH_LLM_REQUIRE_SCCACHE: "1"
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Resolve static ABI toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
pinned_epoch: mesh-llm-cuda-runner-sha256-8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- name: Configure Linux Rust linker
run: |
mkdir -p .cargo
cat > .cargo/config.toml <<'EOF'
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
EOF
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
continue-on-error: true
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: ${{ matrix.cache_key }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Download immutable static ABI input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-static-abi-input
path: ${{ runner.temp }}/static-abi-input
- name: Restore immutable static ABI input
run: scripts/restore-static-abi-input.sh "$RUNNER_TEMP/static-abi-input" "$LLAMA_STAGE_BUILD_DIR" x86_64-unknown-linux-gnu cpu
- name: Restore Skippy smoke model cache
if: ${{ matrix.group == 'skippy-smoke' }}
id: skippy_smoke_model_cache
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/skippy-ci-smoke-models
key: ${{ env.CACHE_NAMESPACE }}-${{ runner.os }}-skippy-ci-smoke-models-SmolLM2-135M-Instruct.Q4_K_M.gguf-Falcon-H1-0.5B-Instruct-Q4_K_M.gguf-${{ hashFiles('.github/cache-version.txt') }}
restore-keys: |
${{ env.CACHE_NAMESPACE }}-${{ runner.os }}-skippy-ci-smoke-models-
- name: Protocol compatibility matrix
if: ${{ matrix.group == 'protocol' && (needs.changes.outputs.all_rust == 'true' || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm-protocol')) }}
run: |
cargo test -p mesh-llm --test protocol_compat_v0_client
cargo test -p mesh-llm --test protocol_convert_matrix
- name: Skippy smoke tests
if: ${{ matrix.group == 'skippy-smoke' }}
timeout-minutes: 45
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
HUGGING_FACE_HUB_TOKEN: ${{ secrets.HF_TOKEN }}
WORK_DIR: ${{ runner.temp }}/skippy-ci-smoke
MODEL_DIR: ${{ runner.temp }}/skippy-ci-smoke-models
run: scripts/skippy-ci-smoke.sh
- name: Save Skippy smoke model cache
if: ${{ matrix.group == 'skippy-smoke' && github.ref == 'refs/heads/main' && steps.skippy_smoke_model_cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/skippy-ci-smoke-models
key: ${{ steps.skippy_smoke_model_cache.outputs.cache-primary-key }}
- name: Capture sccache evidence
if: ${{ !cancelled() }}
uses: ./.github/actions/capture-sccache-stats
with:
artifact_name: sccache-main-linux-tests-${{ matrix.group }}-${{ github.run_attempt }}
hf_download_smoke:
needs: changes
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.all_rust == 'true' || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'model-artifact')) && needs.changes.outputs.docs_only != 'true' }}
name: HuggingFace download smoke
permissions:
contents: read
uses: ./.github/workflows/hf-download-smoke.yml
with:
timeout_minutes: 15
secrets:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
inference_smoke_tests:
needs: [changes, linux_cpu_artifact]
if: ${{ needs.linux_cpu_artifact.result == 'success' && needs.changes.outputs.linux_inference_artifact_required == 'true' && (github.event_name == 'workflow_dispatch' || needs.changes.outputs.all_rust == 'true' || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'skippy-server') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'skippy-runtime') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'openai-frontend') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'model-artifact')) && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/smoke.yml
with:
artifact_name: ci-linux-inference-binaries
mesh_binary_target: target/release/mesh-llm
cache_key_prefix: ''
secrets:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
agent_live_smokes:
needs: [changes, linux_cpu_artifact]
if: ${{ needs.linux_cpu_artifact.result == 'success' && (vars.MESH_AGENT_BASE_URL != '' || vars.MESH_OPENCODE_BASE_URL != '') && (github.event_name == 'workflow_dispatch' || needs.changes.outputs.all_rust == 'true' || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'openai-frontend') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm-client')) && needs.changes.outputs.docs_only != 'true' }}
runs-on: ${{ needs.changes.outputs.runner_4 }}
permissions:
contents: read
timeout-minutes: 45
env:
MESH_AGENT_BASE_URL: ${{ vars.MESH_AGENT_BASE_URL || vars.MESH_OPENCODE_BASE_URL }}
MESH_AGENT_MODEL: ${{ vars.MESH_AGENT_MODEL || vars.MESH_OPENCODE_MODEL }}
MESH_OPENCODE_BASE_URL: ${{ vars.MESH_AGENT_BASE_URL || vars.MESH_OPENCODE_BASE_URL }}
MESH_OPENCODE_MODEL: ${{ vars.MESH_AGENT_MODEL || vars.MESH_OPENCODE_MODEL }}
AGENT_SMOKE_LONG_PROMPT_CHARS: ${{ vars.AGENT_SMOKE_LONG_PROMPT_CHARS || vars.OPENCODE_SMOKE_LONG_PROMPT_CHARS || '65536' }}
OPENCODE_SMOKE_LONG_PROMPT_CHARS: ${{ vars.OPENCODE_SMOKE_LONG_PROMPT_CHARS || '65536' }}
OPENCODE_DISABLE_AUTOUPDATE: "true"
OPENCODE_DISABLE_PRUNE: "true"
OPENCODE_DISABLE_LSP_DOWNLOAD: "true"
steps:
# Keep live-agent smoke on an explicit Node version so CI does not depend
# on GitHub-hosted image defaults.
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 24
- name: Install agent CLIs
run: |
corepack enable
corepack prepare pnpm@10 --activate
export PNPM_HOME="$HOME/.local/share/pnpm"
mkdir -p "$PNPM_HOME"
echo "PNPM_HOME=$PNPM_HOME" >> "$GITHUB_ENV"
echo "$PNPM_HOME" >> "$GITHUB_PATH"
pnpm add --global opencode-ai@latest @earendil-works/pi-coding-agent@latest
opencode --version
pi --version
- name: Install Goose
run: |
curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Check Goose
run: goose --version
- name: OpenCode configured mesh coding smoke
run: scripts/ci-opencode-smoke.sh
- name: Pi configured mesh coding smoke
run: scripts/ci-pi-smoke.sh
- name: Goose configured mesh coding smoke
run: scripts/ci-goose-smoke.sh
two_node_client_serving_smoke:
needs: [changes, linux_cpu_artifact]
if: ${{ needs.linux_cpu_artifact.result == 'success' && needs.changes.outputs.linux_inference_artifact_required == 'true' && (github.event_name == 'workflow_dispatch' || needs.changes.outputs.all_rust == 'true' || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'openai-frontend') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm-client')) && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/scripted-binary-smoke.yml
with:
artifact_name: ci-linux-inference-binaries
artifact_path: ci-artifacts/linux
staged_binary_path: target/release/mesh-llm
model_cache_scope: two-node-smoke-model
smoke_script: scripts/ci-two-node-client-serving-smoke.sh
timeout_minutes: 20
secrets:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
rust_sdk_smoke:
needs: [changes, linux_cpu_artifact]
if: ${{ needs.linux_cpu_artifact.result == 'success' && needs.changes.outputs.linux_inference_artifact_required == 'true' && needs.changes.outputs.sdk_smoke_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/sdk-smoke.yml
with:
sdk_kind: rust
artifact_name: ci-linux-inference-binaries
artifact_path: ci-artifacts/linux
staged_binary_path: target/release/mesh-llm
model_cache_scope: sdk-smoke-model
secrets:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
kotlin_sdk_input:
name: Kotlin native SDK input
needs: [changes, linux_static_abi_input]
if: ${{ needs.linux_static_abi_input.result == 'success' && needs.changes.outputs.sdk_smoke_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
packages: read
uses: ./.github/workflows/native-sdk-artifact.yml
with:
backend: cpu
target: x86_64-unknown-linux-gnu
profile: release
artifact_name: ci-kotlin-native-sdk-input
static_abi_artifact_name: ci-linux-static-abi-input
static_abi_artifact_path: native-sdk-static-abi-input
runner_size: '8'
retention_days: 1
kotlin_sdk_smoke:
needs: [changes, linux_cpu_artifact, kotlin_sdk_input]
if: ${{ needs.linux_cpu_artifact.result == 'success' && needs.kotlin_sdk_input.result == 'success' && needs.changes.outputs.linux_inference_artifact_required == 'true' && needs.changes.outputs.sdk_smoke_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/sdk-smoke.yml
with:
sdk_kind: kotlin
artifact_name: ci-linux-inference-binaries
artifact_path: ci-artifacts/linux
staged_binary_path: target/release/mesh-llm
kotlin_artifact_name: ci-kotlin-native-sdk-input
kotlin_artifact_target: x86_64-unknown-linux-gnu
kotlin_artifact_backend: cpu
kotlin_artifact_profile: release
model_cache_scope: sdk-smoke-model
secrets:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
swift_sdk_input:
name: Swift SDK full input
needs: changes
if: ${{ needs.changes.outputs.macos_inference_artifact_required == 'true' && needs.changes.outputs.sdk_smoke_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/swift-sdk-artifact.yml
with:
mode: full
artifact_name: ci-swift-sdk-input
retention_days: 1
timeout_minutes: 180
swift_sdk_smoke:
needs: [changes, macos_cpu_artifact, swift_sdk_input]
if: ${{ needs.macos_cpu_artifact.result == 'success' && needs.swift_sdk_input.result == 'success' && needs.changes.outputs.sdk_smoke_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
permissions:
contents: read
uses: ./.github/workflows/sdk-smoke.yml
with:
sdk_kind: swift
artifact_name: ci-macos-inference-binaries
artifact_path: ci-artifacts/macos
staged_binary_path: target/release/mesh-llm
swift_artifact_name: ci-swift-sdk-input
swift_artifact_mode: full
model_cache_scope: sdk-smoke-model
timeout_minutes: 30
secrets:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
macos_host_input:
needs: changes
if: ${{ needs.changes.outputs.macos_inference_artifact_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
name: macOS immutable release host
runs-on: macos-15
permissions:
contents: read
env:
MESH_LLM_SKIP_UI: "1"
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.ui == 'true' }}
with:
version: 10
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.ui == 'true' }}
with:
node-version: 24
cache: pnpm
cache-dependency-path: |
.github/cache-version.txt
crates/mesh-llm-ui/pnpm-lock.yaml
- name: Restore UI dist cache
id: ui-cache
if: needs.changes.outputs.ui == 'true' || github.event_name == 'workflow_dispatch'
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: crates/mesh-llm-ui/dist
key: ${{ needs.changes.outputs.ui_dist_cache_key }}
- name: Build UI
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.ui == 'true') && steps.ui-cache.outputs.cache-hit != 'true' }}
working-directory: crates/mesh-llm-ui
run: pnpm i --frozen-lockfile && pnpm run build
- name: Install UI deps (cache hit only)
if: needs.changes.outputs.ui == 'true' && steps.ui-cache.outputs.cache-hit == 'true'
working-directory: crates/mesh-llm-ui
run: pnpm i --frozen-lockfile
- name: Verify UI dist exists
if: needs.changes.outputs.ui == 'true' || github.event_name == 'workflow_dispatch'
run: |
if [ ! -f crates/mesh-llm-ui/dist/index.html ]; then
echo "ERROR: crates/mesh-llm-ui/dist/index.html not found after restore/build"
exit 1
fi
file_count="$(find crates/mesh-llm-ui/dist -mindepth 1 -maxdepth 1 | wc -l | tr -d ' ')"
echo "UI dist OK: ${file_count} files"
- name: Test UI
if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.ui == 'true' }}
working-directory: crates/mesh-llm-ui
run: pnpm test
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable 2026-07-16
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: macos
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install host build dependencies
run: brew install jq lld
- name: Configure macOS Rust linker
run: |
mkdir -p .cargo
lld_prefix="$(brew --prefix lld)"
cat > .cargo/config.toml <<EOF
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=$lld_prefix/bin/ld64.lld"]
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=$lld_prefix/bin/ld64.lld"]
EOF
- name: Prepare UI placeholder
run: |
mkdir -p crates/mesh-llm-ui/dist
test -f crates/mesh-llm-ui/dist/index.html ||
printf '<html></html>' > crates/mesh-llm-ui/dist/index.html
- name: Prepare backend-neutral release host
uses: ./.github/actions/prepare-host-input
with:
profile: release
output_dir: host-input
skip_ui: "true"
- name: Upload immutable macOS host input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-macos-host-input
path: host-input/
if-no-files-found: error
retention-days: 1
macos_metal_runtime_input:
needs: changes
if: ${{ needs.changes.outputs.macos_inference_artifact_required == 'true' && needs.changes.outputs.docs_only != 'true' }}
name: macOS Metal runtime
runs-on: macos-15
permissions:
contents: read
env:
LLAMA_STAGE_BACKEND: metal
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/build-stage-abi-dynamic-metal
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Install native runtime dependencies
run: brew install cmake ninja jq
- name: Resolve macOS native toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
include_tool_versions: "true"
- name: Prepare Metal native runtime
uses: ./.github/actions/prepare-native-runtime-input
with:
backend: metal
output_dir: runtime-input
- name: Upload immutable Metal runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-macos-metal-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
macos_cpu_artifact:
needs: [changes, macos_host_input, macos_metal_runtime_input]
if: ${{ needs.macos_host_input.result == 'success' && needs.macos_metal_runtime_input.result == 'success' }}
name: macOS Metal release product
runs-on: macos-15
permissions:
contents: read
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Download immutable macOS host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-macos-host-input
path: host-input
- name: Download immutable Metal runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-macos-metal-runtime-input
path: runtime-input
- name: Compose macOS Metal product
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: ci-product
backend: metal
- name: Upload macOS composed release product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-macos-inference-binaries
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1
macos_unit_tests:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' && (needs.changes.outputs.all_rust == 'true' || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'model-artifact') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm-host-runtime') || contains(fromJson(needs.changes.outputs.affected_crates || '[]'), 'mesh-llm')) }}
name: macOS unit tests
runs-on: macos-15
permissions:
contents: read
env:
AFFECTED: ${{ needs.changes.outputs.affected_crates }}
ALL_RUST: ${{ needs.changes.outputs.all_rust }}
LLAMA_STAGE_BACKEND: metal
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/build-stage-abi-static-metal
MESH_LLM_SKIP_UI: "1"
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable 2026-07-16
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: macos-unit-tests
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install unit-test dependencies
run: brew install cmake ninja jq lld
- name: Resolve macOS native toolchain epoch
id: native_toolchain
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
include_tool_versions: "true"
- name: Configure macOS Rust linker
run: |
mkdir -p .cargo
lld_prefix="$(brew --prefix lld)"
cat > .cargo/config.toml <<EOF
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=$lld_prefix/bin/ld64.lld"]
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=$lld_prefix/bin/ld64.lld"]
EOF
- name: Ensure static Metal ABI cache directory
run: mkdir -p "$LLAMA_STAGE_BUILD_DIR"
- name: Cache static Metal ABI build
id: llama_cache
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ env.LLAMA_STAGE_BUILD_DIR }}
key: ${{ env.CACHE_NAMESPACE }}-${{ runner.os }}-${{ runner.arch }}-skippy-abi-static-metal-${{ steps.native_toolchain.outputs.epoch }}-${{ hashFiles('scripts/build-mac.sh', 'scripts/build-llama.sh', 'scripts/prepare-llama.sh', 'third_party/llama.cpp/upstream.txt', 'third_party/llama.cpp/patches/**', 'Justfile', '.github/cache-version.txt') }}
- name: Prepare patched llama.cpp ABI checkout
run: scripts/prepare-llama.sh pinned
- name: Verify cached patched llama.cpp ABI libraries
if: ${{ steps.llama_cache.outputs.cache-hit == 'true' }}
run: scripts/build-llama.sh --require-existing
- name: Build patched llama.cpp ABI libraries
if: ${{ steps.llama_cache.outputs.cache-hit != 'true' }}
run: scripts/build-llama.sh
- name: Unit tests
run: |
should_test() {
local crate="$1"
[ "$ALL_RUST" = "true" ] || jq -e --arg crate "$crate" 'index($crate) != null' <<<"$AFFECTED" >/dev/null
}
for c in model-artifact mesh-llm-host-runtime mesh-llm; do
if should_test "$c"; then
cargo test -p "$c" --lib
else
echo "Skipping $c on macOS (not affected)"
fi
done
linux_cuda_runtime_input:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' }}
name: Linux CUDA compatibility runtime
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:c5b85ef527230f77cf9933ef40bcb44316f9bbcb8fd2ce0651b58acda5143dfd
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
LLAMA_STAGE_BACKEND: cuda
LLAMA_STAGE_CUDA_ARCHITECTURES: "75;80;86;87;89;90"
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/build-stage-abi-dynamic-cuda-sm75_80_86_87_89_90
MESH_CUDA_VERSION: "12.9.2"
MESH_LLM_CUDA_TOOLKIT_MAJOR: "12"
# CI containers have no GPU driver (libcuda.so.1). Disable VMM and NCCL
# to avoid linking libraries that require the CUDA driver at runtime.
GGML_CUDA_NO_VMM: "1"
LLAMA_STAGE_SKIP_NCCL: "1"
MESH_LLM_REQUIRE_SCCACHE: "1"
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt backend environment
run: verify-runner-image public cuda
- name: Resolve CUDA native toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
pinned_epoch: mesh-llm-cuda-runner-sha256-c5b85ef527230f77cf9933ef40bcb44316f9bbcb8fd2ce0651b58acda5143dfd
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- name: Prepare Linux CUDA native runtime
uses: ./.github/actions/prepare-native-runtime-input
with:
backend: cuda
target: x86_64-unknown-linux-gnu
output_dir: runtime-input
- name: Upload immutable CUDA runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-cuda-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
linux_cuda_product:
needs: [changes, linux_host_input, linux_cuda_runtime_input]
if: ${{ needs.linux_host_input.result == 'success' && needs.linux_cuda_runtime_input.result == 'success' }}
name: Linux CUDA compatibility
runs-on: ${{ needs.changes.outputs.runner_4 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Download immutable host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-host-input
path: host-input
- name: Download immutable CUDA runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-cuda-runtime-input
path: runtime-input
- name: Compose CUDA product
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: product-input
backend: cuda
- name: Upload composed CUDA product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-cuda-product
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1
linux_rocm_runtime_input:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' }}
name: Linux ROCm slim runtime
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:0e13e5d2d2c121df265ff6c69be81e468989e09f81d6b7ff049b110cc0bb0d2b
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/build-stage-abi-dynamic-rocm-gfx1100
LLAMA_STAGE_BACKEND: rocm
LLAMA_STAGE_AMDGPU_TARGETS: gfx1100
CMAKE_PREFIX_PATH: /opt/rocm
MESH_LLM_REQUIRE_SCCACHE: "1"
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt backend environment
run: verify-runner-image public rocm
- name: Resolve ROCm native toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
pinned_epoch: mesh-llm-cuda-runner-sha256-0e13e5d2d2c121df265ff6c69be81e468989e09f81d6b7ff049b110cc0bb0d2b
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- name: Prepare Linux ROCm native runtime
uses: ./.github/actions/prepare-native-runtime-input
with:
backend: rocm
target: x86_64-unknown-linux-gnu
output_dir: runtime-input
- name: Upload immutable ROCm runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-rocm-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
linux_rocm_product:
needs: [changes, linux_host_input, linux_rocm_runtime_input]
if: ${{ needs.linux_host_input.result == 'success' && needs.linux_rocm_runtime_input.result == 'success' }}
name: Linux ROCm slim
runs-on: ${{ needs.changes.outputs.runner_4 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Download immutable host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-host-input
path: host-input
- name: Download immutable ROCm runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-rocm-runtime-input
path: runtime-input
- name: Compose ROCm product
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: product-input
backend: rocm
- name: Upload composed ROCm product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-rocm-product
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1
linux_vulkan_runtime_input:
needs: changes
if: ${{ needs.changes.outputs.docs_only != 'true' }}
name: Linux Vulkan runtime
runs-on: ${{ needs.changes.outputs.runner_8 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:ce55fed5c680cd3184b5d4770d9a77c43a702687690906e5753efd2cea27ed80
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
LLAMA_STAGE_BACKEND: vulkan
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/build-stage-abi-dynamic-vulkan
MESH_LLM_REQUIRE_SCCACHE: "1"
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt backend environment
run: verify-runner-image public vulkan
- name: Resolve Vulkan native toolchain epoch
uses: ./.github/actions/resolve-native-toolchain-epoch
with:
pinned_epoch: mesh-llm-cuda-runner-sha256-ce55fed5c680cd3184b5d4770d9a77c43a702687690906e5753efd2cea27ed80
- uses: ./.github/actions/configure-sccache-gha
with:
allow_depot_remote_cache: ${{ needs.changes.outputs.allow_depot_remote_cache }}
- name: Prepare Linux Vulkan native runtime
uses: ./.github/actions/prepare-native-runtime-input
with:
backend: vulkan
target: x86_64-unknown-linux-gnu
output_dir: runtime-input
- name: Upload immutable Vulkan runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-vulkan-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
linux_vulkan_product:
needs: [changes, linux_host_input, linux_vulkan_runtime_input]
if: ${{ needs.linux_host_input.result == 'success' && needs.linux_vulkan_runtime_input.result == 'success' }}
name: Linux Vulkan
runs-on: ${{ needs.changes.outputs.runner_4 }}
permissions:
contents: read
packages: read
container:
image: ghcr.io/mesh-llm/mesh-llm-cuda-runner@sha256:8d93de6ba30173e825a16fdecf011f9c632edc6e1259df7289e491b0a05f829d
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- name: Verify prebuilt CI environment
run: verify-runner-image public
- name: Download immutable host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-host-input
path: host-input
- name: Download immutable Vulkan runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-linux-vulkan-runtime-input
path: runtime-input
- name: Compose Vulkan product
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: product-input
backend: vulkan
- name: Upload composed Vulkan product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-linux-vulkan-product
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1
windows_node_checks:
needs: changes
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.rust == 'true') && needs.changes.outputs.docs_only != 'true' }}
name: Windows Node SDK checks
runs-on: windows-2022
permissions:
contents: read
env:
RUN_WINDOWS_NODE_RELEASE: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.windows_cpu == 'true' }}
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable 2026-07-16
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: windows-node
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Check Node SDK addon on Windows
shell: pwsh
run: cargo check --locked -p mesh-llm-nodejs
- name: Build Node SDK addon on Windows
if: ${{ env.RUN_WINDOWS_NODE_RELEASE == 'true' }}
shell: pwsh
run: cargo build --release --locked -p mesh-llm-nodejs
windows_gpu_runtime_inputs:
needs: changes
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.windows_gpu == 'true') && needs.changes.outputs.docs_only != 'true' }}
name: Windows ${{ matrix.name }} runtime input
runs-on: windows-2022
permissions:
contents: read
env:
LLAMA_STAGE_BACKEND: ${{ matrix.backend }}
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/windows/build-stage-abi-${{ matrix.backend }}
RUSTC_WRAPPER: sccache
WINDOWS_CUDA_VERSION: ${{ vars.CUDA_VERSION || '12.6.3' }}
WINDOWS_VULKAN_SDK_VERSION: ${{ vars.VULKAN_SDK_VERSION || '1.4.328.1' }}
ROCM_HIP_SDK_FILENAME: AMD-Software-PRO-Edition-25.Q3-WinSvr2022-For-HIP.exe
strategy:
fail-fast: false
matrix:
include:
- name: CUDA
backend: cuda
cuda_architectures: "75"
- name: ROCm
backend: rocm
rocm_architectures: gfx1100
- name: Vulkan
backend: vulkan
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.x'
- name: Test Windows native-runtime dependency resolver
if: ${{ matrix.backend == 'vulkan' }}
shell: pwsh
run: python -m unittest scripts.tests.test_windows_native_runtime_deps -v
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable 2026-07-16
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: windows-runtime-${{ matrix.backend }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Resolve Windows native toolchain epoch
id: native_toolchain
uses: ./.github/actions/resolve-native-toolchain-epoch
- name: Restore exact Windows GPU ABI cache
uses: ./.github/actions/restore-windows-abi-cache
with:
backend: ${{ matrix.backend }}
build_dir: ${{ env.LLAMA_STAGE_BUILD_DIR }}
toolchain_epoch: ${{ steps.native_toolchain.outputs.epoch }}
architecture_set: ${{ matrix.cuda_architectures || matrix.rocm_architectures || '' }}
cuda_toolchain_version: ${{ env.WINDOWS_CUDA_VERSION }}
vulkan_toolchain_version: ${{ env.WINDOWS_VULKAN_SDK_VERSION }}
rocm_toolchain_version: ${{ env.ROCM_HIP_SDK_FILENAME }}
- name: Install CUDA toolkit
if: ${{ matrix.backend == 'cuda' }}
uses: Jimver/cuda-toolkit@3d45d157f327c09c04b50ee6ccdea2d9d017ec76 # v0.2.35
with:
cuda: ${{ env.WINDOWS_CUDA_VERSION }}
method: network
sub-packages: '["nvcc", "cudart", "cublas", "cublas_dev", "visual_studio_integration"]'
use-github-cache: true
use-local-cache: true
log-file-suffix: windows-cuda
- name: Verify CUDA toolkit
if: ${{ matrix.backend == 'cuda' }}
shell: pwsh
run: |
if (-not $env:CUDA_PATH -or -not (Test-Path $env:CUDA_PATH)) {
throw "CUDA_PATH was not configured by Jimver/cuda-toolkit."
}
& nvcc --version
$cudaMajor = ("$env:WINDOWS_CUDA_VERSION".Trim() -split '\.', 2)[0]
"MESH_LLM_CUDA_TOOLKIT_MAJOR=$cudaMajor" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Install Vulkan SDK
if: ${{ matrix.backend == 'vulkan' }}
uses: jakoch/install-vulkan-sdk-action@3c53c378c9bfbb2ea122a1cc164a837d4004c871 # v1.5.2
with:
vulkan_version: ${{ env.WINDOWS_VULKAN_SDK_VERSION }}
cache: true
stripdown: true
- name: Verify Vulkan SDK
if: ${{ matrix.backend == 'vulkan' }}
shell: pwsh
run: |
if (-not $env:VULKAN_SDK -or -not (Test-Path $env:VULKAN_SDK)) {
throw "VULKAN_SDK was not configured by jakoch/install-vulkan-sdk-action."
}
$glslc = Join-Path $env:VULKAN_SDK "Bin\glslc.exe"
if (-not (Test-Path $glslc)) {
throw "glslc.exe was not found at $glslc"
}
& $glslc --version
- name: Install ROCm HIP SDK
if: ${{ matrix.backend == 'rocm' }}
uses: ./.github/actions/setup-windows-rocm-sdk
with:
rocm-hip-sdk-filename: ${{ env.ROCM_HIP_SDK_FILENAME }}
- name: Initialize MSVC for native runtime
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: x64
- name: Prepare immutable Windows GPU runtime
uses: ./.github/actions/prepare-native-runtime-input
env:
LLAMA_STAGE_CUDA_ARCHITECTURES: ${{ matrix.cuda_architectures }}
LLAMA_STAGE_AMDGPU_TARGETS: ${{ matrix.rocm_architectures }}
with:
backend: ${{ matrix.backend }}
target: x86_64-pc-windows-msvc
output_dir: runtime-input
- name: Upload immutable Windows GPU runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-windows-${{ matrix.backend }}-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
windows_host_input:
needs: changes
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.windows_cpu == 'true' || needs.changes.outputs.windows_gpu == 'true') && needs.changes.outputs.docs_only != 'true' }}
name: Windows immutable release host
runs-on: windows-2022
env:
MESH_LLM_REQUIRE_SCCACHE: "1"
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.x'
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable 2026-07-16
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: windows-host-release
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Initialize MSVC for neutral host
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: x64
- name: Prepare immutable Windows release host
uses: ./.github/actions/prepare-windows-host-input
with:
profile: release
skip_ui: "true"
- name: Upload immutable Windows host input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-windows-host-input
path: host-input/*
if-no-files-found: error
retention-days: 1
windows_cpu_runtime_input:
needs: changes
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.windows_cpu == 'true') && needs.changes.outputs.docs_only != 'true' }}
name: Windows CPU runtime input
runs-on: windows-2022
env:
LLAMA_STAGE_BACKEND: cpu
LLAMA_STAGE_BUILD_DIR: .deps/llama-build/windows/build-stage-abi-cpu
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.x'
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable 2026-07-16
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 snapshot 2026-03-12
with:
workspaces: . -> target
cache-bin: "false"
prefix-key: ${{ env.CACHE_NAMESPACE }}-rust-${{ runner.os }}-${{ hashFiles('.github/cache-version.txt') }}
shared-key: windows-runtime-cpu
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Resolve Windows native toolchain epoch
id: native_toolchain
uses: ./.github/actions/resolve-native-toolchain-epoch
- name: Restore exact Windows CPU ABI cache
uses: ./.github/actions/restore-windows-abi-cache
with:
backend: cpu
build_dir: ${{ env.LLAMA_STAGE_BUILD_DIR }}
toolchain_epoch: ${{ steps.native_toolchain.outputs.epoch }}
- name: Initialize MSVC for CPU runtime
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: x64
- name: Prepare immutable Windows CPU runtime
uses: ./.github/actions/prepare-native-runtime-input
with:
backend: cpu
target: x86_64-pc-windows-msvc
output_dir: runtime-input
- name: Upload immutable Windows CPU runtime input
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-windows-cpu-runtime-input
path: |
runtime-input/*.tar.gz
runtime-input/*.sha256
if-no-files-found: error
retention-days: 1
windows_cpu_product:
needs: [changes, windows_host_input, windows_cpu_runtime_input]
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.windows_cpu == 'true') && needs.changes.outputs.docs_only != 'true' }}
name: Windows CPU
runs-on: windows-2022
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.x'
- name: Download immutable Windows host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-windows-host-input
path: host-input
- name: Download immutable Windows CPU runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-windows-cpu-runtime-input
path: runtime-input
- name: Compose Windows CPU product
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: product-input
backend: cpu
binary_name: mesh-llm.exe
readiness_smoke: "true"
- name: Upload composed Windows CPU product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-windows-cpu-product
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1
windows_gpu_products:
needs: [changes, windows_host_input, windows_gpu_runtime_inputs]
if: ${{ (github.event_name == 'workflow_dispatch' || needs.changes.outputs.windows_gpu == 'true') && needs.changes.outputs.docs_only != 'true' }}
name: Windows ${{ matrix.name }}
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
include:
- name: CUDA
backend: cuda
- name: ROCm
backend: rocm
- name: Vulkan
backend: vulkan
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.x'
- name: Download immutable Windows host input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-windows-host-input
path: host-input
- name: Download immutable Windows GPU runtime input
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ci-windows-${{ matrix.backend }}-runtime-input
path: runtime-input
- name: Compose Windows GPU product
id: compose
uses: ./.github/actions/compose-product-input
with:
host_input_dir: host-input
runtime_input_dir: runtime-input
output_dir: product-input
backend: ${{ matrix.backend }}
binary_name: mesh-llm.exe
readiness_smoke: "true"
- name: Upload composed Windows GPU product
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ci-windows-${{ matrix.backend }}-product
path: ${{ steps.compose.outputs.archive_path }}
if-no-files-found: error
retention-days: 1