Skip to content

feat(wasm): add tools/wasm/ Emscripten entrypoint for browser-resident inference#15

Open
wordingone wants to merge 498 commits into
AtomicBot-ai:masterfrom
wordingone:feat/wasm-llama-entrypoint
Open

feat(wasm): add tools/wasm/ Emscripten entrypoint for browser-resident inference#15
wordingone wants to merge 498 commits into
AtomicBot-ai:masterfrom
wordingone:feat/wasm-llama-entrypoint

Conversation

@wordingone
Copy link
Copy Markdown

Summary

Adds tools/wasm/ containing a C++ entrypoint that links libllama.a and exposes browser-callable functions via EMSCRIPTEN_KEEPALIVE.

Build: emcmake cmake . && emmake make wasm-llama
Artifacts: wasm-llama.html, wasm-llama.js, wasm-llama.wasm (2.6 MiB)

Exported functions

Function Signature Description
wasm_llama_init (target_path, drafter_path) -> int Load target + MTP drafter GGUFs from virtual FS
wasm_llama_health () -> char* Returns {status, mtp_loaded} JSON
wasm_llama_chat_completion (request_json) -> char* OAI-compat completion; returns {choices, _mtp_enabled, _spec_accept_rate, _latency_ms, _tps}
wasm_llama_free_str (ptr) Free heap-allocated return values

Response shape

{"choices":[{"message":{"role":"assistant","content":"..."}}],"_mtp_enabled":true,"_spec_accept_rate":null,"_latency_ms":1234,"_tps":4.2}

_mtp_enabled reflects whether llama_model_load_mtp_from_file succeeded. _spec_accept_rate is null until pthreads (SharedArrayBuffer + COOP/COEP) are wired globally — tracked as follow-up work.

Changes

  • tools/wasm/wasm_llama.cpp — entrypoint implementation (~220 lines, no external deps beyond llama.h)
  • tools/wasm/CMakeLists.txt — Emscripten-specific build configuration
  • tools/CMakeLists.txt — wire add_subdirectory(wasm) in the if (EMSCRIPTEN) block (was previously empty)

Threading note

pthreads require all objects to be compiled with -matomics -mbulk-memory (i.e., -pthread at cmake-configure time). This PR compiles single-threaded, matching the current tools/ build posture. Full pthread support (enabling MTP's mtp_worker_loop) is a separate cmake-level change.

qnixsynapse and others added 30 commits April 13, 2026 09:44
* Add MCP Connection diagnostics and CORS hint to web-ui

* tidy up test

* webui: Refactor and improve MCP diagnostic logging

---------

Co-authored-by: evalstate <1936278+evalstate@users.noreply.github.com>
* webui: add setting for first-line chat titles

Add an opt-in setting (`titleGenerationUseFirstLine`) to use the first
non-empty line of a prompt as the generated conversation title.

Previously, the complete multi-line prompt was being used, which created
long titles for complex queries. Coupled with
"Ask for confirmation before changing conversation title", the dialog
would overflow.

* Update tools/server/webui/src/lib/utils/text.ts

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* Update tools/server/webui/src/lib/utils/text.ts

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* webui: Run build to update the bundle

As requested in:
ggml-org#21797 (review)

* webui: Fix missing import for NEWLINE_SEPARATOR

---------

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>
* CUDA: Limit DeviceSegmentedSort to immediate mode

DeviceSegmentedSort is currently not capturable in a cuda graph. Hence,
we have to go for the slower DeviceSegmentedRadixSort in that case.

Perf numbers on RTX Pro 6000 Blackwell Max-Q:
DeviceSegmentedRadixSort in graph mode (i.e. CUDA Graphs)

  ARGSORT(type=f32,ne=[2048,512,1,1],order=1):                 12291 runs -   105.94 us/run -     8192 kB/run -   73.75 GB/s
  ARGSORT(type=f32,ne=[4096,512,1,1],order=1):                 10245 runs -   115.08 us/run -    16384 kB/run -  135.77 GB/s
  ARGSORT(type=f32,ne=[8192,512,1,1],order=1):                  5125 runs -   221.22 us/run -    32768 kB/run -  141.26 GB/s
  ARGSORT(type=f32,ne=[16384,512,1,1],order=1):                 2565 runs -   430.98 us/run -    65536 kB/run -  145.02 GB/s
  ARGSORT(type=f32,ne=[32768,512,1,1],order=1):                 1028 runs -  1185.83 us/run -   131072 kB/run -  105.41 GB/s
  ARGSORT(type=f32,ne=[65536,512,1,1],order=1):                  387 runs -  2748.62 us/run -   262144 kB/run -   90.95 GB/s

DeviceSegmentedSort in immediate mode

  ARGSORT(type=f32,ne=[2048,512,1,1],order=1):                 16388 runs -    71.17 us/run -     8192 kB/run -  109.78 GB/s
  ARGSORT(type=f32,ne=[4096,512,1,1],order=1):                 12294 runs -    81.38 us/run -    16384 kB/run -  192.00 GB/s
  ARGSORT(type=f32,ne=[8192,512,1,1],order=1):                  5125 runs -   240.81 us/run -    32768 kB/run -  129.77 GB/s
  ARGSORT(type=f32,ne=[16384,512,1,1],order=1):                 2565 runs -   406.60 us/run -    65536 kB/run -  153.71 GB/s
  ARGSORT(type=f32,ne=[32768,512,1,1],order=1):                 1285 runs -   873.23 us/run -   131072 kB/run -  143.15 GB/s
  ARGSORT(type=f32,ne=[65536,512,1,1],order=1):                  516 runs -  2288.46 us/run -   262144 kB/run -  109.24 GB/s

* Add test case for dispatch to DeviceSegmentedRadixSort

We currently lack a way to force graph mode in CUDA, patch callback to
invoke ggml_backend_compare_graph_backend twice to enforce each test to
run in graph mode
)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>
…20797)

