@qvac/llm-llamacpp 0.56.0 → 0.58.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 +25 -4
- package/addon.d.ts +13 -1
- package/addon.js +18 -0
- package/index.d.ts +114 -113
- package/index.js +56 -11
- package/package.json +3 -3
- package/prebuilds/android-arm64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/darwin-arm64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/darwin-arm64/qvac__llm-llamacpp.bare.exports +218 -171
- package/prebuilds/darwin-x64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/darwin-x64/qvac__llm-llamacpp.bare.exports +241 -186
- package/prebuilds/ios-arm64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/ios-arm64/qvac__llm-llamacpp.bare.exports +219 -172
- package/prebuilds/ios-arm64-simulator/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/ios-arm64-simulator/qvac__llm-llamacpp.bare.exports +219 -172
- package/prebuilds/ios-x64-simulator/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/ios-x64-simulator/qvac__llm-llamacpp.bare.exports +244 -189
- package/prebuilds/linux-arm64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/linux-x64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/win32-x64/qvac__llm-llamacpp.bare +0 -0
- package/prebuilds/win32-x64/qvac__llm-llamacpp.bare.exports +0 -0
package/README.md
CHANGED
|
@@ -32,11 +32,26 @@ This native C++ addon, built using the `Bare` Runtime, simplifies running Large
|
|
|
32
32
|
|----------|-------------|-------------|--------|-------------|
|
|
33
33
|
| macOS | arm64, x64 | 14.0+ | ✅ Tier 1 | Metal |
|
|
34
34
|
| iOS | arm64 | 17.0+ | ✅ Tier 1 | Metal |
|
|
35
|
-
| Linux | arm64, x64 | Ubuntu-22+ |
|
|
35
|
+
| Linux | arm64, x64 | Ubuntu-22+ | Tier 1 | CUDA (NVIDIA), Vulkan |
|
|
36
36
|
| Android | arm64 | 12+ | ✅ Tier 1 | Vulkan, OpenCL (Adreno 700+) |
|
|
37
|
-
| Windows | x64 | 10+ |
|
|
37
|
+
| Windows | x64 | 10+ | Tier 1 | CUDA (NVIDIA), Vulkan |
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
**Note on CUDA (Linux and Windows, NVIDIA):**
|
|
41
|
+
The CUDA backend ships as a dynamically loaded module alongside Vulkan, and is preferred over
|
|
42
|
+
Vulkan when an NVIDIA device is present. On Windows it needs a CUDA 13 install: the runtime DLLs
|
|
43
|
+
are loaded from `%CUDA_PATH%\bin\x64`, not from `PATH`.
|
|
44
|
+
|
|
45
|
+
- If the CUDA module, driver, or required runtime DLL is missing, the device never registers and selection
|
|
46
|
+
falls through to Vulkan, then CPU. Nothing needs configuring for that.
|
|
47
|
+
- `backend: "vulkan"` forces Vulkan on an NVIDIA machine. Setting `CUDA_VISIBLE_DEVICES=-1` in the
|
|
48
|
+
environment has the same effect without touching the load config.
|
|
49
|
+
- TurboQuant / PolarQuant KV-cache types (`tbq3_0`, `tbq4_0`, `pq3_0`, `pq4_0`) are **not**
|
|
50
|
+
supported on CUDA and are rejected during model configuration. Use a Vulkan GPU or CPU for those.
|
|
51
|
+
Standard quantized types (`q4_0`, `q8_0`, …) work normally.
|
|
52
|
+
- BitNet (TQ1_0 / TQ2_0) and some LoRA finetuning kernels are not yet available on CUDA. Use
|
|
53
|
+
`backend: "vulkan"` for those workloads until the kernels land.
|
|
54
|
+
|
|
40
55
|
**Note — BitNet models (TQ1_0 / TQ2_0 quantization):**
|
|
41
56
|
BitNet models require special backend handling on Adreno GPUs. When a BitNet model is detected and no explicit `main-gpu` is set:
|
|
42
57
|
- **Adreno 800+** (e.g. Adreno 830): Vulkan is used instead of OpenCL.
|
|
@@ -177,9 +192,15 @@ const config = {
|
|
|
177
192
|
| tools | `"true"` or `"false"` | `"false"` | Enable tool calling with jinja templating |
|
|
178
193
|
| verbosity | 0 – 3 (0=ERROR, 1=WARNING, 2=INFO, 3=DEBUG) | 0 | Logging verbosity level |
|
|
179
194
|
| main-gpu | integer, `"integrated"`, or `"dedicated"` | — | GPU selection for multi-GPU systems |
|
|
195
|
+
| backend | comma-separated list of `cuda`, `vulkan`, `metal`, `opencl`, or `auto` | N/A | Overrides which GPU backend is used, in priority order (e.g. `"cuda,vulkan"`). `auto` means no preference. An unrecognised name is rejected; a recognised one with no device present is skipped. Use `device: "cpu"` to run on CPU. Cannot be combined with `split-mode` or `devices` |
|
|
180
196
|
| split-mode | `"none"`, `"layer"`, or `"tensor"` | `"none"` | How to split the model across GPUs. `"tensor"` is EXPERIMENTAL and desktop-only; `"row"` is rejected ([details](./docs/multi-gpu.md)) |
|
|
181
197
|
| tensor-split | comma-separated proportions (e.g. `"1,1"`) | — | GPU split ratios for the multi-GPU split modes ([details](./docs/multi-gpu.md)) |
|
|
182
198
|
| parallel | integer | 1 | Concurrent sequence slots for continuous batching. Values `>= 2` enable batch `run()` and split the KV cache uniformly across slots ([details](./docs/continuous-batching.md)) |
|
|
199
|
+
| cache_checkpoints | 0 – 1024 | 1 | Per-sequence cap on process-local checkpoints kept for cached requests on hybrid / recurrent models and DeepSeek V4, one per committed request at the end of its history. Each holds only the state a tail trim cannot rebuild (about 20 MB on Qwen3.5-0.8B). `1` serves the next turn and a regenerate; editing the user message k-th from the end (1 = the last) without reprocessing the whole conversation needs `k + 1`, so `2` for the last one; `0` disables them. Also accepted as `cache-checkpoints` ([details](./docs/cache-api.md), [how it works](./docs/cache-lifecycle.md)) |
|
|
200
|
+
| cache_checkpoints_max_bytes | integer (bytes) | 0 (unlimited) | Byte budget for those checkpoints, enforced before the count. The load fails with `InvalidArgument` when it cannot hold `cache_checkpoints` checkpoints of the largest size the context allows. Also accepted as `cache-checkpoints-max-bytes` |
|
|
201
|
+
| cache_checkpoint_storage | `"memory"` or `"disk"` | `"memory"` | Where checkpoints and the per-request rollback snapshot live. `"memory"` keeps them in host RAM so a cached chat never touches the disk; `"disk"` writes them to a private directory inside `cache_checkpoint_dir`, which it requires. Also accepted as `cache-checkpoint-storage` |
|
|
202
|
+
| cache_checkpoint_dir | directory path | none | **Required with `cache_checkpoint_storage: "disk"`**, and refused without it: where checkpoint files go, inside a private directory the addon creates in it (mode 0700; on Windows, the directory itself). On mobile, pass the app's cache directory. The load fails with `InvalidArgument` when it is missing, empty, or unusable. Also accepted as `cache-checkpoint-dir` |
|
|
203
|
+
| cache_ram_mib | 0 – 1048576 (MiB) | 0 (off) | Host-RAM budget for conversations that are not running. A `cacheKey` switch or a batch slot eviction moves the conversation there instead of writing its file, and the next request on that key restores it. A write-back cache: unsaved turns reach the file on `saveCache()`, when the budget pushes the entry out, or at unload; ephemeral entries are dropped instead. Also accepted as `cache-ram-mib` ([details](./docs/cache-api.md#keep-switched-away-conversations-in-ram)) |
|
|
183
204
|
| flash-attn | `"on"`/`"enabled"`/`"true"`/`"1"`, `"off"`/`"disabled"`/`"false"`/`"0"`, or `"auto"` | `"on"`, except when finetuning or on a BitNet model, where it is forced off | Flash attention. The four truthy and four falsey spellings are equivalent; `"auto"` is a third state that defers to qvac-fabric's runtime capability probe. Lower-case only — matching is case-sensitive and any other value is rejected. Affects the KV-cache auto-default — see below. Also accepted as `flash_attn`; supplying both spellings is an error |
|
|
184
205
|
| 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) |
|
|
185
206
|
| cache-type-v | `f16`, `f32`, `bf16`, `q8_0`, `q4_0`, … | auto (see below) | KV-cache **value** quantization type. Quantizing V requires `flash-attn` on |
|
|
@@ -192,7 +213,7 @@ The addon picks a safe KV-cache type when `cache-type-k`/`cache-type-v` are unse
|
|
|
192
213
|
|
|
193
214
|
- **Auto-default:** on a **Metal / Vulkan GPU** (with flash attention on) both K and V default to **`q8_0`** — quality-neutral vs `f16` and ~47% smaller KV cache. **CPU** and **OpenCL (Adreno)** keep **`f16`** (ARM CPU `q8_0` has a quality/throughput cost; quantized KV is unsafe on OpenCL — see below). Finetuning manages its own KV types and is left untouched.
|
|
194
215
|
- **`flash-attn: 'auto'` keeps `f16`.** "Flash attention on" above means a truthy `flash-attn` — `on`, `enabled`, `true` or `1`, or the `on` default when the key is unset. `'auto'` is deliberately excluded: quantizing the V cache forces qvac-fabric to promote AUTO to ENABLED, which skips the runtime capability probe that `'auto'` exists to run. So `'auto'` trades the ~47% KV-cache saving for letting qvac-fabric decide. To get both, set `cache-type-k`/`-v` explicitly alongside `'auto'`, or use `'on'`. **`split-mode: 'tensor'` is the exception:** qvac-fabric promotes AUTO to ENABLED unconditionally for that mode, so there is no probe to preserve and `'auto'` takes the q8_0 default there exactly as `'on'` does.
|
|
195
|
-
- **OpenCL (Adreno) accepts only `f16`/`f32`/`bf16`:** any other cache type — quantized (`q8_0`, `q4_0`, `q4_1`, `q5_0`, …) or unrecognized — throws a `StatusError`. A quantized K or V cache aborts in `llama_kv_cache::update`
|
|
216
|
+
- **OpenCL (Adreno) accepts only `f16`/`f32`/`bf16`:** any other cache type — quantized (`q8_0`, `q4_0`, `q4_1`, `q5_0`, …) or unrecognized — throws a `StatusError`. A quantized K or V cache aborts in `llama_kv_cache::update` during state restore because ggml-opencl has no `F32→quantized` requantize kernel. Use `f16`/`f32`/`bf16`, or a Vulkan GPU / CPU.
|
|
196
217
|
- **Mixed K≠V is a warning, not an error:** if K and V differ and at least one is quantized, the addon logs a warning (asymmetric quantized K/V falls off the fused flash-attention path — a notable GPU decode penalty — for no quality benefit, and is unsupported on Adreno OpenCL) but proceeds. Prefer a symmetric type. (This may be relaxed once qvac-fabric handles asymmetric quantized K/V efficiently.)
|
|
197
218
|
|
|
198
219
|
|
|
@@ -347,7 +368,7 @@ Admission is controlled by `rejectWhenBusy` (instance-level `opts.rejectWhenBusy
|
|
|
347
368
|
|
|
348
369
|
#### Prefill (cache warming) with `parallel >= 2`
|
|
349
370
|
|
|
350
|
-
A prefill-only run (`runOptions.prefill: true`) is admitted on a parallel model only when its product survives the slot teardown, i.e. it
|
|
371
|
+
A prefill-only run (`runOptions.prefill: true`) is admitted on a parallel model only when its product survives the slot teardown, i.e. it has a `cacheKey`: the warmed conversation stays in its slot for the next request on that key. A keyless prefill warms context state that no concurrent job could ever reach, so it is rejected with `InvalidArgument`; run it on a `parallel: 1` model instead. The same rule applies per item in batch runs. See [cache-api.md](./docs/cache-api.md).
|
|
351
372
|
|
|
352
373
|
#### Cancelling a batch
|
|
353
374
|
|
package/addon.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface AddonMessage {
|
|
|
13
13
|
*/
|
|
14
14
|
generationParams?: GenerationParams;
|
|
15
15
|
cacheKey?: string;
|
|
16
|
-
|
|
16
|
+
ephemeral?: boolean;
|
|
17
17
|
}
|
|
18
18
|
export interface AddonMediaMessage {
|
|
19
19
|
type: "media";
|
|
@@ -74,6 +74,8 @@ export interface LlamaBinding {
|
|
|
74
74
|
cancel(handle: unknown, savePauseCheckpoint: number): Promise<void> | void;
|
|
75
75
|
cancelJob(handle: unknown, id: number): Promise<void> | void;
|
|
76
76
|
finetune?(handle: unknown, params: FinetuneOptions): Promise<number | false> | number | false;
|
|
77
|
+
saveCache(handle: unknown, cacheKey: string): Promise<void>;
|
|
78
|
+
discardCache(handle: unknown, cacheKey: string): Promise<void>;
|
|
77
79
|
runJob(handle: unknown, data: AddonRunJobMessage[]): Promise<AddonRunJobResult>;
|
|
78
80
|
runJob(handle: unknown, data: AddonBatchRunItem[]): Promise<AddonBatchRunResult>;
|
|
79
81
|
destroyInstance(handle: unknown): void;
|
|
@@ -127,6 +129,16 @@ export declare class LlamaInterface {
|
|
|
127
129
|
* jobs running. Routes to MultiJobScheduler::cancel(id) -> cancelById(id).
|
|
128
130
|
*/
|
|
129
131
|
cancelJob(id: number): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Write the conversation kept in memory for `cacheKey` to its file (see
|
|
134
|
+
* `LlmLlamacpp.saveCache`).
|
|
135
|
+
*/
|
|
136
|
+
saveCache(cacheKey: string): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Drop the conversation kept in memory for `cacheKey` without writing it
|
|
139
|
+
* (see `LlmLlamacpp.discardCache`).
|
|
140
|
+
*/
|
|
141
|
+
discardCache(cacheKey: string): Promise<void>;
|
|
130
142
|
/**
|
|
131
143
|
* Run finetuning when native binding provides support.
|
|
132
144
|
*/
|
package/addon.js
CHANGED
|
@@ -160,6 +160,24 @@ class LlamaInterface {
|
|
|
160
160
|
}
|
|
161
161
|
await this._binding.cancelJob(this._handle, id);
|
|
162
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Write the conversation kept in memory for `cacheKey` to its file (see
|
|
165
|
+
* `LlmLlamacpp.saveCache`).
|
|
166
|
+
*/
|
|
167
|
+
saveCache(cacheKey) {
|
|
168
|
+
if (!this._handle)
|
|
169
|
+
return Promise.reject(new Error("Model is not loaded"));
|
|
170
|
+
return Promise.resolve(this._binding.saveCache(this._handle, cacheKey));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Drop the conversation kept in memory for `cacheKey` without writing it
|
|
174
|
+
* (see `LlmLlamacpp.discardCache`).
|
|
175
|
+
*/
|
|
176
|
+
discardCache(cacheKey) {
|
|
177
|
+
if (!this._handle)
|
|
178
|
+
return Promise.reject(new Error("Model is not loaded"));
|
|
179
|
+
return Promise.resolve(this._binding.discardCache(this._handle, cacheKey));
|
|
180
|
+
}
|
|
163
181
|
/**
|
|
164
182
|
* Run finetuning when native binding provides support.
|
|
165
183
|
*/
|
package/index.d.ts
CHANGED
|
@@ -55,6 +55,21 @@ interface LlmLlamacpp {
|
|
|
55
55
|
cancel(): Promise<void>;
|
|
56
56
|
pause(): Promise<void>;
|
|
57
57
|
unload(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Write the conversation kept in memory for `cacheKey` to that file now.
|
|
60
|
+
* Waits for a request running on that key, not one still queued, so read a
|
|
61
|
+
* response to the end first. Writes only when the file lacks turns, and
|
|
62
|
+
* rejects when nothing is cached under the key or the write fails (the
|
|
63
|
+
* conversation then stays in memory, still unsaved).
|
|
64
|
+
*/
|
|
65
|
+
saveCache(cacheKey: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Drop the conversation kept in memory for `cacheKey` without writing it,
|
|
68
|
+
* so no later eviction or unload writes it. Waits for a request running on
|
|
69
|
+
* that key. The file, if one was written, is left alone: delete it too to
|
|
70
|
+
* remove the conversation from disk.
|
|
71
|
+
*/
|
|
72
|
+
discardCache(cacheKey: string): Promise<void>;
|
|
58
73
|
getState(): {
|
|
59
74
|
configLoaded: boolean;
|
|
60
75
|
};
|
|
@@ -117,6 +132,13 @@ declare namespace LlmLlamacpp {
|
|
|
117
132
|
}
|
|
118
133
|
interface LlamaConfig {
|
|
119
134
|
device?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Comma-separated GPU backend priority list, e.g. `'cuda,vulkan'`.
|
|
137
|
+
* Accepted names: cuda, vulkan, metal, opencl, plus
|
|
138
|
+
* `auto` for no preference. An unrecognised name is rejected; a recognised
|
|
139
|
+
* one with no device present is skipped. Use `device: 'cpu'` to run on CPU.
|
|
140
|
+
*/
|
|
141
|
+
backend?: string;
|
|
120
142
|
gpu_layers?: NumericLike;
|
|
121
143
|
ctx_size?: NumericLike;
|
|
122
144
|
system_prompt?: string;
|
|
@@ -264,6 +286,63 @@ declare namespace LlmLlamacpp {
|
|
|
264
286
|
* `InvalidArgument` naming both knobs.
|
|
265
287
|
*/
|
|
266
288
|
parallel?: NumericLike;
|
|
289
|
+
/**
|
|
290
|
+
* Per-sequence cap on the process-local full-state checkpoints kept for
|
|
291
|
+
* cached requests on hybrid / recurrent models (Qwen3.5, Jamba,
|
|
292
|
+
* Granite-Hybrid, DeepSeek V4, ...). Such models cannot trim a KV tail,
|
|
293
|
+
* so a diverging history is served by restoring the longest checkpoint
|
|
294
|
+
* that is still a prefix of the new prompt. A committed cached request
|
|
295
|
+
* adds one, at the end of its history, holding only the state a tail
|
|
296
|
+
* trim cannot rebuild. The default is 1: the last one, which an ordinary
|
|
297
|
+
* next turn and a regenerate restore. `2` also keeps the one before it,
|
|
298
|
+
* which an edit of the last user message restores. Changing the user
|
|
299
|
+
* message k-th from the end (1 = the last) without reprocessing the whole
|
|
300
|
+
* conversation needs at least k + 1; with the default 1, editing the last
|
|
301
|
+
* user message reprocesses everything. `0` keeps and takes none (every
|
|
302
|
+
* divergent turn is a cold prefill), the maximum is 1024.
|
|
303
|
+
* Ignored on pure-attention models, which never take checkpoints.
|
|
304
|
+
* Also accepted as `cache-checkpoints`; supplying both is an error.
|
|
305
|
+
*/
|
|
306
|
+
cache_checkpoints?: NumericLike;
|
|
307
|
+
/**
|
|
308
|
+
* Byte budget for the checkpoints kept per sequence, enforced before
|
|
309
|
+
* `cache_checkpoints`: the oldest checkpoints are dropped until the total
|
|
310
|
+
* payload fits. `0` (default) is unlimited. When set, the model load fails
|
|
311
|
+
* with `InvalidArgument` if the budget cannot hold `cache_checkpoints`
|
|
312
|
+
* checkpoints of the largest size the context allows, so a misconfigured
|
|
313
|
+
* budget is reported up front rather than discovered mid-conversation.
|
|
314
|
+
* Also accepted as `cache-checkpoints-max-bytes`.
|
|
315
|
+
*/
|
|
316
|
+
cache_checkpoints_max_bytes?: NumericLike;
|
|
317
|
+
/**
|
|
318
|
+
* Where checkpoints and the per-request rollback snapshot live:
|
|
319
|
+
* `'memory'` (default) keeps them in host RAM so a cached chat never
|
|
320
|
+
* touches the disk, `'disk'` writes them to files in
|
|
321
|
+
* `cache_checkpoint_dir`, which it requires. Each snapshot holds only the
|
|
322
|
+
* state a tail trim cannot rebuild, a size fixed by the model (about 20 MB
|
|
323
|
+
* on Qwen3.5-0.8B). Also accepted as `cache-checkpoint-storage`.
|
|
324
|
+
*/
|
|
325
|
+
cache_checkpoint_storage?: "disk" | "memory";
|
|
326
|
+
/**
|
|
327
|
+
* Required with `cache_checkpoint_storage: 'disk'`, and refused without
|
|
328
|
+
* it: where checkpoint files go, inside a private directory the addon
|
|
329
|
+
* creates in it (mode 0700; on Windows, the directory itself). There is
|
|
330
|
+
* no default, because the OS temp directory is not usable on every
|
|
331
|
+
* platform (Android); on mobile, pass the app's cache directory. The load
|
|
332
|
+
* fails with `InvalidArgument` when it is missing, empty, or unusable.
|
|
333
|
+
* Also accepted as `cache-checkpoint-dir`.
|
|
334
|
+
*/
|
|
335
|
+
cache_checkpoint_dir?: string;
|
|
336
|
+
/**
|
|
337
|
+
* Host-RAM budget, in MiB, for conversations that are not running: a
|
|
338
|
+
* `cacheKey` switch (single prompt) or a slot eviction (batch) moves the
|
|
339
|
+
* conversation there instead of writing its file, and the next request on
|
|
340
|
+
* that key restores it from there. A write-back cache: unsaved turns reach
|
|
341
|
+
* the file on `saveCache()`, when the budget pushes the entry out, or at
|
|
342
|
+
* unload; ephemeral entries are dropped instead. `0` (default) is off; the
|
|
343
|
+
* maximum is 1048576 (1 TiB). Also accepted as `cache-ram-mib`.
|
|
344
|
+
*/
|
|
345
|
+
cache_ram_mib?: NumericLike;
|
|
267
346
|
[key: string]: string | number | boolean | string[] | undefined;
|
|
268
347
|
}
|
|
269
348
|
interface LlmLlamacppArgs {
|
|
@@ -287,6 +366,14 @@ declare namespace LlmLlamacpp {
|
|
|
287
366
|
interface UserTextMessage {
|
|
288
367
|
role: "system" | "assistant" | "user" | "tool" | "session" | string;
|
|
289
368
|
content: string;
|
|
369
|
+
/**
|
|
370
|
+
* An assistant turn's reasoning, separate from its answer in `content`, as
|
|
371
|
+
* in OpenAI-compatible APIs. Optional: an answer sent back with its
|
|
372
|
+
* reasoning inline (`<think>…</think>` first, as the model streamed it) is
|
|
373
|
+
* split into the two, using the reasoning markers the chat template
|
|
374
|
+
* reports, so the template can drop or place it. Ignored on other roles.
|
|
375
|
+
*/
|
|
376
|
+
reasoning_content?: string;
|
|
290
377
|
type?: undefined;
|
|
291
378
|
[key: string]: any;
|
|
292
379
|
}
|
|
@@ -349,6 +436,12 @@ declare namespace LlmLlamacpp {
|
|
|
349
436
|
* `"required"` or a function name without tools throws.
|
|
350
437
|
*/
|
|
351
438
|
tool_choice?: "auto" | "none" | "required" | (string & {});
|
|
439
|
+
/**
|
|
440
|
+
* Whether one response may carry more than one tool call. `true` lets the
|
|
441
|
+
* template and the tool-call grammar accept several; unset or `false`
|
|
442
|
+
* keeps one. Ignored when the prompt carries no tools.
|
|
443
|
+
*/
|
|
444
|
+
parallel_tool_calls?: boolean;
|
|
352
445
|
/**
|
|
353
446
|
* Per-request reasoning channel budget. `-1` keeps the model's reasoning
|
|
354
447
|
* channel on; `0` disables it for this request; any positive integer caps
|
|
@@ -357,120 +450,37 @@ declare namespace LlmLlamacpp {
|
|
|
357
450
|
* value is restored afterwards.
|
|
358
451
|
*/
|
|
359
452
|
reasoning_budget?: number;
|
|
360
|
-
/**
|
|
361
|
-
* When the model emits a reasoning block during generation (e.g.
|
|
362
|
-
* `<think>...</think>` for the Qwen3 family, `<|channel>thought ...
|
|
363
|
-
* <channel|>` for Gemma 4), drop those tokens from the KV cache at
|
|
364
|
-
* end-of-generation so subsequent turns do not accumulate reasoning
|
|
365
|
-
* history.
|
|
366
|
-
*
|
|
367
|
-
* Defaults to `false` for all models except the Qwen3 reasoning family
|
|
368
|
-
* (Qwen3, Qwen3.5, and Qwen3.6, including MoE variants), which defaults
|
|
369
|
-
* to `true`. Set this per-request `generationParams` value to override the
|
|
370
|
-
* model default. Set to `false` to preserve reasoning tokens in the KV / SSM
|
|
371
|
-
* cache across turns (e.g. chain-of-thought agents that want the next turn
|
|
372
|
-
* to attend to prior reasoning, interpretability tooling, or cache-reuse
|
|
373
|
-
* patterns that depend on the reasoning-inclusive state). Supported on both
|
|
374
|
-
* text and multimodal contexts. No-op for models without a recognised
|
|
375
|
-
* reasoning channel.
|
|
376
|
-
*
|
|
377
|
-
* Every model kind is handled the same way: the sequence is rewound to a
|
|
378
|
-
* boundary anchored BEFORE the reasoning span, and the tokens that sit
|
|
379
|
-
* outside the span, the pre-reasoning preamble and the answer tail, are
|
|
380
|
-
* replayed through the decoder. Only the anchor's form differs. Recurrent
|
|
381
|
-
* / hybrid-SSM models (Qwen3.5, Qwen3-Next, Jamba, Granite-Hybrid, ...)
|
|
382
|
-
* anchor a full-state snapshot, because the recurrent half cannot be
|
|
383
|
-
* rewound by dropping cells; pure-attention models anchor a bare position
|
|
384
|
-
* and rewind with a tail trim.
|
|
385
|
-
*
|
|
386
|
-
* No structural reasoning marker is seeded or replayed, so the compacted
|
|
387
|
-
* cache is preamble plus answer with no `<think>` / `</think>` scaffold
|
|
388
|
-
* left behind, and close-marker length decides nothing: a marker that
|
|
389
|
-
* tokenises to several pieces is supported like any other. Chat templates
|
|
390
|
-
* that force-open the reasoning channel during prefill and templates that
|
|
391
|
-
* let the model generate the opener are both supported; on the
|
|
392
|
-
* generated-opener path the sampled pieces that open the block are clipped
|
|
393
|
-
* out of the replay rather than rebuilt.
|
|
394
|
-
*
|
|
395
|
-
* Prefill-only (cache-warm) requests anchor nothing: they never enter
|
|
396
|
-
* generation and cannot emit reasoning tokens.
|
|
397
|
-
*
|
|
398
|
-
* Uniform hard-fail contract: any inability to remove the reasoning
|
|
399
|
-
* span from cache, whether the boundary anchor, the rewind, or the
|
|
400
|
-
* replay step, is surfaced to the caller as a `StatusError`. There is no
|
|
401
|
-
* soft-failure counter: if the feature is
|
|
402
|
-
* enabled and cache cleanup cannot complete, the final request result is
|
|
403
|
-
* failed rather than reported as a successful answer with the reasoning span
|
|
404
|
-
* still resident in cache.
|
|
405
|
-
*
|
|
406
|
-
* Streaming caveat: token callbacks (`outputCallback` / batch `onToken`) are
|
|
407
|
-
* invoked during generation, while reasoning-block compaction runs at
|
|
408
|
-
* end-of-generation. If compaction fails, streaming callers may already have
|
|
409
|
-
* received partial or complete text. Treat streamed text as tentative until
|
|
410
|
-
* the request completes successfully; non-streaming callers receive no
|
|
411
|
-
* successful returned answer on this failure path.
|
|
412
|
-
*
|
|
413
|
-
* Before throwing, the affected sequence is cleaned up so that the
|
|
414
|
-
* next request on the same context starts from a coherent state:
|
|
415
|
-
* * Boundary-anchor failure: nothing has been rewound yet, so the
|
|
416
|
-
* driver rolls back to its pre-prompt checkpoint (or clears the
|
|
417
|
-
* sequence entirely on restore underflow) and resets positional
|
|
418
|
-
* accounting, then rethrows.
|
|
419
|
-
* * Rewind or replay failure: compaction rewinds before it replays,
|
|
420
|
-
* so live KV has already been written to by this point and a tail
|
|
421
|
-
* trim can no longer reach a coherent state. The compactor
|
|
422
|
-
* best-effort wipes the sequence and the driver zeroes its
|
|
423
|
-
* positional accounting to match, so subsequent turns cannot decode
|
|
424
|
-
* into contaminated positions.
|
|
425
|
-
*
|
|
426
|
-
* On the continuous-batch path, the scheduler's error-recovery leg
|
|
427
|
-
* deliberately does NOT persist the failed slot's cache: when the
|
|
428
|
-
* request was configured with `cacheKey` + `saveCacheToDisk`, the
|
|
429
|
-
* last known-good on-disk cache is preserved rather than being
|
|
430
|
-
* overwritten with the post-failure state. The same skip-save rule
|
|
431
|
-
* applies to graceful cancels of hybrid / recurrent requests when
|
|
432
|
-
* rollback to the pre-request cursor cannot be completed (recurrent
|
|
433
|
-
* full-state restore refused, or no pre-request snapshot was captured
|
|
434
|
-
* yet the driver has advanced past the pre-request cursor). Cancels
|
|
435
|
-
* that can be rolled back cleanly still persist as usual.
|
|
436
|
-
*/
|
|
437
|
-
remove_thinking_from_context?: boolean;
|
|
438
453
|
}
|
|
439
454
|
interface RunOptions {
|
|
440
455
|
/**
|
|
441
456
|
* Run prefill only (cache warming): the prompt is evaluated but no tokens
|
|
442
|
-
* are generated. On a model loaded with `parallel >= 2` a prefill
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* job could reach and is rejected with `InvalidArgument`; run
|
|
446
|
-
* prefills on a `parallel: 1` model. The same rule applies per
|
|
457
|
+
* are generated. On a model loaded with `parallel >= 2` a prefill needs a
|
|
458
|
+
* `cacheKey` (the warmed conversation stays in its slot for the next
|
|
459
|
+
* request on that key) — a keyless prefill warms context state that no
|
|
460
|
+
* concurrent job could reach and is rejected with `InvalidArgument`; run
|
|
461
|
+
* keyless prefills on a `parallel: 1` model. The same rule applies per
|
|
462
|
+
* batch item.
|
|
447
463
|
*/
|
|
448
464
|
prefill?: boolean;
|
|
449
465
|
generationParams?: GenerationParams;
|
|
466
|
+
/**
|
|
467
|
+
* Enables addon-owned prompt caching at this path. Every cached request
|
|
468
|
+
* must resend the complete authoritative message history and tool list;
|
|
469
|
+
* delta-only continuations are not supported. The addon renders once and
|
|
470
|
+
* decodes only the suffix after the longest matching token/media prefix.
|
|
471
|
+
*/
|
|
450
472
|
cacheKey?: string;
|
|
451
473
|
/**
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* failure with `SaveCachePolicy::Skip`, or a
|
|
461
|
-
* `remove_thinking_from_context` hard-fail).
|
|
462
|
-
* - Graceful cancel of a hybrid / recurrent request whose driver
|
|
463
|
-
* cannot roll live memory back to the pre-request cursor —
|
|
464
|
-
* either the recurrent full-state restore was refused, or no
|
|
465
|
-
* pre-request snapshot exists yet the driver advanced past the
|
|
466
|
-
* pre-request cursor. Cancels that roll back cleanly still save.
|
|
467
|
-
*
|
|
468
|
-
* On both skip paths the sequence's in-memory KV is still cleared, so
|
|
469
|
-
* subsequent requests decode from a coherent baseline; only the
|
|
470
|
-
* on-disk cache is untouched. Pure-attention drivers always roll back
|
|
471
|
-
* via `removeLastNTokens` and therefore save on cancel as usual.
|
|
474
|
+
* Keep this conversation in memory only. By default a cached
|
|
475
|
+
* conversation is written to its `cacheKey` file whenever it would
|
|
476
|
+
* otherwise be lost with turns the file lacks: when it is set aside for
|
|
477
|
+
* another key, evicted from its batch slot or from the RAM tier, or the
|
|
478
|
+
* model is reloaded or unloaded. With `ephemeral: true` each of those
|
|
479
|
+
* drops it instead, so its next request starts cold. It still loads an
|
|
480
|
+
* existing file, and an explicit `saveCache(cacheKey)` still writes it.
|
|
481
|
+
* The flag belongs to the conversation's latest request.
|
|
472
482
|
*/
|
|
473
|
-
|
|
483
|
+
ephemeral?: boolean;
|
|
474
484
|
/**
|
|
475
485
|
* Admission policy when the worker pool is full. `true` rejects before
|
|
476
486
|
* submitting with an `Error` carrying `code === 'RUN_BUSY'` — branch on the
|
|
@@ -523,15 +533,6 @@ declare namespace LlmLlamacpp {
|
|
|
523
533
|
CacheTokens: number;
|
|
524
534
|
generatedTokens: number;
|
|
525
535
|
promptTokens: number;
|
|
526
|
-
/**
|
|
527
|
-
* Number of `<think>` (or model-equivalent) reasoning blocks dropped
|
|
528
|
-
* from the KV cache at end-of-generation by the
|
|
529
|
-
* `remove_thinking_from_context` feature. Per-inference for single
|
|
530
|
-
* requests; summed across completed slots for batch requests. 0 when
|
|
531
|
-
* the model has no recognised reasoning channel, when the feature
|
|
532
|
-
* was disabled per-request, or when no reasoning blocks were emitted.
|
|
533
|
-
*/
|
|
534
|
-
thinkingBlockDiscards: number;
|
|
535
536
|
/**
|
|
536
537
|
* Number of prompt renders in this request that provably left the tool
|
|
537
538
|
* definitions out — the template either rejected them, or supplying them
|
package/index.js
CHANGED
|
@@ -15,7 +15,7 @@ function normalizeRunOptions(runOptions) {
|
|
|
15
15
|
prefill: false,
|
|
16
16
|
generationParams: undefined,
|
|
17
17
|
cacheKey: undefined,
|
|
18
|
-
|
|
18
|
+
ephemeral: false,
|
|
19
19
|
rejectWhenBusy: undefined,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
@@ -35,8 +35,13 @@ function normalizeRunOptions(runOptions) {
|
|
|
35
35
|
if (options.cacheKey !== undefined && typeof options.cacheKey !== "string") {
|
|
36
36
|
throw new TypeError("cacheKey must be a string when provided");
|
|
37
37
|
}
|
|
38
|
-
if (options.saveCacheToDisk !== undefined
|
|
39
|
-
throw new TypeError("saveCacheToDisk
|
|
38
|
+
if (options.saveCacheToDisk !== undefined) {
|
|
39
|
+
throw new TypeError("saveCacheToDisk was removed: a cached conversation stays in memory and is written to " +
|
|
40
|
+
"its cacheKey file when it is set aside, evicted or the model unloads; call " +
|
|
41
|
+
"saveCache(cacheKey) to write it now, or set ephemeral: true to never write it");
|
|
42
|
+
}
|
|
43
|
+
if (options.ephemeral !== undefined && typeof options.ephemeral !== "boolean") {
|
|
44
|
+
throw new TypeError("ephemeral must be a boolean when provided");
|
|
40
45
|
}
|
|
41
46
|
if (options.rejectWhenBusy !== undefined && typeof options.rejectWhenBusy !== "boolean") {
|
|
42
47
|
throw new TypeError("rejectWhenBusy must be a boolean when provided");
|
|
@@ -45,7 +50,7 @@ function normalizeRunOptions(runOptions) {
|
|
|
45
50
|
prefill: options.prefill === true,
|
|
46
51
|
generationParams: normalizeGenerationParams(options.generationParams),
|
|
47
52
|
cacheKey: options.cacheKey,
|
|
48
|
-
|
|
53
|
+
ephemeral: options.ephemeral === true,
|
|
49
54
|
// Left undefined when unset so admission falls back to the instance default.
|
|
50
55
|
rejectWhenBusy: options.rejectWhenBusy,
|
|
51
56
|
};
|
|
@@ -54,7 +59,7 @@ function promptToAddonMessages(prompt, runOptions) {
|
|
|
54
59
|
if (!Array.isArray(prompt)) {
|
|
55
60
|
throw new TypeError("Prompt input must be Message[]");
|
|
56
61
|
}
|
|
57
|
-
const { prefill, generationParams, cacheKey,
|
|
62
|
+
const { prefill, generationParams, cacheKey, ephemeral } = normalizeRunOptions(runOptions);
|
|
58
63
|
const textMessages = [];
|
|
59
64
|
const mediaItems = [];
|
|
60
65
|
for (const message of prompt) {
|
|
@@ -77,7 +82,7 @@ function promptToAddonMessages(prompt, runOptions) {
|
|
|
77
82
|
prefill,
|
|
78
83
|
generationParams,
|
|
79
84
|
cacheKey,
|
|
80
|
-
|
|
85
|
+
ephemeral,
|
|
81
86
|
});
|
|
82
87
|
return promptMessages;
|
|
83
88
|
}
|
|
@@ -97,8 +102,8 @@ const GENERATION_PARAM_KEYS = new Set([
|
|
|
97
102
|
"grammar",
|
|
98
103
|
"json_schema",
|
|
99
104
|
"tool_choice",
|
|
105
|
+
"parallel_tool_calls",
|
|
100
106
|
"reasoning_budget",
|
|
101
|
-
"remove_thinking_from_context",
|
|
102
107
|
]);
|
|
103
108
|
// Normalizes the per-request `generationParams.json_schema` field. The
|
|
104
109
|
// addon binding expects a string; callers commonly pass a plain object
|
|
@@ -137,14 +142,14 @@ function normalizeGenerationParams(generationParams) {
|
|
|
137
142
|
params[key] = value;
|
|
138
143
|
}
|
|
139
144
|
const sanitized = params;
|
|
140
|
-
if (sanitized.remove_thinking_from_context !== undefined &&
|
|
141
|
-
typeof sanitized.remove_thinking_from_context !== "boolean") {
|
|
142
|
-
throw new TypeError("generationParams.remove_thinking_from_context must be a boolean when provided");
|
|
143
|
-
}
|
|
144
145
|
if (sanitized.tool_choice !== undefined &&
|
|
145
146
|
(typeof sanitized.tool_choice !== "string" || sanitized.tool_choice.length === 0)) {
|
|
146
147
|
throw new TypeError('generationParams.tool_choice must be "auto", "none", "required" or a declared function name');
|
|
147
148
|
}
|
|
149
|
+
if (sanitized.parallel_tool_calls !== undefined &&
|
|
150
|
+
typeof sanitized.parallel_tool_calls !== "boolean") {
|
|
151
|
+
throw new TypeError("generationParams.parallel_tool_calls must be a boolean when provided");
|
|
152
|
+
}
|
|
148
153
|
const hasGrammar = typeof sanitized.grammar === "string" && sanitized.grammar.length > 0;
|
|
149
154
|
const hasJsonSchema = sanitized.json_schema !== undefined &&
|
|
150
155
|
sanitized.json_schema !== null &&
|
|
@@ -781,6 +786,46 @@ const LlmLlamacpp = class LlmLlamacpp {
|
|
|
781
786
|
get _hasActiveResponse() {
|
|
782
787
|
return (this.addon ? this.addon.activeJobs() : 0) > 0;
|
|
783
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* Write the conversation kept in memory for `cacheKey` to that file now:
|
|
791
|
+
* the active single-prompt conversation, a batch conversation kept in its
|
|
792
|
+
* slot, or one in the RAM tier. Resolves once the file is written. When a
|
|
793
|
+
* request on that key is running it waits for it to finish, so the file
|
|
794
|
+
* always holds a committed state. A request still queued is not waited
|
|
795
|
+
* for: `run()` resolves when the request is accepted, so read its response
|
|
796
|
+
* to the end before saving when the file must hold that turn. Resolves without writing when the file
|
|
797
|
+
* already holds the conversation. Rejects when nothing is cached under the
|
|
798
|
+
* key and no file exists, or when the write fails (the conversation then
|
|
799
|
+
* stays in memory, still unsaved, so the call can be retried). Writes
|
|
800
|
+
* ephemeral conversations too.
|
|
801
|
+
*/
|
|
802
|
+
async saveCache(cacheKey) {
|
|
803
|
+
if (typeof cacheKey !== "string" || cacheKey.length === 0) {
|
|
804
|
+
throw new TypeError("saveCache(cacheKey) requires a non-empty string");
|
|
805
|
+
}
|
|
806
|
+
if (!this.addon) {
|
|
807
|
+
throw new Error("Model is not loaded");
|
|
808
|
+
}
|
|
809
|
+
await this.addon.saveCache(cacheKey);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Drop the conversation kept in memory for `cacheKey` — the active
|
|
813
|
+
* single-prompt conversation, a batch conversation kept in its slot, its
|
|
814
|
+
* RAM-tier entry and its checkpoints — without writing it, so no later
|
|
815
|
+
* eviction or unload writes it. When a request on that key is running it
|
|
816
|
+
* waits for it to finish. The file, if one was written, is left alone:
|
|
817
|
+
* delete it as well to remove the conversation from disk. Resolves when
|
|
818
|
+
* nothing is kept for the key.
|
|
819
|
+
*/
|
|
820
|
+
async discardCache(cacheKey) {
|
|
821
|
+
if (typeof cacheKey !== "string" || cacheKey.length === 0) {
|
|
822
|
+
throw new TypeError("discardCache(cacheKey) requires a non-empty string");
|
|
823
|
+
}
|
|
824
|
+
if (!this.addon) {
|
|
825
|
+
throw new Error("Model is not loaded");
|
|
826
|
+
}
|
|
827
|
+
await this.addon.discardCache(cacheKey);
|
|
828
|
+
}
|
|
784
829
|
getState() {
|
|
785
830
|
return this.state;
|
|
786
831
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/llm-llamacpp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "llama addon for qvac",
|
|
5
5
|
"addon": true,
|
|
6
6
|
"scripts": {
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@types/node": "^24.2.1",
|
|
79
79
|
"bare-crypto": "^1.15.3",
|
|
80
80
|
"bare-process": "^4.2.2",
|
|
81
|
-
"bare-subprocess": "^
|
|
81
|
+
"bare-subprocess": "^6.1.0",
|
|
82
82
|
"bare-url": "^2.1.6",
|
|
83
83
|
"brittle": "^3.16.5",
|
|
84
84
|
"cmake-bare": "^1.7.5",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"typescript-eslint": "^8.63.0"
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"@qvac/fabric": "^0.20.
|
|
99
|
+
"@qvac/fabric": "^0.20.2",
|
|
100
100
|
"@qvac/infer-base": "^0.6.2",
|
|
101
101
|
"@qvac/logging": "^0.1.0",
|
|
102
102
|
"bare-fs": "^4.5.1",
|
|
Binary file
|
|
Binary file
|