diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb06ee15..471dfe6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Releases before 0.10.0 are in the ## Unreleased - Chat app microphone: a microphone that disconnects mid-recording now transcribes the audio captured so far with a warning, a missing input device on Web is reported as such instead of as a permission denial, and a failed Web start releases the microphone ([#329](https://github.com/leehack/llamadart/issues/329)). +- Aligned the default WebGPU bridge assets to `v0.1.85`: a page without cross-origin isolation now fails at once with a `LlamaUnsupportedException` naming COOP/COEP `require-corp` instead of waiting minutes for a generic init timeout, and Firefox CPU runs (`gpuLayers: 0`) no longer create the WebGPU backend (stories15M, 32 tokens: 640 to 762 tok/s against 10 before) ([#1025](https://github.com/leehack/llamadart/issues/1025)). +- `ComputeDevice.gpu` on Web now names why WebGPU was not used (for example `adapter_missing_shader_f16`: Chrome on Linux with NVIDIA needs driver 615.71 or newer); documented the measured Safari 27, Firefox 160 and NVIDIA L4 results ([#1025](https://github.com/leehack/llamadart/issues/1025)). + +Runtime baseline: native llama.cpp `v0.9.0` and Web use upstream `v0.9.0@cd959bae99ff4bed22bef2604f45241c09bbb8c8`; native LiteRT-LM uses `v0.19.1-4` and stable-diffusion uses `v0.3.0-3`. Web LiteRT-LM uses `@litert-lm/core@0.19.0`. Manifest SHA-256: `6767783edd85e14853a8a79cd6ce53d53f4eb2d99aeb62601014bd68c1b6b7b3`. ## 0.19.9 diff --git a/README.md b/README.md index 512b80086..565deb916 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Current default runtime pins: | --- | --- | | Native llama.cpp / GGUF | `leehack/llamadart-native@v0.9.0` | | Native LiteRT-LM / `.litertlm` | `leehack/litert-lm-native@v0.19.1-4` | -| Web llama.cpp / GGUF | `leehack/llama-web-bridge-assets@v0.1.84` | +| Web llama.cpp / GGUF | `leehack/llama-web-bridge-assets@v0.1.85` | | Web LiteRT-LM / `.litertlm` | `@litert-lm/core@0.19.0` | Native LiteRT-LM uses a lazy ABI-aware client for direct calls, services and diff --git a/doc/multimodal_embeddings_web.md b/doc/multimodal_embeddings_web.md index 9577841f3..b37d7c127 100644 --- a/doc/multimodal_embeddings_web.md +++ b/doc/multimodal_embeddings_web.md @@ -39,7 +39,7 @@ Bridge source contract inspected read-only at restore and structured-clone ownership; those are bridge responsibilities. Qualification target: EmbeddingGemma-2 BF16 with its matching -mmproj and pinned assets `v0.1.84` on Chrome CPU wasm memory64 +mmproj and pinned assets `v0.1.85` on Chrome CPU wasm memory64 (`gpuLayers: 0`), Chrome CPU wasm32 and Chrome WebGPU on an Apple GPU (Metal backend, `gpuLayers: 99`, memory64 core). The scenario below checks the bridge's production worker budget against references from the matching native `v0.9.0`. diff --git a/doc/testing_matrix.md b/doc/testing_matrix.md index 5fa7db2f7..e52cea4ca 100644 --- a/doc/testing_matrix.md +++ b/doc/testing_matrix.md @@ -305,8 +305,8 @@ compares that generation with its exact baseline before reusing TTS. These bounded controls do not qualify every output length or load setting. The `chrome-webgpu` test pages enable SharedArrayBuffer with browser flags but -remain non-isolated (`crossOriginIsolated == false`), so the bridge caps native -threads at one. In the [#1371 controls recorded in PR #1418](https://github.com/leehack/llamadart/pull/1418), +remain non-isolated (`crossOriginIsolated == false`), so assets without the `v0.1.85+` isolation check +capped native threads at one. In the [#1371 controls recorded in PR #1418](https://github.com/leehack/llamadart/pull/1418), with that run's staged Qwen3-TTS fixture and bridge manifest, the 4096/4096 batch profile aborted even on fresh one- and eight-frame synthesis, before cancellation. The 512/256 fresh controls succeeded. This historical diagnostic diff --git a/doc/webgpu_bridge.md b/doc/webgpu_bridge.md index ffc10f919..422b8e89f 100644 --- a/doc/webgpu_bridge.md +++ b/doc/webgpu_bridge.md @@ -60,18 +60,18 @@ an earlier ` ``` That release embeds llama.cpp `v0.9.0`, matching the `hook/build.dart` native pin (`v0.9.0`, both built from upstream llama.cpp `v0.9.0@cd959bae99ff4bed22bef2604f45241c09bbb8c8`) -even though the bridge asset tag `v0.1.84` differs from the native runtime tag -`v0.9.0`. Provenance for this immutable consumer artifact: release `443341300`, -tag commit `0a3c45f24fd6293050e1772bed4a425b2b5a8493`, bridge source -`dc359398bc6445ec15878ddb873555295f957f85`, and manifest SHA-256 -`55abeb377597148156059a8ee5400f2314c6254ad91b57db65b709e304c0dea0`. +even though the bridge asset tag `v0.1.85` differs from the native runtime tag +`v0.9.0`. Provenance for this immutable consumer artifact: release `444874209`, +tag commit `28a6180925e74e1038ee4c22cde6b403ae21ed84`, bridge source +`f1ac2fc1d4d4d3fda56c161ac6a629ae4c67a7d3`, and manifest SHA-256 +`6767783edd85e14853a8a79cd6ce53d53f4eb2d99aeb62601014bd68c1b6b7b3`. They expose the API-version-1 media embedding contract. State and extraction qualification covers wasm32 and memory64 through direct and worker runtimes. Existing decision, scoring, sampling, LoRA, speculative decoding and speech APIs remain available. @@ -205,6 +205,10 @@ physical playback, intelligibility or speaker-reference fidelity. suppress tokens, so Qwen3-TTS no longer fails with `code out of range` when it samples a codec control token ([llama-web-bridge#211](https://github.com/leehack/llama-web-bridge/pull/211)). +- `v0.1.85+`: a page without cross-origin isolation rejects the load at once + with `CrossOriginIsolationError` (`code: 'CROSS_ORIGIN_ISOLATION_REQUIRED'`); + `gpu_unavailable:` appears in the runtime notes when `nGpuLayers > 0` + and the GPU is not active; at 0 GPU layers the WebGPU backend is not created. - `v0.1.84+`: `getCompletionCapabilities()` reports `thinkingBudgetPauseGrammar`, and `thinkingBudget.pauseGrammar: false` keeps a grammar applied while the model reasons @@ -336,8 +340,11 @@ isolated (`Cross-Origin-Opener-Policy: same-origin`, `Cross-Origin-Embedder-Policy: require-corp`; `credentialless` isolates only in Chromium-based browsers, see [#1025](https://github.com/leehack/llamadart/issues/1025)). Without it -the bridge caps threads to one, and fetch-backed loading fails with -thread-constructor errors. +the web core cannot start: assets `v0.1.85+` reject the load at once +with `CrossOriginIsolationError` (`code: 'CROSS_ORIGIN_ISOLATION_REQUIRED'`), +which `WebGpuLlamaBackend` throws as `LlamaUnsupportedException` on the direct +and worker paths; earlier assets waited minutes (the init timeout, retried: 180 s direct, +360 s worker) before a generic `Bridge core init timeout` error. `credentialless` does not isolate Safari. For Hugging Face Spaces with `sdk: static`, set the headers in the Space README frontmatter; keys and values must be lowercase: @@ -384,6 +391,14 @@ On macOS headless Chromium, keep the smoke script's default adapter can lack `shader-f16`, and llama.cpp may abort in `ggml-webgpu` even on `gpuLayers = 0` runs. +When `nGpuLayers > 0` and the GPU is not active, assets `v0.1.85+` add +the runtime note `gpu_unavailable:` (`no_webgpu`, `no_adapter`, +`adapter_missing_shader_f16` or `unknown`), and the `ComputeDevice.gpu` error +"loaded the model without GPU layers" names it. Chrome on Linux hides +`shader-f16` from NVIDIA adapters with a driver older than 615.71. At 0 GPU +layers the bridge no longer creates the WebGPU backend: on Firefox 160 (isolated +page, CPU, stories15M, 32 tokens) that is 640 to 762 tok/s against 10 tok/s before. + The speech smokes read the loaded runtime from the bridge, not from the request. `chat-app-web-speech-to-text-webgpu-smoke` requires `isGpuActive()` to be `true`, `-mem64-smoke` requires `llamadart.webgpu.core_variant` to be diff --git a/example/chat_app/lib/providers/chat_provider.dart b/example/chat_app/lib/providers/chat_provider.dart index 850c2d43c..d790ca9c3 100644 --- a/example/chat_app/lib/providers/chat_provider.dart +++ b/example/chat_app/lib/providers/chat_provider.dart @@ -2661,13 +2661,6 @@ class ChatProvider extends ChangeNotifier { .where((value) => value.isNotEmpty) .toSet(); - if (runtimeNotes.contains('threads_capped_no_coi')) { - _addInfoMessage( - 'Web runtime is not cross-origin isolated, so inference threads are capped to 1. ' - 'Enable COOP/COEP headers for better throughput.', - ); - } - if (runtimeNotes.contains('threads_capped_no_pthread')) { _addInfoMessage( 'Loaded bridge core does not include pthread support, so runtime threads are capped to 1. ' diff --git a/example/chat_app/test/runtime_profile_service_test.dart b/example/chat_app/test/runtime_profile_service_test.dart index 1684f05f5..5d4b27d60 100644 --- a/example/chat_app/test/runtime_profile_service_test.dart +++ b/example/chat_app/test/runtime_profile_service_test.dart @@ -14,9 +14,9 @@ void main() { 'llamadart.webgpu.thread_pool_size': '2', 'llamadart.webgpu.execution': 'worker', 'llamadart.webgpu.core_variant': 'wasm64', - 'llamadart.webgpu.worker_fallback_reason': 'threads_capped_no_coi', + 'llamadart.webgpu.worker_fallback_reason': 'worker_unavailable', 'llamadart.webgpu.runtime_notes': - 'threads_capped_no_coi;model_fetch_backend_attempt', + 'core_pthreads:1;model_fetch_backend_attempt', 'llamadart.webgpu.model_source': 'network-fetch', 'llamadart.webgpu.model_cache_state': 'hit', }, @@ -27,10 +27,10 @@ void main() { expect(diagnostics.runtimeThreadPoolSize, 2); expect(diagnostics.runtimeExecution, 'worker'); expect(diagnostics.runtimeCoreVariant, 'wasm64'); - expect(diagnostics.runtimeWorkerFallbackReason, 'threads_capped_no_coi'); + expect(diagnostics.runtimeWorkerFallbackReason, 'worker_unavailable'); expect( diagnostics.runtimeNotes, - 'threads_capped_no_coi;model_fetch_backend_attempt', + 'core_pthreads:1;model_fetch_backend_attempt', ); expect(diagnostics.runtimeModelSource, 'network-fetch'); expect(diagnostics.runtimeModelCacheState, 'hit'); diff --git a/example/chat_app/web/index.html b/example/chat_app/web/index.html index 6dcaae91a..db3653132 100644 --- a/example/chat_app/web/index.html +++ b/example/chat_app/web/index.html @@ -157,7 +157,7 @@ typeof configuredRepo === 'string' && configuredRepo.length > 0 ? configuredRepo : 'leehack/llama-web-bridge-assets'; - const defaultBridgeAssetsTag = 'v0.1.84'; + const defaultBridgeAssetsTag = 'v0.1.85'; const defaultBridgeLlamaCppTag = 'v0.9.0'; const bridgeAssetsTag = typeof configuredTag === 'string' && configuredTag.length > 0 diff --git a/lib/src/backends/webgpu/webgpu_backend.dart b/lib/src/backends/webgpu/webgpu_backend.dart index d2f48adab..e030fcdd2 100644 --- a/lib/src/backends/webgpu/webgpu_backend.dart +++ b/lib/src/backends/webgpu/webgpu_backend.dart @@ -1332,9 +1332,18 @@ class WebGpuLlamaBackend await loadPromise.toDart; } if (setup.requireGpu && bridge.isGpuActive() != true) { + var gpuDetail = ''; + try { + gpuDetail = webGpuUnavailableDetail( + _collectBridgeRuntimeHints( + bridge, + )['llamadart.webgpu.runtime_notes'] ?? + '', + ); + } catch (_) {} throw gpuInactive = LlamaUnsupportedException( 'ComputeDevice.gpu needs WebGPU for llama.cpp on the Web, but the ' - 'runtime loaded the model without GPU layers. Use ' + 'runtime loaded the model without GPU layers.$gpuDetail Use ' 'ComputeDevice.auto or cpu.', ); } @@ -1360,6 +1369,11 @@ class WebGpuLlamaBackend await _safeDisposeBridge(); rethrow; } + final isolationError = webGpuCrossOriginIsolationError(e); + if (isolationError != null) { + await _safeDisposeBridge(); + throw isolationError; + } lastError = e; Map runtimeHints = const {}; if (bridgeForAttempt != null) { diff --git a/lib/src/backends/webgpu/webgpu_errors.dart b/lib/src/backends/webgpu/webgpu_errors.dart index b213bf5f2..85332593f 100644 --- a/lib/src/backends/webgpu/webgpu_errors.dart +++ b/lib/src/backends/webgpu/webgpu_errors.dart @@ -103,3 +103,52 @@ LlamaException? webGpuBridgeRequestError( } return null; } + +/// The bridge's `CrossOriginIsolationError` code, which the worker path keeps +/// on the error it rebuilds in the page. +const String _crossOriginIsolationRequiredCode = + 'CROSS_ORIGIN_ISOLATION_REQUIRED'; + +/// Returns [LlamaUnsupportedException] when [error] is the bridge's +/// `CrossOriginIsolationError` (assets `v0.1.85+`), otherwise null. +LlamaUnsupportedException? webGpuCrossOriginIsolationError(Object error) { + if (error is LlamaException) return null; + try { + final object = error as JSObject; + final code = object.getProperty('code'.toJS); + final name = object.getProperty('name'.toJS); + final isIsolationError = + (code.isA() && + (code as JSString).toDart == _crossOriginIsolationRequiredCode) || + (name.isA() && + (name as JSString).toDart == 'CrossOriginIsolationError'); + if (!isIsolationError) return null; + } catch (_) { + return null; + } + return LlamaUnsupportedException( + 'The Web runtime needs cross-origin isolation (COOP/COEP) for shared ' + 'memory, which this page does not have. Serve the page with ' + '"Cross-Origin-Opener-Policy: same-origin" and ' + '"Cross-Origin-Embedder-Policy: require-corp". ' + '"credentialless" does not isolate Safari.', + ); +} + +final RegExp _gpuUnavailableNote = RegExp(r'(?:^|;)gpu_unavailable:([^;]+)'); + +/// Why the bridge ran without GPU layers, from its `gpu_unavailable:` +/// runtime note (assets `v0.1.85+`), or an empty string. +String webGpuUnavailableDetail(String runtimeNotes) { + final reason = _gpuUnavailableNote.firstMatch(runtimeNotes)?.group(1); + return switch (reason) { + null => '', + 'no_webgpu' => ' The browser exposes no WebGPU.', + 'no_adapter' => ' The browser granted no WebGPU adapter.', + 'adapter_missing_shader_f16' => + ' WebGPU needs the shader-f16 feature, which this adapter does not ' + 'expose. On Linux with an NVIDIA GPU, Chrome exposes it only with ' + 'driver 615.71 or newer.', + _ => ' The bridge reported gpu_unavailable:$reason.', + }; +} diff --git a/lib/src/web_setup/web_bridge_setup.dart b/lib/src/web_setup/web_bridge_setup.dart index 4f7d18740..f0a8ea736 100644 --- a/lib/src/web_setup/web_bridge_setup.dart +++ b/lib/src/web_setup/web_bridge_setup.dart @@ -15,14 +15,14 @@ import '../core/exceptions.dart'; const String webBridgeAssetsRepository = 'leehack/llama-web-bridge-assets'; /// The bridge asset release this llamadart version is qualified against. -const String webBridgeAssetsTag = 'v0.1.84'; +const String webBridgeAssetsTag = 'v0.1.85'; /// SHA-256 of the exact approved `manifest.json` of [webBridgeAssetsTag]. /// /// The manifest lists the SHA-256 and size of every asset, so pinning its /// bytes pins every file the tool writes. const String webBridgeManifestSha256 = - '55abeb377597148156059a8ee5400f2314c6254ad91b57db65b709e304c0dea0'; + '6767783edd85e14853a8a79cd6ce53d53f4eb2d99aeb62601014bd68c1b6b7b3'; /// Directory under the app's `web/` directory that receives the assets. const String webBridgeAssetsDirName = 'webgpu_bridge'; diff --git a/scripts/fetch_webgpu_bridge_assets.sh b/scripts/fetch_webgpu_bridge_assets.sh index 5a09b3b04..4c6f8fe92 100755 --- a/scripts/fetch_webgpu_bridge_assets.sh +++ b/scripts/fetch_webgpu_bridge_assets.sh @@ -4,7 +4,7 @@ set -euo pipefail ROOT_DIR="$(git rev-parse --show-toplevel)" OUT_DIR="${WEBGPU_BRIDGE_OUT_DIR:-$ROOT_DIR/example/chat_app/web/webgpu_bridge}" ASSETS_REPO="${WEBGPU_BRIDGE_ASSETS_REPO:-leehack/llama-web-bridge-assets}" -ASSETS_TAG="${WEBGPU_BRIDGE_ASSETS_TAG:-v0.1.84}" +ASSETS_TAG="${WEBGPU_BRIDGE_ASSETS_TAG:-v0.1.85}" CDN_BASE="${WEBGPU_BRIDGE_CDN_BASE:-https://cdn.jsdelivr.net/gh/${ASSETS_REPO}@${ASSETS_TAG}}" CURL_DOWNLOAD_ARGS=( --connect-timeout 15 @@ -19,7 +19,7 @@ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then Downloads prebuilt WebGPU bridge assets into the chat_app web directory. Default source: - https://cdn.jsdelivr.net/gh/leehack/llama-web-bridge-assets@v0.1.84 + https://cdn.jsdelivr.net/gh/leehack/llama-web-bridge-assets@v0.1.85 Environment variables: WEBGPU_BRIDGE_ASSETS_REPO Asset repo in owner/repo format @@ -31,7 +31,7 @@ Usage: ./scripts/fetch_webgpu_bridge_assets.sh Examples: - WEBGPU_BRIDGE_ASSETS_TAG=v0.1.84 ./scripts/fetch_webgpu_bridge_assets.sh + WEBGPU_BRIDGE_ASSETS_TAG=v0.1.85 ./scripts/fetch_webgpu_bridge_assets.sh WEBGPU_BRIDGE_ASSETS_REPO=acme/llama-web-bridge-assets WEBGPU_BRIDGE_ASSETS_TAG=v2 ./scripts/fetch_webgpu_bridge_assets.sh USAGE exit 0 diff --git a/skills/llamadart-web/SKILL.md b/skills/llamadart-web/SKILL.md index 2678c5aa6..08ddefb56 100644 --- a/skills/llamadart-web/SKILL.md +++ b/skills/llamadart-web/SKILL.md @@ -54,16 +54,18 @@ in the llamadart-getting-started and llamadart-chat-streaming skills. - Serve from a secure context (`https://`, `http://localhost` or `http://127.0.0.1`) and send `Cross-Origin-Opener-Policy: same-origin` with `Cross-Origin-Embedder-Policy: require-corp` (`credentialless` isolates only - in Chromium-based browsers). Without - isolation (`window.crossOriginIsolated == false`) the bridge caps inference - at one thread (`threads_capped_no_coi` in its notes) and large single-file - GGUF loads become unreliable. + in Chromium-based browsers; it does not isolate Safari). Without + isolation (`window.crossOriginIsolated == false`) assets `v0.1.85+` + reject the load at once and llamadart throws `LlamaUnsupportedException` + naming COOP/COEP; earlier assets wait minutes for the init timeout before a generic error. - Treat WebGPU as a runtime capability. A supported browser (Chrome/Edge 128+, Firefox 129+, Safari 17.4+) can still lack an adapter, features or memory. Prove a first load with a small quantized GGUF, `contextSize: 2048` or less and `gpuLayers: 0`, then raise GPU offload. - `device: ComputeDevice.gpu` requires a WebGPU adapter and active GPU layers - after load. The bridge ignores negative `mainGpu` values, including with + after load; with assets `v0.1.85+` the error names the bridge's + `gpu_unavailable:*` reason (for example `adapter_missing_shader_f16`: Chrome + on Linux with NVIDIA needs driver 615.71 or newer). The bridge ignores negative `mainGpu` values, including with `splitMode: ModelSplitMode.none`; use `device: ComputeDevice.cpu` to select WebAssembly CPU execution. - Expect automatic fallback: a failed GPU load retries on CPU, then at smaller diff --git a/test/unit/backends/webgpu/webgpu_backend_test.dart b/test/unit/backends/webgpu/webgpu_backend_test.dart index 63e1fd423..43d907839 100644 --- a/test/unit/backends/webgpu/webgpu_backend_test.dart +++ b/test/unit/backends/webgpu/webgpu_backend_test.dart @@ -1671,6 +1671,80 @@ void main() { expect(backend.isReady, isFalse); }); + for (final (reason, expected) in <(String, String)>[ + ('adapter_missing_shader_f16', 'driver 615.71 or newer'), + ('no_adapter', 'granted no WebGPU adapter'), + ('no_webgpu', 'exposes no WebGPU'), + ('unknown', 'gpu_unavailable:unknown'), + ]) { + test('gpu names the bridge reason $reason when the runtime runs ' + 'without GPU layers', () async { + addTearDown(fakeNavigatorGpu(FakeWebGpu.adapter)); + runtimeGpuActive = false; + bridgeRuntimeHints['llamadart.webgpu.runtime_notes'] = + 'core_wasm32_active;gpu_unavailable:$reason;threads_batch:1'; + + await expectLater( + backend.modelLoadFromUrl( + url, + const ModelParams(device: ComputeDevice.gpu), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + allOf(contains('without GPU layers'), contains(expected)), + ), + ), + ); + }); + } + + test('gpu ignores a note that only ends in gpu_unavailable:', () async { + addTearDown(fakeNavigatorGpu(FakeWebGpu.adapter)); + runtimeGpuActive = false; + bridgeRuntimeHints['llamadart.webgpu.runtime_notes'] = + 'core_wasm32_active;not_gpu_unavailable:no_adapter'; + + await expectLater( + backend.modelLoadFromUrl( + url, + const ModelParams(device: ComputeDevice.gpu), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('without GPU layers. Use ComputeDevice.auto or cpu.'), + ), + ), + ); + }); + + test('gpu without a reason note keeps the plain message', () async { + addTearDown(fakeNavigatorGpu(FakeWebGpu.adapter)); + runtimeGpuActive = false; + bridgeRuntimeHints['llamadart.webgpu.runtime_notes'] = + 'core_wasm32_active'; + + await expectLater( + backend.modelLoadFromUrl( + url, + const ModelParams(device: ComputeDevice.gpu), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + allOf( + contains('without GPU layers. Use ComputeDevice.auto or cpu.'), + isNot(contains('gpu_unavailable')), + ), + ), + ), + ); + }); + test('auto keeps loading without WebGPU', () async { addTearDown(fakeNavigatorGpu(FakeWebGpu.missing)); runtimeGpuActive = false; @@ -2090,7 +2164,7 @@ void main() { bridgeRuntimeHints['llamadart.webgpu.core_variant'] = 'wasm64'; bridgeRuntimeHints['llamadart.webgpu.runtime_notes'] = 'core_mem64_attempt;core_mem64_active;core_pthreads:1;' - 'thread_pool_size:4;threads_capped_no_coi;threads_batch:1;' + 'thread_pool_size:4;threads_batch:1;' 'model_network_stream;model_response_stream;' 'model_fs_write_loaded:0;model_fs_write_arraybuffer_oom'; failLoads(message: 'Array buffer allocation failed', firstAttempts: 1); @@ -2114,10 +2188,117 @@ void main() { }, ); + group('cross-origin isolation error', () { + JSPromise rejectIsolation({String? name, String? code}) { + final error = JSObject(); + error.setProperty('message'.toJS, 'bridge isolation text'.toJS); + if (name != null) error.setProperty('name'.toJS, name.toJS); + if (code != null) error.setProperty('code'.toJS, code.toJS); + return _rejectPromise(error); + } + + for (final (label, name, code) in <(String, String?, String?)>[ + ( + 'direct path', + 'CrossOriginIsolationError', + 'CROSS_ORIGIN_ISOLATION_REQUIRED', + ), + ( + 'worker path with the code only', + null, + 'CROSS_ORIGIN_ISOLATION_REQUIRED', + ), + ('worker path with the name only', 'CrossOriginIsolationError', null), + ]) { + test('maps to LlamaUnsupportedException on the $label', () async { + var loadCalls = 0; + bridge.setProperty( + 'loadModelFromUrl'.toJS, + ((String url, JSObject? config) { + loadCalls += 1; + return rejectIsolation(name: name, code: code); + }).toJS, + ); + + await expectLater( + backend.modelLoadFromUrl( + 'https://example.com/no-coi.gguf', + const ModelParams(contextSize: 4096, gpuLayers: 99), + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + allOf( + contains('cross-origin isolation'), + contains('COOP/COEP'), + contains('require-corp'), + contains('"credentialless" does not isolate Safari'), + ), + ), + ), + ); + expect(loadCalls, 1); + expect(backend.isReady, isFalse); + }); + } + + test('disposes the bridge after the rejection', () async { + var disposeCalls = 0; + bridge.setProperty( + 'dispose'.toJS, + (() { + disposeCalls += 1; + return Future.value().toJS; + }).toJS, + ); + bridge.setProperty( + 'loadModelFromUrl'.toJS, + ((String url, JSObject? config) => rejectIsolation( + name: 'CrossOriginIsolationError', + code: 'CROSS_ORIGIN_ISOLATION_REQUIRED', + )).toJS, + ); + + await expectLater( + backend.modelLoadFromUrl( + 'https://example.com/no-coi.gguf', + const ModelParams(contextSize: 4096, gpuLayers: 99), + ), + throwsA(isA()), + ); + expect(disposeCalls, 1); + }); + + test('does not remap an unrelated error carrying another code', () async { + bridge.setProperty( + 'loadModelFromUrl'.toJS, + ((String url, JSObject? config) => rejectIsolation( + name: 'Error', + code: 'OTHER', + )).toJS, + ); + + await expectLater( + backend.modelLoadFromUrl( + 'https://example.com/other.gguf', + const ModelParams(contextSize: 4096, gpuLayers: 0), + ), + throwsA( + isA().having( + (e) => '${e.details}', + 'details', + contains('bridge isolation text'), + ), + ), + ); + }); + }); + group('on a page without cross-origin isolation', () { const noCoiNotes = 'core_wasm32_active;core_pthreads:1;thread_pool_size:4;' - 'threads_capped_no_coi;threads_batch:1;model_network_stream'; + 'threads_batch:1;model_network_stream'; test('reports an ordinary load failure as a model error', () async { bridgeRuntimeHints['llamadart.webgpu.core_variant'] = 'wasm32'; diff --git a/test/unit/tooling/check_webgpu_bridge_tag_test.dart b/test/unit/tooling/check_webgpu_bridge_tag_test.dart index 2c8d605ff..1d5f25ff4 100644 --- a/test/unit/tooling/check_webgpu_bridge_tag_test.dart +++ b/test/unit/tooling/check_webgpu_bridge_tag_test.dart @@ -68,12 +68,12 @@ const String _approvedManifestJson = ''' { "artifacts": { "llama_webgpu_bridge.d.ts": { - "sha256": "766adcfe0b48c07c26c478a7c9b9dc72bff9386ff297c532baae709ff6367db4", - "size_bytes": 23141 + "sha256": "e897f8308267d6e461140e5ad46badecfb8629283c4a7815cba1230a9802ba89", + "size_bytes": 23878 }, "llama_webgpu_bridge.js": { - "sha256": "0ecc0ebb7a0f752f48a95de7cefe1e8f2f6b4e955e8a4674f09d49e03dbcb06e", - "size_bytes": 317431 + "sha256": "c91d386f5b7a42c1a2d9ba9208203cea2e062bc494e98cfcccdfa398a2a2ba24", + "size_bytes": 320015 }, "llama_webgpu_bridge_worker.js": { "sha256": "47bbfa0fe897e708455b9497e9aa41d2e94489c329300c33b12936fb3169deca", @@ -84,21 +84,21 @@ const String _approvedManifestJson = ''' "size_bytes": 123583 }, "llama_webgpu_core.wasm": { - "sha256": "7e17fac640d50a33a2277036a891b2939f023c4864652b9866236092ac2cb6ee", - "size_bytes": 11099356 + "sha256": "06be047419dcd3fb55b8d6d28577dee658d9dbe0b7e240153841d05f37a1b8a5", + "size_bytes": 11099393 }, "llama_webgpu_core_mem64.js": { "sha256": "32b4689944aa4e3a940bddaf31928ab3476e57b9963b918cb38c1281e5688297", "size_bytes": 147340 }, "llama_webgpu_core_mem64.wasm": { - "sha256": "249ab51961555ccf42139b7cb1c05cbd83f62ce3d181aad45671cfc83bb94501", - "size_bytes": 11390643 + "sha256": "d81d1b4e7fecade75da7446885777ea7af7a31292fc43ed062fac9613092165d", + "size_bytes": 11390682 } }, "assets_repository": "leehack/llama-web-bridge-assets", - "bridge_assets_tag": "v0.1.84", - "bridge_commit": "dc359398bc6445ec15878ddb873555295f957f85", + "bridge_assets_tag": "v0.1.85", + "bridge_commit": "f1ac2fc1d4d4d3fda56c161ac6a629ae4c67a7d3", "bridge_repository": "leehack/llama-web-bridge", "capabilities": { "memory64": true, @@ -129,12 +129,12 @@ const String _approvedManifestJson = ''' "emscripten_version": "6.0.8", "files": { "llama_webgpu_bridge.d.ts": { - "sha256": "766adcfe0b48c07c26c478a7c9b9dc72bff9386ff297c532baae709ff6367db4", - "size_bytes": 23141 + "sha256": "e897f8308267d6e461140e5ad46badecfb8629283c4a7815cba1230a9802ba89", + "size_bytes": 23878 }, "llama_webgpu_bridge.js": { - "sha256": "0ecc0ebb7a0f752f48a95de7cefe1e8f2f6b4e955e8a4674f09d49e03dbcb06e", - "size_bytes": 317431 + "sha256": "c91d386f5b7a42c1a2d9ba9208203cea2e062bc494e98cfcccdfa398a2a2ba24", + "size_bytes": 320015 }, "llama_webgpu_bridge_worker.js": { "sha256": "47bbfa0fe897e708455b9497e9aa41d2e94489c329300c33b12936fb3169deca", @@ -145,27 +145,27 @@ const String _approvedManifestJson = ''' "size_bytes": 123583 }, "llama_webgpu_core.wasm": { - "sha256": "7e17fac640d50a33a2277036a891b2939f023c4864652b9866236092ac2cb6ee", - "size_bytes": 11099356 + "sha256": "06be047419dcd3fb55b8d6d28577dee658d9dbe0b7e240153841d05f37a1b8a5", + "size_bytes": 11099393 }, "llama_webgpu_core_mem64.js": { "sha256": "32b4689944aa4e3a940bddaf31928ab3476e57b9963b918cb38c1281e5688297", "size_bytes": 147340 }, "llama_webgpu_core_mem64.wasm": { - "sha256": "249ab51961555ccf42139b7cb1c05cbd83f62ce3d181aad45671cfc83bb94501", - "size_bytes": 11390643 + "sha256": "d81d1b4e7fecade75da7446885777ea7af7a31292fc43ed062fac9613092165d", + "size_bytes": 11390682 } }, - "github_run_id": "43272135497", - "github_run_url": "https://github.com/leehack/llama-web-bridge/actions/runs/43272135497", + "github_run_id": "43520247188", + "github_run_url": "https://github.com/leehack/llama-web-bridge/actions/runs/43520247188", "llama_cpp_commit": "cd959bae99ff4bed22bef2604f45241c09bbb8c8", "llama_cpp_tag": "v0.9.0", "native_commit": "a604639131105c42a25a3e7384ca10314d385f80", "native_manifest_sha256": "0d2119cdcf34d347a96a996423094498de3393026efb6ad1a96ba286b621c39e", "native_release_tag": "v0.9.0", "native_repository": "leehack/llamadart-native", - "orchestrator_correlation_id": "manual-v0.1.84", + "orchestrator_correlation_id": "manual-v0.1.85", "qualification_gates": { "multimodal": "passed", "speech_to_text": "required-automated-qualification", @@ -174,9 +174,9 @@ const String _approvedManifestJson = ''' }, "release_channel": "stable", "release_rebuild": 0, - "release_tag": "v0.1.84", + "release_tag": "v0.1.85", "schema_version": 2, - "source_commit": "dc359398bc6445ec15878ddb873555295f957f85", + "source_commit": "f1ac2fc1d4d4d3fda56c161ac6a629ae4c67a7d3", "source_repository": "leehack/llama-web-bridge", "unproven_capabilities": { "hardware_gpu_acceleration": "unavailable-on-hosted-runners", @@ -197,7 +197,7 @@ Future> _verifyManifestJson( }) { final bytes = utf8.encode(manifestJson); return verifyManifest( - expectedTag: 'v0.1.84', + expectedTag: 'v0.1.85', expectedLlamaCppTag: bridgeLlamaCppTag, expectedLlamaCppCommit: bridgeLlamaCppCommit, expectedBridgeCommit: bridgeSourceCommit, @@ -223,7 +223,7 @@ String _currentReleaseNotes(String path) { void main() { test('default assets include the qualified media embedding API', () { - expect(readPinnedBridgeTag(Directory.current), 'v0.1.84'); + expect(readPinnedBridgeTag(Directory.current), 'v0.1.85'); expect(bridgeNativeReleaseTag, 'v0.9.0'); expect(bridgeApprovedNativePin, 'v0.9.0'); expect( @@ -285,7 +285,7 @@ void main() { 'website/docs/changelog/recent-releases.md', ]) { final current = _currentReleaseNotes(path); - expect(current, contains('`v0.1.84`'), reason: path); + expect(current, contains('`v0.1.85`'), reason: path); expect(current, contains(bridgeManifestSha256), reason: path); expect( current, @@ -1262,8 +1262,8 @@ void main() { }); test('immutable release identity stays pinned to the approved release', () { - expect(bridgeAssetsReleaseId, '443341300'); - expect(bridgeAssetsTagCommit, '0a3c45f24fd6293050e1772bed4a425b2b5a8493'); + expect(bridgeAssetsReleaseId, '444874209'); + expect(bridgeAssetsTagCommit, '28a6180925e74e1038ee4c22cde6b403ae21ed84'); }); test('accepts equivalent CRLF documentation passages', () { diff --git a/tool/testing/check_webgpu_bridge_tag.dart b/tool/testing/check_webgpu_bridge_tag.dart index a0a06bc04..60fdd9742 100644 --- a/tool/testing/check_webgpu_bridge_tag.dart +++ b/tool/testing/check_webgpu_bridge_tag.dart @@ -372,7 +372,7 @@ const String bridgeLlamaCppTag = 'v0.9.0'; const String bridgeLlamaCppCommit = 'cd959bae99ff4bed22bef2604f45241c09bbb8c8'; /// The exact bridge source commit used to build the pinned bridge assets. -const String bridgeSourceCommit = 'dc359398bc6445ec15878ddb873555295f957f85'; +const String bridgeSourceCommit = 'f1ac2fc1d4d4d3fda56c161ac6a629ae4c67a7d3'; /// Canonical repository identities recorded in the approved manifest. const String bridgeAssetsRepository = 'leehack/llama-web-bridge-assets'; @@ -393,10 +393,10 @@ const String bridgeApprovedNativePin = 'v0.9.0'; const String bridgeApprovedNativeUpstreamAhead = 'v0.9.0'; /// The asset repository release that published the pinned bridge assets. -const String bridgeAssetsReleaseId = '443341300'; +const String bridgeAssetsReleaseId = '444874209'; /// The asset repository commit the pinned bridge asset tag points at. -const String bridgeAssetsTagCommit = '0a3c45f24fd6293050e1772bed4a425b2b5a8493'; +const String bridgeAssetsTagCommit = '28a6180925e74e1038ee4c22cde6b403ae21ed84'; /// SHA-256 hash of the exact approved published manifest.json. /// diff --git a/website/docs/changelog/recent-releases.md b/website/docs/changelog/recent-releases.md index be3488433..21a838fb4 100644 --- a/website/docs/changelog/recent-releases.md +++ b/website/docs/changelog/recent-releases.md @@ -14,6 +14,10 @@ validate against the exact release tag notes. ## Unreleased - Chat app microphone: a microphone that disconnects mid-recording now transcribes the audio captured so far with a warning, a missing input device on Web is reported as such instead of as a permission denial, and a failed Web start releases the microphone ([#329](https://github.com/leehack/llamadart/issues/329)). +- Aligned default WebGPU bridge assets to `v0.1.85`: a page without cross-origin isolation now fails at once with a `LlamaUnsupportedException` naming COOP/COEP `require-corp` instead of waiting minutes for a generic init timeout, and Firefox CPU runs (`gpuLayers: 0`) no longer create the WebGPU backend (stories15M, 32 tokens: 640 to 762 tok/s against 10 before) ([#1025](https://github.com/leehack/llamadart/issues/1025)). +- `ComputeDevice.gpu` on Web now names why WebGPU was not used (for example `adapter_missing_shader_f16`: Chrome on Linux with NVIDIA needs driver 615.71 or newer); documented the measured Safari 27, Firefox 160 and NVIDIA L4 results ([#1025](https://github.com/leehack/llamadart/issues/1025)). + +Runtime baseline: native llama.cpp `v0.9.0` and Web use upstream `v0.9.0@cd959bae99ff4bed22bef2604f45241c09bbb8c8`; native LiteRT-LM uses `v0.19.1-4` and stable-diffusion uses `v0.3.0-3`. Web LiteRT-LM uses `@litert-lm/core@0.19.0`. Manifest SHA-256: `6767783edd85e14853a8a79cd6ce53d53f4eb2d99aeb62601014bd68c1b6b7b3`. ## 0.19.9 diff --git a/website/docs/platforms/known-platform-issues.md b/website/docs/platforms/known-platform-issues.md index 91372cf29..864db730f 100644 --- a/website/docs/platforms/known-platform-issues.md +++ b/website/docs/platforms/known-platform-issues.md @@ -223,6 +223,20 @@ feature. For which runtime supports which feature, see the memory limit. Loading Gemma 4 E2B through LiteRT-LM Web peaks near 7 GB, so keep the tab visible until the model finishes loading ([#1025](https://github.com/leehack/llamadart/issues/1025)). +- Measured on the pinned bridge assets (`v0.1.85+`): + - Safari 27: CPU and WebGPU chat work. It needs + `Cross-Origin-Embedder-Policy: require-corp` and runs the 32-bit core only + (no memory64; Safari caps 64-bit memory at 4 GiB). It keeps one GPU layer + by default ([llama-web-bridge#217](https://github.com/leehack/llama-web-bridge/issues/217)), + and media embeddings run on the CPU. + - Firefox 160: works. CPU at 0 GPU layers on an isolated page runs 640 to 762 tok/s (stories15M, + 32 tokens; 10 tok/s before); WebGPU + is slow (about 0.7 tok/s, Firefox syncs the GPU on a 100 ms tick), so use + the CPU there. + - Chrome 157 on Linux with an NVIDIA L4: WebGPU is active, text matches the + CPU and embedding cosine similarity is at least 0.9999, but only with + NVIDIA driver 615.71 or newer. Older drivers make Chrome hide `shader-f16`, + so the load runs on the CPU (`gpu_unavailable:adapter_missing_shader_f16`). - Browsers without WebAssembly relaxed SIMD, including Safari 27, run LiteRT-LM Web on CPU through the runtime's compat build, which can generate different tokens from Chrome's. With Qwen3 0.6B and default thinking, a diff --git a/website/docs/platforms/support-matrix.md b/website/docs/platforms/support-matrix.md index 8addb371e..3931a6f03 100644 --- a/website/docs/platforms/support-matrix.md +++ b/website/docs/platforms/support-matrix.md @@ -37,7 +37,7 @@ unchecked. See [scope and limits](./diagnose-setup#isolated-native-check). | macOS (arm64, x86_64) | CPU, Metal; on x86_64, Rosetta included, `auto` uses the CPU because Metal returns garbled output on Intel Macs with AMD GPUs ([#1597](https://github.com/leehack/llamadart/issues/1597)) | arm64: CPU, GPU; x86_64: none ([litert-lm-native#72](https://github.com/leehack/litert-lm-native/issues/72)) | macOS 14.0 (Flutter) | Qwen3-ASR (GGUF); LiteRT-LM ASR | Qwen3-TTS; LFM3-Audio, Soprano, Chatterbox, Pocket TTS | Validated on Metal and CPU | Metal, macOS 13.3; validated on arm64 | Supported | | Linux (arm64, x64) | CPU, Vulkan; BLAS opt-in; x64: CUDA, HIP opt-in | arm64: CPU; x64: CPU, explicit GPU | Not set by llamadart | Qwen3-ASR (GGUF); LiteRT-LM ASR | Qwen3-TTS; LFM3-Audio, Soprano, Chatterbox, Pocket TTS | Untested | CPU or Vulkan; x64 needs AVX2; validated on x64 (CPU, NVIDIA L4) | Supported | | Windows (arm64, x64) | CPU, Vulkan; BLAS opt-in; x64: CUDA opt-in | x64: CPU, explicit GPU ([Qwen unqualified](./known-platform-issues#linux-and-windows)); arm64: none | Not set by llamadart | Qwen3-ASR (GGUF); LiteRT-LM ASR on x64 | Qwen3-TTS; LFM3-Audio, Soprano, Chatterbox, Pocket TTS | Untested | x64 CPU or Vulkan, needs AVX2 and the Visual C++ runtime; validated on Windows Server 2022 (CPU, NVIDIA L4) | Supported | -| Web | WebGPU, WebAssembly CPU | CPU, GPU through `@litert-lm/core` | Chrome 128, Firefox 129, Safari 17.4 (LiteRT-LM GPU text, history and chat app checked in Safari 27 on Apple silicon with `@litert-lm/core@0.19.0`; CPU [differs](./known-platform-issues#vulkan-and-the-web), [#1802](https://github.com/leehack/llamadart/issues/1802)) | Qwen3-ASR, WAV, MP3 or FLAC bytes | Qwen3-TTS, memory64 | Checked in headless Chromium on macOS | Not yet ([#969](https://github.com/leehack/llamadart/issues/969)) | Experimental | +| Web | WebGPU, WebAssembly CPU | CPU, GPU through `@litert-lm/core` | Chrome 128, Firefox 129, Safari 17.4 (llama.cpp measured on Safari 27, Firefox 160 and Linux NVIDIA Chrome 157: [known issues](./known-platform-issues#vulkan-and-the-web); needs `require-corp` isolation; LiteRT-LM GPU text, history and chat app checked in Safari 27 on Apple silicon with `@litert-lm/core@0.19.0`; CPU [differs](./known-platform-issues#vulkan-and-the-web), [#1802](https://github.com/leehack/llamadart/issues/1802)) | Qwen3-ASR, WAV, MP3 or FLAC bytes | Qwen3-TTS, memory64 | Checked in headless Chromium on macOS | Not yet ([#969](https://github.com/leehack/llamadart/issues/969)) | Experimental | On Windows, the llama.cpp and LiteRT-LM bundles ship the Visual C++ runtime DLLs they need, so they need no Visual C++ Redistributable. The opt-in diff --git a/website/docs/platforms/webgpu-bridge.md b/website/docs/platforms/webgpu-bridge.md index fdacf6bd9..eb7e763b4 100644 --- a/website/docs/platforms/webgpu-bridge.md +++ b/website/docs/platforms/webgpu-bridge.md @@ -49,9 +49,12 @@ after load. The bridge ignores negative `mainGpu` values, including with credentialless` isolates the page in Chromium-based browsers only: Safari 27 reports `crossOriginIsolated === false` with it and `true` with `require-corp` ([#1025](https://github.com/leehack/llamadart/issues/1025)). - Without isolation (`window.crossOriginIsolated === false`) the bridge caps - inference at one thread and records `threads_capped_no_coi` in its runtime - notes. + Without isolation (`window.crossOriginIsolated === false`) the web core + cannot start, and bridge assets `v0.1.85+` reject the load at once + with `CrossOriginIsolationError` (`code: 'CROSS_ORIGIN_ISOLATION_REQUIRED'`). + llamadart reports it as a `LlamaUnsupportedException` that names COOP/COEP + `require-corp`; earlier assets waited minutes (the init timeout, retried) before a generic + `Bridge core init timeout` error. A supported browser version does not guarantee that a GGUF loads with WebGPU offload; the GPU, driver, OS, flags and memory pressure all matter. @@ -383,6 +386,8 @@ When retries run out, the load throws an error with runtime hints such as | Memory, OOM, `bad_alloc` or abort during load | Model or config pressure | Reduce model size, context, threads or GPU layers; use memory64. | | Safari forces CPU | Safari safeguard | Set `__llamadartBridgeAdaptiveSafariGpu` from the loaded assets, or `__llamadartAllowSafariWebGpu` for testing. | | Works on `localhost` but not hosted | Deployment | Check base href, asset paths, COOP/COEP headers and service-worker cache. | +| `CrossOriginIsolationError` or "needs cross-origin isolation" | Page is not isolated | Send `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`; `credentialless` does not isolate Safari. | +| `ComputeDevice.gpu` fails with "without GPU layers" and `gpu_unavailable:adapter_missing_shader_f16` | Adapter lacks `shader-f16` | WebGPU needs `shader-f16`. On Linux with an NVIDIA GPU, Chrome exposes it only with driver 615.71 or newer; older drivers run on the CPU. | | GPU output unstable, CPU fine | Adapter, feature or driver | Check adapter features such as `shader-f16`; lower GPU layers. | More cases: [Troubleshooting](../troubleshooting/common-issues#web). @@ -406,10 +411,10 @@ first model load, for diagnosis or controlled deployments: ## Pinned bridge assets -This llamadart version pins bridge assets `v0.1.84`, which include the +This llamadart version pins bridge assets `v0.1.85`, which include the API-version-1 media embedding contract. -- The pinned `v0.1.84` bridge assets embed llama.cpp `v0.9.0`, matching the native runtime. +- The pinned `v0.1.85` bridge assets embed llama.cpp `v0.9.0`, matching the native runtime. Provenance and the pinned manifest digest are in [`doc/webgpu_bridge.md`](https://github.com/leehack/llamadart/blob/main/doc/webgpu_bridge.md). @@ -417,7 +422,7 @@ Provenance and the pinned manifest digest are in In a llamadart checkout, vendor the pinned assets into the chat app with: ```bash -WEBGPU_BRIDGE_ASSETS_TAG=v0.1.84 ./scripts/fetch_webgpu_bridge_assets.sh +WEBGPU_BRIDGE_ASSETS_TAG=v0.1.85 ./scripts/fetch_webgpu_bridge_assets.sh ``` The chat app bootstrap takes its CDN source from these globals: @@ -425,7 +430,7 @@ The chat app bootstrap takes its CDN source from these globals: ```html ``` @@ -464,4 +469,5 @@ probe, not the tag. | `v0.1.75` | A draft context that does not fit throws `LlamaUnsupportedException` and the target stays usable | The draft leaks until the runtime aborts | | `v0.1.76` | The projector copy behind the repeated wasm32 media batch abort is freed (repeated batches qualified on wasm32, measured on the pinned assets; memory64 recommended for headroom) | Repeated media batches can abort on wasm32 | | `v0.1.79+` | A worker image or audio `embed` gets 10 minutes per input | Two minutes per request | +| `v0.1.85+` | A page without cross-origin isolation rejects the load at once (`CrossOriginIsolationError`); `gpu_unavailable:` runtime note; 0 GPU layers skip the WebGPU backend | Minutes of init timeout, then a generic error; no reason note; the WebGPU backend is created anyway | | `v0.1.84+` | A thinking budget keeps a grammar that takes the thought applied (`thinkingBudgetPauseGrammar`) | That combination throws `LlamaUnsupportedException` | diff --git a/website/docs/troubleshooting/common-issues.md b/website/docs/troubleshooting/common-issues.md index 2c3ecd804..9e371555c 100644 --- a/website/docs/troubleshooting/common-issues.md +++ b/website/docs/troubleshooting/common-issues.md @@ -235,7 +235,13 @@ In the browser console, `window.LlamaWebGpuBridge` should exist and 1. Check browser capability: secure context, `navigator.gpu`, `requestAdapter()`, adapter features and limits, and current GPU drivers. 2. For large single-file GGUF loads, check `window.crossOriginIsolated === true` - and that the origin sends COOP/COEP headers. + and that the origin sends COOP/COEP headers (`require-corp`; `credentialless` + does not isolate Safari). Bridge assets `v0.1.85+` fail at once with + a `LlamaUnsupportedException` naming cross-origin isolation. + When `ComputeDevice.gpu` fails with "without GPU layers", the message names + the bridge's `gpu_unavailable:*` reason; `adapter_missing_shader_f16` means + Chrome hid `shader-f16` (on Linux with NVIDIA, update to driver 615.71 or + newer). 3. `bad_alloc`, `memory access out of bounds` or aborts usually mean the model, context size, thread count or GPU-layer count is too large for the browser. Reduce them before treating the failure as a bridge problem.