* use integer dot product for quantized KV flash attention

* small improvements

* fix SHMEM_STAGING indexing

* add missing KV type quants

* fixes

* add supported quants to FA tests

* readd fast paths for <8bit quants

* fix mmq gate and shmem checks
* docs: listing qwen3-asr and qwen3-omni as supported

* nits
* server: support OAI /v1/audio/transcriptions API

* address autoreview comments

* correct default response_format value
This adds nvfp4 support for get_rows, dequant, and mul_mat(_id). For
mul_mat, it does not add support for the dp4/q8_1 path, it's all via
fp16/fp32.
…ml-org#21870)

* common: skip reasoning budget sampler when no budget is requested

After I added thinking_start_tag / thinking_end_tag for gemma4 in ggml-org#21697, the reasoning budget sampler gets unconditionally created even when no budget is configured (the default -1). The same applies to kimi_k2, lfm2, lfm2_5, and ministral_3 which also set these tags. The budget gets converted to INT_MAX, so the sampler never actually forces any tokens but still runs per-token checks (start tag matching in IDLE state, token-to-piece conversion + UTF-8 checks in COUNTING state).

More importantly, the mere existence of the sampler (non-null rbudget) disables backend sampling. Backend sampling lets the GPU select tokens directly, avoiding a full logits transfer from GPU to CPU every token. This could explain the 30% speed regression reported in ggml-org#21784 (98 t/s to 70 t/s on Vulkan).

So I added a reasoning_budget_tokens >= 0 check to the sampler creation condition. When the budget is unlimited, the sampler is not created, backend sampling stays enabled, and no per-token overhead is added. When a budget is explicitly set (0, 128, 1024, etc.), the sampler is created and works as before.

* common: preserve rbudget when grammar is lazy

Following up on the review feedback on ggml-org#21870: keep the reasoning budget sampler when grammar_lazy is true, so the thinking-block grammar suppression from ggml-org#20970 still works when tools are in use. This way, we only skip the sampler when both no budget is set AND grammar is not lazy.
…gml-org#21644)

* Update register tiling matmul to use f32 accumulation

* fix profiling code

* Fix register tiling matmul for chrome, i'm blaming dawn

* Update batch tuning value for iOS

* compile fix

* Fix use of new load function
* cmake: fix CMP0194 warning on Windows with MSVC

Set CMP0194 policy to NEW before project() call in ggml/CMakeLists.txt to suppress the "MSVC is not an assembler for language ASM" warning introduced in CMake 4.1.

The ggml project enables ASM globally for Metal (macOS) and KleidiAI (ARM) backends. On Windows/MSVC, no assembler sources are used, but CMake 4.1+ warns because cl.exe is not a valid ASM compiler.

This follows the same pattern used in ggml-vulkan (CMP0114, CMP0147).

Closes ggml-org#20311

* cmake: apply cisc's formatting suggestion

---------

Co-authored-by: texasich <texasich@users.noreply.github.com>
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
* ci : re-enable mac workflows

