This document describes the browser-facing JavaScript API exported by
llama_webgpu_bridge.js. It is the reference for applications that use the
bridge assets directly instead of going through a higher-level SDK such as
llamadart.
The TypeScript declaration shipped with the browser asset is
llama_webgpu_bridge.d.ts; this page explains the runtime behavior behind those
signatures.
import {
LlamaWebGpuBridge,
enableBridgeWorkerHost,
} from './webgpu_bridge/llama_webgpu_bridge.js';A browser page that imports the bridge also receives window.LlamaWebGpuBridge
when the global is not already defined. Worker bootstrap code can either import
llama_webgpu_bridge_worker.js or import llama_webgpu_bridge.js and call
enableBridgeWorkerHost().
const bridge = new LlamaWebGpuBridge({
coreModuleUrl: './webgpu_bridge/llama_webgpu_core.js',
wasmUrl: './webgpu_bridge/llama_webgpu_core.wasm',
workerUrl: './webgpu_bridge/llama_webgpu_bridge_worker.js',
threads: 2,
});
try {
await bridge.loadModelFromUrl('./models/model.gguf', {
nCtx: 2048,
nGpuLayers: 0,
progressCallback: ({ loaded, total }) => {
if (total) {
console.log(`loaded ${Math.round((loaded / total) * 100)}%`);
}
},
});
const answer = await bridge.createCompletion('2+2 =', {
nPredict: 16,
temp: 0,
onToken: (piece, currentText) => {
const text = typeof piece === 'string'
? piece
: new TextDecoder().decode(piece);
console.log('token', text, currentText);
},
});
console.log(answer);
} finally {
await bridge.dispose();
}LlamaWebGpuBridge is a facade over two execution modes:
- Worker mode is the default when a worker can be created. Model loading, generation, tokenization, embeddings, state APIs, and multimodal operations run in the worker runtime.
- Direct runtime mode is used when
disableWorker: true, when a customcoreModuleFactoryis supplied, or after recoverable worker setup/load errors.
Some APIs deliberately keep worker and direct runtime state separate. In particular, worker WASMFS paths and direct-runtime WASMFS paths are not shared. Use the byte state APIs when an application needs to persist data outside the active bridge runtime.
Most methods require a model loaded by loadModelFromUrl() and reject with
Error when the model or runtime capability is unavailable.
Runtime-backed methods execute through a per-instance single-writer FIFO queue in both worker and direct runtime modes:
loadModelFromUrl, loadMultimodalProjector, unloadMultimodalProjector,
loadDraftModel, unloadDraftModel, getTextToSpeechCapabilities,
synthesizeSpeech, getDecisionCapabilities, loadDecisionHead,
runDecision, freeDecisionHead, getLoraAdapterCapabilities,
loadLoraAdapter, setLoraAdapter, removeLoraAdapter, clearLoraAdapters,
createCompletion, getCompletionCapabilities, tokenize, detokenize,
stateSaveFile, stateLoadFile, stateSaveBytes,
stateLoadBytes, embed, embedBatch, scoreNextToken, applyChatTemplate.
Overlapping calls wait their turn and run in call order. A failing operation releases the queue for the calls behind it, and separate bridge instances never block each other.
These methods are deliberately not queued:
| Method | Observable guarantee |
|---|---|
cancel() |
Out-of-band control signal that reaches the running operation immediately, scoped to the worker generation and runtime that operation captured. |
setLogLevel(level) |
Synchronous, best-effort control signal; delivery failures are absorbed without switching execution modes. |
prefetchModelToCache(), evictModelFromCache() |
Cache and network operations that do not enter the model runtime. |
getModelMetadata(), getContextSize(), isGpuActive(), getBackendName(), supportsVision(), supportsAudio() |
Synchronous accessors served from the current bridge snapshot. |
createCompletion(), synthesizeSpeech() and loadModelFromUrl() accept a
signal. A call whose signal is already aborted, or that aborts while it is
still waiting in the queue, rejects with an AbortError DOMException, never
dispatches to the worker and never enters the core; its slot is skipped when its
turn arrives, and cancel() is not invoked, so the unrelated operation that
currently owns the runtime is unaffected.
Once an operation has started, cancellation is scoped to that operation.
Aborting its signal, or calling cancel() while it owns the runtime, reaches
the worker generation and runtime the operation captured when it started, so a
cancel issued after an internal worker restart or main-thread fallback still
targets the owner of the request being cancelled rather than the replacement. A
queued successor is unaffected and dispatches normally once the cancelled
operation reaches a terminal state.
Cancellation remains cooperative, not preemptive. A direct model transfer is checked before each stream read/write and immediately before native model-load entry; an abort that arrives before that entry skips the native call and removes partial files. Once an unpreemptible native call has started, cancellation waits for it to return and then discards and cleans up its result.
dispose() marks the bridge disposed synchronously and then tears down on the
queue. Calls made after disposal, and calls still waiting in the queue, reject
with Bridge has been disposed. The operation that was already running is not
interrupted by disposal — it settles first, and teardown follows. dispose() is
idempotent and repeated calls return the identical promise object.
new LlamaWebGpuBridge(config?: LlamaWebGpuBridgeConfig)Creates a bridge instance. The constructor may start a worker proxy immediately, but the llama.cpp core is initialized lazily by the first operation that needs it.
| Option | Type | Description |
|---|---|---|
coreModuleUrl |
string |
URL for the wasm32 Emscripten JS loader, usually llama_webgpu_core.js. |
coreModuleUrlMem64 |
string |
URL for the optional wasm64 Emscripten JS loader. |
wasmUrl |
string |
URL for the wasm32 llama_webgpu_core.wasm binary. |
wasmUrlMem64 |
string |
URL for the optional wasm64 wasm binary. |
coreModuleFactory |
function | Promise<function> |
Preloaded Emscripten factory. Supplying this disables worker mode because factories cannot be transferred to the worker. |
workerUrl |
string |
URL for llama_webgpu_bridge_worker.js. Defaults to a sibling of the bridge module when possible. |
disableWorker |
boolean |
Force direct runtime mode. |
preferMemory64 |
boolean |
Prefer the wasm64 core when available. Defaults to true; the runtime can still fall back to wasm32. |
workerRequestTimeoutMs |
number |
Default timeout for worker RPC calls. |
workerInitTimeoutMs |
number |
Timeout for worker initialization. |
workerModelLoadTimeoutMs |
number |
Timeout for model load requests sent to a worker. |
workerMmprojLoadTimeoutMs |
number |
Timeout for multimodal projector load requests sent to a worker. |
workerCompletionTimeoutMs |
number |
Timeout for worker createCompletion RPC requests. |
workerTextToSpeechTimeoutMs |
number |
Timeout for a worker text-to-speech request. Defaults to 20 minutes and is refreshed by progress events. |
workerGenerationStallTimeoutMs |
number |
Stall timeout for worker generation after no token events arrive; clamped by the bridge. |
coreInitTimeoutMs |
number |
Timeout while initializing the Emscripten core module. |
cacheName |
string |
Cache Storage name used by model prefetch/load helpers. |
threads |
number |
Requested llama.cpp thread count. The bridge caps this to the compiled pthread pool and runtime isolation support. |
threadsBatch |
number |
Requested batch thread count; defaults to threads. |
threadPoolSize |
number |
Hint for the compiled pthread pool size, used before the core can report it. |
nBatch |
number |
llama.cpp batch size override. |
nUbatch |
number |
llama.cpp micro-batch size override. |
nGpuLayers |
number |
Default GPU layer count. 0 forces CPU/WASM execution; negative/omitted values let the bridge choose. |
userAgent |
string |
User-agent override for Safari-specific GPU safeguards. |
remoteFetchThresholdBytes |
number |
Size threshold for trying the native remote-fetch backend for large single-file models. |
remoteFetchChunkBytes |
number |
Chunk size used by remote-fetch streaming paths. |
mediaMaxImagePixels |
number |
Maximum image pixel count before multimodal image downscaling. |
mediaMaxImageEdge |
number |
Maximum image width/height before multimodal image downscaling. |
disableImageDownscale |
boolean |
Disable bridge-side image downscaling for multimodal image parts. |
allowAutoRemoteFetchBackend |
boolean |
Allow automatic selection of the native remote-fetch backend when the model size qualifies. |
logLevel |
string | number |
Bridge/core logging level. Numeric values are clamped to the supported core range. |
Unknown config keys are accepted and may be consumed by current or future bridge internals.
Worker timeout options are forwarded to the proxy, and coreInitTimeoutMs also
reaches the runtime inside the worker. Positive request budgets are truncated
and clamped to 5 seconds–1 hour; per-method settings override the global request
budget. Non-positive or non-finite settings retain the existing fallback
budgets. Worker initialization uses a 3-second–2-minute clamp (20-second
default), and core initialization uses 10 seconds–10 minutes (90-second default).
boolean flag indicating that the bridge applies Safari-specific GPU layer
capping logic when Safari is detected.
true when createCompletion accepts onUsage. Bridges before v0.1.54 lack
the flag; through v0.1.52 their worker mode rejects a function option with a
DataCloneError. Check it before passing onUsage.
When text generation on wasm64/WebGPU qualifies for CPU recovery, the reload preserves the model's resolved native load settings, including sequence count, batch sizes, memory mapping/locking, KV cache types, flash attention, RoPE and device split settings. It changes GPU placement to CPU and retains the existing memory32 preference and transfer safeguards for recovery. Thread counts remain subject to the replacement core's pool and browser isolation limits. Recovery continues to refuse active LoRA adapters and speculative generation.
loadModelFromUrl(url: string | string[], options?: LoadModelOptions): Promise<unknown>Loads a GGUF model from one URL, an explicit shard URL array, or an auto-expanded
split GGUF URL such as model-00001-of-00002.gguf. The bridge streams model
bytes into the active WASM filesystem unless the native remote-fetch backend is
used for a qualifying large single-file model.
Common options keys:
| Option | Description |
|---|---|
progressCallback(progress) |
Receives aggregate { loaded, total } events. Split models may also include shard progress metadata. |
signal |
AbortSignal used to cancel model transfer. |
nCtx |
Context size. Defaults to the bridge runtime default, initially 4096. |
nThreads, nThreadsBatch |
Per-load thread overrides, capped to the active runtime. |
nGpuLayers |
Per-load GPU layer override. Use 0 for CPU/WASM mode. |
nBatch, nUbatch |
Per-load batch and micro-batch overrides. |
useCache, force |
Cache Storage controls for model responses. |
streamResumeRetries |
Retry count for resumable streamed model loads. |
remoteFetchThresholdBytes, remoteFetchChunkBytes |
Per-load remote-fetch tuning. |
loadMtp |
Loads the model's MTP layers, which draft-mtp speculative decoding needs. Defaults to false. |
speculativeRollbackTokenMax |
llama.cpp's n_rs_seq: rollback snapshots per sequence. A model with recurrent state, such as Qwen3.5, needs at least the draft length for draft-* speculative decoding. Defaults to 0. |
Returns the underlying load result from the active runtime. After a successful load, metadata and capability helpers reflect the loaded model.
Calling it again replaces the loaded model in the same runtime, in both direct
and worker modes. The bridge first frees the current model, its multimodal
projector and their filesystem copies, then downloads the new model, so both
never occupy the WASM heap at once. If the download or native load then
fails, no model stays loaded; call loadModelFromUrl() again. The core refuses
the release while a generation or speech synthesis is active, and the current
model stays loaded. Load a projector, LoRA adapters and a draft model again
after switching models.
prefetchModelToCache(url: string | string[], options?: LoadModelOptions): Promise<unknown>Fetches model URL(s) into Cache Storage without loading them into llama.cpp.
This uses a direct runtime helper even when the main bridge instance is currently
worker-backed. Use it to warm the browser cache before calling
loadModelFromUrl().
evictModelFromCache(url: string | string[], options?: Record<string, unknown>): Promise<unknown>Removes one model URL, explicit shard array, or expanded split-model URL set from Cache Storage. Returns the runtime eviction result.
createCompletion(prompt: string, options?: CompletionOptions): Promise<string>Runs llama.cpp generation for a loaded model and resolves to the final generated text. The bridge cleans up active generation state before returning or throwing.
Common options keys:
| Option | Description |
|---|---|
nPredict |
Maximum number of generated tokens. Defaults to 256. |
temp |
Sampling temperature. Defaults to 0.8. |
topK |
Top-k sampling. Defaults to 40. |
topP |
Top-p sampling. Defaults to 0.95. |
minP |
Min-P threshold from 0 to 1, applied after top-p. Defaults to 0, which disables it. |
penalty |
Repetition penalty. Defaults to 1.1. |
presencePenalty |
Subtracted once from the logit of each token among this completion's last 64 tokens; prompt tokens do not count. Defaults to 0, which disables it. |
grammar |
Optional llama.cpp GBNF grammar with a root rule. An invalid grammar rejects before generation starts with an error containing (invalid grammar); worker mode rethrows it without falling back to the main thread. Each rejection leaks the partly parsed grammar (a few KiB at most for typical grammars), so validate generated grammars before sending them in a loop. |
seed |
Integer seed; random when omitted. |
thinkingBudget |
{ maxTokens, startTag, endTag, forcedMessage? }: llama.cpp's reasoning budget. After maxTokens tokens inside a block opened by startTag, plus any that finish a UTF-8 character, the sampler forces forcedMessage (default empty) and endTag. A block the prompt leaves open counts from the start of generation, so maxTokens: 0 forces the end at once. Each new startTag gets a fresh budget. A grammar constrains startTag like any other text, then pauses until endTag completes. maxTokens is an integer from 0 to 2147483647; the tags must be non-empty. Each tag is tokenized on its own, as native llamadart does: with a vocabulary that adds a leading space to text, such as Llama 2's SentencePiece, a tag in the prompt matches only after a space, and the forced end tag starts with one. Text-only prompts: with parts it rejects. |
onUsage(usage) |
Called at most once, with the CompletionUsage of the last attempt whose generation returned, before the promise resolves or rejects with an AbortError. Not called when no generation returned: other rejections, a skipped multimodal warmup, or a worker failure after a cancel. |
onToken(piece, currentText) |
Token callback. By default piece is a Uint8Array containing stable UTF-8 bytes. Direct runtime mode provides the current full text by default; worker mode provides '' unless emitCurrentTextOnToken: true is set. |
signal |
AbortSignal; aborting cancels this operation and rejects with AbortError. |
warmup |
Marks a warmup generation. Some multimodal worker setup failures return an empty string instead of failing warmup. |
emitCurrentTextOnToken |
Direct runtime defaults to current text and uses null when set to false; worker mode sends current text only when this is true and otherwise sends ''. |
tokenEventEncoding |
'bytes' (default) sends Uint8Array pieces; 'text' sends string pieces. Worker events may already provide text pieces. |
tokenEventFlushMs |
Worker mode coalesces token events for up to this many milliseconds, for both byte and text encoding. 0 (default) disables coalescing. Values are clamped to 0..200. |
tokenEventFlushChars |
When worker coalescing is enabled, flush once the buffered decoded text reaches this many JavaScript characters. 0 (default) disables the size threshold. Values are clamped to 1..1024 when positive. |
parts |
Optional multimodal parts after a projector is loaded. Image parts require { type: 'image', bytes } or { type: 'image', url }; audio parts require { type: 'audio', samples }, { type: 'audio', bytes }, or { type: 'audio', url }. bytes and samples accept an ArrayBuffer, any ArrayBuffer view, or a number array. Image width and height identify raw RGB bytes when their dimensions match the byte length. |
mediaMaxPredict |
Cap for multimodal generation token count. |
speculativeDecoding |
{ strategies, ... }: llama.cpp speculative decoding; see Speculative decoding. |
Call cancel() or abort the supplied signal to request a best-effort stop.
An invalid minP, a presencePenalty that is not finite as a 32-bit float,
or an invalid thinkingBudget rejects with a RangeError or TypeError before
generation starts. A nonzero minP or presencePenalty, or any
thinkingBudget, rejects when the loaded core does not apply that option.
CompletionUsage fields:
| Field | Description |
|---|---|
promptTokens |
Prompt tokens in the context when generation started, including cachedPromptTokens. For a multimodal prompt, the context positions it filled. |
cachedPromptTokens |
Leading prompt tokens kept from the previous prompt or a loaded state instead of being evaluated again; 0 for a multimodal prompt. |
completionTokens |
Generated tokens in the returned text. The end-of-generation token is not counted. |
timeToFirstTokenMs |
Milliseconds from the runtime starting the completion to its first streamed text, or null when it streamed none. |
durationMs |
Milliseconds from the runtime starting the completion to its end. |
finishReason |
stop at an end-of-generation token, length at nPredict or the context limit, cancelled after cancel() or an abort during generation. |
speculative |
Present only for a speculativeDecoding completion: { draftTokens, acceptedDraftTokens, draftAttempts, verifyTokens, replayTokens }, the drafted and accepted tokens, draft steps (including those that drafted nothing), target tokens decoded to verify drafts, and target tokens decoded again after restoring a recurrent-state checkpoint. |
Both times are measured by the runtime that generates, inside the worker in
worker mode. They include staging media and evaluating the prompt, and exclude
time queued behind another operation. After an internal retry or main-thread
fallback, the usage is that of the last attempt whose generation returned. In
worker mode an abort during generation rejects the promise, while the direct
runtime resolves it with the partial text; in both, onUsage reports the
cancelled generation once it returns.
getCompletionCapabilities(): Promise<CompletionCapabilities>Returns { presencePenalty, minP, thinkingBudget, speculativeDecoding }:
whether the loaded core applies each createCompletion option. Every flag is
false until a model load initializes the core. speculativeDecoding maps each
strategy to whether the loaded models can run it now: the n-gram strategies need
only a speculative-capable core, draft-mtp needs a model with MTP layers
loaded with loadMtp: true, and the other draft-* strategies need a matching
draft from loadDraftModel().
speculativeDecoding drafts tokens and verifies them with the loaded model, as
llama.cpp's llama-server does. Every sampled token comes from the target
model, so greedy sampling (temp: 0 or topK: 1) produces the same tokens as a
completion without it. Strategies use llama.cpp's --spec-type names:
| Strategy | Drafts from |
|---|---|
ngram-simple, ngram-map-k, ngram-map-k4v |
Matches of the last ngramSizeN tokens in the prompt and output. |
ngram-mod |
A hashed pool of earlier n-grams. |
ngram-cache |
Prompt and output n-grams, optionally with ngramCacheStatic/ngramCacheDynamic files from llama-lookup-create. |
draft-simple |
A standalone draft model from loadDraftModel() that shares the target's vocabulary. |
draft-eagle3 |
An EAGLE3 draft from loadDraftModel(). |
draft-dflash, draft-dspark |
A DFlash-architecture block draft from loadDraftModel(); llama.cpp reads which of the two it is from its tensors. |
draft-mtp |
The target's own MTP layers; load the model with loadMtp: true. |
strategies combines any n-gram strategies with at most one draft-*
strategy. The other keys are optional; omitted ones keep llama.cpp's defaults:
| Key | Description |
|---|---|
draftTokenMax |
Maximum draft tokens per step, an integer from 0. draft-* strategies use it or 3, ngram-simple/ngram-map-k/ngram-map-k4v use ngramSizeM or 48, ngram-mod uses ngramTokenMax, then it, then 64, and ngram-cache uses it or 8. The largest applies; 0 becomes 64. The resolved value must not exceed the context size. |
draftTokenMin, minProbability, draftSplitProbability |
Draft-model minimum length, minimum token probability and split probability. They need a draft-* strategy. |
ngramSizeN, ngramSizeM, ngramMinHits |
Lookup size, draft size and minimum hits of ngram-simple, ngram-map-k and ngram-map-k4v, from 1 to 65535. |
ngramMatch, ngramTokenMin, ngramTokenMax |
Lookup length (1 to 65535) and draft length bounds of ngram-mod. ngramTokenMax defaults to draftTokenMax and must not exceed the context size. |
ngramCacheStatic, ngramCacheDynamic |
ngram-cache files as a URL string, ArrayBuffer or typed array. The dynamic cache is read, never written back. A malformed file rejects. |
The rules are those of native llamadart's SpeculativeDecodingConfig, and an
invalid combination rejects with a TypeError, RangeError or Error before
any work. Speculative decoding needs a text-only prompt and rejects with
grammar, thinkingBudget or parts. It evaluates the whole prompt instead of
reusing a cached prefix, and each step drafts at most nPredict minus the
tokens generated so far, minus one.
A model with recurrent state cannot drop rejected draft tokens from its memory.
With the n-gram strategies the bridge saves that state before each verification
and replays the accepted tokens after a rejection (replayTokens). The
draft-* strategies instead need the model loaded with
speculativeRollbackTokenMax of at least the draft length, and each draft is
capped at that value. A request the loaded models cannot run, such as
draft-mtp without MTP layers or draft-dflash with a DSpark draft, rejects
with a message naming what is missing.
loadDraftModel(url: string, options?: DraftModelLoadOptions): Promise<DraftModelInfo>Downloads a draft GGUF and loads it with the target model's load settings,
replacing any draft. options takes progressCallback, signal, useCache
and force, as loadModelFromUrl() does. Resolves to { architecture, strategy? }: the GGUF's general.architecture, and draft-dflash or
draft-dspark for a DFlash-architecture draft. An EAGLE3 or DFlash draft built
for a different hidden size than the loaded model's rejects, naming both sizes.
A target model load unloads the
draft; the bridge reloads it after it reloads the target on its own, such as
after a worker restart, and warns and forgets it when that reload fails.
The draft is staged in the WASM heap before it loads. When the server reports the draft's size and it would not fit in the memory left, the call rejects before reading the body; on the wasm32 core, whose heap is limited to 4 GiB, the message says a memory64 core is required.
unloadDraftModel(): Promise<void>Frees the draft model. draft-simple, draft-eagle3, draft-dflash and
draft-dspark then reject until a draft is loaded again.
tokenize(text: string, addSpecial = true): Promise<number[]>Tokenizes text with the loaded model. addSpecial controls whether llama.cpp
adds model-specific special tokens.
detokenize(tokens: number[] | ArrayLike<number>, special = false): Promise<string>Converts token IDs back to text. Non-array inputs are converted with
Array.from().
applyChatTemplate(
messages: Array<Record<string, unknown>>,
addAssistant = true,
customTemplate: string | null = null,
): Promise<string>Builds a prompt from chat messages. The current JavaScript bridge uses its
built-in fallback formatter and ignores customTemplate; higher-level SDKs may
supply their own template logic before calling createCompletion().
The bridge exposes llama.cpp state/session save and load helpers after a model is loaded. State snapshots are tied to the same model, llama.cpp build, and compatible load parameters.
The tokens argument records the already-evaluated prompt/prefix token list in
the state file; it does not cause the bridge to evaluate those tokens. Save only
after the prompt or prefix you want to restore has already been evaluated.
stateSaveFile(path: string, tokens?: number[] | ArrayLike<number>): Promise<true>Saves the current llama.cpp session to a path inside the active runtime's WASMFS
and stores the supplied token list in the session metadata. The method returns
true on success.
stateLoadFile(path: string, tokenCapacity = bridge.getContextSize()): Promise<{ tokens: number[] }>Loads a state/session file from the active runtime's WASMFS. A tokenCapacity
whose numeric conversion is greater than zero is truncated and used; all other
values fall back to the active context size. The resolved capacity must be at
least large enough for the stored token list and no larger than the active
context size. The resolved { tokens } value is the token list stored at save
time.
stateSaveBytes(tokens?: number[] | ArrayLike<number>): Promise<Uint8Array>Saves the current state to a temporary runtime file and returns its bytes. This is the preferred API for durable browser storage because the application can then store the bytes in IndexedDB, OPFS, Cache API, or another app-managed store.
stateLoadBytes(
bytes: Uint8Array | ArrayBuffer | ArrayLike<number>,
tokenCapacity = bridge.getContextSize(),
): Promise<{ tokens: number[] }>Loads state from bytes by staging them into a temporary runtime file. It uses the
same tokenCapacity resolution as stateLoadFile(). Empty input is rejected.
Worker mode transfers an internal copy to the worker, so caller-owned
ArrayBuffer or Uint8Array inputs are not detached.
getEmbeddingCapabilities(): Promise<{
apiVersion: 1; text: boolean; image: boolean; audio: boolean;
}>Probes the loaded model, allocated context, projector and mixed-batch API in
the active runtime. An unloaded model returns all false. Probe again after
projector load/unload; completion vision/audio support alone does not enable
media embeddings. Pooled embedding models support text; mixed media requires
a noncausal decoder with MEAN pooling and the extended batch API. Audio also
requires a 16 kHz projector. The production source pin lacks this mixed API;
it reports image/audio false. There is no minimum published assets tag yet.
An absent, malformed or unknown capability protocol throws a typed
EmbeddingError with code: 'EMBEDDING_CAPABILITY_UNAVAILABLE'; legacy string
embeddings remain available through their existing route.
After an unusable worker, embedding operations reload the model on the main
thread with the same options and GPU policy as existing string embeddings.
A remembered projector is restored eagerly for string, parts, batch and probe
fallbacks. Restoration is best effort: failure logs a payload-free warning and
keeps the model loaded and projector URL remembered. Capabilities report actual
loaded support, so image/audio remain false after failure. Each subsequent media
embedding or capability probe makes one further restoration attempt if the
projector is still unloaded. A failed media attempt rejects with
EMBEDDING_PROJECTOR_MISSING; strings and text-only parts remain usable without
retrying the projector. An already loaded or explicitly unloaded projector is
not retried. Cancellation during a restore rejects with AbortError; if the
bridge is disposed while a restore fails, a media call may reject with
EMBEDDING_PROJECTOR_MISSING.
type EmbedPart =
| { type: 'text'; text: string }
| { type: 'image'; bytes?: Uint8Array; url?: string; width?: number; height?: number }
| { type: 'audio'; samples?: Float32Array; bytes?: Uint8Array; url?: string };
type EmbedInput = string | EmbedPart[];
embed(input: EmbedInput, options?: { normalize?: boolean }): Promise<number[]>One string or one ordered parts array produces one full pooled vector. Strings
retain the existing execution route and defaults. Adjacent text is concatenated
verbatim, including empty strings, whitespace and Unicode. No task prefix,
separator, chat template, truncation or per-part averaging is added. Empty parts
arrays and sparse arrays reject. The literal <__media__> marker is reserved
in parts text, because it would make media ordering ambiguous.
For each media part provide exactly one source. Encoded image/audio bytes and
URLs use the completion media decoder and configured image downscaling limits;
raw RGB bytes use width * height * 3 as in completion input. PCM must be finite
mono 16 kHz Float32Array samples. Browser filesystem paths and video parts are
unsupported. URL inputs use the completion fetch behavior and require browser
fetch/CORS access; support for a modality does not guarantee every codec/source.
Caller buffers remain attached and unchanged; worker requests use structured
clone without transferring the caller's buffers. Lists and part descriptors are
snapshotted when queued. Keep buffers unchanged until the promise settles.
The entire expanded parts input must fit the actual context, per-sequence
context, logical batch and physical microbatch, with at least one sequence.
Overflow rejects before media encoding or model inference; the bridge never
chunks a parts input. Each mixed input uses one extended batch and one decode,
copying each media row into the batch before mtmd reuses its output buffer. Output width comes from
the pooled model output, which can differ from token embedding width.
Vectors are L2-normalized unless normalize === false; zero/nonfinite handling
matches the string path. Rank-pooled classifiers and encoder-decoder models
are unsupported by the parts API. Media capabilities are false for M-RoPE
projectors requiring multiple positions per embedding row.
const capabilities = await bridge.getEmbeddingCapabilities();
if (capabilities.apiVersion === 1 && capabilities.image) {
const vector = await bridge.embed([
{ type: 'text', text: 'A document: ' },
{ type: 'image', bytes: imageBytes },
]);
}Parts failures are payload-free EmbeddingError instances with a stable code:
| Code | Meaning |
|---|---|
INVALID_EMBEDDING_INPUT |
Empty/malformed parts, ambiguous media sources or reserved marker |
EMBEDDING_CAPABILITY_UNAVAILABLE |
Missing/unknown bridge or mixed-batch API |
MODEL_NOT_EMBEDDING |
Unsupported model, pooling or attention configuration |
EMBEDDING_PROJECTOR_MISSING |
Media requires a loaded projector |
EMBEDDING_MODALITY_UNSUPPORTED |
Unsupported part, projector modality or PCM rate |
EMBEDDING_INPUT_TOO_LARGE |
Expanded input exceeds allocated capacity |
EMBEDDING_FAILED |
Media preparation, format, batch construction or inference failure |
Worker transport preserves these error types/codes. cancel() keeps the
existing best-effort semantics: observed cancellation rejects with AbortError,
and work that finishes before observing it can still resolve. Active work settles
before cleanup, disposal or the next operation. Interrupt latency is not guaranteed.
A subsequent text request can reuse the runtime.
embedBatch(inputs: EmbedInput[], options?: { normalize?: boolean }): Promise<number[][]>Returns one vector per input, in input order. Empty input resolves to []
without inference. Part shapes and requested modalities across the batch are
checked before the first embedding; expanded capacity/format is checked for
each input before that input's inference. Execution is sequential; worker mode
forwards the batch as one request. Each parts array is one vector.
scoreNextToken(
prompt: string,
options?: {
candidates?: ArrayLike<number>;
topK?: number;
reusePromptPrefix?: boolean;
},
): Promise<{
candidates: { token: number; bytes: Uint8Array; logprob: number }[];
top: { token: number; bytes: Uint8Array; logprob: number }[];
promptTokens: number;
}>Evaluates prompt (tokenized with special tokens, like createCompletion) and
returns natural-log probabilities for the next position without generating. The
values are a softmax over the raw logits, like llama-server n_probs; sampling
settings do not apply.
candidatesscores the given token ids, in request order.topKreturns the most probable tokens, highest first; ties keep the lower id. Defaults to 0.reusePromptPrefixkeeps the KV cache prefix shared with the previous prompt or completion. Defaults totrue.bytesis the token's text as raw bytes; one piece can hold part of a UTF-8 sequence.logprobis-Infinityfor a zero probability, or when a non-finite logit leaves it undefined.
Each returned token costs a piece conversion, so keep topK small; values up to
the vocabulary size are accepted.
Pass candidates, a positive topK, or both. The call rejects when a token id
or topK is outside the vocabulary (the message contains
is outside the vocabulary), when media is pending, for encoder models, and
when the prompt exceeds the context. A cancel() issued while idle does not
affect later calls.
loadMultimodalProjector(url: string): Promise<unknown>Loads a multimodal projector (mmproj) file and updates the vision/audio
capability helpers. Multimodal generation uses createCompletion(prompt, { parts }) after this succeeds. The bridge may restart or switch worker/direct
execution modes to satisfy projector constraints.
unloadMultimodalProjector(): Promise<unknown>Unloads the active multimodal projector and clears cached multimodal capability state.
supportsVision(): boolean
supportsAudio(): booleanReturn the multimodal capabilities reported by the loaded projector. They return
false before a projector is loaded or after it is unloaded.
Text-to-speech is a versioned, capability-gated bridge feature. It requires a compatible text model and generated-audio projector; ordinary audio-capable multimodal models are not automatically TTS models.
The Qwen3-TTS 1.7B Q4 model plus Q8 projector used by the real-model smoke is about 1.48 GB before runtime buffers. It is not practical in the wasm32 runtime. Use the published memory64 core on a cross-origin-isolated Chromium page and warn users that synthesis has a high browser-memory and latency cost. The generated-audio path is experimental upstream. The checksum-pinned real-model gate passes with WebGPU selected in both direct and worker runtimes. CPU/WASM is a functional fallback, but it is considerably slower for this model pair.
getTextToSpeechCapabilities(): Promise<TextToSpeechCapabilities>Returns the versioned capability record for the loaded model/projector. Check
supported before showing synthesis controls. supportsLanguage and
supportsSpeakerReference describe optional request inputs; sampleRate and
channels describe generated PCM. reason explains an unsupported state.
synthesizeSpeech(options: TextToSpeechOptions): Promise<TextToSpeechResult>A failed worker-mode synthesis request gets at most one main-thread retry, and only when the model was not already loaded CPU-only and the error text matches one of these existing recovery classifiers:
- WebGPU failure:
dispatch workgroup count,max compute workgroups per dimension,invalid commandbuffer,ggml_webgpu: device error,RuntimeError: Aborted, orAborted(). - Worker timeout:
worker request timeout,worker init timeout, orworker timed out.
The bridge disposes the worker, reloads the cached model and projector in the
main-thread runtime, and reruns the request there. WebGPU-classified failures
reload CPU-only and emit a CPU-fallback warning. The generic worker timed out
form also selects that CPU-only path. The exact worker request timeout and
worker init timeout forms preserve the original GPU offload settings and emit
no CPU-fallback warning. The retry itself is never recovered a second time.
Every other worker failure is rejected as-is. A timeout-classified failure disposes the worker even when the model was already CPU-only and no retry runs.
Cancellation is classified before recovery. An aborted signal, cancel(), an
AbortError, or a worker error containing cancel rejects with an AbortError
DOMException and runs no main-thread retry.
Synthesizes speech and returns mono Float32Array PCM plus its sample rate,
sample count, generated-frame count, and truncation flag. The bridge does not
play or encode the audio. Applications can create a WAV or Web Audio buffer
from the returned PCM.
| Option | Description |
|---|---|
text |
Required non-empty text to speak. |
language |
Optional model language code, such as en; accepted values remain model-specific. |
speakerAudio |
Optional encoded reference-audio bytes when supportsSpeakerReference is true. |
promptBatchSize |
Prompt-processing batch size. Defaults to 512. |
maxFrames |
Maximum generated audio frames. Defaults to 512; a completed result reports truncated when this cap is reached. |
topK, topP, minP, temperature, seed |
Model sampling controls. Defaults are 40, 0.95, 0, 0.8, and a random seed. |
signal |
AbortSignal for cancellation. Cancellation rejects with AbortError. |
onProgress |
Receives task state, remaining prompt tokens, generated frames, and truncation state. |
Direct and worker modes use the same API, and worker mode transfers the output PCM buffer to the caller. Apart from the single eligible main-thread retry described above, a failed request is surfaced to the caller, who may retry explicitly.
A second synthesis is not rejected. It — and any other shared-runtime operation issued while synthesis is running — waits in the FIFO queue and runs in call order; see Operation serialization.
Decision heads run Laya-style decision models: a ModernBERT encoder GGUF loaded
with loadModelFromUrl() plus a safetensors head that scores the options of a
question. The bridge returns raw scores; tokenizing prompts and turning scores
into answers (temperatures, probabilities, act thresholds) belong to the
caller, for example llamadart's DecisionEngine.
The loaded model must report general.architecture modern-bert, CLS (BOS),
SEP and MASK tokens, non-empty MASK text, and last-hidden-state output. The head
file holds F32, F16 or BF16 tensors under Laya's PyTorch names; the official
Laya model.safetensors checkpoint works too, and its encoder.* tensors are
ignored. The head's width must match the encoder's hidden size.
Each loaded head owns a private encoder context of the config's max_len
tokens (512 for Laya) on the loaded model, plus its weights (about 100 MB for
Laya). The head runs on WebGPU when the model was loaded with GPU layers and on
the CPU otherwise; deviceName reports which. Sequences run one at a time.
getDecisionCapabilities(): Promise<DecisionCapabilities>Returns { apiVersion, supported, reason? } for the loaded model. reason
explains an unsupported state, such as no model, a model that is not a
ModernBERT encoder, a missing special token, or a core build without decision
heads.
loadDecisionHead(
source: string | ArrayBuffer | ArrayBufferView,
options?: {
configJson?: string | null;
onProgress?: (progress: { loaded: number; total: number }) => void;
},
): Promise<DecisionHeadInfo>Loads a head from a URL or from bytes and returns its handle, hidden size,
CLS/SEP/MASK token ids, MASK text, config text and head device. configJson
is the head's rl_agent_config.json text; without it the bridge reads the
head's laya.config safetensors metadata, and a head with neither is rejected.
The config sets max_len (default 512), which must not exceed the encoder's
trained context, and head_layers (default 2).
URL heads are fetched into the runtime's in-memory WASMFS and deleted after
loading, so peak memory includes the whole file: the official checkpoint is
about 800 MB. onProgress reports the download of a URL head. Worker mode
resolves a relative URL against the page and sends the worker a copy of byte
sources, so caller buffers are not detached. A worker head load fails after 10
minutes without download progress.
runDecision(handle: number, sequences: readonly DecisionSequence[]): Promise<DecisionOutput[]>Each sequence has tokens (the full token ids, including CLS and SEP),
markers (the token index of each option marker, in option order) and
questionType (0 choice, 1 score, 2 noul). Each output has logits,
one raw value per marker, and the act head's actLogits, both Float32Array;
worker mode transfers them to the caller.
The bridge rejects non-integer values before calling the core. The core then
checks every sequence before its first encoder pass: 1 to max_len tokens, ids
inside the vocabulary, 1 to token-count markers, markers inside the sequence,
and a question type from 0 to 2. One invalid sequence rejects the whole call.
A run cannot be cancelled: cancel() does not stop it, and later operations
wait for it in the queue. A worker run fails after 10 minutes plus 1 minute per
sequence; the bridge then falls back to the main-thread runtime as described
below.
freeDecisionHead(handle: number): Promise<void>Frees a head. Freeing an unknown or already-freed handle does nothing.
Handles are not durable. dispose(), a worker restart, a fallback to the
main-thread runtime, and any model load that reaches the core, even one that
then fails, free every head, and a later
runDecision() with such a handle rejects and asks for the head to be loaded
again. When the worker fails during runDecision(), the bridge reloads the
model on the main thread and rejects the run; heads must then be loaded again.
Handles are never reused within one bridge instance.
LoRA adapters change the loaded model's output without reloading it, through
llama.cpp's llama_adapter_lora_init_from_file_ptr and
llama_set_adapters_lora, with native llamadart's setLora, removeLora and
clearLoras semantics. An
adapter is a GGUF LoRA file made for the loaded base model. Several adapters
apply at once, each at its own scale, to completions, embeddings and
next-token scores; decision heads use their own contexts and are unaffected.
Changing the applied set drops the cached prompt, so the next call evaluates
its whole prompt.
getLoraAdapterCapabilities(): Promise<LoraAdapterCapabilities>Returns { apiVersion, supported, reason? }. supported is true when the
core build implements the bridge's LoRA API version. Before the first model
load the core has not started, so it is false with reason WebGPU core is not initialized. Gate LoRA support on this probe rather than on the asset tag.
loadLoraAdapter(
source: string | ArrayBuffer | ArrayBufferView,
options?: LoraAdapterLoadOptions,
): Promise<LoraAdapterInfo>Loads an adapter from a URL or from bytes and returns its handle. Loading
does not apply it; setLoraAdapter() does.
| Option | Description |
|---|---|
progressCallback(progress) |
Receives { loaded, total } download events for a URL adapter. |
signal |
Cancels the download. A load whose adapter already reached the runtime still completes. |
useCache |
Reads and stores a URL adapter in Cache Storage, as models are. Defaults to true. |
cacheName |
Cache Storage cache for a URL adapter; defaults to the bridge's cacheName. |
The adapter is staged in the runtime's in-memory WASMFS and deleted after loading. Worker mode resolves a relative URL against the page and sends the worker a copy of byte sources, so caller buffers are not detached. A worker adapter load fails after 10 minutes without download progress.
The load rejects, and the runtime stays usable, for a file llama.cpp rejects,
including an adapter made for another base model (for example tensor 'blk.0.attn_k.weight' has incorrect shape (hint: maybe wrong base model?)),
and for an aLoRA adapter, which must activate only once its invocation tokens
appear in the prompt. Errors never include the adapter URL.
setLoraAdapter(handle: number, scale?: number): Promise<void>Applies the adapter at scale, 1 by default. Setting an applied adapter again
changes its scale. A scale of 0 gives the same output as not applying the
adapter. scale must be a finite number and handle a positive integer, or
the call rejects with a TypeError. The core applies scale as a 32-bit
float, so a finite scale outside that range rejects with a RangeError.
removeLoraAdapter(handle: number): Promise<void>
clearLoraAdapters(): Promise<void>removeLoraAdapter() stops applying one adapter and clearLoraAdapters()
stops applying all of them. Adapters stay loaded and can be set again.
Handles belong to the loaded model. dispose() and any model load that reaches
the core, even one that then fails, free every adapter, and a later call with
such a handle rejects and asks for the adapter to be loaded again. There is no
per-adapter free. Handles are never reused within one bridge instance.
When the bridge reloads the model itself, after a worker restart, a fallback to the main-thread runtime or a switch to multimodal CPU mode, it then reloads the applied adapters and applies them at their scales; the others reload when next set. If this fails in the worker, the call rejects and the next call tries again. With the worker enabled, the bridge keeps a copy of byte sources for this. While an adapter is loaded, a generation that fails on WebGPU is not retried by reloading the model on the CPU, since the reload would free the adapters; the generation error is returned instead.
getModelMetadata(): Record<string, unknown> | nullReturns model metadata from llama.cpp plus bridge diagnostic keys. Useful keys include:
| Key | Meaning |
|---|---|
llamadart.webgpu.execution |
worker or main-thread. |
llamadart.webgpu.backends |
Comma-separated backend labels detected by the bridge. |
llamadart.webgpu.model_bytes |
Loaded model byte count. |
llamadart.webgpu.n_threads |
Active llama.cpp thread count. |
llamadart.webgpu.n_threads_batch |
Active batch thread count. |
llamadart.webgpu.thread_pool_size |
Detected or configured pthread pool size. |
llamadart.webgpu.n_gpu_layers |
Active GPU layer count. |
llamadart.webgpu.core_variant |
wasm32, wasm64, or initialization state. |
llamadart.webgpu.model_source |
Model load source, such as network/cache/remote fetch. |
llamadart.webgpu.model_cache_state |
Cache Storage state for the loaded model. |
llamadart.webgpu.runtime_notes |
Semicolon-separated bridge notes such as thread caps or fallback reasons. |
llamadart.webgpu.mmproj_loaded |
1 when a projector is loaded. |
llamadart.webgpu.supports_vision |
1 when the active projector supports vision. |
llamadart.webgpu.supports_audio |
1 when the active projector supports audio. |
llamadart.webgpu.worker_fallback_reason |
Present after falling back from worker mode to direct runtime. |
getContextSize(): numberReturns the active llama.cpp context size after model load. Before load, direct
runtime mode can return its cached/default context size, while worker-backed
instances return 0 until worker state has been populated.
isGpuActive(): booleanReturns whether the loaded model is actively using the WebGPU backend.
getBackendName(): stringReturns a user-facing backend label such as WebGPU (Prototype bridge),
WASM (Prototype bridge), or detected backend labels joined by comma.
setLogLevel(level: string | number): voidUpdates the bridge/core logging level. Numeric values are clamped by the bridge; string values are accepted for compatibility with callers but may be interpreted by current or future bridge internals.
cancel(): voidBest-effort cancellation for active model transfer, token generation, or text-to-speech synthesis. It aborts the transfer controller of the operation that currently owns the runtime and asks that operation's captured worker generation and runtime to stop, so an internal worker restart or main-thread fallback cannot redirect the cancel to an unrelated replacement. It never cancels a queued operation, and it stays out-of-band: it does not wait behind the operation it controls. After disposal it is a no-op and never resurrects torn-down state.
dispose(): Promise<void>Terminates the worker proxy, shuts down the direct runtime if it exists, unloads model/projector state, and clears cached metadata. Call this when an application is done with a bridge instance.
enableBridgeWorkerHost(): voidInstalls the worker-side message handler used by LlamaWebGpuBridge worker
mode. Applications normally import llama_webgpu_bridge_worker.js, which calls
this automatically. Custom worker bundles can import the bridge module and call
this function themselves.
- Use a secure context for WebGPU.
- Large single-file model loads and pthread-backed runtime paths require
cross-origin isolation (
Cross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: require-corp) soSharedArrayBufferis available. - Serve
llama_webgpu_core.jsandllama_webgpu_core.wasmfrom URLs reachable by both the page and worker. If using wasm64, also serve the_mem64files. - Keep
llama_webgpu_bridge.js,llama_webgpu_bridge_worker.js, the core JS, and wasm files from the same published bridge asset set. - Use
dispose()before dropping references when switching models or tearing down a page component.