@qvac/llm-llamacpp 0.62.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,7 @@ This native C++ addon, built using the `Bare` Runtime, simplifies running Large
25
25
  - [Architecture](#architecture)
26
26
  - [Benchmarking](#benchmarking)
27
27
  - [Tests](#tests)
28
+ - [Fuzzing](#fuzzing)
28
29
  - [Glossary](#glossary)
29
30
  - [License](#license)
30
31
 
@@ -210,6 +211,11 @@ const config = {
210
211
  | cache-type-k | `f16`, `f32`, `bf16`, `q8_0`, `q4_0`, … | auto (see below) | KV-cache **key** quantization type. Unset = auto-default (see KV-cache type below) |
211
212
  | cache-type-v | `f16`, `f32`, `bf16`, `q8_0`, `q4_0`, … | auto (see below) | KV-cache **value** quantization type. Quantizing V requires `flash-attn` on |
212
213
  | mmproj-use-gpu | `"true"`/`"on"`/`"1"` or `"false"`/`"off"`/`"0"` | auto (see below) | Run the multimodal projector (mmproj / vision encoder) on the GPU. Only honoured when a GPU backend is selected (ignored with a warning on CPU / GPU-fallback). Unset = auto-default (see mmproj backend below) |
214
+ | spec-type | `"none"`, `"draft-mtp"` or `"draft-dflash"` | `"none"` | Speculative decoding, as llama-server's `--spec-type`: `draft-mtp` drafts with the model's own multi-token-prediction head, `draft-dflash` with the DFlash model in `spec-draft-model` (see [Speculative decoding](#speculative-decoding-mtp-dflash) below). Also accepted as `spec_type` |
215
+ | spec-draft-model | absolute path | – | DFlash draft model, as llama-server's `--spec-draft-model` (`-md`). Required by, and only accepted with, `spec-type: "draft-dflash"`. Placed like the model (`device`, `gpu-layers`). Also accepted as `spec_draft_model` |
216
+ | spec-draft-n-max | 1 – 64 | 3 | Longest draft per step. Also accepted as `spec_draft_n_max` |
217
+ | spec-draft-n-min | 0 – `spec-draft-n-max` | 0 | Drafts shorter than this are not verified. Also accepted as `spec_draft_n_min` |
218
+ | spec-draft-p-min | 0 – 1 | 0 | Minimum draft-head probability for a token to extend the draft. Also accepted as `spec_draft_p_min` |
213
219
 
214
220
 
215
221
  #### KV-cache type & auto-default
@@ -254,6 +260,21 @@ itself runs on the CPU backend (`device: "cpu"` or GPU fallback), the key is ign
254
260
  projector runs on CPU. The resolved choice is logged at verbosity ≥ 2 as
255
261
  `[LlamaModel] multimodal projector backend: …`.
256
262
 
263
+
264
+ #### Speculative decoding (MTP, DFlash)
265
+
266
+ `spec-type: 'draft-mtp'` turns on llama-server's multi-token-prediction speculative decoding for models that ship their MTP layers (`blk.N.nextn.*`, e.g. Qwen3.5 / Qwen3.6 GGUFs that keep them; quantizations that strip them fail to load with this option). Every step the MTP head drafts up to `spec-draft-n-max` tokens from the model's own hidden states, the model verifies the sampled token and the draft in one decode, and the longest matching prefix plus one new token is kept. The sampler decides every kept token, so sampling settings, grammars and stop conditions behave as without speculation. The implementation follows llama-server step by step: the same draft limits, rollback by trimming or by a checkpoint for memory that cannot trim, and the same replay.
267
+
268
+ `spec-type: 'draft-dflash'` drafts with a separate DFlash model instead, given as `spec-draft-model` (an absolute path; llama-server's `-md`). The DFlash model reads the hidden states of the target layers it was trained on and denoises a whole block of draft tokens in one pass, so a step drafts up to its block size minus one tokens (`spec-draft-n-max` caps it further). It is loaded next to the model and placed like it (`device`, `gpu-layers`). The load's fit and `assessFit` pass it to fabric as an extra model, but fabric cannot measure a DFlash model yet: it logs `failed to measure the memory of the extra model, fitting without it`, so neither counts the draft model's memory. Leave room for it yourself. Everything else (verification, rollback, batching, caches, stats) is the same as for MTP.
269
+
270
+ Both work on single prompts, multimodal prompts and continuous batching (`parallel >= 2`), together with `cacheKey` conversations: the draft context's state travels with checkpoints and the RAM tier, and a `cacheKey` file (target state only, like llama-server's slot files) restarts drafting from the next decoded token.
271
+
272
+ - Runtime stats gain `draftTokens` and `draftAcceptedTokens` (llama-server's `draft_n` / `draft_n_accepted`); per-position acceptance rates are logged at debug level.
273
+ - `spec-draft-n-max` is lowered at load, with a warning, so the sampled token and the draft fit one `ubatch-size` and each sequence's share of `batch-size` (`batch-size / parallel`); a batch too small for even one draft token fails the load.
274
+ - `spec-draft-model` must be a regular local file.
275
+ - Greedy output can differ from plain decoding where two tokens are nearly tied: the verification batch evaluates several positions at once, which changes floating-point rounding.
276
+ - The gain depends on the device being bandwidth bound. On a Radeon 8060S, Qwen3.6-27B-MTP Q4_K_M decodes a single prompt at about 30 t/s instead of 13 t/s with 80% of the drafts accepted; with four concurrent sequences the larger verification batches cost more than they save, as they do in llama-server.
277
+
257
278
  ### 4. Create Model Instance
258
279
 
259
280
  ```js
@@ -678,6 +699,28 @@ npm run test:cpp:models:ci # exactly what .github/workflows/cpp-tests-llm.yml
678
699
 
679
700
  First-run downloads pull several GB from Hugging Face. Every fixture is SHA256-verified against a digest pinned in `scripts/download-unit-test-models.js`; mismatched or partial files are re-downloaded automatically. Set `HF_TOKEN` if a repo requires authentication. Override paths with env vars such as `SHARDED_MODEL_FIRST_SHARD_PATH` (see `test/unit/test_common.hpp`).
680
701
 
702
+ ### Fuzzing
703
+
704
+ `forLogMessage`, `toLowerAscii`, and `matchesAnyStopString` have a
705
+ [Google FuzzTest][fuzztest] target, `llm-string-fuzz`. Fuzzing is Linux-only:
706
+ it needs clang with libFuzzer and AddressSanitizer, CMake 3.25 or newer,
707
+ [vcpkg](https://vcpkg.io/) (set `VCPKG_ROOT`) and `bare-make`. The target does
708
+ not link `@qvac/fabric`, so it runs with full ASan and LeakSanitizer.
709
+
710
+ ```bash
711
+ npm install
712
+ npm run fuzz # bounded run of every FUZZ_TEST, as Linux CI does
713
+ npm run fuzz:continuous # coverage-guided, one FUZZ_TEST at a time
714
+ npm run fuzz:continuous -- LlmStringFuzz.MatchesAnyStopNeverCrashes --fuzz_for=30m
715
+ ```
716
+
717
+ Flags other than `--continuous` and `--build-dir` go to the fuzz binary. See
718
+ [`docs/architecture/ADDON-FUZZING.md`](../../docs/architecture/ADDON-FUZZING.md)
719
+ for the fuzzing design, the vcpkg-supplied FuzzTest stack, and how to add a
720
+ target.
721
+
722
+ [fuzztest]: https://github.com/google/fuzztest
723
+
681
724
  ## Glossary
682
725
 
683
726
  • **Bare Runtime** – Small and modular JavaScript runtime for desktop and mobile. [Learn more](https://docs.pears.com/reference/bare-overview).
package/index.d.ts CHANGED
@@ -364,6 +364,41 @@ declare namespace LlmLlamacpp {
364
364
  * maximum is 1048576 (1 TiB). Also accepted as `cache-ram-mib`.
365
365
  */
366
366
  cache_ram_mib?: NumericLike;
367
+ /**
368
+ * Speculative decoding, as llama-server's `--spec-type`. `'draft-mtp'`
369
+ * drafts with the model's own multi-token-prediction (MTP / NextN) head;
370
+ * the model must ship its MTP layers (`blk.N.nextn.*`), or the load
371
+ * fails. `'draft-dflash'` drafts with the DFlash model given as
372
+ * `spec_draft_model`. Either way the model verifies the draft, so the
373
+ * output keeps the distribution of plain decoding. `'none'` (default)
374
+ * decodes one token per step. Works on single prompts, multimodal
375
+ * prompts and continuous batching. Also accepted as `spec-type`.
376
+ */
377
+ spec_type?: "none" | "draft-mtp" | "draft-dflash";
378
+ /**
379
+ * Absolute path of the DFlash draft model, as llama-server's
380
+ * `--spec-draft-model` (`-md`). Required by, and only accepted with,
381
+ * `spec_type: 'draft-dflash'`. The draft model is placed like the model
382
+ * (`device`, `gpu_layers`). Also accepted as `spec-draft-model`.
383
+ */
384
+ spec_draft_model?: string;
385
+ /**
386
+ * Longest draft per step (default 3, maximum 64), as
387
+ * `--spec-draft-n-max`. Requires `spec_type`. Also accepted as
388
+ * `spec-draft-n-max`.
389
+ */
390
+ spec_draft_n_max?: NumericLike;
391
+ /**
392
+ * Shortest draft worth verifying (default 0); shorter drafts are
393
+ * dropped. As `--spec-draft-n-min`. Also accepted as `spec-draft-n-min`.
394
+ */
395
+ spec_draft_n_min?: NumericLike;
396
+ /**
397
+ * Minimum draft-head probability for a token to extend the draft
398
+ * (default 0, between 0 and 1), as `--spec-draft-p-min`. Also accepted
399
+ * as `spec-draft-p-min`.
400
+ */
401
+ spec_draft_p_min?: NumericLike;
367
402
  [key: string]: string | number | boolean | string[] | undefined;
368
403
  }
369
404
  interface LlmLlamacppArgs {
@@ -599,6 +634,13 @@ declare namespace LlmLlamacpp {
599
634
  */
600
635
  avgConcurrentSeq: number;
601
636
  backendDevice: "cpu" | "gpu";
637
+ /**
638
+ * Draft tokens proposed by speculative decoding, and how many of them
639
+ * the model accepted (llama-server's `draft_n` / `draft_n_accepted`).
640
+ * Present only when the model was loaded with `spec_type`.
641
+ */
642
+ draftTokens?: number;
643
+ draftAcceptedTokens?: number;
602
644
  /**
603
645
  * Why generation stopped. Per-sequence, so it is reported for a single
604
646
  * request on either path (sequential or one prompt on a parallel model).
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@qvac/llm-llamacpp",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "llama addon for qvac",
5
5
  "addon": true,
6
6
  "scripts": {
7
7
  "build": "npm run build:ts && npm run build:native",
8
- "build:native": "bare-make generate && bare-make build && bare-make install",
8
+ "build:native": "bare-make generate -D BUILD_FUZZING=OFF && bare-make build && bare-make install",
9
9
  "build:ts": "tsc -p tsconfig.build.json",
10
10
  "build:pack": "npm run build:ts && mkdir -p dist && npm pack --pack-destination dist",
11
11
  "mobile:copy-prebuilds": "cp -r prebuilds/android-arm64 prebuilds/android-ia32 || echo 'Warning: Failed to copy llamacpp prebuilds to android-ia32'; cp -r prebuilds/android-arm64 prebuilds/android-arm || echo 'Warning: Failed to copy llamacpp prebuilds to android-arm'; cp -r prebuilds/android-arm64 prebuilds/android-x64 || echo 'Warning: Failed to copy llamacpp prebuilds to android-x64'; cp -r prebuilds/ios-arm64 prebuilds/ios-arm64-simulator 2>/dev/null || echo 'iOS prebuilds already present'; cp -r prebuilds/ios-arm64 prebuilds/ios-x64-simulator 2>/dev/null || echo 'iOS prebuilds already present'",
@@ -32,13 +32,13 @@
32
32
  "test:unit:generate": "brittle -r test/unit/all.js test/unit/*.test.js",
33
33
  "test:prestage": "node --test scripts/__tests__/*.test.js ../../.github/actions/cache-models/warm-models.test.mjs",
34
34
  "test:unit": "npm run build:ts && npm run test:unit:generate && bare test/unit/all.js --exit && npm run test:prestage && npm run test:mobile:validate",
35
- "test:cpp:build": "bare-make generate -D BUILD_TESTING=ON && bare-make build --target addon-test",
35
+ "test:cpp:build": "bare-make generate -D BUILD_TESTING=ON -D BUILD_FUZZING=OFF && bare-make build --target addon-test",
36
36
  "test:cpp:models": "node scripts/download-unit-test-models.js",
37
37
  "test:cpp:models:ci": "node scripts/download-unit-test-models.js --ci",
38
38
  "test:cpp:run": "node scripts/run-cpp-tests.js",
39
39
  "test:cpp:run:ci": "node scripts/run-cpp-tests.js --ci",
40
40
  "test:cpp": "npm run test:cpp:build && npm run test:cpp:run",
41
- "coverage:cpp:build": "bare-make generate -D BUILD_TESTING=ON -D ENABLE_COVERAGE=ON && bare-make build --target addon-test",
41
+ "coverage:cpp:build": "bare-make generate -D BUILD_TESTING=ON -D ENABLE_COVERAGE=ON -D BUILD_FUZZING=OFF && bare-make build --target addon-test",
42
42
  "coverage:cpp:run": "node scripts/run-cpp-tests.js --coverage",
43
43
  "coverage:cpp:run:ci": "node scripts/run-cpp-tests.js --coverage --ci",
44
44
  "coverage:cpp:summary": "cd build/test/unit && llvm-cov report ./addon-test --instr-profile=coverage.profdata -ignore-filename-regex='(tests|build|node_modules|gtest|gmock|\\.vcpkg|/usr)/' > coverage-summary.txt",
@@ -46,7 +46,13 @@
46
46
  "coverage:cpp": "npm run coverage:cpp:build && npm run coverage:cpp:run && npm run coverage:cpp:report",
47
47
  "test:all": "npm run test:unit && npm run test:integration && npm run test:cpp",
48
48
  "benchmarks": "./benchmarks/run-benchmarks.sh",
49
- "benchmarks:windows": "powershell -ExecutionPolicy Bypass -File ./benchmarks/run-benchmarks.ps1"
49
+ "benchmarks:windows": "powershell -ExecutionPolicy Bypass -File ./benchmarks/run-benchmarks.ps1",
50
+ "fuzz:build": "bare-make generate -D BUILD_FUZZING=ON -D BUILD_TESTING=OFF -D FUZZTEST_FUZZING_MODE=OFF && bare-make build --target llm-string-fuzz",
51
+ "fuzz:build:continuous": "bare-make generate -D BUILD_FUZZING=ON -D BUILD_TESTING=OFF -D FUZZTEST_FUZZING_MODE=ON && bare-make build --target llm-string-fuzz",
52
+ "fuzz:run": "npm run test:scripts && node scripts/run-cpp-fuzz.js",
53
+ "test:scripts": "node --test scripts/__tests__/run-cpp-fuzz.test.js",
54
+ "fuzz": "npm run fuzz:build && npm run fuzz:run",
55
+ "fuzz:continuous": "npm run fuzz:build:continuous && node scripts/run-cpp-fuzz.js --continuous"
50
56
  },
51
57
  "files": [
52
58
  "binding.js",
@@ -73,8 +79,8 @@
73
79
  "bugs": "https://github.com/tetherto/qvac/issues",
74
80
  "homepage": "https://qvac.tether.io",
75
81
  "devDependencies": {
76
- "@qvac/fabric-android-arm64": "0.22.0",
77
- "@qvac/fabric-ios": "0.22.0",
82
+ "@qvac/fabric-android-arm64": "0.23.0",
83
+ "@qvac/fabric-ios": "0.23.0",
78
84
  "@types/node": "^24.2.1",
79
85
  "bare-crypto": "^1.15.3",
80
86
  "bare-process": "^4.2.2",
@@ -96,22 +102,22 @@
96
102
  "typescript-eslint": "^8.63.0"
97
103
  },
98
104
  "dependencies": {
99
- "@qvac/fabric": "^0.22.0",
105
+ "@qvac/fabric": "^0.23.0",
100
106
  "@qvac/infer-base": "^0.8.0",
101
107
  "@qvac/logging": "^0.1.0",
102
108
  "bare-fs": "^4.5.1",
103
109
  "bare-path": "^3.0.0"
104
110
  },
105
111
  "optionalDependencies": {
106
- "@qvac/fabric-linux-x64": "^0.22.0",
107
- "@qvac/fabric-linux-arm64": "^0.22.0",
108
- "@qvac/fabric-darwin-arm64": "^0.22.0",
109
- "@qvac/fabric-darwin-x64": "^0.22.0",
110
- "@qvac/fabric-win32-x64": "^0.22.0"
112
+ "@qvac/fabric-linux-x64": "^0.23.0",
113
+ "@qvac/fabric-linux-arm64": "^0.23.0",
114
+ "@qvac/fabric-darwin-arm64": "^0.23.0",
115
+ "@qvac/fabric-darwin-x64": "^0.23.0",
116
+ "@qvac/fabric-win32-x64": "^0.23.0"
111
117
  },
112
118
  "peerDependencies": {
113
- "@qvac/fabric-android-arm64": "^0.22.0",
114
- "@qvac/fabric-ios": "^0.22.0"
119
+ "@qvac/fabric-android-arm64": "^0.23.0",
120
+ "@qvac/fabric-ios": "^0.23.0"
115
121
  },
116
122
  "peerDependenciesMeta": {
117
123
  "@qvac/fabric-android-arm64": {