* vulkan : fix compile warning
…device supports it (ggml-org#21572)

* vulkan: Programmatically add RoundingModeRTE to all shaders when the device supports it

* use FetchContent to get SPIRV-Headers

* Fetch spirv-headers unconditionally

* remove fetchcontent, rely on installed headers

* fix ubuntu job

* Update docs/build.md
* mtmd: add mtmd_image_tokens_get_decoder_pos() API

* consistent naming

* fix build
* ggml: correct placement of ggml-ext.h

* ggml : remove ggml-ext.h

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
* hexagon: add async HMX worker

Introduce hmx-worker (dedicated thread for HMX compute) to overlap HMX
matmul with HVX dequant/DMA stages in the pipeline path, replacing the
previous synchronous HMX calls that blocked the main thread.

* hexagon: cost-based VTCM chunk search for out-stationary matmul

* hexagon: fix futex race in hmx_worker_drain
Store the boolean to local variable avoid atomic load twice

* hex-mm: hmx optimize scatter/transpose and use HMX intrinsics

* hex-vmem: drop vmem limit a touch under 3GB on v73

* hexagon: add fwd declaration of htp_context

* hex-hmx: replace hmx-worker with hmx-queue that mimics dma-queue interface

Simplifies the overall implemantion, reduces thread wakeup roundtrips.

* hex-mm: add debug log to hmx work func called from hmx-queue

* Update hmx-queue.h

Co-authored-by: Max Krasnyansky <max.krasnyansky@gmail.com>

---------

Co-authored-by: Kim-Chyan Gan <kgan@qti.qualcomm.com>
Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>
Co-authored-by: Max Krasnyansky <max.krasnyansky@gmail.com>
Ooooze and others added 4 commits May 13, 2026 17:37
…pp-turboquant

- Updated NEXTN.md to document the integration of `--mmproj` with speculative decoding types `mtp`, `nextn`, and `eagle3`, allowing coexistence on a single slot.
- Revised README.md to reflect the new multimodal capabilities and their implications for text and image processing.
- Added functions in `common/speculative.cpp` and `common/speculative.h` to check compatibility of speculative types with multimodal settings.
- Enhanced server context handling to manage multimodal prompts and ensure correct behavior during speculative decoding.
- Introduced a new script for running Gemma 4 with multimodal projector support, detailing expected behavior for text and image turns.
- Updated documentation in `docs/speculative.md` to clarify per-turn behavior and future roadmap for draft acceleration on vision turns.
Enhance multimodal support and speculative decoding in atomic-llama-c…
…t inference

Exposes libllama.a (with MTP + gemma4-assistant support) to the browser via
four EMSCRIPTEN_KEEPALIVE exports: wasm_llama_init, wasm_llama_health,
wasm_llama_chat_completion, wasm_llama_free_str.

Response shape: {choices:[...], _mtp_enabled:bool, _spec_accept_rate:null}
MTP threading requires SharedArrayBuffer (COOP/COEP) and global -pthread build.
Build: emcmake cmake + emmake make wasm-llama -> .html/.js/.wasm (2.6M)
@wordingone
Copy link
Copy Markdown
Author

Advisory review — Eli requested gate before merge.

Read tools/wasm/wasm_llama.cpp (213 lines) end-to-end against issue wordingone/gemma-architect#737 AC. All 5 AC items satisfied:

  • tools/wasm/ directory with CMakeLists.txt (52 lines) + entrypoint .cpp (213 lines)
  • Emscripten build produces .wasm + .js (artifacts at build-wasm3/bin/)
  • Exports wasm_llama_init / wasm_llama_chat_completion / wasm_llama_health (+ wasm_llama_free_str for heap-string ownership)
  • Response shape matches {choices, _mtp_enabled, _spec_accept_rate, _latency_ms, _tps} (lines 196-203)
  • Upstream PR opened (this PR)

Code quality — competent spike-tier work:

  • Resource lifecycle: globals freed on re-init (54-64), heap_str/free_str pair documented as caller-owns (lines 4, 43-47, 208-211). Clean.
  • MTP loading: two-step verify — llama_model_load_mtp_from_file then llama_model_has_mtp_assistant (80-81). Honest about partial-load failure modes.
  • _spec_accept_rate: null with inline // MTP threading requires SharedArrayBuffer (#739). Honest about the follow-up.
  • Single-threaded build acknowledged in PR body; pthreads is a separate cmake change.

Pragmatic shortcuts (acceptable for spike, worth follow-up):

  1. JSON parsing by req.find("\"content\"", ...) (119-143) is brittle — escaped-quote handling at line 137 mishandles \\" (backslash-backslash-quote) sequences, and "content" appearing in nested objects would be matched. The "no heavy parser dep" trade-off is fine for the spike; once the WASM loader (gemma-architect Windows: reactivate sigint handler after each Ctrl-C ggml-org/llama.cpp#736 step 2) starts sending real OAI-compat payloads, a follow-up to vendor nlohmann/json or roll a minimal proper tokenizer is advisable.
  2. std::stoi on max_tokens substring (line 124) is uncaught — throws std::invalid_argument / std::out_of_range on malformed input. In Emscripten, uncaught C++ exceptions crash the wasm runtime. Wrap in try/catch or pre-validate.
  3. Sampler labeled "Greedy" at line 165 but chain is min_p(0.05) → temp(0.8) → dist(LLAMA_DEFAULT_SEED) — stochastic with deterministic seed. Comment-vs-code drift; either rename the comment or swap in llama_sampler_init_greedy() if argmax was intended.
  4. n_ctx = 2048 hardcoded (line 85). gemma-architect's WebGPU path expanded to 16384 per Stops talking mid sentence ggml-org/llama.cpp#988. WASM loader call-site should override via init param; follow-up at Windows: reactivate sigint handler after each Ctrl-C ggml-org/llama.cpp#736 step 2.

Mergeable=CONFLICTING — needs rebase against fork master (upstream-llama.cpp churn carrying through, not Eli's diff conflict; the actual Eli contribution is 3 files / 265 added lines).

Advisory verdict: approve with the 4 follow-up notes above. Rebase + auto-merge per the standard auto-merge model.

— Leo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.