wllama

repository·master·Indexed 22 days ago

https://github.com/ngxson/wllama

WebAssembly binding for llama.cpp that enables high-performance LLM inference directly in the browser. It provides an OpenAI-compatible API and supports WebGPU, multimodal inputs (image/audio), and tool calling. The library includes a ModelManager for handling GGUF model downloading, caching, and splitting, and offers a compatibility mode via @wllama/wllama-compat for browsers lacking JSPI or MEMORY64 support.

Tokens
15.7K
Snippets
50
Records
65
Agent score
77%

What's inside wllama

  1. What is @wllama/wllama-compat and when to use it

    master

    The @wllama/wllama-compat package provides compatibility WASM assets for @wllama/wllama on browsers that lack JSPI (JS Promise Integration) or MEMORY64 support. This is primarily necessary for Safari and older browsers.

    When these features are missing, wllama enters compat mode, which uses Asyncify instead of JSPI and drops MEMORY64 support.

    Warning: Compat mode has significantly lower performance than the default build. It should only be used as a fallback for unsupported browsers.

  2. How the threading model works in Wllama

    master

    Wllama uses a single Wasm build that dynamically supports both single-threaded and multi-threaded execution at runtime.

    Multi-threading Requirements:

    • The browser must support SharedArrayBuffer.
    • The server must be configured with COOP/COEP headers to allow access to SharedArrayBuffer.
    • The Wasm atomics feature must be available.

    Thread Pool Logic:

    • If shared memory is supported: The thread pool size (pthreadPoolSize) is set to the desired count, defaulting to hardwareConcurrency / 2.
    • If shared memory is NOT supported: pthreadPoolSize is set to 0, disabling pthreads and falling back to single-threaded execution.
  3. How Wllama handles file access (Async vs HeapFS)

    master

    Wllama uses two primary mechanisms to read GGUF files without making unnecessary copies. Note that Wllama only accepts Blob as input data.

    1. Async File Read

    This method hooks into fopen, fseek, and fread to forward calls to the main thread, where Blob.slice() is used to read data. This requires JSPI / Asyncify.

    • Mechanism: When fread() is called, it triggers an fs.read_req message. The main thread reads the slice and returns it via fs.read_res.
    • Optimization: To prevent bottlenecks from small metadata reads, the minimum read size is 1MB. If a request is smaller than 1MB, the full 1MB block is cached for subsequent reads.
    • Activation: Set the environment variable USE_ASYNC_FILE to signal the Wasm module to use this mode.

    2. HeapFS

    HeapFS is a wrapper around Emscripten's FS driver designed to allow mmap() to map to existing data instead of copying it.

    • Mechanism: A ReadableStream for the Blob is created in the main thread. Data is streamed to the worker via fs.write messages. Once complete, model loading is triggered with mmap = true.
    • Limitation: On WebGPU, even if tensors are offloaded to the GPU, the full model must still be allocated in main memory (e.g., a 4GB model occupies 4GB of main memory).
  4. How the GLUE binary protocol works

    master

    GLUE is a custom binary protocol used for low-overhead, type-safe communication between the Wasm context and the JavaScript context. It serializes messages into ArrayBuffer objects and uses Transferable objects to avoid data copying during transfers.

    Wire Format Structure:

    • 4 bytes: magic number (GLUE)
    • 4 bytes: version number (GLUE_VERSION)
    • 8 bytes: message prototype ID
    • 4 bytes: message length (unsigned)
    • Message fields: Each field consists of:
      • 4 bytes: data type (e.g., int, float, str, raw, and array variants)
      • 4 bytes: size (only for arrays and strings)
      • Data bytes

    Supported Field Types: str, int, float, bool, raw (arbitrary bytes), and their respective array variants.

  5. Migrate from Wllama V2.0 to V3.0

    master

    V3.0 introduces breaking changes due to the architectural shift to server-context:

    Removed APIs

    • tokenize / detokenize (Low-level tokenizer API removed)
    • decode / encode (Replaced by OAI completion API)
    • samplingInit / samplingAccept / samplingSample (Sampling is now per-request)
    • Sequence shift/remove operations

    Configuration Changes

    • Sampling Parameters: No longer passed at model load time. They must be provided per-request in createChatCompletion or createCompletion.
    • Context Length: n_ctx_auto is removed. You must set n_ctx explicitly at load time.
    • Multimodal Loading: Use mmprojUrl in loadModelFromUrl instead of separate file selection.
    • Chat Templates: Chat templates are now parsed with Jinja. Enable this by setting jinja: true at load time, or override with chat_template.

    Error Handling

    • A new WllamaError type 'kv_cache_full' is available when the context runs out of space.
  6. Generate compressed source maps

    master

    To reduce the size of the TypeScript source map file, scripts/build_source_map.js can be used to create a compact, gzip-compressed, and base64-encoded name table from Emscripten's .js.symbols file.

    # uses build/ and build-compat/ by default
    node scripts/build_source_map.js
    
    # or with explicit paths
    node scripts/build_source_map.js \
      --input default:build \
      --input compat:build-compat \
      --output src/wasm/source-map.ts
    node scripts/build_source_map.js --input default:build --input compat:build-compat --output src/wasm/source-map.ts
  7. Initialize Wllama with simplified configuration

    master

    In v2.0, the Wllama constructor has been simplified. You no longer need to provide .js files in the configuration paths; you only need to specify the *.wasm files for each thread configuration.

    Standard Configuration

    Provide a mapping of thread types to their corresponding .wasm files:

    const CONFIG_PATHS = {
      'single-thread/wllama.wasm': '../../esm/single-thread/wllama.wasm',
      'multi-thread/wllama.wasm' : '../../esm/multi-thread/wllama.wasm',
    };
    const wllama = new Wllama(CONFIG_PATHS);

    If you cannot embed WASM files in your project, you can use the provided CDN helper:

    import WasmFromCDN from '@wllama/wllama/esm/wasm-from-cdn.js';
    const wllama = new Wllama(WasmFromCDN);

    Constructor Options

    The constructor accepts an optional second parameter of type WllamaConfig. Note that many options previously passed to loadModelFromUrl (like parallelDownloads and allowOffline) have moved here.

    const CONFIG_PATHS = {
      'single-thread/wllama.wasm': '../../esm/single-thread/wllama.wasm',
      'multi-thread/wllama.wasm' : '../../esm/multi-thread/wllama.wasm',
    };
    
    const wllama = new Wllama(CONFIG_PATHS, {
      parallelDownloads: 5, // maximum concurrent downloads
      allowOffline: false, // whether to allow offline model loading
    });
  8. Implement Tool Calling with Wllama

    master

    Tool calling is supported for models that include tool-call templates (e.g., Qwen, Llama). You define tools in the tools array and pass them to createChatCompletion. If the model returns a finish_reason of 'tool_calls', you must execute the tool locally and feed the result back to the model in a subsequent turn using the 'tool' role.

    const tools = [
      {
        type: 'function',
        function: {
          name: 'get_weather',
          description: 'Get the current weather for a given city.',
          parameters: {
            type: 'object',
            properties: {
              city: { type: 'string', description: 'City name' },
            },
            required: ['city'],
          },
        },
      },
    ];
    
    const messages = [{ role: 'user', content: 'What is the weather in Tokyo?' }];
    
    // First turn: model decides to call a tool
    const response = await wllama.createChatCompletion({
      messages,
      tools,
      tool_choice: 'auto',
      max_tokens: 256,
    });
    
    const choice = response.choices[0];
    if (choice.finish_reason === 'tool_calls') {
      const toolCall = choice.message.tool_calls[0];
      const args = JSON.parse(toolCall.function.arguments);
      const result = { condition: 'rain', temperature_celsius: 21 };
    
      // Second turn: feed tool result back
      messages.push(choice.message);
      messages.push({
        role: 'tool',
        tool_call_id: toolCall.id,
        content: JSON.stringify(result),
      });
    
      const final = await wllama.createChatCompletion({ messages, max_tokens: 256 });
      console.log(final.choices[0].message.content);
    }
  9. Self-host compat WASM assets

    master

    By default, wllama fetches compat assets from the jsDelivr CDN. If you want to avoid external CDN dependencies and self-host the assets, follow these steps:

    1. Install the compatibility package:
      npm install @wllama/wllama-compat
    2. Copy the assets from node_modules/@wllama/wllama-compat/wasm/ to your public directory.
    3. Configure wllama with the local URLs using setCompat().

    Standard usage:

    import { Wllama } from '@wllama/wllama';
    
    const wllama = new Wllama({ default: '/wasm/wllama.wasm' });
    
    wllama.setCompat({
      wasm: '/wllama-compat/wasm/wllama.wasm',
      worker: '/wllama-compat/wasm/wllama.js',
    });
  10. Use Multimodal (Vision/Audio) Support

    master

    Models with a vision projector (mmproj) can process image and audio inputs. You can load these models from Hugging Face or explicit URLs using loadModelFromHF or loadModelFromUrl. When calling createChatCompletion, pass the media as an ArrayBuffer within the messages array using the image or audio type.

    // Load from Hugging Face
    await wllama.loadModelFromHF({
      repo: 'user/model-GGUF',
      quant: 'Q4_K_M',
      mmprojQuant: 'Q8_0',
    });
    
    // Or load from explicit URLs
    await wllama.loadModelFromUrl({
      url: 'https://example.com/model.gguf',
      mmprojUrl: 'https://example.com/mmproj.gguf',
    });
    
    // Pass an image as ArrayBuffer alongside text
    const imageData = await fetch('./photo.jpg').then(r => r.arrayBuffer());
    
    const response = await wllama.createChatCompletion({
      messages: [
        {
          role: 'user',
          content: [
            { type: 'image', data: imageData },
            { type: 'text', text: 'Describe this image.' },
          ],
        },
      ],
      max_tokens: 512,
    });
  11. Migrate to the Single WASM build in Wllama V3.1

    master

    Wllama V3.1 has removed the distinction between single-threaded and multi-threaded builds. A single WASM build is now provided that supports both single/multi-threading and WebGPU, with features toggled at runtime.

    When migrating from older versions, you no longer need to maintain separate paths for single-thread and multi-thread builds. Instead, point your configuration to the single default WASM path.

    // Old config
    const CONFIG_PATHS = {
      'single-thread/wllama.wasm': './path_to_source/single-thread/wllama.wasm',
      'multi-thread/wllama.wasm' : './path_to_source/multi-thread/wllama.wasm',
    };
    
    // New config
    const CONFIG_PATHS = {
      default: './path_to_source/wasm/wllama.wasm',
    };
  12. Compile the Wllama binary from source

    master

    If you need to compile the binary yourself (e.g., to use bleeding-edge llama.cpp changes), you must have docker compose installed. Follow these steps:

    1. Clone the repository with submodules: git clone --recurse-submodules https://github.com/ngxson/wllama.git
    2. Install dependencies: npm i
    3. Build llama.cpp into WASM: npm run build:wasm
    4. Build the ES module: npm run build
    git clone --recurse-submodules https://github.com/ngxson/wllama.git
    cd wllama
    npm i
    npm run build:wasm
    npm run build