From 24fa335c4213618097886d113149228d708917cd Mon Sep 17 00:00:00 2001 From: Jhin Lee Date: Sun, 8 Nov 2026 06:21:55 -0500 Subject: [PATCH] Prepare Web qualification for llama.cpp v0.9.0 Move the exact-upstream CI lane from v0.7.0 to v0.9.0 (cd959bae), matching published llamadart-native v0.9.0, so the bridge source is built and smoked against the upstream the next release candidate will use. The development pin and release workflows are unchanged; the bridge source needs no change. --- .github/workflows/ci.yml | 24 ++++++++++---------- AGENTS.md | 6 ++--- README.md | 6 ++--- docs/v090_qualification.md | 46 ++++++++++++++++++++++++++++++++++++++ tests/ci/ci_scope_test.mjs | 11 ++++----- 5 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 docs/v090_qualification.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e044004..55bb161 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,11 +60,11 @@ jobs: build-webgpu-bridge: needs: [changes, checks] if: needs.changes.outputs.native == 'true' - name: ${{ matrix.upstream == 'pinned' && 'Build WebGPU Bridge (WASM)' || 'Build WebGPU Bridge (WASM v0.7.0)' }} + name: ${{ matrix.upstream == 'pinned' && 'Build WebGPU Bridge (WASM)' || 'Build WebGPU Bridge (WASM v0.9.0)' }} strategy: fail-fast: false matrix: - upstream: [pinned, v0.7.0] + upstream: [pinned, v0.9.0] runs-on: ubuntu-latest env: LLAMA_WEBGPU_SMOKE_MODEL_URL: https://huggingface.co/aladar/llama-2-tiny-random-GGUF/resolve/main/llama-2-tiny-random.gguf @@ -111,8 +111,8 @@ jobs: echo "error: llama_cpp.version is empty" exit 1 fi - if [ "${{ matrix.upstream }}" = "v0.7.0" ]; then - LLAMA_CPP_TAG=v0.7.0 + if [ "${{ matrix.upstream }}" = "v0.9.0" ]; then + LLAMA_CPP_TAG=v0.9.0 fi echo "LLAMA_CPP_TAG=$LLAMA_CPP_TAG" >> "$GITHUB_ENV" echo "Using llama.cpp ${LLAMA_CPP_TAG}" @@ -138,8 +138,8 @@ jobs: - name: Clone llama.cpp source run: | git clone --depth 1 --branch "$LLAMA_CPP_TAG" https://github.com/ggml-org/llama.cpp.git third_party/llama_cpp - if [ "${{ matrix.upstream }}" = "v0.7.0" ]; then - test "$(git -C third_party/llama_cpp rev-parse HEAD)" = 620d2225cc436ab0b001e3200e590e3b286ef9ab + if [ "${{ matrix.upstream }}" = "v0.9.0" ]; then + test "$(git -C third_party/llama_cpp rev-parse HEAD)" = cd959bae99ff4bed22bef2604f45241c09bbb8c8 fi # Installed at a fixed path, not with setup-emsdk: its random temp @@ -264,7 +264,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.upstream == 'pinned' && 'state-persistence-smoke-artifacts' || 'state-persistence-smoke-artifacts-v0.7.0' }} + name: ${{ matrix.upstream == 'pinned' && 'state-persistence-smoke-artifacts' || 'state-persistence-smoke-artifacts-v0.9.0' }} path: ${{ env.LLAMA_WEBGPU_SMOKE_ARTIFACTS_DIR }} if-no-files-found: ignore @@ -272,7 +272,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.upstream == 'pinned' && 'multimodal-smoke-artifacts' || 'multimodal-smoke-artifacts-v0.7.0' }} + name: ${{ matrix.upstream == 'pinned' && 'multimodal-smoke-artifacts' || 'multimodal-smoke-artifacts-v0.9.0' }} path: ${{ env.LLAMA_WEBGPU_MULTIMODAL_ARTIFACTS_DIR }} if-no-files-found: ignore @@ -280,7 +280,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.upstream == 'pinned' && 'grammar-smoke-artifacts' || 'grammar-smoke-artifacts-v0.7.0' }} + name: ${{ matrix.upstream == 'pinned' && 'grammar-smoke-artifacts' || 'grammar-smoke-artifacts-v0.9.0' }} path: ${{ env.LLAMA_WEBGPU_GRAMMAR_ARTIFACTS_DIR }} if-no-files-found: ignore @@ -288,7 +288,7 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.upstream == 'pinned' && 'next-token-scores-smoke-artifacts' || 'next-token-scores-smoke-artifacts-v0.7.0' }} + name: ${{ matrix.upstream == 'pinned' && 'next-token-scores-smoke-artifacts' || 'next-token-scores-smoke-artifacts-v0.9.0' }} path: ${{ env.LLAMA_WEBGPU_NEXT_TOKEN_SCORES_ARTIFACTS_DIR }} if-no-files-found: ignore @@ -296,14 +296,14 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.upstream == 'pinned' && 'speculative-smoke-artifacts' || 'speculative-smoke-artifacts-v0.7.0' }} + name: ${{ matrix.upstream == 'pinned' && 'speculative-smoke-artifacts' || 'speculative-smoke-artifacts-v0.9.0' }} path: ${{ env.LLAMA_WEBGPU_SPECULATIVE_ARTIFACTS_DIR }} if-no-files-found: ignore - name: Upload bridge artifacts uses: actions/upload-artifact@v4 with: - name: ${{ matrix.upstream == 'pinned' && 'webgpu-bridge-dist' || 'webgpu-bridge-dist-v0.7.0' }} + name: ${{ matrix.upstream == 'pinned' && 'webgpu-bridge-dist' || 'webgpu-bridge-dist-v0.9.0' }} path: | ${{ runner.temp }}/webgpu_bridge_dist/llama_webgpu_bridge.js ${{ runner.temp }}/webgpu_bridge_dist/llama_webgpu_bridge_worker.js diff --git a/AGENTS.md b/AGENTS.md index f4907dd..4a0d7e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,11 +82,11 @@ GGUFs or smoke artifacts. - CI build gate: `.github/workflows/ci.yml` - Builds wasm32/memory64 against the `llama_cpp.version` pin and exact - v0.7.0 candidate in separate lanes. Its exact SHA lives in the workflow. + v0.9.0 candidate in separate lanes. Its exact SHA lives in the workflow. Preserve its check/artifact identities (`Build WebGPU Bridge (WASM)`, `webgpu-bridge-dist`). Neither lane publishes or changes the source pin; candidate check - and artifact identities use the `v0.7.0` suffix. See - `docs/v070_qualification.md` for the preparation evidence and limits. + and artifact identities use the `v0.9.0` suffix. See + `docs/v090_qualification.md` for the preparation evidence and limits. Builds against llama.cpp v0.4.0 are no longer tested in CI; the `src/llama_webgpu_mtmd_compat.h` shim and its static contract remain. - Verifies the active `emcc` identity against `emsdk.version` and diff --git a/README.md b/README.md index aa29779..f4da8ef 100644 --- a/README.md +++ b/README.md @@ -215,16 +215,16 @@ This repo includes a wasm build gate in: - `.github/workflows/ci.yml` After the JS/compatibility contracts pass, it builds wasm32 and memory64 against -the pinned `llama.cpp` tag in `llama_cpp.version` and the exact v0.7.0 +the pinned `llama.cpp` tag in `llama_cpp.version` and the exact v0.9.0 candidate in separate lanes. Both run real state-persistence, multimodal, grammar and next-token-score browser smokes. A successful run uploads its seven built files as `webgpu-bridge-dist`; a failed run uploads no dist and instead the smoke diagnostics that exist (`state-persistence-smoke-artifacts`, `multimodal-smoke-artifacts`, -`grammar-smoke-artifacts`, `next-token-scores-smoke-artifacts`). Candidate checks and artifacts use the `v0.7.0` suffix; +`grammar-smoke-artifacts`, `next-token-scores-smoke-artifacts`). Candidate checks and artifacts use the `v0.9.0` suffix; the pinned identities remain unchanged. CI never changes a pin or publishes assets. Builds against llama.cpp v0.4.0 are no longer tested in CI. See -[the v0.7.0 preparation evidence](docs/v070_qualification.md) for validation +[the v0.9.0 preparation evidence](docs/v090_qualification.md) for validation and qualification limits. To run the media-helper and static CI contracts locally: diff --git a/docs/v090_qualification.md b/docs/v090_qualification.md new file mode 100644 index 0000000..570ac2f --- /dev/null +++ b/docs/v090_qualification.md @@ -0,0 +1,46 @@ +# llama.cpp v0.9.0 compatibility preparation + +The candidate lane builds upstream `cd959bae99ff4bed22bef2604f45241c09bbb8c8` +(v0.9.0) alongside the unchanged v0.5.0 development pin, matching published +native `v0.9.0`. The bridge source needs no change for v0.9.0. No Web assets or +Dart packages are published by this change. + +## Upstream and native patches + +- The v0.7.0 embedding-mode reserve repair + ([llamadart-native#114](https://github.com/leehack/llamadart-native/issues/114), + [v0.7.0 notes](v070_qualification.md)) is upstream since v0.8.0: + `set_embeddings` re-reserves on every toggle. `cmake/embedding_mode_reserve.cmake` + finds no affected guard and stays inactive. +- The other native patches do not apply to the Web build: 0001/0002 are + Vulkan, 0004 Metal, and 0005/0006 (F16 dot and GEMM F32 accumulation, + [llamadart-native#174](https://github.com/leehack/llamadart-native/issues/174)) + change code only under `__ARM_NEON`/`__ARM_FEATURE_FP16_VECTOR_ARITHMETIC`, + which WASM never defines. +- The mtmd-audio single-thread rewrite still finds both `n_threads` markers. +- v0.9.0 drops `mtmd_helper_gen_audio_inp::voice` and adds + `mtmd_gen_audio_info::n_voices` and Chatterbox voices. The bridge TTS path + sets neither field, so Qwen3-TTS, with or without a speaker reference, is + unaffected. + +## Validation + +Local evidence uses Emscripten 6.0.8, Node 24 and the locked Playwright +Chromium on macOS arm64, against a clean upstream checkout at the commit above. +Model/projector pins and commands live in +[CONTRIBUTING.md](../CONTRIBUTING.md#validate-outputs). + +| Check | Result | +| --- | --- | +| `npm run check:js` | 602 passed, 4 existing skips, zero failures | +| CI reliability contract | PASS | +| v0.9.0 production build | PASS, wasm32 and memory64 | +| Extraction/state/generation smoke | PASS, direct and worker on both memory modes | +| Qwen3.5 image inference | PASS, direct and worker on wasm32 | +| Tiny and Qwen3.5 grammar, next-token scores, tiny speculative | PASS | +| Qwen3-ASR transcript, silence and cancellation | PASS, four runtime rows | +| Qwen3-TTS synthesis, cancellation and unload, WebGPU offload | PASS, four runtime rows with a speaker reference; direct and worker on memory64 without one | + +Four runtime rows mean direct and worker execution on wasm32 and memory64. +Hosted exact-head CI, candidate qualification, publication approval and Dart +adoption remain separate gates. diff --git a/tests/ci/ci_scope_test.mjs b/tests/ci/ci_scope_test.mjs index 490568d..3030421 100644 --- a/tests/ci/ci_scope_test.mjs +++ b/tests/ci/ci_scope_test.mjs @@ -156,8 +156,8 @@ const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../. const { jobs } = workflow; const build = jobs['build-webgpu-bridge']; const { checks } = jobs; - assert.equal(build.name, "${{ matrix.upstream == 'pinned' && 'Build WebGPU Bridge (WASM)' || 'Build WebGPU Bridge (WASM v0.7.0)' }}"); - assert.deepEqual(build.strategy.matrix.upstream, ['pinned', 'v0.7.0']); + assert.equal(build.name, "${{ matrix.upstream == 'pinned' && 'Build WebGPU Bridge (WASM)' || 'Build WebGPU Bridge (WASM v0.9.0)' }}"); + assert.deepEqual(build.strategy.matrix.upstream, ['pinned', 'v0.9.0']); assert.equal(build.strategy['fail-fast'], false); assert.deepEqual(build.needs, ['changes', 'checks']); assert.equal(build.if, "needs.changes.outputs.native == 'true'"); @@ -170,15 +170,16 @@ const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../. 1, ); const clone = build.steps.find((step) => step.name === 'Clone llama.cpp source').run; - assert.match(clone, /620d2225cc436ab0b001e3200e590e3b286ef9ab/); + assert.match(clone, /cd959bae99ff4bed22bef2604f45241c09bbb8c8/); + assert.match(clone, /"\$\{\{ matrix.upstream \}\}" = "v0.9.0"/); assert.match(clone, /git -C third_party\/llama_cpp rev-parse HEAD/); const resolver = build.steps.find((step) => step.name === 'Resolve llama.cpp pin').run; assert.match(resolver, /llama_cpp.version/); assert.match(resolver, /matrix.upstream/); - assert.match(resolver, /LLAMA_CPP_TAG=v0.7.0/); + assert.match(resolver, /LLAMA_CPP_TAG=v0.9.0/); for (const step of build.steps.filter((step) => step.uses?.startsWith('actions/upload-artifact@'))) { assert.match(step.with.name, /matrix.upstream == 'pinned'/); - assert.match(step.with.name, /-v0.7.0'/); + assert.match(step.with.name, /-v0.9.0'/); } const buildSteps = Object.fromEntries(build.steps.map((step) => [step.name, step])); for (const name of ['Run state persistence browser smoke', 'Run multimodal browser smoke', 'Build bridge artifacts', 'Verify outputs']) {