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.