@mlx-node/core 0.0.6 → 0.0.8

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.
Files changed (3) hide show
  1. package/index.cjs +94 -69
  2. package/index.d.cts +2132 -784
  3. package/package.json +7 -6
package/index.d.cts CHANGED
@@ -26,7 +26,12 @@ export declare class BatchGenerationResult {
26
26
  get groupSize(): number;
27
27
  }
28
28
 
29
- /** Handle returned by `chat_stream()` to control an in-progress streaming generation. */
29
+ /**
30
+ * Handle returned by the streaming chat-session entry points
31
+ * (`chat_stream_session_start`, `chat_stream_session_continue`,
32
+ * `chat_stream_session_continue_tool`) to control an in-progress
33
+ * streaming generation.
34
+ */
30
35
  export declare class ChatStreamHandle {
31
36
  cancel(): void;
32
37
  }
@@ -103,6 +108,168 @@ export declare class DocUnwarpModel {
103
108
  unwarp(imageData: Uint8Array): UnwarpResult;
104
109
  }
105
110
 
111
+ /**
112
+ * Gemma 4 dense language model.
113
+ *
114
+ * Supports E2B (2.3B), E4B (4.5B), and 31B variants.
115
+ * Features: hybrid attention (sliding + global), GeGLU MLP, logit softcapping,
116
+ * embedding scaling, and optional per-layer embeddings.
117
+ *
118
+ * All model state lives on a dedicated OS thread. NAPI methods dispatch
119
+ * commands via channels and await responses.
120
+ */
121
+ export declare class Gemma4Model {
122
+ /**
123
+ * Create an uninitialized `Gemma4Model` stub from a config.
124
+ *
125
+ * **Prefer [`Gemma4Model::load`]** for any real usage — `new(config)`
126
+ * is a config-only stub that matches the OCR-model pattern
127
+ * (`VLModel::new(config)`, `QianfanOCRModel::new(config)`) and is
128
+ * intentionally NOT runnable. It was introduced in the cache-limit
129
+ * coordinator work so that the coordinator's per-model delta is
130
+ * registered exclusively on the `load()` path, eliminating a
131
+ * baseline-registration gap where a no-op `new(config)` would have
132
+ * leaked an empty guard into the coordinator.
133
+ *
134
+ * This path does NOT spawn a model thread, NOT materialize any
135
+ * weights, and NOT register with the cache-limit coordinator. The
136
+ * returned instance is only useful for config inspection — every
137
+ * session method (`chatSessionStart` / `chatSessionContinue` /
138
+ * `chatSessionContinueTool` and their streaming variants) rejects
139
+ * with a `napi::Error` whose message is exactly
140
+ * `"Model not initialized. Call Gemma4Model.load() first."` until
141
+ * `load()` runs and installs the underlying model thread. The
142
+ * synchronous `resetCaches()` call is a silent no-op on the stub
143
+ * to keep `ChatSession.reset()` idempotent across both runnable
144
+ * and stub instances.
145
+ *
146
+ * A runnable model requires `await Gemma4Model.load(path)`. The
147
+ * constructor signature is fixed by NAPI-RS; the stub-only behavior is
148
+ * covered by the regression tests in
149
+ * `__test__/models/model-loader-gemma4.test.ts`.
150
+ */
151
+ constructor(config: Gemma4Config);
152
+ /** Returns true if weights have been loaded via `load()`. */
153
+ get isInitialized(): boolean;
154
+ /**
155
+ * Whether the block-paged KV cache adapter is active on this model
156
+ * instance.
157
+ *
158
+ * `true` iff `Gemma4Inner::paged_adapter` was successfully
159
+ * constructed at load time (driven by
160
+ * `Gemma4Config::use_block_paged_cache`). The
161
+ * `gemma4_paged_vs_flat_parity` integration test pins greedy
162
+ * byte-equal at BF16 against real Gemma-4-E2B-IT weights. Stubs
163
+ * constructed via `new(config)` always return `false`. Surfaced
164
+ * through this NAPI method so server endpoints can branch on it
165
+ * without a model-thread roundtrip.
166
+ */
167
+ hasBlockPagedCache(): boolean;
168
+ /**
169
+ * Whether this loaded instance can execute image-bearing chat turns.
170
+ * Config-only stubs and incomplete/non-paged physical paths return false.
171
+ */
172
+ supportsImages(): boolean;
173
+ modelId(): number;
174
+ /**
175
+ * Whether a draft model — DSpark or Google assistant — is loaded on
176
+ * this instance (via `Gemma4LoadOptions::draft_model_path`), enabling
177
+ * the speculative-decode whole-turn path.
178
+ *
179
+ * Note: this only reports draft availability. Whether speculative
180
+ * decoding actually runs on a given call also requires the per-request
181
+ * `enableMtp` flag. Named `hasMtpWeights` for parity with the Qwen3.5
182
+ * surface, but it reports an external draft model (either variant),
183
+ * not in-checkpoint MTP heads. Stubs from `new(config)` always return
184
+ * `false`.
185
+ */
186
+ hasMtpWeights(): boolean;
187
+ /** Load a Gemma4 model from a directory. */
188
+ static load(modelPath: string, options?: Gemma4LoadOptions | undefined | null): Promise<Gemma4Model>;
189
+ /**
190
+ * Reset all caches and clear cached token history. Exposed
191
+ * so tests and session-management code can start from a
192
+ * known clean state between turns.
193
+ */
194
+ resetCaches(): void;
195
+ /**
196
+ * Start a new chat session.
197
+ *
198
+ * Runs the full jinja chat template once, decodes until the
199
+ * family's session stop token, and leaves the KV caches on a
200
+ * clean turn boundary so subsequent `chatSessionContinue` /
201
+ * `chatSessionContinueTool` calls can append a raw delta on
202
+ * top without re-rendering the chat template.
203
+ */
204
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
205
+ /**
206
+ * Continue an existing chat session with a new user message.
207
+ *
208
+ * Appends a raw user/assistant delta to the session's cached
209
+ * KV state, then decodes the assistant reply, stopping on the
210
+ * family's session boundary token.
211
+ *
212
+ * `images` is an opt-in guard parameter: when non-empty the
213
+ * native side returns an error whose message begins with
214
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
215
+ * `ChatSession` layer can route image-changes back through a
216
+ * fresh `chatSessionStart`.
217
+ */
218
+ chatSessionContinue(
219
+ userMessage: string,
220
+ images: Uint8Array[] | null | undefined,
221
+ audio: Uint8Array[] | null | undefined,
222
+ config: ChatConfig | null | undefined,
223
+ ): Promise<ChatResult>;
224
+ /**
225
+ * Continue an existing chat session with a tool-result turn.
226
+ *
227
+ * Builds the family's tool-result delta from `content` and
228
+ * prefills it on top of the live session caches, then decodes
229
+ * the assistant reply.
230
+ *
231
+ * `is_error` is the structured tool-error signal. When
232
+ * `Some(true)`, the renderer prepends the shared
233
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
234
+ * rendered tool block.
235
+ */
236
+ chatSessionContinueTool(
237
+ toolCallId: string,
238
+ content: string,
239
+ config?: ChatConfig | undefined | null,
240
+ isError?: boolean | undefined | null,
241
+ ): Promise<ChatResult>;
242
+ /** Streaming variant of `chatSessionStart`. */
243
+ chatStreamSessionStart(
244
+ messages: ChatMessage[],
245
+ config: ChatConfig | null | undefined,
246
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
247
+ ): Promise<ChatStreamHandle>;
248
+ /** Streaming variant of `chatSessionContinue`. */
249
+ chatStreamSessionContinue(
250
+ userMessage: string,
251
+ images: Uint8Array[] | null | undefined,
252
+ audio: Uint8Array[] | null | undefined,
253
+ config: ChatConfig | null | undefined,
254
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
255
+ ): Promise<ChatStreamHandle>;
256
+ /**
257
+ * Streaming variant of `chatSessionContinueTool`.
258
+ *
259
+ * `is_error` mirrors the non-streaming entry point — when
260
+ * `Some(true)`, the renderer prepends the shared
261
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the rendered
262
+ * tool block.
263
+ */
264
+ chatStreamSessionContinueTool(
265
+ toolCallId: string,
266
+ content: string,
267
+ config: ChatConfig | null | undefined,
268
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
269
+ isError?: boolean | null | undefined,
270
+ ): Promise<ChatStreamHandle>;
271
+ }
272
+
106
273
  /** Result from text generation with detailed metadata */
107
274
  export declare class GenerationResult {
108
275
  /** Get the decoded text */
@@ -120,14 +287,15 @@ export declare class GenerationResult {
120
287
  /**
121
288
  * GRPO Training Engine
122
289
  *
123
- * Complete training engine that runs entirely in Rust.
290
+ * Thin coordinator that routes all MLX operations through the model thread.
291
+ * No MxArrays or model state live here — only plain data crosses the boundary.
124
292
  */
125
293
  export declare class GrpoTrainingEngine {
126
294
  /**
127
295
  * Create a new training engine from a Qwen3 model
128
296
  *
129
297
  * # Arguments
130
- * * `model` - The Qwen3 model to train (will be cloned internally)
298
+ * * `model` - The Qwen3 model (must be loaded via load())
131
299
  * * `config` - Engine configuration
132
300
  */
133
301
  constructor(model: Qwen3Model, config: GrpoEngineConfig);
@@ -143,8 +311,8 @@ export declare class GrpoTrainingEngine {
143
311
  * This method performs the complete training cycle:
144
312
  * 1. Generate completions for each prompt (G times per prompt)
145
313
  * 2. Use provided rewards to compute advantages
146
- * 3. Compute GRPO loss and gradients
147
- * 4. Apply gradients (respecting accumulation steps)
314
+ * 3. Compute GRPO loss and gradients (on model thread)
315
+ * 4. Apply gradients (respecting accumulation steps, on model thread)
148
316
  *
149
317
  * # Arguments
150
318
  * * `prompts` - Array of chat conversations to use as prompts
@@ -171,13 +339,14 @@ export declare class GrpoTrainingEngine {
171
339
  /**
172
340
  * Run a training step with pre-generated completions
173
341
  *
174
- * This method performs training using pre-generated completions,
175
- * eliminating the double-generation issue.
342
+ * Uses the cached MxArrays from the most recent generate_batch_for_training
343
+ * call on the model thread. The generation_result parameter is used only for
344
+ * validation (the actual MxArrays are cached on the model thread).
176
345
  *
177
346
  * # Arguments
178
347
  * * `prompts` - Array of chat conversations to use as prompts
179
348
  * * `rewards` - Reward values for each completion (num_prompts * group_size)
180
- * * `generation_result` - Pre-generated completion data from generate_batch_for_training
349
+ * * `generation_result` - Pre-generated completion data (used for validation)
181
350
  *
182
351
  * # Returns
183
352
  * * Training step metrics
@@ -190,8 +359,8 @@ export declare class GrpoTrainingEngine {
190
359
  /**
191
360
  * Unified training step with JS reward callback and optional output recording
192
361
  *
193
- * Same as `train_step_auto` but optionally captures the full RewardOutput data
194
- * for persistence to an output store database.
362
+ * Generates completions via the model thread, calls the JS reward function
363
+ * with plain data, then dispatches the training step to the model thread.
195
364
  *
196
365
  * # Arguments
197
366
  * * `prompts` - Array of chat conversations to use as prompts
@@ -222,7 +391,16 @@ export declare class GrpoTrainingEngine {
222
391
  startEpoch(): void;
223
392
  /** End the current epoch and get metrics */
224
393
  endEpoch(epochTimeSecs: number): EngineEpochMetrics;
225
- /** Reset the engine for a fresh training run */
394
+ /**
395
+ * Reset the engine for a fresh training run.
396
+ *
397
+ * This is a TERMINAL operation on this handle. It drops the training
398
+ * state (optimizer, step counter) on the model thread so a fresh
399
+ * `GRPOTrainingEngine` can be constructed on the same model, and marks
400
+ * THIS handle as invalidated. Any subsequent dispatch-requiring method
401
+ * on this handle returns an error — callers must construct a new
402
+ * engine to continue training.
403
+ */
226
404
  reset(): void;
227
405
  /** Check if reward registry has any rewards registered */
228
406
  get hasBuiltinRewards(): boolean;
@@ -242,25 +420,214 @@ export declare class GrpoTrainingEngine {
242
420
  /**
243
421
  * Save optimizer state (moment tensors + step) to a SafeTensors file.
244
422
  *
245
- * The step counter is stored in the `__metadata__` field.
246
- * Each parameter's first moment (m) and second moment (v) are stored as
247
- * `{param_name}.m` and `{param_name}.v` tensors.
248
- *
249
- * No-op if the engine uses SGD (no optimizer state to save).
423
+ * Routes through the model thread so AdamW moments and step counter
424
+ * survive across checkpoint/resume. No-op if the engine uses SGD
425
+ * (no optimizer to save) or before the first optimizer update has
426
+ * populated any moment tensors.
250
427
  */
251
- saveOptimizerState(path: string): void;
428
+ saveOptimizerState(path: string): Promise<void>;
252
429
  /**
253
430
  * Load optimizer state (moment tensors + step) from a SafeTensors file.
254
431
  *
255
- * Restores the step counter from metadata and sets first/second moment
256
- * tensors for each parameter found in the file.
257
- *
258
- * No-op if the engine uses SGD (no optimizer to restore).
432
+ * Routes through the model thread. No-op if the engine uses SGD.
259
433
  */
260
- loadOptimizerState(path: string): void;
434
+ loadOptimizerState(path: string): Promise<void>;
261
435
  }
262
436
  export type GRPOTrainingEngine = GrpoTrainingEngine;
263
437
 
438
+ /**
439
+ * Harrier embedding model (Qwen3 backbone for text embeddings).
440
+ *
441
+ * Uses last-token pooling and L2 normalization to produce fixed-size
442
+ * embedding vectors from variable-length text inputs.
443
+ */
444
+ export declare class HarrierModel {
445
+ constructor(config: HarrierConfig);
446
+ /**
447
+ * Forward pass returning hidden states (no lm_head projection).
448
+ *
449
+ * # Arguments
450
+ * * `input_ids` - Token IDs, shape: [batch_size, seq_len]
451
+ *
452
+ * # Returns
453
+ * * Hidden states, shape: [batch_size, seq_len, hidden_size]
454
+ */
455
+ forward(inputIds: MxArray): MxArray;
456
+ /**
457
+ * Encode a single text into a normalized embedding vector.
458
+ *
459
+ * Tokenizes the text, runs the forward pass, applies last-token pooling,
460
+ * and L2-normalizes the result. Truncates to `max_position_embeddings`.
461
+ *
462
+ * # Arguments
463
+ * * `text` - Input text to encode
464
+ * * `instruction` - Optional task instruction prefix or preset name
465
+ * (e.g. `"web_search_query"` resolves to the full Harrier prompt).
466
+ * Pass `null` for documents/passages that need no instruction.
467
+ *
468
+ * # Returns
469
+ * * Embedding vector, shape: [hidden_size]
470
+ */
471
+ encode(text: string, instruction?: string | undefined | null): Promise<MxArray>;
472
+ /**
473
+ * Encode a batch of texts into normalized embedding vectors.
474
+ *
475
+ * Each text is independently tokenized and encoded (no padding needed
476
+ * since each goes through its own forward pass). Truncates each text
477
+ * to `max_position_embeddings`.
478
+ *
479
+ * # Arguments
480
+ * * `texts` - Input texts to encode
481
+ * * `instruction` - Optional task instruction prefix or preset name
482
+ * (e.g. `"web_search_query"` resolves to the full Harrier prompt).
483
+ * Pass `null` for documents/passages that need no instruction.
484
+ *
485
+ * # Returns
486
+ * * Embedding matrix, shape: [batch_size, hidden_size]
487
+ */
488
+ encodeBatch(texts: Array<string>, instruction?: string | undefined | null): Promise<MxArray>;
489
+ /** Get the model configuration. */
490
+ getConfig(): HarrierConfig;
491
+ /**
492
+ * Get available prompt presets loaded from config_sentence_transformers.json.
493
+ *
494
+ * Returns a map of task name -> full instruction prefix.
495
+ * Pass a task name to `encode()`/`encodeBatch()` as the `instruction` parameter
496
+ * to use a preset instead of a raw prefix string.
497
+ */
498
+ getPrompts(): Record<string, string>;
499
+ /** Get the total number of model parameters. */
500
+ numParameters(): number;
501
+ /**
502
+ * Load a Harrier embedding model from a directory.
503
+ *
504
+ * Expects the standard HuggingFace layout:
505
+ * - config.json (model configuration)
506
+ * - model.safetensors or weights.safetensors (weights)
507
+ * - tokenizer.json (tokenizer)
508
+ * - config_sentence_transformers.json (optional, prompt presets)
509
+ */
510
+ static load(modelPath: string): Promise<HarrierModel>;
511
+ }
512
+
513
+ /**
514
+ * LFM2 language model (LFM2.5-1.2B-Thinking).
515
+ *
516
+ * Hybrid conv+attention architecture from Liquid AI. 16 layers total:
517
+ * 10 conv layers + 6 full_attention layers. Features gated short
518
+ * convolutions for local processing and standard attention for global context.
519
+ *
520
+ * All model state lives on a dedicated OS thread. NAPI methods dispatch
521
+ * commands via channels and await responses.
522
+ */
523
+ export declare class Lfm2Model {
524
+ /** Load an LFM2 model from a directory containing safetensors and config.json. */
525
+ static load(modelPath: string): Promise<Lfm2Model>;
526
+ /**
527
+ * Whether the block-paged KV cache adapter is active on this model
528
+ * instance.
529
+ *
530
+ * `true` iff `Lfm2Inner::paged_adapter` was successfully constructed
531
+ * at load time (driven by `Lfm2Config::use_block_paged_cache`,
532
+ * defaulting to `true` after paged-vs-flat parity verification).
533
+ * LFM2 is hybrid (10 conv + 6 full-attention layers); only the
534
+ * full-attention layers route through the adapter, conv layers stay
535
+ * on flat `Lfm2LayerCache::Conv` regardless. When `true`, the native
536
+ * cache reuses SYS blocks across `chatSessionStart` calls via
537
+ * content-addressing, so the JS-side warm slot in
538
+ * `SessionRegistry.getOrCreateWarmAny` is redundant and the
539
+ * `/v1/messages` server endpoint allocates a fresh `ChatSession` per
540
+ * request.
541
+ */
542
+ hasBlockPagedCache(): boolean;
543
+ /** Get the model configuration. */
544
+ getConfig(): Lfm2Config;
545
+ /** Estimated number of model parameters. */
546
+ numParameters(): number;
547
+ /**
548
+ * Reset all caches and clear cached token history. Exposed
549
+ * so tests and session-management code can start from a
550
+ * known clean state between turns.
551
+ */
552
+ resetCaches(): void;
553
+ /**
554
+ * Start a new chat session.
555
+ *
556
+ * Runs the full jinja chat template once, decodes until the
557
+ * family's session stop token, and leaves the KV caches on a
558
+ * clean turn boundary so subsequent `chatSessionContinue` /
559
+ * `chatSessionContinueTool` calls can append a raw delta on
560
+ * top without re-rendering the chat template.
561
+ */
562
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
563
+ /**
564
+ * Continue an existing chat session with a new user message.
565
+ *
566
+ * Appends a raw user/assistant delta to the session's cached
567
+ * KV state, then decodes the assistant reply, stopping on the
568
+ * family's session boundary token.
569
+ *
570
+ * `images` is an opt-in guard parameter: when non-empty the
571
+ * native side returns an error whose message begins with
572
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
573
+ * `ChatSession` layer can route image-changes back through a
574
+ * fresh `chatSessionStart`.
575
+ */
576
+ chatSessionContinue(
577
+ userMessage: string,
578
+ images: Uint8Array[] | null | undefined,
579
+ audio: Uint8Array[] | null | undefined,
580
+ config: ChatConfig | null | undefined,
581
+ ): Promise<ChatResult>;
582
+ /**
583
+ * Continue an existing chat session with a tool-result turn.
584
+ *
585
+ * Builds the family's tool-result delta from `content` and
586
+ * prefills it on top of the live session caches, then decodes
587
+ * the assistant reply.
588
+ *
589
+ * `is_error` is the structured tool-error signal. When
590
+ * `Some(true)`, the renderer prepends the shared
591
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
592
+ * rendered tool block.
593
+ */
594
+ chatSessionContinueTool(
595
+ toolCallId: string,
596
+ content: string,
597
+ config?: ChatConfig | undefined | null,
598
+ isError?: boolean | undefined | null,
599
+ ): Promise<ChatResult>;
600
+ /** Streaming variant of `chatSessionStart`. */
601
+ chatStreamSessionStart(
602
+ messages: ChatMessage[],
603
+ config: ChatConfig | null,
604
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
605
+ ): Promise<ChatStreamHandle>;
606
+ /** Streaming variant of `chatSessionContinue`. */
607
+ chatStreamSessionContinue(
608
+ userMessage: string,
609
+ images: Uint8Array[] | null | undefined,
610
+ audio: Uint8Array[] | null | undefined,
611
+ config: ChatConfig | null,
612
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
613
+ ): Promise<ChatStreamHandle>;
614
+ /**
615
+ * Streaming variant of `chatSessionContinueTool`.
616
+ *
617
+ * `is_error` mirrors the non-streaming entry point — when
618
+ * `Some(true)`, the renderer prepends the shared
619
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the rendered
620
+ * tool block.
621
+ */
622
+ chatStreamSessionContinueTool(
623
+ toolCallId: string,
624
+ content: string,
625
+ config: ChatConfig | null,
626
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
627
+ isError?: boolean | null | undefined,
628
+ ): Promise<ChatStreamHandle>;
629
+ }
630
+
264
631
  export declare class MxArray {
265
632
  equal(other: MxArray): MxArray;
266
633
  notEqual(other: MxArray): MxArray;
@@ -382,9 +749,20 @@ export declare class MxArray {
382
749
  divScalar(value: number): MxArray;
383
750
  matmul(other: MxArray): MxArray;
384
751
  /**
385
- * Fused matrix multiply-add: D = beta * C + alpha * (self @ B)
386
- * where self is A. More efficient than separate matmul and add operations.
387
- * Default: alpha=1.0, beta=1.0, giving D = C + (self @ B)
752
+ * Matrix multiply-add: D = beta * C + alpha * (self @ B), where self is A.
753
+ * Default: alpha=1.0, beta=1.0, giving D = C + (self @ B).
754
+ *
755
+ * Computed explicitly (matmul, optional alpha scale, then add beta*C)
756
+ * rather than via the fused `mlx::core::addmm` primitive. The fused
757
+ * primitive is correct on a well-formed metallib, but this project's local
758
+ * release build is known to non-deterministically miscompile fused GEMM
759
+ * kernels (see the metallib-corruption notes), which manifested as the
760
+ * fused addmm dropping `beta*C` and corrupting every biased linear — most
761
+ * visibly the vision tower (`qkv`, `proj`, `fc1`, `fc2`, merger all carry a
762
+ * bias; bias-free LM/MoE linears pass a zero `C` and were unaffected). The
763
+ * explicit form keeps the `C` term robust to that build hazard; the
764
+ * `nn::linear` unit tests assert it applies a non-zero `C`, so they double
765
+ * as a canary if a future build corrupts the matmul kernel too.
388
766
  */
389
767
  addmm(c: MxArray, b: MxArray, alpha?: number | undefined | null, beta?: number | undefined | null): MxArray;
390
768
  abs(): MxArray;
@@ -399,6 +777,8 @@ export declare class MxArray {
399
777
  sinh(): MxArray;
400
778
  cosh(): MxArray;
401
779
  tanh(): MxArray;
780
+ /** Error function: erf(x) = (2/sqrt(pi)) * integral(0..x, exp(-t^2) dt) */
781
+ erf(): MxArray;
402
782
  floor(): MxArray;
403
783
  ceil(): MxArray;
404
784
  round(): MxArray;
@@ -640,522 +1020,542 @@ export declare class OutputStore {
640
1020
  }
641
1021
 
642
1022
  /**
643
- * Opaque handle to KV cache state from a previous chat() call.
644
- *
645
- * Pass this back to the next chat() call via `model.setCache(cache)`
646
- * to enable incremental prefill — only new tokens since the last turn
647
- * are processed, avoiding redundant computation.
1023
+ * NAPI-exported view of [`PrivacyFilterModel`].
648
1024
  *
649
- * Created internally by the model when `reuseCache: true` (default).
650
- * Extract via `model.takeCache()`, restore via `model.setCache(cache)`.
1025
+ * Construct via [`PrivacyFilterModelJs::load`] and run end-to-end
1026
+ * classification via [`PrivacyFilterModelJs::classify`].
651
1027
  */
652
- export declare class PromptCache {
653
- /** Number of tokens stored in this cache. */
654
- get tokenCount(): number;
655
- /** Whether this cache has been consumed (caches moved out). */
656
- get isEmpty(): boolean;
657
- /** Release GPU memory held by this cache. */
658
- dispose(): void;
1028
+ export declare class PrivacyFilterModel {
1029
+ /**
1030
+ * Load a privacy-filter checkpoint from a directory.
1031
+ *
1032
+ * The directory must contain `config.json`, `model.safetensors`,
1033
+ * `tokenizer.json`, and optionally `viterbi_calibration.json` and
1034
+ * `tokenizer_config.json`. Synchronous to match the loader pattern
1035
+ * used by every other model in this crate (e.g. `TextDetModel`).
1036
+ */
1037
+ static load(modelPath: string): PrivacyFilterModel;
1038
+ /**
1039
+ * Classify `text` and return detected PII entities (and optionally
1040
+ * per-token tags).
1041
+ *
1042
+ * Pipeline:
1043
+ * 1. Tokenize the text with byte offsets, no special tokens.
1044
+ * 2. Run the forward pass to get `[1, T, 33]` logits.
1045
+ * 3. Compute softmax (for per-tag confidences) and log-softmax (for
1046
+ * Viterbi emissions) over the class axis.
1047
+ * 4. Build the transition matrix from the default calibration
1048
+ * merged with any per-call overrides.
1049
+ * 5. Viterbi-decode using log-softmax emissions to get the BIOES
1050
+ * tag sequence.
1051
+ * 6. For each token, take the softmax probability of the
1052
+ * Viterbi-emitted tag (so per-token `tag` and `score` share
1053
+ * decoders), then walk the tags + offsets + those probabilities
1054
+ * to extract coherent spans whose mean probability clears
1055
+ * `threshold`.
1056
+ */
1057
+ classify(text: string, opts?: PrivacyClassifyOptions | undefined | null): PrivacyClassifyResult;
659
1058
  }
1059
+ export type PrivacyFilterModelJs = PrivacyFilterModel;
660
1060
 
661
1061
  /**
662
- * Qwen3.5 Model -- hybrid linear/full attention with optional MoE.
1062
+ * Qianfan-OCR Vision-Language Model (InternVL architecture).
1063
+ *
1064
+ * Combines InternViT vision encoder, MLP bridge with pixel shuffle,
1065
+ * and Qwen3 language model for OCR and document understanding.
663
1066
  *
664
- * Uses interior mutability (RwLock) for layers, final_norm, lm_head, and caches
665
- * to allow async generation via spawn_blocking without blocking the Node.js event loop.
666
- * This matches the pattern used by Qwen3Model.
1067
+ * All inference state lives on a dedicated OS thread. NAPI methods
1068
+ * dispatch commands via channels and await responses.
667
1069
  */
668
- export declare class Qwen35Model {
669
- /** Create a new Qwen3.5 model with the given configuration. */
670
- constructor(config: Qwen35Config);
671
- /** Initialize caches for incremental generation. */
672
- initCaches(): void;
673
- /** Reset all caches. */
674
- resetCaches(): void;
675
- /**
676
- * Take the KV cache from the model, returning a `PromptCache` handle.
677
- *
678
- * The cache is moved out of the model — calling `takeCache()` twice
679
- * returns `null` the second time. Pass the cache back via `setCache()`
680
- * before the next `chat()` call for incremental prefill.
681
- */
682
- takeCache(): PromptCache | null;
683
- /**
684
- * Restore a previously taken `PromptCache` into the model.
685
- *
686
- * On the next `chat()` call with `reuseCache: true`, the model will
687
- * prefix-match the new tokens against the cache and only prefill the delta.
688
- */
689
- setCache(cache: PromptCache): void;
690
- /**
691
- * Forward pass through the model.
692
- *
693
- * # Arguments
694
- * * `input_ids` - Token IDs [B, T]
695
- *
696
- * # Returns
697
- * Logits [B, T, vocab_size]
698
- */
699
- forward(inputIds: MxArray): MxArray;
700
- /** Forward pass with cache for incremental generation. */
701
- forwardWithCache(inputIds: MxArray): MxArray;
1070
+ export declare class QianfanOCRModel {
702
1071
  /**
703
- * Load a pretrained model from a directory.
1072
+ * Create a new QianfanOCRModel from config (uninitialized, no weights).
704
1073
  *
705
- * Expects the directory to contain:
706
- * - config.json
707
- * - model.safetensors (or model-*.safetensors)
708
- * - tokenizer.json + tokenizer_config.json
1074
+ * This constructor path does not spawn a model thread — the returned
1075
+ * instance is only useful for `is_initialized` queries until
1076
+ * [`QianfanOCRModel::load`] is called to actually run inference. The
1077
+ * `config` argument is accepted to preserve the `new
1078
+ * QianfanOCRModel(config)` JS surface; the value is discarded because
1079
+ * nothing on the uninitialized path consults it (any future config
1080
+ * getter would forward to the inner thread state populated by
1081
+ * `load()`).
709
1082
  */
710
- static load(path: string): Promise<Qwen35Model>;
1083
+ constructor(config: QianfanOcrConfig);
1084
+ /** Returns true if weights have been loaded via `load()`. */
1085
+ get isInitialized(): boolean;
711
1086
  /**
712
- * Generate text from a prompt token sequence.
1087
+ * Load a QianfanOCRModel from a directory.
713
1088
  *
714
- * Runs generation on a worker thread via spawn_blocking to avoid
715
- * blocking the Node.js event loop.
1089
+ * Reads config.json, loads SafeTensors weights (single or sharded),
1090
+ * builds vision encoder, bridge, and language model, and loads tokenizer.
1091
+ * All heavy work runs on the dedicated model thread.
716
1092
  */
717
- generate(promptTokens: MxArray, config: Qwen35GenerationConfig): Promise<Qwen35GenerationResult>;
1093
+ static load(modelPath: string): Promise<QianfanOCRModel>;
718
1094
  /**
719
- * Chat API with tool calling support.
1095
+ * Generate text tokens given pre-tokenized input.
720
1096
  *
721
- * Runs tokenization + generation on a worker thread via spawn_blocking
722
- * to avoid blocking the Node.js event loop.
1097
+ * Lower-level API prefer the session chat methods
1098
+ * (`chatSessionStart` / `chatSessionContinue` and their streaming
1099
+ * variants) for typical usage.
723
1100
  */
724
- chat(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1101
+ generate(
1102
+ inputIds: MxArray,
1103
+ maxNewTokens?: number | undefined | null,
1104
+ temperature?: number | undefined | null,
1105
+ ): Promise<Array<number>>;
1106
+ /** Reset KV caches and token history. */
1107
+ resetCaches(): void;
725
1108
  /**
726
- * Streaming chat API with tool calling support.
727
- *
728
- * Same as `chat()` but streams tokens one-by-one via the callback.
729
- * Returns a `ChatStreamHandle` immediately; generation runs in background.
730
- * Call `handle.cancel()` to abort generation early.
731
- */
732
- chatStream(
1109
+ * Start a new chat session.
1110
+ *
1111
+ * Runs the full chat template once, decodes until `<|im_end|>`,
1112
+ * and leaves the KV caches on a clean turn boundary so subsequent
1113
+ * `chatSessionContinue` / `chatSessionContinueTool` calls can
1114
+ * append a raw ChatML delta on top without re-rendering the chat
1115
+ * template.
1116
+ *
1117
+ * Qianfan-OCR is always a VLM (InternViT + Qwen3 language model), so
1118
+ * this entry point accepts images in `messages` without the text-only
1119
+ * fast-fail used by plain language models.
1120
+ */
1121
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1122
+ /**
1123
+ * Continue an existing chat session with a new user message.
1124
+ *
1125
+ * Appends a raw ChatML user/assistant delta to the session's cached
1126
+ * KV state, then decodes the model reply. Stops on `<|im_end|>` so
1127
+ * the cache remains on a clean turn boundary for the next turn.
1128
+ *
1129
+ * Requires a live session started via `chatSessionStart`. Errors
1130
+ * if the session is empty or if `config.reuse_cache` is
1131
+ * explicitly set to `false`.
1132
+ *
1133
+ * `images` is an opt-in guard parameter: when non-empty the native
1134
+ * side returns an error whose message begins with
1135
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1136
+ * `ChatSession` layer can catch the prefix and route image-changes
1137
+ * back through a fresh `chatSessionStart` uniformly across all
1138
+ * model backends. Qianfan-OCR is a VLM but the continue path cannot
1139
+ * splice new vision features into a live KV cache — image changes
1140
+ * always require a fresh session start.
1141
+ *
1142
+ * `audio` exists only to keep this method's positional ABI aligned
1143
+ * with the shared chat surface every other family exposes (the
1144
+ * `chat_napi_surface!` macro inserts `audio` between `images` and
1145
+ * `config`). Qianfan-OCR has no audio support, so a non-empty
1146
+ * `audio` is rejected at the boundary with the shared no-audio
1147
+ * error; `None` / empty is a complete no-op and audio is never
1148
+ * threaded into the model thread.
1149
+ */
1150
+ chatSessionContinue(
1151
+ userMessage: string,
1152
+ images: Uint8Array[] | null | undefined,
1153
+ audio: Uint8Array[] | null | undefined,
1154
+ config: ChatConfig | null | undefined,
1155
+ ): Promise<ChatResult>;
1156
+ /**
1157
+ * Continue an existing chat session with a tool-result turn.
1158
+ *
1159
+ * Builds a ChatML `<tool_response>` delta from `tool_call_id` and
1160
+ * `content` and prefills it on top of the live session caches, then
1161
+ * decodes the model reply. Stops on `<|im_end|>` so the cache stays
1162
+ * on a clean turn boundary for the next turn.
1163
+ *
1164
+ * `is_error` is the structured tool-error signal. When `Some(true)`,
1165
+ * the renderer prepends the shared
1166
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
1167
+ * `<tool_response>` wrapper so the model receives a clear text-level
1168
+ * cue. `None` / `Some(false)` keep the wire bytes byte-equal to the
1169
+ * pre-feature output.
1170
+ *
1171
+ * Requires a live session started via `chatSessionStart`.
1172
+ */
1173
+ chatSessionContinueTool(
1174
+ toolCallId: string,
1175
+ content: string,
1176
+ config?: ChatConfig | undefined | null,
1177
+ isError?: boolean | undefined | null,
1178
+ ): Promise<ChatResult>;
1179
+ /** Streaming variant of `chatSessionStart`. */
1180
+ chatStreamSessionStart(
733
1181
  messages: ChatMessage[],
734
- config: ChatConfig | null,
1182
+ config: ChatConfig | null | undefined,
735
1183
  callback: (err: Error | null, chunk: ChatStreamChunk) => void,
736
1184
  ): Promise<ChatStreamHandle>;
737
- /** Get the number of parameters in the model. */
738
- numParameters(): number;
739
1185
  /**
740
- * Save the model weights and configuration to a directory.
1186
+ * Streaming variant of `chatSessionContinue`.
741
1187
  *
742
- * This saves:
743
- * - config.json: Model configuration (with model_type for detectModelType)
744
- * - weights.safetensors: Full model weights in SafeTensors format
745
- * - weights.mlx: Parameter metadata (for reference)
746
- *
747
- * # Arguments
748
- * * `save_path` - Directory to save the model
749
- */
750
- saveModel(savePath: string): Promise<undefined>;
751
- }
752
- export type Qwen3_5Model = Qwen35Model;
753
-
754
- /**
755
- * Qwen3.5 MoE Model -- hybrid linear/full attention with Mixture-of-Experts.
756
- *
757
- * Supports C++ MoE forward path (non-compiled, builds fresh graph per step)
758
- * when weights are registered via `register_moe_weights_with_cpp`.
759
- * Falls back to Rust forward_inner path for test models without stored weights.
760
- */
761
- export declare class Qwen35MoeModel {
762
- constructor(config: Qwen35MoeConfig);
763
- /**
764
- * Take the KV cache from the model, returning a `PromptCache` handle.
765
- *
766
- * The cache is moved out of the model — calling `takeCache()` twice
767
- * returns `null` the second time. Pass the cache back via `setCache()`
768
- * before the next `chat()` call for incremental prefill.
1188
+ * `audio` mirrors the non-streaming entry point: it exists only to
1189
+ * keep the positional ABI aligned with the shared chat surface, and
1190
+ * a non-empty value is rejected at the boundary with the shared
1191
+ * no-audio error. `None` / empty is a complete no-op.
769
1192
  */
770
- takeCache(): PromptCache | null;
771
- /**
772
- * Restore a previously taken `PromptCache` into the model.
773
- *
774
- * On the next `chat()` call with `reuseCache: true`, the model will
775
- * prefix-match the new tokens against the cache and only prefill the delta.
776
- */
777
- setCache(cache: PromptCache): void;
778
- initCaches(): void;
779
- resetCaches(): void;
780
- forward(inputIds: MxArray): MxArray;
781
- forwardWithCache(inputIds: MxArray): MxArray;
782
- static load(path: string): Promise<Qwen35MoeModel>;
783
- generate(promptTokens: MxArray, config: Qwen35MoeGenerationConfig): Promise<Qwen35MoeGenerationResult>;
784
- chat(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
785
- /**
786
- * Streaming chat API with tool calling support.
787
- *
788
- * Same as `chat()` but streams tokens one-by-one via the callback.
789
- * Returns a `ChatStreamHandle` immediately; generation runs in background.
790
- * Call `handle.cancel()` to abort generation early.
791
- */
792
- chatStream(
793
- messages: ChatMessage[],
794
- config: ChatConfig | null,
1193
+ chatStreamSessionContinue(
1194
+ userMessage: string,
1195
+ images: Uint8Array[] | null | undefined,
1196
+ audio: Uint8Array[] | null | undefined,
1197
+ config: ChatConfig | null | undefined,
795
1198
  callback: (err: Error | null, chunk: ChatStreamChunk) => void,
796
1199
  ): Promise<ChatStreamHandle>;
797
- numParameters(): number;
798
1200
  /**
799
- * Save the model weights and configuration to a directory.
1201
+ * Streaming variant of `chatSessionContinueTool`.
800
1202
  *
801
- * This saves:
802
- * - config.json: Model configuration (with model_type for detectModelType)
803
- * - weights.safetensors: Full model weights in SafeTensors format
804
- * - weights.mlx: Parameter metadata (for reference)
805
- *
806
- * # Arguments
807
- * * `save_path` - Directory to save the model
1203
+ * `is_error` mirrors the non-streaming entry point — when
1204
+ * `Some(true)`, the renderer prepends the shared
1205
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
1206
+ * `<tool_response>` wrapper.
808
1207
  */
809
- saveModel(savePath: string): Promise<undefined>;
1208
+ chatStreamSessionContinueTool(
1209
+ toolCallId: string,
1210
+ content: string,
1211
+ config: ChatConfig | null | undefined,
1212
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1213
+ isError?: boolean | null | undefined,
1214
+ ): Promise<ChatStreamHandle>;
810
1215
  }
811
- export type Qwen3_5MoeModel = Qwen35MoeModel;
812
1216
 
813
1217
  /**
814
- * Qwen3 Model with automatic differentiation support
1218
+ * Qwen3.5 Model -- hybrid linear/full attention with optional MoE.
815
1219
  *
816
- * Uses interior mutability (RwLock) for layers, final_norm, and lm_head
817
- * to allow gradient application without deep cloning the model.
818
- * This eliminates the previous ~4GB memory overhead from clone_for_session().
1220
+ * All inference and training state lives on a dedicated OS thread. NAPI methods
1221
+ * dispatch commands via channels and await responses. Training commands are
1222
+ * routed through `TrainingDispatch` to the model thread.
819
1223
  */
820
- export declare class Qwen3Model {
821
- /** Create a new Qwen3 model with the given configuration */
822
- constructor(config: Qwen3Config);
823
- /**
824
- * Reset the KV cache used for cache reuse across chat() calls.
825
- * Call this when starting a new conversation to ensure a full prefill.
826
- */
827
- resetCache(): void;
828
- /**
829
- * Forward pass through the model
830
- *
831
- * # Arguments
832
- * * `input_ids` - Token IDs, shape: [batch_size, seq_len]
833
- *
834
- * # Returns
835
- * * Logits, shape: [batch_size, seq_len, vocab_size]
836
- */
837
- forward(inputIds: MxArray): MxArray;
838
- /**
839
- * Initialize KV caches for incremental generation
840
- *
841
- * Creates one KV cache per transformer layer. Call this before starting generation.
842
- */
843
- initKvCaches(): void;
1224
+ export declare class Qwen35Model {
844
1225
  /**
845
- * Reset all KV caches
1226
+ * Whether the block-paged KV cache adapter is active on this model
1227
+ * instance.
846
1228
  *
847
- * Clears cached key-value states. Call this between different generation sequences.
1229
+ * `true` iff `Qwen35Inner::paged_adapter` was successfully
1230
+ * constructed at load time (driven by
1231
+ * `Qwen3_5Config::use_block_paged_cache`, default-OFF for text-only
1232
+ * checkpoints because parity is pending real-weights validation, and
1233
+ * default-ON for VLM checkpoints). On VLM checkpoints dense image turns
1234
+ * ONLY run on the paged-vision core; a vision turn that reaches a None
1235
+ * adapter errors at dispatch. Surfaced through this NAPI method so
1236
+ * server endpoints can branch on it without round-tripping through
1237
+ * the model thread.
848
1238
  */
849
- resetKvCaches(): void;
850
- /** Check if paged attention is enabled for this model */
851
- hasPagedAttention(): boolean;
1239
+ hasBlockPagedCache(): boolean;
852
1240
  /**
853
- * Get paged attention memory statistics (if enabled)
1241
+ * Whether this checkpoint shipped an MTP head (module loaded by
1242
+ * `persistence::apply_weights_inner`). Snapshotted at load time from
1243
+ * `Qwen35Inner::has_mtp_weights()` so the TS `ChatSession` can
1244
+ * auto-default `enableMtp = true` for MTP-capable checkpoints without
1245
+ * dispatching a command into the model thread.
854
1246
  *
855
- * Returns memory usage statistics for the paged KV cache.
1247
+ * Note: this only reports weight availability. Whether the
1248
+ * speculative-decode path actually runs on a given call also requires the
1249
+ * per-request `enableMtp` flag.
856
1250
  */
857
- pagedCacheStats(): PagedCacheStats | null;
1251
+ hasMtpWeights(): boolean;
858
1252
  /**
859
- * Get scheduler statistics (if paged attention is enabled)
1253
+ * Whether this loaded model instance can execute image-bearing turns.
860
1254
  *
861
- * Returns the number of waiting, running, and completed sequences.
1255
+ * This is an authoritative load-time snapshot, not a `config.json`
1256
+ * family guess: it requires a loaded vision encoder, image processor,
1257
+ * and the block-paged KV adapter used by the dense vision path.
862
1258
  */
863
- schedulerStats(): SchedulerStatsNapi | null;
1259
+ supportsImages(): boolean;
864
1260
  /**
865
- * Forward pass with KV caching for incremental generation
866
- *
867
- * # Arguments
868
- * * `input_ids` - Token IDs, shape: [batch_size, seq_len]
869
- * * `use_cache` - Whether to use KV caching (must call init_kv_caches() first)
870
- *
871
- * # Returns
872
- * * Logits, shape: [batch_size, seq_len, vocab_size]
1261
+ * Synchronous snapshot used by higher layers to preflight rendered
1262
+ * prompts and clamp output before native cache allocation.
873
1263
  */
874
- forwardWithCache(inputIds: MxArray, useCache: boolean): MxArray;
1264
+ contextLimits(): Qwen35ContextLimits;
875
1265
  /**
876
- * Forward pass with paged attention for memory-efficient inference.
1266
+ * Compute the exact prompt length after Qwen image-placeholder expansion
1267
+ * without running the vision encoder or touching inference caches.
877
1268
  *
878
- * This method uses block-based KV cache management via Metal kernels for:
879
- * - Variable-length sequences with efficient memory usage
880
- * - Continuous batching with dynamic batch composition
881
- * - Long context support beyond GPU memory limits
882
- *
883
- * # Arguments
884
- * * `input_ids` - Token IDs, shape: [num_seqs, 1] for decode
885
- * * `slot_mapping` - Slot indices for cache updates, shape: [num_seqs]
886
- * * `seq_ids` - Sequence IDs in the batch (for looking up block tables/context lens)
887
- * * `positions` - Token positions for RoPE, shape: [num_seqs] (per-sequence positions)
888
- *
889
- * # Returns
890
- * * Logits, shape: [num_seqs, 1, vocab_size] for decode
891
- */
892
- forwardPaged(inputIds: MxArray, slotMapping: MxArray, seqIds: Array<number>, positions: MxArray): MxArray;
893
- /**
894
- * Prefill a sequence using standard attention and write K/V to paged cache.
895
- *
896
- * This method should be called before `step_paged_generation()` for each
897
- * new prompt. It runs the full forward pass using standard attention
898
- * (which is faster for long sequences), then writes the K/V cache to
899
- * the paged cache for subsequent decode steps.
900
- *
901
- * # Arguments
902
- * * `prompt_tokens` - Token IDs for the prompt (as u32 array)
903
- * * `seq_id` - Sequence ID (obtained from scheduler)
904
- *
905
- * # Returns
906
- * * Logits for the last token, shape: [1, vocab_size]
1269
+ * `prompt_tokens` is the already-rendered chat-template output. `messages`
1270
+ * supplies the complete image history so both fresh and leased-session
1271
+ * preflights account for every image in template order.
907
1272
  */
908
- prefillPaged(promptTokens: Array<number>, seqId: number): MxArray;
1273
+ expandedPromptTokenCount(promptTokens: Uint32Array, messages: Array<ChatMessage>): Promise<number>;
909
1274
  /**
910
- * Add a request to the paged attention scheduler.
911
- *
912
- * The scheduler queues requests and allocates blocks for KV cache.
913
- * Use `step_paged_generation()` to process the scheduled batch.
914
- *
915
- * Note: The actual sequence ID is assigned during scheduling, not when the
916
- * request is added. Use the `request_id` to track your requests through
917
- * the generation process.
918
- *
919
- * # Arguments
920
- * * `request_id` - Unique identifier for the request (returned in outputs)
921
- * * `prompt_tokens` - Token IDs for the prompt
922
- * * `max_new_tokens` - Maximum new tokens to generate
923
- * * `priority` - Optional priority (higher = scheduled first)
924
- *
925
- * # Returns
926
- * * Number of pending requests in the queue
927
- */
928
- addPagedRequest(
929
- requestId: string,
930
- promptTokens: Array<number>,
931
- maxNewTokens: number,
932
- priority?: number | undefined | null,
933
- ): number;
934
- /**
935
- * Schedule and execute one step of paged generation.
936
- *
937
- * This method:
938
- * 1. Schedules the next batch of sequences
939
- * 2. Runs forward pass with paged attention
940
- * 3. Samples next tokens
941
- * 4. Returns the generated tokens for each sequence
942
- *
943
- * # Arguments
944
- * * `config` - Generation configuration (temperature, top_k, etc.)
945
- *
946
- * # Returns
947
- * * `PagedGenerationStep` with token outputs for each sequence
948
- */
949
- stepPagedGeneration(config?: GenerationConfig | undefined | null): PagedGenerationStep | null;
950
- /**
951
- * Get completed sequences from the scheduler.
1275
+ * Load a pretrained model from a directory.
952
1276
  *
953
- * Call this after `step_paged_generation()` returns outputs with `is_finished: true`.
1277
+ * Expects the directory to contain:
1278
+ * - config.json
1279
+ * - model.safetensors (or model-*.safetensors)
1280
+ * - tokenizer.json + tokenizer_config.json
954
1281
  */
955
- getCompletedSequences(): Array<PagedCompletedSequence>;
956
- /** Check if the scheduler has pending work. */
957
- hasPagedWork(): boolean;
958
- /** Get model configuration */
959
- getConfig(): Qwen3Config;
1282
+ static load(path: string): Promise<Qwen35Model>;
1283
+ /** Generate text from a prompt token sequence. */
1284
+ generate(promptTokens: MxArray, config: Qwen35GenerationConfig): Promise<Qwen35GenerationResult>;
960
1285
  /**
961
- * Generate tokens using speculative decoding with a draft model.
962
- *
963
- * Speculative decoding uses a smaller draft model to generate tokens speculatively,
964
- * then verifies them with the target model in a single forward pass. This can achieve
965
- * 2-3x speedup when the draft model has high acceptance rate.
966
- *
967
- * # Algorithm
968
- * 1. Draft model generates N tokens speculatively (cheap forward passes)
969
- * 2. Target model (self) verifies all N tokens in one forward pass
970
- * 3. Accept/reject using rejection sampling
971
- * 4. On rejection, resample from adjusted distribution
972
- * 5. Rewind caches and continue
973
- *
974
- * # Arguments
975
- * * `draft_model` - Smaller model for speculative generation (should share tokenizer)
976
- * * `input_ids` - Input token IDs [1, seq_len]
977
- * * `config` - Generation configuration (includes num_draft_tokens)
1286
+ * Get the number of parameters in the model.
978
1287
  *
979
- * # Returns
980
- * GenerationResult with tokens, logprobs, and speculative stats in finish_reason
981
- *
982
- * # Example (TypeScript)
983
- * ```typescript
984
- * const targetModel = await loadModel('qwen3-7b');
985
- * const draftModel = await loadModel('qwen3-0.5b');
986
- *
987
- * const result = targetModel.generateSpeculativeSync(draftModel, inputIds, {
988
- * numDraftTokens: 5,
989
- * maxNewTokens: 100,
990
- * temperature: 0.7,
991
- * });
992
- * ```
1288
+ * Pure config computation — no model-thread dispatch needed.
993
1289
  */
994
- generateSpeculativeSync(
995
- draftModel: Qwen3Model,
996
- inputIds: MxArray,
997
- config?: GenerationConfig | undefined | null,
998
- ): GenerationResult;
999
- /** Count total number of parameters in the model */
1000
1290
  numParameters(): number;
1001
1291
  /**
1002
- * Get all model parameters as a dictionary mapping names to arrays
1292
+ * Save the model weights and configuration to a directory.
1003
1293
  *
1004
- * This matches the TypeScript API for compatibility
1294
+ * Dispatches to model thread.
1005
1295
  */
1006
- getParameters(): Record<string, MxArray>;
1007
- /** Load parameters from a dictionary */
1008
- loadParameters(params: Record<string, MxArray>): void;
1296
+ saveModel(savePath: string): Promise<undefined>;
1009
1297
  /**
1010
- * Compute forward pass and loss (for evaluation)
1011
- *
1012
- * # Arguments
1013
- * * `input_ids` - Input token IDs, shape: [batch_size, seq_len]
1014
- * * `labels` - Target token IDs, shape: [batch_size, seq_len]
1015
- *
1016
- * # Returns
1017
- * * Scalar loss value
1298
+ * Reset all caches and clear cached token history. Exposed
1299
+ * so tests and session-management code can start from a
1300
+ * known clean state between turns.
1018
1301
  */
1019
- computeLoss(inputIds: MxArray, labels: MxArray): MxArray;
1302
+ resetCaches(): void;
1020
1303
  /**
1021
- * Compute loss and gradients using a hybrid approach
1022
- *
1023
- * This implementation computes gradients for the output layers and uses
1024
- * numerical approximations for other parameters. This is sufficient to
1025
- * demonstrate that training works while we build out full MLX autograd integration.
1026
- *
1027
- * # Arguments
1028
- * * `input_ids` - Input token IDs, shape: [batch_size, seq_len]
1029
- * * `labels` - Target token IDs, shape: [batch_size, seq_len]
1030
- *
1031
- * # Returns
1032
- * * A tuple of (loss, gradients_dict) where gradients_dict maps parameter names to gradient arrays
1033
- *
1034
- * # Phase 6A Status
1035
- * Current implementation computes:
1036
- * - ✅ Exact gradients for LM head (output layer)
1037
- * - ⚠吅 Numerical approximations for other layers
1304
+ * Start a new chat session.
1305
+ *
1306
+ * Runs the full jinja chat template once, decodes until the
1307
+ * family's session stop token, and leaves the KV caches on a
1308
+ * clean turn boundary so subsequent `chatSessionContinue` /
1309
+ * `chatSessionContinueTool` calls can append a raw delta on
1310
+ * top without re-rendering the chat template.
1311
+ */
1312
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1313
+ /**
1314
+ * Continue an existing chat session with a new user message.
1315
+ *
1316
+ * Appends a raw user/assistant delta to the session's cached
1317
+ * KV state, then decodes the assistant reply, stopping on the
1318
+ * family's session boundary token.
1319
+ *
1320
+ * `images` is an opt-in guard parameter: when non-empty the
1321
+ * native side returns an error whose message begins with
1322
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1323
+ * `ChatSession` layer can route image-changes back through a
1324
+ * fresh `chatSessionStart`.
1325
+ */
1326
+ chatSessionContinue(
1327
+ userMessage: string,
1328
+ images: Uint8Array[] | null | undefined,
1329
+ audio: Uint8Array[] | null | undefined,
1330
+ config: ChatConfig | null | undefined,
1331
+ ): Promise<ChatResult>;
1332
+ /**
1333
+ * Continue an existing chat session with a tool-result turn.
1334
+ *
1335
+ * Builds the family's tool-result delta from `content` and
1336
+ * prefills it on top of the live session caches, then decodes
1337
+ * the assistant reply.
1338
+ *
1339
+ * `is_error` is the structured tool-error signal. When
1340
+ * `Some(true)`, the renderer prepends the shared
1341
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
1342
+ * rendered tool block.
1343
+ */
1344
+ chatSessionContinueTool(
1345
+ toolCallId: string,
1346
+ content: string,
1347
+ config?: ChatConfig | undefined | null,
1348
+ isError?: boolean | undefined | null,
1349
+ ): Promise<ChatResult>;
1350
+ /** Streaming variant of `chatSessionStart`. */
1351
+ chatStreamSessionStart(
1352
+ messages: ChatMessage[],
1353
+ config: ChatConfig | null,
1354
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1355
+ ): Promise<ChatStreamHandle>;
1356
+ /** Streaming variant of `chatSessionContinue`. */
1357
+ chatStreamSessionContinue(
1358
+ userMessage: string,
1359
+ images: Uint8Array[] | null | undefined,
1360
+ audio: Uint8Array[] | null | undefined,
1361
+ config: ChatConfig | null,
1362
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1363
+ ): Promise<ChatStreamHandle>;
1364
+ /**
1365
+ * Streaming variant of `chatSessionContinueTool`.
1038
1366
  *
1039
- * Future: Full MLX autograd will compute exact gradients for all 250+ parameters
1367
+ * `is_error` mirrors the non-streaming entry point when
1368
+ * `Some(true)`, the renderer prepends the shared
1369
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the rendered
1370
+ * tool block.
1040
1371
  */
1041
- computeLossAndGradients(inputIds: MxArray, labels: MxArray): [MxArray, Record<string, MxArray>];
1372
+ chatStreamSessionContinueTool(
1373
+ toolCallId: string,
1374
+ content: string,
1375
+ config: ChatConfig | null,
1376
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1377
+ isError?: boolean | null | undefined,
1378
+ ): Promise<ChatStreamHandle>;
1379
+ }
1380
+ export type Qwen3_5Model = Qwen35Model;
1381
+
1382
+ /**
1383
+ * Qwen3.5 MoE Model -- hybrid linear/full attention with Mixture-of-Experts.
1384
+ *
1385
+ * All inference and training state lives on a dedicated OS thread. NAPI methods
1386
+ * dispatch commands via channels and await responses. Training commands are
1387
+ * routed through `TrainingDispatch` to the model thread.
1388
+ */
1389
+ export declare class Qwen35MoeModel {
1042
1390
  /**
1043
- * Complete GRPO training step using MLX Autograd (RECOMMENDED)
1044
- *
1045
- * This method uses automatic differentiation to compute gradients, eliminating
1046
- * the need for manual backward pass implementation. This is the preferred approach.
1391
+ * Whether the block-paged KV cache adapter is active on this model
1392
+ * instance.
1047
1393
  *
1048
- * # Arguments
1049
- * * `prompt_tokens` - Prompt token sequences [batch_size, seq_len] (1D arrays)
1050
- * * `completion_tokens` - Completion sequences [batch*G, completion_len] (1D arrays)
1051
- * * `completion_logprobs` - Logprobs from generation [batch*G, completion_len] (1D arrays)
1052
- * * `rewards` - Reward scores for each completion [batch*G]
1053
- * * `group_size` - Number of completions per prompt (G)
1054
- * * `config` - GRPO loss configuration
1055
- * * `learning_rate` - Learning rate for parameter updates
1056
- *
1057
- * # Returns
1058
- * * Tuple of (loss_value, metrics_dict)
1394
+ * `true` iff `Qwen35MoeInner::paged_adapter` was successfully
1395
+ * constructed at load time (driven by
1396
+ * `Qwen3_5MoeConfig::use_block_paged_cache`, currently default-OFF
1397
+ * because parity is pending real-weights validation). On VLM
1398
+ * checkpoints the adapter can still be active for text-only
1399
+ * inference; image-bearing chat turns are rejected at runtime by
1400
+ * the chat-entry sites. Surfaced through this NAPI method so
1401
+ * server endpoints can branch on it without round-tripping through
1402
+ * the model thread.
1059
1403
  */
1060
- trainStepGrpoAutograd(
1061
- promptTokens: Array<MxArray>,
1062
- completionTokens: Array<MxArray>,
1063
- completionLogprobs: Array<MxArray>,
1064
- rewards: Float64Array,
1065
- groupSize: number,
1066
- config: GrpoLossConfig,
1067
- learningRate: number,
1068
- ): [number, Record<string, number>];
1404
+ hasBlockPagedCache(): boolean;
1069
1405
  /**
1070
- * Compute gradients only without applying them (for gradient accumulation)
1406
+ * Whether this checkpoint shipped an MTP head (module loaded by
1407
+ * `persistence::apply_weights_moe_inner`). Snapshotted at load time from
1408
+ * `Qwen35MoeInner::has_mtp_weights()` so the TS `ChatSession` can
1409
+ * auto-default `enableMtp = true` for MTP-capable checkpoints without
1410
+ * dispatching a command into the model thread. Mirrors
1411
+ * `Qwen3_5Model::has_mtp_weights`.
1071
1412
  *
1072
- * This method computes GRPO loss and gradients but does NOT update parameters.
1073
- * Used for gradient accumulation where gradients are summed across multiple
1074
- * micro-batches before applying them.
1075
- *
1076
- * # Arguments
1077
- * * `prompt_tokens` - Prompt token sequences [batch_size, seq_len] (1D arrays)
1078
- * * `completion_tokens` - Completion sequences [batch*G, completion_len] (1D arrays)
1079
- * * `completion_logprobs` - Logprobs from generation [batch*G, completion_len] (1D arrays)
1080
- * * `rewards` - Reward scores for each completion [batch*G]
1081
- * * `group_size` - Number of completions per prompt (G)
1082
- * * `config` - GRPO loss configuration
1083
- *
1084
- * # Returns
1085
- * * Tuple of (loss_value, gradients_dict, metrics_dict)
1413
+ * Note: this only reports weight availability. Whether the
1414
+ * speculative-decode path actually runs on a given call also requires
1415
+ * the per-request `enableMtp` flag.
1086
1416
  */
1087
- computeGradientsOnlyGrpoAutograd(
1088
- promptTokens: Array<MxArray>,
1089
- completionTokens: Array<MxArray>,
1090
- completionLogprobs: Array<MxArray>,
1091
- rewards: Float64Array,
1092
- groupSize: number,
1093
- config: GrpoLossConfig,
1094
- ): [number, Record<string, MxArray>, Record<string, number>];
1417
+ hasMtpWeights(): boolean;
1095
1418
  /**
1096
- * Accumulate gradients into existing gradient dictionary
1419
+ * Whether this loaded model instance can execute image-bearing turns.
1097
1420
  *
1098
- * This is a helper method for gradient accumulation. It adds new_gradients
1099
- * to accumulated_gradients element-wise.
1100
- *
1101
- * # Arguments
1102
- * * `accumulated_gradients` - Existing accumulated gradients (will be modified in-place conceptually, but returns new dict)
1103
- * * `new_gradients` - New gradients to add
1104
- *
1105
- * # Returns
1106
- * * Updated gradient dictionary with accumulated values
1421
+ * This is an authoritative load-time snapshot, not a model-family guess:
1422
+ * it requires the loaded vision encoder, image processor, and block-paged
1423
+ * KV adapter used by the MoE vision path.
1107
1424
  */
1108
- static accumulateGradients(
1109
- accumulatedGradients: Record<string, MxArray>,
1110
- newGradients: Record<string, MxArray>,
1111
- ): Record<string, MxArray>;
1425
+ supportsImages(): boolean;
1426
+ /** Synchronous active-context snapshot shared with the dense wrapper. */
1427
+ contextLimits(): Qwen35ContextLimits;
1112
1428
  /**
1113
- * Complete GRPO training step using manual gradients (Legacy)
1114
- *
1115
- * This method performs a full GRPO training iteration:
1116
- * 1. Takes completions (already generated) with their logprobs and rewards
1117
- * 2. Computes advantages
1118
- * 3. Computes GRPO loss and gradients
1119
- * 4. Updates model parameters
1120
- *
1121
- * NOTE: Use train_step_grpo_autograd instead for automatic differentiation.
1122
- *
1123
- * # Arguments
1124
- * * `prompt_tokens` - Prompt token sequences [batch_size, seq_len] (1D arrays)
1125
- * * `completion_tokens` - Completion sequences [batch*G, completion_len] (1D arrays)
1126
- * * `completion_logprobs` - Logprobs from generation [batch*G, completion_len] (1D arrays)
1127
- * * `rewards` - Reward scores for each completion [batch*G]
1128
- * * `group_size` - Number of completions per prompt (G)
1129
- * * `config` - GRPO loss configuration
1130
- * * `learning_rate` - Learning rate for parameter updates
1131
- *
1132
- * # Returns
1133
- * * Tuple of (loss_value, metrics_dict)
1429
+ * Exact, non-mutating Qwen image-placeholder expansion count for a fully
1430
+ * rendered prompt and complete message history.
1134
1431
  */
1135
- trainStepGrpo(
1136
- promptTokens: Array<MxArray>,
1137
- completionTokens: Array<MxArray>,
1138
- completionLogprobs: Array<MxArray>,
1139
- rewards: Float64Array,
1140
- groupSize: number,
1141
- config: GrpoLossConfig,
1142
- learningRate: number,
1143
- ): [number, Record<string, number>];
1432
+ expandedPromptTokenCount(promptTokens: Uint32Array, messages: Array<ChatMessage>): Promise<number>;
1433
+ /** Load a pretrained model from a directory. */
1434
+ static load(path: string): Promise<Qwen35MoeModel>;
1435
+ /** Generate text from a prompt token sequence. */
1436
+ generate(promptTokens: MxArray, config: Qwen35MoeGenerationConfig): Promise<Qwen35MoeGenerationResult>;
1144
1437
  /**
1145
- * Apply gradients to model parameters
1438
+ * Get the number of parameters in the model.
1146
1439
  *
1147
- * # Arguments
1148
- * * `gradients` - Dictionary mapping parameter names to gradient arrays
1149
- * * `learning_rate` - Learning rate for gradient descent
1440
+ * Pure config computation -- no model-thread dispatch needed.
1441
+ */
1442
+ numParameters(): number;
1443
+ /**
1444
+ * Save the model weights and configuration to a directory.
1150
1445
  *
1151
- * This performs a simple SGD update: param = param - lr * grad
1152
- * Only updates parameters that have gradients; others remain unchanged.
1446
+ * Dispatches to model thread.
1447
+ */
1448
+ saveModel(savePath: string): Promise<undefined>;
1449
+ /**
1450
+ * Reset all caches and clear cached token history. Exposed
1451
+ * so tests and session-management code can start from a
1452
+ * known clean state between turns.
1453
+ */
1454
+ resetCaches(): void;
1455
+ /**
1456
+ * Start a new chat session.
1457
+ *
1458
+ * Runs the full jinja chat template once, decodes until the
1459
+ * family's session stop token, and leaves the KV caches on a
1460
+ * clean turn boundary so subsequent `chatSessionContinue` /
1461
+ * `chatSessionContinueTool` calls can append a raw delta on
1462
+ * top without re-rendering the chat template.
1463
+ */
1464
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1465
+ /**
1466
+ * Continue an existing chat session with a new user message.
1467
+ *
1468
+ * Appends a raw user/assistant delta to the session's cached
1469
+ * KV state, then decodes the assistant reply, stopping on the
1470
+ * family's session boundary token.
1471
+ *
1472
+ * `images` is an opt-in guard parameter: when non-empty the
1473
+ * native side returns an error whose message begins with
1474
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1475
+ * `ChatSession` layer can route image-changes back through a
1476
+ * fresh `chatSessionStart`.
1477
+ */
1478
+ chatSessionContinue(
1479
+ userMessage: string,
1480
+ images: Uint8Array[] | null | undefined,
1481
+ audio: Uint8Array[] | null | undefined,
1482
+ config: ChatConfig | null | undefined,
1483
+ ): Promise<ChatResult>;
1484
+ /**
1485
+ * Continue an existing chat session with a tool-result turn.
1486
+ *
1487
+ * Builds the family's tool-result delta from `content` and
1488
+ * prefills it on top of the live session caches, then decodes
1489
+ * the assistant reply.
1490
+ *
1491
+ * `is_error` is the structured tool-error signal. When
1492
+ * `Some(true)`, the renderer prepends the shared
1493
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
1494
+ * rendered tool block.
1495
+ */
1496
+ chatSessionContinueTool(
1497
+ toolCallId: string,
1498
+ content: string,
1499
+ config?: ChatConfig | undefined | null,
1500
+ isError?: boolean | undefined | null,
1501
+ ): Promise<ChatResult>;
1502
+ /** Streaming variant of `chatSessionStart`. */
1503
+ chatStreamSessionStart(
1504
+ messages: ChatMessage[],
1505
+ config: ChatConfig | null,
1506
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1507
+ ): Promise<ChatStreamHandle>;
1508
+ /** Streaming variant of `chatSessionContinue`. */
1509
+ chatStreamSessionContinue(
1510
+ userMessage: string,
1511
+ images: Uint8Array[] | null | undefined,
1512
+ audio: Uint8Array[] | null | undefined,
1513
+ config: ChatConfig | null,
1514
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1515
+ ): Promise<ChatStreamHandle>;
1516
+ /**
1517
+ * Streaming variant of `chatSessionContinueTool`.
1153
1518
  *
1154
- * IMPORTANT: This function preserves the original dtype of parameters.
1155
- * The learning rate scalar is cast to match param dtype to prevent
1156
- * promotion to float32 during arithmetic operations.
1519
+ * `is_error` mirrors the non-streaming entry point when
1520
+ * `Some(true)`, the renderer prepends the shared
1521
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the rendered
1522
+ * tool block.
1157
1523
  */
1158
- applyGradients(gradients: Record<string, MxArray>, learningRate: number): void;
1524
+ chatStreamSessionContinueTool(
1525
+ toolCallId: string,
1526
+ content: string,
1527
+ config: ChatConfig | null,
1528
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1529
+ isError?: boolean | null | undefined,
1530
+ ): Promise<ChatStreamHandle>;
1531
+ }
1532
+ export type Qwen3_5MoeModel = Qwen35MoeModel;
1533
+
1534
+ /**
1535
+ * Qwen3 Model with automatic differentiation support
1536
+ *
1537
+ * Uses a dedicated model thread for inference and training commands.
1538
+ * Training commands are routed via `TrainingDispatch`.
1539
+ */
1540
+ export declare class Qwen3Model {
1541
+ /**
1542
+ * Whether the block-paged KV cache adapter is active on this model
1543
+ * instance.
1544
+ *
1545
+ * `true` iff `Qwen3Inner::paged_adapter` was successfully constructed
1546
+ * at load time (driven by `Qwen3Config::use_block_paged_cache`,
1547
+ * defaulting to `true` for Qwen3 since paged-vs-flat parity has been
1548
+ * verified). When `true`, the native cache reuses SYS blocks across
1549
+ * `chatSessionStart` calls via content-addressing in
1550
+ * `BlockAllocator`'s prefix-hash table — the JS-side warm slot in
1551
+ * `SessionRegistry.getOrCreateWarmAny` becomes redundant and the
1552
+ * `/v1/messages` server endpoint allocates a fresh `ChatSession` per
1553
+ * request. See `packages/server/src/endpoints/messages.ts` for the
1554
+ * runtime-routing decision.
1555
+ */
1556
+ hasBlockPagedCache(): boolean;
1557
+ /** Get model configuration */
1558
+ getConfig(): Qwen3Config;
1159
1559
  /**
1160
1560
  * Text-to-text generation with integrated tokenization
1161
1561
  *
@@ -1187,77 +1587,6 @@ export declare class Qwen3Model {
1187
1587
  * ```
1188
1588
  */
1189
1589
  generate(messages: Array<ChatMessage>, config?: GenerationConfig | undefined | null): Promise<GenerationResult>;
1190
- /**
1191
- * High-level chat API with structured response parsing
1192
- *
1193
- * The primary API for conversational AI. Handles:
1194
- * - Chat message formatting with Jinja2 templates
1195
- * - Tool/function calling with structured output
1196
- * - Thinking extraction from `<think>` tags
1197
- * - Clean response text with all special tags stripped
1198
- *
1199
- * ## `chat()` vs `generate()`
1200
- *
1201
- * | Feature | `chat()` | `generate()` |
1202
- * |---------|----------|--------------|
1203
- * | **Purpose** | Conversational AI with tools | Raw text generation |
1204
- * | **Input** | Chat messages | Token IDs (MxArray) |
1205
- * | **Tool Support** | Built-in parsing | None |
1206
- * | **Thinking** | Extracts `<think>` content | Raw text only |
1207
- * | **Output** | Structured `ChatResult` | Basic `GenerationResult` |
1208
- * | **Use Case** | Chat apps, agents, assistants | Training, low-level control |
1209
- *
1210
- * ## When to use `chat()`
1211
- * - Building conversational applications
1212
- * - Need tool/function calling
1213
- * - Want structured responses with thinking separated
1214
- * - Working with chat message format
1215
- *
1216
- * ## When to use `generate()`
1217
- * - Training and fine-tuning (need raw logprobs)
1218
- * - Custom tokenization pipeline
1219
- * - Low-level generation control
1220
- * - Non-chat use cases
1221
- *
1222
- * # Arguments
1223
- * * `messages` - Array of chat messages (user/assistant/system roles)
1224
- * * `config` - Chat configuration including optional tools and generation params
1225
- *
1226
- * # Returns
1227
- * * `ChatResult` containing:
1228
- * - `text`: Clean response (tool_call and think tags stripped)
1229
- * - `thinking`: Extracted chain-of-thought reasoning (or null)
1230
- * - `toolCalls`: Parsed tool calls with native JS object arguments
1231
- * - `finishReason`: "stop" | "length" | "tool_calls"
1232
- * - `rawText`: Original text before processing (for debugging)
1233
- *
1234
- * # Example
1235
- * ```typescript
1236
- * // Simple chat
1237
- * const result = await model.chat(messages);
1238
- * console.log(result.text);
1239
- *
1240
- * // With tools
1241
- * const result = await model.chat(messages, {
1242
- * tools: [{ type: 'function', function: { name: 'get_weather' } }],
1243
- * maxNewTokens: 2048,
1244
- * temperature: 0.7,
1245
- * });
1246
- *
1247
- * // Handle tool calls
1248
- * for (const call of result.toolCalls) {
1249
- * if (call.status === 'ok') {
1250
- * console.log(call.name, call.arguments); // Arguments is a JS object!
1251
- * }
1252
- * }
1253
- *
1254
- * // Access thinking (chain-of-thought)
1255
- * if (result.thinking) {
1256
- * console.log('Model reasoning:', result.thinking);
1257
- * }
1258
- * ```
1259
- */
1260
- chat(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1261
1590
  /**
1262
1591
  * Generate multiple completions for multiple prompts in batch
1263
1592
  *
@@ -1297,20 +1626,6 @@ export declare class Qwen3Model {
1297
1626
  groupSize: number,
1298
1627
  config?: GenerationConfig | undefined | null,
1299
1628
  ): Promise<BatchGenerationResult>;
1300
- /**
1301
- * Decode token IDs to text using the internal tokenizer
1302
- *
1303
- * Helper method for decoding generated tokens. The model must have been loaded
1304
- * via load() to have a tokenizer available.
1305
- *
1306
- * # Arguments
1307
- * * `token_ids` - Token IDs to decode as Uint32Array
1308
- * * `skip_special_tokens` - Whether to skip special tokens (default: true)
1309
- *
1310
- * # Returns
1311
- * * Decoded text string
1312
- */
1313
- decode(tokenIds: Uint32Array, skipSpecialTokens?: boolean | undefined | null): Promise<string>;
1314
1629
  /**
1315
1630
  * Apply chat template and encode to token IDs
1316
1631
  *
@@ -1332,6 +1647,88 @@ export declare class Qwen3Model {
1332
1647
  tools?: Array<ToolDefinition> | undefined | null,
1333
1648
  enableThinking?: boolean | undefined | null,
1334
1649
  ): Promise<Uint32Array>;
1650
+ /**
1651
+ * Reset all caches and clear cached token history. Exposed
1652
+ * so tests and session-management code can start from a
1653
+ * known clean state between turns.
1654
+ */
1655
+ resetCaches(): void;
1656
+ /**
1657
+ * Start a new chat session.
1658
+ *
1659
+ * Runs the full jinja chat template once, decodes until the
1660
+ * family's session stop token, and leaves the KV caches on a
1661
+ * clean turn boundary so subsequent `chatSessionContinue` /
1662
+ * `chatSessionContinueTool` calls can append a raw delta on
1663
+ * top without re-rendering the chat template.
1664
+ */
1665
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1666
+ /**
1667
+ * Continue an existing chat session with a new user message.
1668
+ *
1669
+ * Appends a raw user/assistant delta to the session's cached
1670
+ * KV state, then decodes the assistant reply, stopping on the
1671
+ * family's session boundary token.
1672
+ *
1673
+ * `images` is an opt-in guard parameter: when non-empty the
1674
+ * native side returns an error whose message begins with
1675
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1676
+ * `ChatSession` layer can route image-changes back through a
1677
+ * fresh `chatSessionStart`.
1678
+ */
1679
+ chatSessionContinue(
1680
+ userMessage: string,
1681
+ images: Uint8Array[] | null | undefined,
1682
+ audio: Uint8Array[] | null | undefined,
1683
+ config: ChatConfig | null | undefined,
1684
+ ): Promise<ChatResult>;
1685
+ /**
1686
+ * Continue an existing chat session with a tool-result turn.
1687
+ *
1688
+ * Builds the family's tool-result delta from `content` and
1689
+ * prefills it on top of the live session caches, then decodes
1690
+ * the assistant reply.
1691
+ *
1692
+ * `is_error` is the structured tool-error signal. When
1693
+ * `Some(true)`, the renderer prepends the shared
1694
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the
1695
+ * rendered tool block.
1696
+ */
1697
+ chatSessionContinueTool(
1698
+ toolCallId: string,
1699
+ content: string,
1700
+ config?: ChatConfig | undefined | null,
1701
+ isError?: boolean | undefined | null,
1702
+ ): Promise<ChatResult>;
1703
+ /** Streaming variant of `chatSessionStart`. */
1704
+ chatStreamSessionStart(
1705
+ messages: ChatMessage[],
1706
+ config: ChatConfig | null,
1707
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1708
+ ): Promise<ChatStreamHandle>;
1709
+ /** Streaming variant of `chatSessionContinue`. */
1710
+ chatStreamSessionContinue(
1711
+ userMessage: string,
1712
+ images: Uint8Array[] | null | undefined,
1713
+ audio: Uint8Array[] | null | undefined,
1714
+ config: ChatConfig | null,
1715
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1716
+ ): Promise<ChatStreamHandle>;
1717
+ /**
1718
+ * Streaming variant of `chatSessionContinueTool`.
1719
+ *
1720
+ * `is_error` mirrors the non-streaming entry point — when
1721
+ * `Some(true)`, the renderer prepends the shared
1722
+ * [`crate::tokenizer::TOOL_ERROR_MARKER`] inside the rendered
1723
+ * tool block.
1724
+ */
1725
+ chatStreamSessionContinueTool(
1726
+ toolCallId: string,
1727
+ content: string,
1728
+ config: ChatConfig | null,
1729
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1730
+ isError?: boolean | null | undefined,
1731
+ ): Promise<ChatStreamHandle>;
1335
1732
  /**
1336
1733
  * Load a pretrained model from disk
1337
1734
  *
@@ -1355,6 +1752,10 @@ export declare class Qwen3Model {
1355
1752
  * - weights.safetensors: Full model weights in SafeTensors format
1356
1753
  * - weights.mlx: Parameter metadata (for reference)
1357
1754
  *
1755
+ * Dispatches to the dedicated model thread — all MxArray reads must
1756
+ * happen on the thread that owns them to avoid the MLX cross-thread
1757
+ * `CommandEncoder` crash.
1758
+ *
1358
1759
  * # Arguments
1359
1760
  * * `save_path` - Directory to save the model
1360
1761
  */
@@ -1507,7 +1908,33 @@ export declare class Qwen3Tokenizer {
1507
1908
  getEndoftextToken(): string;
1508
1909
  }
1509
1910
 
1510
- /** SFT Training Engine */
1911
+ /**
1912
+ * Response store for OpenAI Responses API persistence.
1913
+ *
1914
+ * Stores responses in SQLite to support `previous_response_id`
1915
+ * for multi-turn conversation state.
1916
+ */
1917
+ export declare class ResponseStore {
1918
+ /** Open (or create) a response store at the given path. */
1919
+ static open(path: string): Promise<ResponseStore>;
1920
+ /** Store a response. */
1921
+ store(response: StoredResponseRecord): Promise<void>;
1922
+ /** Get a single response by ID. */
1923
+ get(id: string): Promise<StoredResponseRecord | null>;
1924
+ /** Get the full conversation chain for a response (oldest first). */
1925
+ getChain(id: string): Promise<Array<StoredResponseRecord>>;
1926
+ /** Delete a response by ID. Returns true if a row was deleted. */
1927
+ delete(id: string): Promise<boolean>;
1928
+ /** Delete expired responses. Returns the number of rows deleted. */
1929
+ cleanupExpired(): Promise<number>;
1930
+ }
1931
+
1932
+ /**
1933
+ * SFT Training Engine
1934
+ *
1935
+ * Thin coordinator that routes all MLX operations through the model thread.
1936
+ * No MxArrays or model state live here - only plain data crosses the boundary.
1937
+ */
1511
1938
  export declare class SftTrainingEngine {
1512
1939
  /** Create a new SFT training engine from a Qwen3 model */
1513
1940
  constructor(model: Qwen3Model, config: SftEngineConfig);
@@ -1524,9 +1951,10 @@ export declare class SftTrainingEngine {
1524
1951
  /**
1525
1952
  * Flush any accumulated gradients at epoch end
1526
1953
  *
1527
- * When stepsPerEpoch % gradient_accumulation_steps != 0, there may be
1528
- * leftover gradients from the final micro-batches. This method applies
1529
- * them with proper averaging, matching TRL behavior.
1954
+ * With the model thread architecture, gradient accumulation is handled
1955
+ * on the model thread. This method is kept for API compatibility but
1956
+ * currently logs a warning. Partial accumulation at epoch boundaries
1957
+ * will be handled in a future update.
1530
1958
  */
1531
1959
  flushGradients(): boolean;
1532
1960
  /**
@@ -1549,15 +1977,46 @@ export declare class SftTrainingEngine {
1549
1977
  startEpoch(epoch: number): void;
1550
1978
  /** End current epoch and return metrics */
1551
1979
  endEpoch(epochTimeSecs: number): SftEpochMetrics;
1552
- /** Reset training state (for new training run) */
1980
+ /**
1981
+ * Reset training state (for new training run)
1982
+ *
1983
+ * This is a TERMINAL operation on this handle. It drops the training
1984
+ * state (optimizer, step counter) on the model thread so a fresh
1985
+ * `SftTrainingEngine` can be constructed on the same model, and marks
1986
+ * THIS handle as invalidated. Any subsequent dispatch-requiring method
1987
+ * on this handle returns an error — callers must construct a new
1988
+ * engine to continue training.
1989
+ */
1553
1990
  reset(): void;
1554
- /** Restore training state (for resuming from checkpoint) */
1991
+ /**
1992
+ * Restore training state (for resuming from checkpoint)
1993
+ *
1994
+ * Updates both the engine's read-through cache and the model thread's
1995
+ * authoritative `ts.step`. Does NOT touch optimizer state — that is
1996
+ * loaded via `loadOptimizerState`, which restores the AdamW bias-
1997
+ * correction step separately.
1998
+ */
1555
1999
  restoreState(step: number, epoch: number): void;
1556
- /** Get the underlying Qwen3 model for checkpointing */
2000
+ /**
2001
+ * Get the underlying Qwen3 model for checkpointing
2002
+ *
2003
+ * NOTE: With the model thread architecture, direct model access is no longer
2004
+ * supported. Use save_checkpoint() on the model directly instead.
2005
+ */
1557
2006
  getModel(): Qwen3Model;
1558
- /** Get the underlying Qwen3.5 dense model for checkpointing */
2007
+ /**
2008
+ * Get the underlying Qwen3.5 dense model for checkpointing
2009
+ *
2010
+ * NOTE: With the model thread architecture, direct model access is no longer
2011
+ * supported. Use save_checkpoint() on the model directly instead.
2012
+ */
1559
2013
  getQwen35Model(): Qwen35Model;
1560
- /** Get the underlying Qwen3.5 MoE model for checkpointing */
2014
+ /**
2015
+ * Get the underlying Qwen3.5 MoE model for checkpointing
2016
+ *
2017
+ * NOTE: With the model thread architecture, direct model access is no longer
2018
+ * supported. Use save_checkpoint() on the model directly instead.
2019
+ */
1561
2020
  getQwen35MoeModel(): Qwen35MoeModel;
1562
2021
  }
1563
2022
 
@@ -1730,14 +2189,18 @@ export type VLMChatResult = VlmChatResult;
1730
2189
  *
1731
2190
  * A generic VLM for OCR and document understanding tasks.
1732
2191
  * Currently supports PaddleOCR-VL architecture (vision encoder + ERNIE language model).
2192
+ *
2193
+ * All model state lives on a dedicated OS thread. NAPI methods dispatch
2194
+ * commands via channels and await responses.
1733
2195
  */
1734
2196
  export declare class VLModel {
1735
- /** Create a new PaddleOCR-VL model */
2197
+ /**
2198
+ * Create a new PaddleOCR-VL model (empty, not loaded).
2199
+ *
2200
+ * Creates a model thread with an empty inner. Use `VLModel.load()` instead
2201
+ * for loading a model from disk.
2202
+ */
1736
2203
  constructor(config: ModelConfig);
1737
- /** Set the tokenizer */
1738
- setTokenizer(tokenizer: Qwen3Tokenizer): void;
1739
- /** Check if tokenizer is available */
1740
- get hasTokenizer(): boolean;
1741
2204
  /**
1742
2205
  * Chat with the VLM model
1743
2206
  *
@@ -1778,82 +2241,6 @@ export declare class VLModel {
1778
2241
  * ```
1779
2242
  */
1780
2243
  ocr(imageData: Buffer, prompt?: string | undefined | null): Promise<string>;
1781
- /**
1782
- * Get input embeddings with vision features merged
1783
- *
1784
- * # Arguments
1785
- * * `input_ids` - Token IDs [batch, seq_len]
1786
- * * `pixel_values` - Optional image patches [batch, seq, channels, patch_h, patch_w]
1787
- * * `image_grid_thw` - Optional grid dimensions [num_images, 3]
1788
- *
1789
- * # Returns
1790
- * * Input embeddings with vision features inserted at image token positions
1791
- */
1792
- getInputEmbeddings(
1793
- inputIds: MxArray,
1794
- pixelValues?: MxArray | undefined | null,
1795
- imageGridThw?: MxArray | undefined | null,
1796
- ): MxArray;
1797
- /**
1798
- * Forward pass
1799
- *
1800
- * # Arguments
1801
- * * `input_ids` - Token IDs [batch, seq_len]
1802
- * * `pixel_values` - Optional image patches
1803
- * * `image_grid_thw` - Optional grid dimensions
1804
- * * `mask` - Optional attention mask
1805
- *
1806
- * # Returns
1807
- * * Logits [batch, seq_len, vocab_size]
1808
- */
1809
- forward(
1810
- inputIds: MxArray,
1811
- pixelValues?: MxArray | undefined | null,
1812
- imageGridThw?: MxArray | undefined | null,
1813
- mask?: MxArray | undefined | null,
1814
- ): MxArray;
1815
- /**
1816
- * Generate text tokens given input tokens and optional image
1817
- *
1818
- * Uses KV caching for efficient generation - each step only processes the
1819
- * new token(s) while reusing cached key-value states from previous tokens.
1820
- * Vision features are computed once at the start and cached.
1821
- *
1822
- * # Arguments
1823
- * * `input_ids` - Input token IDs [1, seq_len]
1824
- * * `pixel_values` - Optional image patches [1, num_patches, C, H, W]
1825
- * * `image_grid_thw` - Optional grid dimensions [1, 3]
1826
- * * `config` - Generation configuration
1827
- *
1828
- * # Returns
1829
- * * GenerationResult with tokens, logprobs, and finish reason
1830
- */
1831
- generate(
1832
- inputIds: MxArray,
1833
- pixelValues?: MxArray | undefined | null,
1834
- imageGridThw?: MxArray | undefined | null,
1835
- config?: GenerationConfig | undefined | null,
1836
- ): Promise<GenerationResult>;
1837
- /**
1838
- * Batch OCR: extract text from multiple images simultaneously
1839
- *
1840
- * Processes N images with sequential prefill + batched decode for ~N× decode throughput.
1841
- *
1842
- * # Arguments
1843
- * * `images` - Encoded image buffers
1844
- * * `config` - Optional chat configuration (shared across all items)
1845
- *
1846
- * # Returns
1847
- * * Vec of extracted text strings, one per image
1848
- *
1849
- * # Example
1850
- * ```typescript
1851
- * import { readFileSync } from 'fs';
1852
- * const images = ['page1.jpg', 'page2.jpg'].map(p => readFileSync(p));
1853
- * const texts = await model.ocrBatch(images);
1854
- * ```
1855
- */
1856
- ocrBatch(images: Array<Buffer>, config?: VlmChatConfig | undefined | null): Promise<Array<string>>;
1857
2244
  /**
1858
2245
  * Batch chat: process multiple items simultaneously
1859
2246
  *
@@ -1892,25 +2279,6 @@ export declare class VLModel {
1892
2279
  * ```
1893
2280
  */
1894
2281
  static load(modelPath: string): Promise<VLModel>;
1895
- /**
1896
- * Load model configuration from disk without loading weights
1897
- *
1898
- * This is useful for inspecting model configuration before loading the full model.
1899
- *
1900
- * # Arguments
1901
- * * `model_path` - Path to the model directory containing config.json
1902
- *
1903
- * # Returns
1904
- * * ModelConfig with vision and text configuration
1905
- *
1906
- * # Example
1907
- * ```typescript
1908
- * import { VLModel } from '@mlx-node/vlm';
1909
- * const config = await VLModel.loadConfig('./models/paddleocr-vl');
1910
- * console.log(config.visionConfig.hiddenSize);
1911
- * ```
1912
- */
1913
- static loadConfig(modelPath: string): Promise<ModelConfig>;
1914
2282
  }
1915
2283
 
1916
2284
  /**
@@ -1961,8 +2329,68 @@ export declare const enum BuiltinRewardType {
1961
2329
  JsonSchema = 'JsonSchema',
1962
2330
  }
1963
2331
 
2332
+ /**
2333
+ * Data-free static FP8 activation-amax calibration over RAW-text PREFILL
2334
+ * (NVIDIA modelopt `MaxCalibrator` parity), end to end in native code.
2335
+ *
2336
+ * The nvidia recipe covers BOTH `qwen3_5` (dense) and `qwen3_5_moe` (MoE), so
2337
+ * this reads `<model_path>/config.json`'s `model_type` and dispatches to the
2338
+ * matching loader + `CalibratePrefillRaw` command (any other `model_type` is a
2339
+ * clear error). Both loaders are the SAME ones the inference session uses
2340
+ * ([`persistence::load_with_thread`]) — the model is only usable on its
2341
+ * dedicated model thread. Then:
2342
+ * 1. dispatches `{Qwen35Cmd,Qwen35MoeCmd}::CalibratePrefillRaw`, which on the
2343
+ * model thread SELF-ARMS that thread's thread-local
2344
+ * [`ActivationAmaxCollector`] flag (RAII, AFTER load so no load-time eval
2345
+ * is recorded), tokenizes each `text` WITHOUT the chat template, truncates
2346
+ * to `calib_seq` tokens, and runs PREFILL ONLY (no generation) so every
2347
+ * mxfp8 attn/GDN projection's activation tap fires over realistic raw-text
2348
+ * activations, resetting caches between rows, then disarms on exit;
2349
+ * 2. ONLY if the full loop succeeded — drains the per-tensor amax and
2350
+ * ATOMICALLY writes it into `<model_path>/config.json` (temp file +
2351
+ * `rename`).
2352
+ *
2353
+ * CONCURRENCY: the whole clear→prefill→take→write section is serialized by
2354
+ * [`calib_guard`] (a process-wide `try_lock`); a second concurrent calibration
2355
+ * fails fast with "another calibration is in progress". The arm flag is
2356
+ * thread-local (so a concurrent inference model can't contaminate the run), but
2357
+ * the running-max MAP is process-global, so serializing RUNS keeps two
2358
+ * calibrations from interleaving `record`/`take` on it. The map is CLEARED at
2359
+ * the very start so stale amax from a prior PANICKED run cannot leak into this
2360
+ * write.
2361
+ *
2362
+ * On ANY error before the final write, the partial amax is discarded and
2363
+ * `config.json` is left UNTOUCHED (a failed calibration must not mutate the
2364
+ * live model in place). A run that prefilled ZERO rows (empty dataset, or every
2365
+ * row tokenized to nothing) is likewise an ERROR that leaves `config.json`
2366
+ * untouched — a no-op calibration must not report a silent success. Returns the
2367
+ * number of projections calibrated (the count of collected amax entries); 0
2368
+ * means a real prefill ran but the model exercised no activation-fp8 sites (not
2369
+ * an nvidia-recipe checkpoint), and in that case `config.json` is left
2370
+ * UNCHANGED (no rewrite).
2371
+ */
2372
+ export declare function calibrateActivationAmaxRaw(
2373
+ modelPath: string,
2374
+ texts: Array<string>,
2375
+ calibSeq: number,
2376
+ ): Promise<number>;
2377
+
1964
2378
  /** Unified chat configuration shared by all model variants (Qwen3, Qwen3.5, Qwen3.5 MoE). */
1965
2379
  export interface ChatConfig {
2380
+ /**
2381
+ * Internal logical cache owner. The agent provider forwards Pi's stable
2382
+ * session id so model-global GDN sidecars can retain parent and child
2383
+ * branches independently. This does not namespace the physical paged KV
2384
+ * cache; exact token/extra-key hashes remain shareable across owners.
2385
+ */
2386
+ cacheOwnerId?: string | undefined;
2387
+ /**
2388
+ * Internal top-level owner for the bounded Qwen3.5 GDN sidecar store.
2389
+ * `cache_owner_id` may identify a child Pi session; this separately
2390
+ * identifies the current interactive root so /new and /resume can rotate
2391
+ * the protected branch without changing PagedAttention cache identity.
2392
+ */
2393
+ cacheRootOwnerId?: string | undefined;
1966
2394
  maxNewTokens?: number | undefined;
1967
2395
  temperature?: number | undefined;
1968
2396
  topK?: number | undefined;
@@ -1972,28 +2400,107 @@ export interface ChatConfig {
1972
2400
  repetitionPenalty?: number | undefined;
1973
2401
  /** Size of the context window for repetition penalty (default: 256) */
1974
2402
  repetitionContextSize?: number | undefined;
1975
- /** Max consecutive identical tokens before stopping (default: 16, 0 = disabled) */
2403
+ /**
2404
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
2405
+ * token that appeared at least once in context. Matches OpenAI API semantics.
2406
+ */
2407
+ presencePenalty?: number | undefined;
2408
+ /** Number of recent tokens to consider for presence penalty (default: 20) */
2409
+ presenceContextSize?: number | undefined;
2410
+ /**
2411
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
2412
+ * logits of each token in context. Matches OpenAI API semantics.
2413
+ */
2414
+ frequencyPenalty?: number | undefined;
2415
+ /** Number of recent tokens to consider for frequency penalty (default: 20) */
2416
+ frequencyContextSize?: number | undefined;
2417
+ /** Max consecutive identical tokens before stopping (default: 0 = disabled; opt in with a positive value) */
1976
2418
  maxConsecutiveTokens?: number | undefined;
1977
- /** Max n-gram repetitions before stopping (default: 3, 0 = disabled) */
2419
+ /** Max n-gram repetitions before stopping (default: 0 = disabled; opt in with a positive value) */
1978
2420
  maxNgramRepeats?: number | undefined;
1979
- /** Max pattern size for n-gram repetition detection (default: 64) */
2421
+ /** Max pattern size for n-gram repetition detection (default: 0 = disabled; opt in with a positive value) */
1980
2422
  ngramSize?: number | undefined;
1981
2423
  tools?: Array<ToolDefinition>;
1982
2424
  /**
1983
- * Enable thinking mode (Qwen3's <think> tags). Default: true (model thinks naturally).
1984
- * Set to false to suppress thinking by injecting empty <think></think> tags.
2425
+ * Reasoning effort level. Controls whether the model thinks before answering.
2426
+ * - "none" / "low": thinking disabled (template injects closed think block).
2427
+ * "none" also sets includeReasoning to false by default.
2428
+ * - "medium" / "high": thinking enabled (default behavior).
2429
+ * - Not set: thinking enabled (model thinks naturally).
2430
+ */
2431
+ reasoningEffort?: string | undefined;
2432
+ /**
2433
+ * Maximum number of thinking tokens before forcing </think>.
2434
+ * When the model has generated this many tokens while in thinking mode,
2435
+ * the next token is forced to be the think_end token. None = unlimited.
2436
+ */
2437
+ thinkingTokenBudget?: number | undefined;
2438
+ /**
2439
+ * Whether to include reasoning/thinking content in the output.
2440
+ * When false, the `thinking` field of ChatResult/ChatStreamChunk will always be None.
2441
+ * Default: true (false when reasoningEffort is "none").
1985
2442
  */
1986
- enableThinking?: boolean | undefined;
2443
+ includeReasoning?: boolean | undefined;
1987
2444
  /** When true, include performance metrics (TTFT, prefill tok/s, decode tok/s) in the result */
1988
2445
  reportPerformance?: boolean | undefined;
1989
2446
  /**
1990
- * Reuse KV cache across chat() calls for incremental prefill. Default: true.
2447
+ * Reuse KV cache across chat-session turns for incremental prefill. Default: true.
1991
2448
  * When true, the model preserves its KV cache after generation. On the next
1992
- * chat() call, it prefix-matches the new token sequence against the cached
1993
- * tokens and only prefills the delta — avoiding redundant computation for
1994
- * multi-turn conversations.
2449
+ * `chatSessionStart` / `chatSessionContinue` call, it prefix-matches the new
2450
+ * token sequence against the cached tokens and only prefills the delta —
2451
+ * avoiding redundant computation for multi-turn conversations.
1995
2452
  */
1996
2453
  reuseCache?: boolean | undefined;
2454
+ /**
2455
+ * MTP: opt-in flag enabling the Multi-Token Prediction speculative decode
2456
+ * loop (pure-Rust eager; qwen3.5 dense and MoE). Requires the model
2457
+ * checkpoint to carry an MTP head (otherwise silently ignored). Default:
2458
+ * `false`.
2459
+ */
2460
+ enableMtp?: boolean | undefined;
2461
+ /**
2462
+ * MTP: number of draft tokens per speculative cycle.
2463
+ *
2464
+ * On Qwen3.5 native MTP heads it is clamped to `[1, 5]` by the verify
2465
+ * FFI contract, and when unset native code currently pins depth 1.
2466
+ * When `mtpAdaptiveDepth` is `true`, this value is used as the
2467
+ * throughput-policy seed and the expected-value policy's max depth.
2468
+ * Adaptive depth is opt-in; set `mtpAdaptiveDepth: true` explicitly to
2469
+ * enable it.
2470
+ *
2471
+ * Gemma4 external drafts (`draftModelPath`) resolve the field per draft
2472
+ * variant instead (`gemma4/model.rs` `resolve_params`, always from the
2473
+ * RAW config value — the engine's central `[1, 5]` clamp is an MTP-head
2474
+ * contract that does not apply to external drafts):
2475
+ * - DSpark: with both knobs unset, full draft blocks (the checkpoint's
2476
+ * block size — 7 tokens on `dspark_gemma4_12b_block7`) run behind a
2477
+ * short target-AR/DSpark break-even calibration. A short generation
2478
+ * budget that cannot finish calibration retains the fixed-block
2479
+ * schedule. An explicit
2480
+ * `mtpDepth` caps and pins the block unless `mtpAdaptiveDepth: true`
2481
+ * opts the guard back in; explicit `false` disables it.
2482
+ * - Assistant (Google `gemma-4-*-it-assistant`): an unset `mtpDepth`
2483
+ * drafts 3 tokens per cycle (`ASSISTANT_DEFAULT_DEPTH`), and an
2484
+ * explicit `mtpDepth` clamps to `[1, 8]` (`ASSISTANT_MAX_DEPTH`).
2485
+ *
2486
+ * `mtpAdaptiveDepth` is ignored for the Gemma4 assistant variant.
2487
+ */
2488
+ mtpDepth?: number | undefined;
2489
+ /**
2490
+ * MTP: when true, the decode loop runs the adaptive
2491
+ * depth policy. Default mode is a per-depth EMA hill-climb plus
2492
+ * DFlash-style 3-state machine `full | reduced | probe`.
2493
+ * `MLX_MTP_ADAPTIVE_DEPTH_MODE=expected-value` instead uses the
2494
+ * MTPLX-style intra-cycle expected-value gate, which deepens toward
2495
+ * `mtpDepth` by default (T=0 byte-parity verified); set
2496
+ * `MLX_MTP_EV_ALLOW_DEEPEN=0` to pin the base depth.
2497
+ * When false, the loop pins `mtpDepth` for every cycle.
2498
+ *
2499
+ * Default: false, except Gemma4 DSpark enables its measured break-even
2500
+ * guard when both this field and `mtpDepth` are unset. An explicit value
2501
+ * always wins over the family default.
2502
+ */
2503
+ mtpAdaptiveDepth?: boolean | undefined;
1997
2504
  }
1998
2505
 
1999
2506
  /** Chat message with tool calling support */
@@ -2006,10 +2513,40 @@ export interface ChatMessage {
2006
2513
  toolCalls?: Array<ToolCall>;
2007
2514
  /** Tool call ID this message is responding to (for tool messages) */
2008
2515
  toolCallId?: string;
2516
+ /**
2517
+ * Whether this tool-role message represents an errored tool result.
2518
+ *
2519
+ * Authoritative, structured signal of tool-call failure. Set to
2520
+ * `Some(true)` when the caller (e.g. the Anthropic
2521
+ * `tool_result.is_error === true` translator) wants the model to
2522
+ * treat the tool output as an error. The renderer prepends a short
2523
+ * `[tool error]` prefix to `content` when emitting the wire-format
2524
+ * tool response so the model receives a clear text-level cue, but
2525
+ * the original `content` stays byte-for-byte intact in the
2526
+ * structured form — no JSON wrapping, no in-band marker that could
2527
+ * collide with a successful tool result whose literal content
2528
+ * happens to start with the same prefix.
2529
+ *
2530
+ * `None` / `Some(false)` produce the unmarked wire format.
2531
+ */
2532
+ isError?: boolean;
2009
2533
  /** Reasoning content for thinking mode (used with <think> tags) */
2010
2534
  reasoningContent?: string;
2535
+ /**
2536
+ * Thinking mode used when this assistant message was generated.
2537
+ *
2538
+ * This is replay provenance, not a request override. Gemma4's disabled-
2539
+ * thinking generation prefix contains an explicit empty thought channel,
2540
+ * while an enabled-thinking turn that emitted no reasoning contains no
2541
+ * such channel. Keeping the historical mode on the message lets the chat
2542
+ * template reproduce either byte sequence even when a later request
2543
+ * changes its current thinking setting.
2544
+ */
2545
+ thinkingEnabled?: boolean;
2011
2546
  /** Image data for VLM models (encoded image bytes: PNG/JPEG, passed as Uint8Array/Buffer) */
2012
2547
  images?: Array<Uint8Array> | undefined;
2548
+ /** Audio data for unified Gemma 4 (encoded audio bytes: WAV, passed as Uint8Array/Buffer) */
2549
+ audio?: Array<Uint8Array> | undefined;
2013
2550
  }
2014
2551
 
2015
2552
  /** Unified chat result shared by all model variants (Qwen3, Qwen3.5, Qwen3.5 MoE). */
@@ -2018,8 +2555,21 @@ export interface ChatResult {
2018
2555
  toolCalls: Array<ToolCallResult>;
2019
2556
  thinking?: string;
2020
2557
  numTokens: number;
2558
+ promptTokens: number;
2559
+ reasoningTokens: number;
2021
2560
  finishReason: string;
2022
2561
  rawText: string;
2562
+ /**
2563
+ * Number of prompt tokens served from the reused KV-cache prefix.
2564
+ *
2565
+ * When the native prefix-cache machinery successfully matches the new
2566
+ * prompt against the cached conversation history (via
2567
+ * `verify_cache_prefix_direct`), only the trailing delta is re-prefilled
2568
+ * and this field reports the length of the reused prefix. `0` when
2569
+ * the cache was missed or disabled and the full prompt had to be
2570
+ * re-prefilled.
2571
+ */
2572
+ cachedTokens: number;
2023
2573
  /** Performance metrics (present when `reportPerformance: true` in config) */
2024
2574
  performance?: PerformanceMetrics;
2025
2575
  }
@@ -2044,9 +2594,30 @@ export interface ChatStreamChunk {
2044
2594
  toolCalls?: Array<ToolCallResult>;
2045
2595
  thinking?: string;
2046
2596
  numTokens?: number;
2597
+ promptTokens?: number;
2598
+ reasoningTokens?: number;
2047
2599
  rawText?: string;
2600
+ /**
2601
+ * Number of prompt tokens served from the reused KV-cache prefix on
2602
+ * this turn. Populated on the terminal chunk (`done == true`) only;
2603
+ * `None` on mid-stream delta chunks.
2604
+ *
2605
+ * Zero on a cache miss or disabled reuse; equal to the matched
2606
+ * prefix length on a hit. Mirrors `ChatResult.cached_tokens`
2607
+ * verbatim so session-aware streaming consumers can observe
2608
+ * prefix-cache reuse without round-tripping to the non-streaming
2609
+ * path. Non-terminal chunks always carry `None` — only the
2610
+ * terminal chunk is authoritative.
2611
+ */
2612
+ cachedTokens?: number | undefined;
2048
2613
  /** Performance metrics (only present in the final chunk when `reportPerformance: true`) */
2049
2614
  performance?: PerformanceMetrics;
2615
+ /**
2616
+ * Whether this delta chunk contains reasoning/thinking content.
2617
+ * true = reasoning (inside <think>...</think>), false = content (after </think>).
2618
+ * Only present on intermediate (non-final) chunks.
2619
+ */
2620
+ isReasoning?: boolean | undefined;
2050
2621
  }
2051
2622
 
2052
2623
  /** Result from classify_and_rotate: orientation info + corrected image bytes. */
@@ -2109,7 +2680,11 @@ export interface ConversionOptions {
2109
2680
  quantBits?: number;
2110
2681
  /** Quantization group size (default: 64 for affine, 32 for mxfp8) */
2111
2682
  quantGroupSize?: number;
2112
- /** Quantization mode: "affine" (default) or "mxfp8" */
2683
+ /**
2684
+ * Quantization mode: "affine" (default), "mxfp4", "mxfp8", "nvfp4", or
2685
+ * "sym8" (per-output-channel symmetric int8; qwen3_5 + qwen3_5_moe + lfm2/lfm2_moe + gemma4,
2686
+ * implies bits=8, no group_size — consciously NOT mlx-lm-loadable)
2687
+ */
2113
2688
  quantMode?: string;
2114
2689
  /**
2115
2690
  * Quantization recipe for per-layer mixed-bit quantization.
@@ -2121,6 +2696,35 @@ export interface ConversionOptions {
2121
2696
  * Improves quantization quality by amplifying important weight channels.
2122
2697
  */
2123
2698
  imatrixPath?: string;
2699
+ /**
2700
+ * Upgrade quantization to micro-scaling FP (mxfp4 / mxfp8).
2701
+ * When true, applies after the recipe predicate: eligible 8-bit affine
2702
+ * decisions become mxfp8 and 4-bit become mxfp4. Kept affine (not upgraded):
2703
+ * affine-only loaders (lm_head, embed_tokens, router.proj,
2704
+ * embedding_projection) at their recipe bits, MoE router gates (8-bit affine),
2705
+ * and the recipe-pinned attention/GDN projections (o_proj / out_proj /
2706
+ * in_proj_a / in_proj_b, 8-bit affine). Requires `quant_mode = "affine"`.
2707
+ * Forces `group_size = 32` for upgraded layers.
2708
+ */
2709
+ quantMxfp?: boolean;
2710
+ /**
2711
+ * Optional Qwen MTP quantization policy: "off" (default), "cyankiwi", "all",
2712
+ * or "split" (alias "drafter").
2713
+ * "cyankiwi" keeps mtp.fc dense and quantizes only the MTP layer linears as
2714
+ * 4-bit affine group_size=32. For dense `qwen3_5` the quantized linears are
2715
+ * emitted into an MTPLX-compatible mtp.safetensors sidecar; for MoE
2716
+ * (`qwen3_5_moe`) there is no sidecar — they are quantized in place and stored
2717
+ * inline in the main safetensors shards.
2718
+ * "all" additionally quantizes mtp.fc. For dense `qwen3_5` the quantized MTP
2719
+ * linears land in the mtp.safetensors sidecar; for MoE (`qwen3_5_moe`) there
2720
+ * is no sidecar — they are quantized in place and stored inline in the main
2721
+ * safetensors shards.
2722
+ * "split"/"drafter" emits a body checkpoint with NO mtp.* tensors plus a
2723
+ * separate `mtp-drafter/` directory in mlx-vlm's `qwen3_5_mtp` format
2724
+ * (bare-keyed MTP head, format:mlx). It does NOT require --quantize/--q-recipe;
2725
+ * the body may be bf16 or already-quantized and the MTP head stays bf16.
2726
+ */
2727
+ quantMtp?: string;
2124
2728
  }
2125
2729
 
2126
2730
  export interface ConversionResult {
@@ -2174,6 +2778,45 @@ export declare function convertParquetToJsonl(inputPath: string, outputPath: str
2174
2778
  /** Create a default PaddleOCR-VL 1.5 configuration (JS factory function) */
2175
2779
  export declare function createPaddleocrVlConfig(): ModelConfig;
2176
2780
 
2781
+ /** Create a default Qianfan-OCR configuration (JS factory function) */
2782
+ export declare function createQianfanOcrConfig(): QianfanOcrConfig;
2783
+
2784
+ /**
2785
+ * Create a random-init Qwen3.5 model and save it to disk.
2786
+ *
2787
+ * Spawns a dedicated `ModelThread<Qwen35Cmd>` whose init builds a fresh
2788
+ * random-weight `Qwen35Inner` directly, then dispatches `Qwen35Cmd::SaveModel`
2789
+ * on that thread. The thread is dropped at the end of the promise, so the
2790
+ * in-memory model is released once the checkpoint has been written. Used by
2791
+ * TypeScript test fixtures that need an on-disk checkpoint without keeping a
2792
+ * NAPI model instance alive.
2793
+ */
2794
+ export declare function createRandomQwen35Checkpoint(config: Qwen35Config, savePath: string): Promise<undefined>;
2795
+
2796
+ /**
2797
+ * Create a random-init Qwen3.5 MoE model and save it to disk.
2798
+ *
2799
+ * Spawns a dedicated model thread whose init runs
2800
+ * [`create_random_qwen35_moe_checkpoint_sync`] (random-init inner + save);
2801
+ * the thread holds no state and is dropped once the promise resolves, so
2802
+ * the in-memory model is released as soon as the checkpoint has been
2803
+ * written. Used by TypeScript test fixtures that need an on-disk
2804
+ * checkpoint without keeping a NAPI model instance alive.
2805
+ */
2806
+ export declare function createRandomQwen35MoeCheckpoint(config: Qwen35MoeConfig, savePath: string): Promise<undefined>;
2807
+
2808
+ /**
2809
+ * Create a random-init Qwen3 model and save it to disk.
2810
+ *
2811
+ * Spawns a dedicated `ModelThread<Qwen3Cmd>` whose init builds a fresh
2812
+ * random-weight `Qwen3Inner` directly, then dispatches `Qwen3Cmd::SaveModel`
2813
+ * on that thread. The thread is dropped at the end of the promise, so the
2814
+ * in-memory model is released once the checkpoint has been written. Used by
2815
+ * TypeScript test fixtures that need an on-disk checkpoint without keeping a
2816
+ * NAPI model instance alive.
2817
+ */
2818
+ export declare function createRandomQwen3Checkpoint(config: Qwen3Config, savePath: string): Promise<undefined>;
2819
+
2177
2820
  /** Document element - either a table or paragraph */
2178
2821
  export interface DocumentElement {
2179
2822
  elementType: ElementType;
@@ -2208,6 +2851,8 @@ export declare const enum DType {
2208
2851
  BFloat16 = 3,
2209
2852
  Uint32 = 4,
2210
2853
  Uint8 = 5,
2854
+ /** Signed int8 — sym8 per-output-channel symmetric quantized weights. */
2855
+ Int8 = 6,
2211
2856
  }
2212
2857
 
2213
2858
  /** Document element type */
@@ -2290,14 +2935,186 @@ export interface FunctionDefinition {
2290
2935
  parameters?: FunctionParameters;
2291
2936
  }
2292
2937
 
2293
- /** Function parameters schema (JSON Schema subset) */
2294
- export interface FunctionParameters {
2295
- /** Type (usually "object") */
2296
- type: string;
2297
- /** JSON string of property definitions */
2298
- properties?: string;
2299
- /** List of required parameter names */
2300
- required?: Array<string>;
2938
+ /** Function parameters schema (JSON Schema subset) */
2939
+ export interface FunctionParameters {
2940
+ /** Type (usually "object") */
2941
+ type: string;
2942
+ /** JSON string of property definitions */
2943
+ properties?: string;
2944
+ /** List of required parameter names */
2945
+ required?: Array<string>;
2946
+ }
2947
+
2948
+ /**
2949
+ * Gemma 4 model configuration (dense variant).
2950
+ *
2951
+ * Supports E2B (2.3B), E4B (4.5B), and 31B dense models.
2952
+ * For MoE models (26B-A4B), use `Gemma4MoeConfig` from `gemma4_moe`.
2953
+ */
2954
+ export interface Gemma4Config {
2955
+ vocabSize: number;
2956
+ hiddenSize: number;
2957
+ numHiddenLayers: number;
2958
+ numAttentionHeads: number;
2959
+ numKeyValueHeads: number;
2960
+ headDim: number;
2961
+ intermediateSize: number;
2962
+ rmsNormEps: number;
2963
+ tieWordEmbeddings: boolean;
2964
+ maxPositionEmbeddings: number;
2965
+ slidingWindow: number;
2966
+ /**
2967
+ * Explicit per-layer attention type: "sliding_attention" or "full_attention".
2968
+ * Parsed from `text_config.layer_types` in the HuggingFace config.
2969
+ */
2970
+ layerTypes: Array<string>;
2971
+ /** RoPE theta for global (full) attention layers. */
2972
+ ropeTheta: number;
2973
+ /** RoPE theta for sliding (local) attention layers. */
2974
+ ropeLocalBaseFreq: number;
2975
+ /** Fraction of head_dim to rotate for global attention (0.25 = 25%). */
2976
+ partialRotaryFactor: number;
2977
+ /** KV heads for global layers. If None, uses num_key_value_heads. */
2978
+ globalNumKeyValueHeads?: number;
2979
+ /** Head dimension for global layers. If None, uses head_dim. */
2980
+ globalHeadDim?: number;
2981
+ attentionKEqV: boolean;
2982
+ /**
2983
+ * True for the unified multimodal Gemma 4 checkpoint
2984
+ * (`model_type == "gemma4_unified"` or
2985
+ * `architectures[0] == "Gemma4UnifiedForConditionalGeneration"`).
2986
+ * The text decoder is shared, but the unified checkpoint carries
2987
+ * vision/audio embedder weights that must be dropped in a text-only load.
2988
+ */
2989
+ isUnified: boolean;
2990
+ /**
2991
+ * `text_config.use_bidirectional_attention` from the unified checkpoint
2992
+ * (e.g. `"vision"`). Parsed for a stable struct surface; the text-only
2993
+ * decode path does not consume it.
2994
+ */
2995
+ useBidirectionalAttention?: string;
2996
+ finalLogitSoftcapping?: number;
2997
+ perLayerInputEmbeds: boolean;
2998
+ hiddenSizePerLayerInput?: number;
2999
+ vocabSizePerLayerInput?: number;
3000
+ padTokenId: number;
3001
+ eosTokenIds: Array<number>;
3002
+ bosTokenId: number;
3003
+ attentionBias: boolean;
3004
+ useDoubleWideMlp: boolean;
3005
+ numKvSharedLayers?: number;
3006
+ defaultTemperature?: number;
3007
+ defaultTopK?: number;
3008
+ defaultTopP?: number;
3009
+ enableMoeBlock: boolean;
3010
+ numExperts?: number;
3011
+ topKExperts?: number;
3012
+ moeIntermediateSize?: number;
3013
+ visionConfig?: Gemma4VisionConfig;
3014
+ /**
3015
+ * Encoder-free vision config for the unified multimodal checkpoint.
3016
+ * `Some` only when `is_unified` and the checkpoint carries a
3017
+ * `vision_config` sub-dict. Disjoint from `vision_config` (the SigLIP
3018
+ * path) — the unified vision embedder is built from this instead.
3019
+ */
3020
+ unifiedVisionConfig?: UnifiedVisionConfig;
3021
+ imageTokenId?: number;
3022
+ boiTokenId?: number;
3023
+ eoiTokenId?: number;
3024
+ visionSoftTokensPerImage?: number;
3025
+ /**
3026
+ * True when the checkpoint declares an `audio_config` sub-dict. Parallels
3027
+ * `unified_vision_config.is_some()`; gates the un-drop + load of
3028
+ * `embed_audio` weights and the audio merge path.
3029
+ */
3030
+ hasAudio: boolean;
3031
+ /**
3032
+ * Audio placeholder token id (258881). Each `<audio>` placeholder expands to
3033
+ * `boa + audio_token × n_frames + eoa`.
3034
+ */
3035
+ audioTokenId?: number;
3036
+ /** Begin-of-audio token id (256000), emitted before the audio token run. */
3037
+ boaTokenId?: number;
3038
+ /**
3039
+ * End-of-audio token id, parsed from the config's `eoa_token_index` (258883).
3040
+ * A real appended token (like `eoi`), despite the "index" name.
3041
+ */
3042
+ eoaTokenId?: number;
3043
+ /**
3044
+ * Raw audio samples per audio token (640 = 40 ms @ 16 kHz), from
3045
+ * `audio_config.audio_samples_per_token`. Frame size for the encoder-free
3046
+ * pad+reshape feature extractor.
3047
+ */
3048
+ audioSamplesPerToken?: number;
3049
+ /**
3050
+ * GPU memory budget for paged KV cache in megabytes.
3051
+ * Only used when `use_block_paged_cache` is true.
3052
+ * Default: auto-sized to cover `max_position_embeddings` for the
3053
+ * physical full-attention layers.
3054
+ */
3055
+ pagedCacheMemoryMb?: number | undefined;
3056
+ /**
3057
+ * Block size for paged attention (tokens per block).
3058
+ * Only used when `use_block_paged_cache` is true.
3059
+ * Default: 16.
3060
+ */
3061
+ pagedBlockSize?: number | undefined;
3062
+ /**
3063
+ * Use the new block-paged KV cache adapter (`PagedKVCacheAdapter`).
3064
+ *
3065
+ * When `Some(true)` or unset (the default), `Gemma4Inner` builds
3066
+ * model-independent KV-cache specs, groups them, and allocates a
3067
+ * `BlockAllocator` + `LayerKVPool` pair for physical full-attention
3068
+ * layers. Sliding-window layers still use `RotatingKVCache` until
3069
+ * true paged sliding-window groups are wired. KV-shared layers are
3070
+ * aliases: they reuse their anchor's cache slot and do not allocate
3071
+ * separate physical storage.
3072
+ *
3073
+ * Default: `true` (paged adapter on; opt-out via
3074
+ * `use_block_paged_cache: false` in `config.json` to use the flat
3075
+ * (non-paged) all-`Gemma4LayerCache` path instead). Parity between
3076
+ * the two paths is verified by
3077
+ * `crates/mlx-core/tests/gemma4_paged_vs_flat_parity.rs` against
3078
+ * real Gemma-4-E2B weights.
3079
+ */
3080
+ useBlockPagedCache?: boolean | undefined;
3081
+ }
3082
+
3083
+ /** Optional load-time settings for [`Gemma4Model::load`]. */
3084
+ export interface Gemma4LoadOptions {
3085
+ /**
3086
+ * Directory of a draft checkpoint (config.json + safetensors) to load
3087
+ * alongside the target model for speculative decoding — either a
3088
+ * DSpark draft or a Google assistant draft; the kind is probed from
3089
+ * the draft config.json. When omitted, `<model_path>/draft/` is loaded
3090
+ * automatically when present. Draft decoding runs only on the flat
3091
+ * KV-cache path: setting this while the model config explicitly enables
3092
+ * `use_block_paged_cache` is a hard load error, and an unset
3093
+ * `use_block_paged_cache` is forced to `false`.
3094
+ */
3095
+ draftModelPath?: string;
3096
+ }
3097
+
3098
+ /**
3099
+ * Vision encoder configuration for Gemma4 multimodal models.
3100
+ *
3101
+ * Parsed from the `vision_config` sub-dict in config.json.
3102
+ */
3103
+ export interface Gemma4VisionConfig {
3104
+ hiddenSize: number;
3105
+ intermediateSize: number;
3106
+ numHiddenLayers: number;
3107
+ numAttentionHeads: number;
3108
+ numKeyValueHeads: number;
3109
+ headDim: number;
3110
+ rmsNormEps: number;
3111
+ patchSize: number;
3112
+ positionEmbeddingSize: number;
3113
+ defaultOutputLength: number;
3114
+ poolingKernelSize: number;
3115
+ useClippedLinears: boolean;
3116
+ ropeTheta: number;
3117
+ standardize: boolean;
2301
3118
  }
2302
3119
 
2303
3120
  /** Result from generate_batch_for_training with all data needed for training */
@@ -2334,19 +3151,33 @@ export interface GenerationConfig {
2334
3151
  */
2335
3152
  repetitionContextSize?: number;
2336
3153
  /**
2337
- * Stop if same token repeats this many times consecutively (default: 16)
2338
- * Set to 0 to disable. Prevents OOM from degenerate repetitive generation.
3154
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
3155
+ * token that appeared at least once in context. Matches OpenAI API semantics.
3156
+ */
3157
+ presencePenalty?: number;
3158
+ /** Number of recent tokens to consider for presence penalty (default: 20) */
3159
+ presenceContextSize?: number;
3160
+ /**
3161
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
3162
+ * logits of each token in context. Matches OpenAI API semantics.
3163
+ */
3164
+ frequencyPenalty?: number;
3165
+ /** Number of recent tokens to consider for frequency penalty (default: 20) */
3166
+ frequencyContextSize?: number;
3167
+ /**
3168
+ * Stop if same token repeats this many times consecutively (default: 0 = disabled).
3169
+ * Opt in by setting a positive value to guard against degenerate repetitive generation.
2339
3170
  */
2340
3171
  maxConsecutiveTokens?: number;
2341
3172
  /**
2342
- * Stop if a pattern repeats this many times consecutively (default: 3)
2343
- * Set to 0 to disable. Detects patterns like "A B A B A B".
3173
+ * Stop if a pattern repeats this many times consecutively (default: 0 = disabled).
3174
+ * Opt in with a positive value to detect patterns like "A B A B A B".
2344
3175
  * Uses range-based detection: checks all pattern sizes from 2 to ngram_size.
2345
3176
  */
2346
3177
  maxNgramRepeats?: number;
2347
3178
  /**
2348
- * Maximum pattern size for repetition detection (default: 64)
2349
- * All pattern sizes from 2 up to this value are checked each decode step.
3179
+ * Maximum pattern size for repetition detection (default: 0 = disabled).
3180
+ * When enabled, all pattern sizes from 2 up to this value are checked each decode step.
2350
3181
  * Larger values catch long phrase-level repetition common in small models.
2351
3182
  */
2352
3183
  ngramSize?: number;
@@ -2361,29 +3192,6 @@ export interface GenerationConfig {
2361
3192
  * Set to 0 to disable chunking and process the entire prompt at once.
2362
3193
  */
2363
3194
  prefillStepSize?: number;
2364
- /**
2365
- * KV cache quantization bits (default: 16 = no quantization)
2366
- * - 16: Full precision (bfloat16/float16), no quantization
2367
- * - 8: 8-bit quantization, ~2x memory savings, minimal quality loss
2368
- * - 4: 4-bit quantization, ~4x memory savings, some quality degradation
2369
- *
2370
- * Quantized KV cache is useful for long sequences where memory becomes a bottleneck.
2371
- * Note: Adds dequantization overhead per forward pass.
2372
- */
2373
- kvCacheBits?: number;
2374
- /**
2375
- * KV cache quantization group size (default: 64)
2376
- * Number of elements per quantization group. Smaller groups = better accuracy
2377
- * but more overhead from storing scales/biases.
2378
- * Only used when kv_cache_bits is 4 or 8.
2379
- */
2380
- kvCacheGroupSize?: number;
2381
- /**
2382
- * Number of draft tokens to generate speculatively (default: 5)
2383
- * Only used when a draft model is provided for speculative decoding.
2384
- * Higher values can increase throughput but may reduce acceptance rate.
2385
- */
2386
- numDraftTokens?: number;
2387
3195
  }
2388
3196
 
2389
3197
  export interface GenerationProfile {
@@ -2407,6 +3215,24 @@ export interface GenerationProfile {
2407
3215
  timeToFirstTokenMs: number;
2408
3216
  /** Per-phase breakdown. */
2409
3217
  phases: Array<PhaseProfile>;
3218
+ /**
3219
+ * MTP speculative decode: mean accepted *draft* tokens per cycle
3220
+ * (excludes the always-verified token). Historical drafts-only metric.
3221
+ */
3222
+ mtpMeanAcceptedTokens?: number;
3223
+ /**
3224
+ * MTP speculative decode: mean *committed* tokens per cycle, INCLUDING
3225
+ * the always-verified token (`mtp_accepted_drafts_total / mtp_cycles
3226
+ * + 1.0`). mlx-vlm-comparable headline; equals mlx-vlm's
3227
+ * `(accepted_drafts + rounds) / rounds` (`common.py:247`).
3228
+ */
3229
+ mtpMeanAcceptedTokensTotal?: number;
3230
+ /** MTP speculative decode: per-draft-position acceptance rate. */
3231
+ mtpAcceptanceByPosition?: Array<number>;
3232
+ /** MTP speculative decode: number of draft+verify cycles executed. */
3233
+ mtpCycles?: number;
3234
+ /** MTP speculative decode: mean attempted draft depth per cycle. */
3235
+ mtpMeanDepth?: number;
2410
3236
  /** Memory snapshot before generation. */
2411
3237
  memoryBefore?: MemorySnapshot;
2412
3238
  /** Memory snapshot after generation. */
@@ -2433,8 +3259,8 @@ export interface GenerationWithToolCalls {
2433
3259
  toolCalls: Array<ToolCallRecord>;
2434
3260
  }
2435
3261
 
2436
- /** Get expected weight keys for PaddleOCR-VL model */
2437
- export declare function getExpectedWeightKeys(): Array<string>;
3262
+ /** Sample MLX's GPU memory counters. See [`GpuMemorySnapshot`]. */
3263
+ export declare function getMemorySnapshot(): GpuMemorySnapshot;
2438
3264
 
2439
3265
  /** Retrieve all collected profiling data as a `ProfilingSession`. */
2440
3266
  export declare function getProfilingData(): ProfilingSession;
@@ -2444,6 +3270,13 @@ export interface GgufConversionOptions {
2444
3270
  inputPath: string;
2445
3271
  /** Output directory for converted SafeTensors model */
2446
3272
  outputDir: string;
3273
+ /**
3274
+ * Optional directory containing the authoritative HuggingFace config and
3275
+ * tokenizer/processor assets. GGUF metadata is not rich enough to recreate
3276
+ * unified Gemma4 config fields such as head_dim, layer_types, vision, and
3277
+ * audio configuration exactly.
3278
+ */
3279
+ configSourceDir?: string;
2447
3280
  /** Target dtype: "float32", "float16", "bfloat16" (default: keep original) */
2448
3281
  dtype?: string;
2449
3282
  /** Enable verbose logging */
@@ -2477,6 +3310,17 @@ export interface GgufConversionOptions {
2477
3310
  * This makes the safetensors compatible with mlx-vlm.
2478
3311
  */
2479
3312
  vlmKeyPrefix?: boolean;
3313
+ /**
3314
+ * Upgrade quantization to micro-scaling FP (mxfp4 / mxfp8).
3315
+ * When true, applies after the recipe predicate: eligible 8-bit affine
3316
+ * decisions become mxfp8 and 4-bit become mxfp4. Kept affine (not upgraded):
3317
+ * affine-only loaders (lm_head, embed_tokens, router.proj,
3318
+ * embedding_projection) at their recipe bits, MoE router gates (8-bit affine),
3319
+ * and the recipe-pinned attention/GDN projections (o_proj / out_proj /
3320
+ * in_proj_a / in_proj_b, 8-bit affine). Requires `quant_mode = "affine"`.
3321
+ * Forces `group_size = 32` for upgraded layers.
3322
+ */
3323
+ quantMxfp?: boolean;
2480
3324
  }
2481
3325
 
2482
3326
  export interface GgufConversionResult {
@@ -2492,6 +3336,33 @@ export interface GpuInfo {
2492
3336
  architectureGen: number;
2493
3337
  }
2494
3338
 
3339
+ /**
3340
+ * Snapshot of MLX's GPU memory counters at this instant. All values
3341
+ * are in bytes. On Apple Silicon, GPU and CPU share unified memory,
3342
+ * so these are NOT a separate "VRAM" pool — they reflect MLX's own
3343
+ * tracking of `StorageModePrivate` Metal buffers (model weights,
3344
+ * `LayerKVPool`, transient intermediate tensors) attributed to the
3345
+ * MLX runtime in this process.
3346
+ *
3347
+ * Useful for live observability during long-running sessions:
3348
+ *
3349
+ * ```js
3350
+ * const { getMemorySnapshot } = require('@mlx-node/core');
3351
+ * setInterval(() => {
3352
+ * const m = getMemorySnapshot();
3353
+ * console.log(`active=${(m.activeBytes/1e9).toFixed(2)}GB peak=${(m.peakBytes/1e9).toFixed(2)}GB cache=${(m.cacheBytes/1e9).toFixed(2)}GB`);
3354
+ * }, 1000);
3355
+ * ```
3356
+ */
3357
+ export interface GpuMemorySnapshot {
3358
+ /** Current actively-used GPU buffer bytes (excludes cache pool). */
3359
+ activeBytes: number;
3360
+ /** Peak GPU buffer bytes since the last `resetPeakMemory()` call. */
3361
+ peakBytes: number;
3362
+ /** Bytes held in MLX's caching allocator (released by `clearCache`). */
3363
+ cacheBytes: number;
3364
+ }
3365
+
2495
3366
  /** Configuration for the GRPO training engine */
2496
3367
  export interface GrpoEngineConfig {
2497
3368
  /** Learning rate (default: 1e-6) */
@@ -2526,6 +3397,16 @@ export interface GrpoEngineConfig {
2526
3397
  topK?: number;
2527
3398
  /** Repetition penalty (default: 1.1) */
2528
3399
  repetitionPenalty?: number;
3400
+ /**
3401
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
3402
+ * token that appeared at least once in context.
3403
+ */
3404
+ presencePenalty?: number;
3405
+ /**
3406
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
3407
+ * logits of each token in context.
3408
+ */
3409
+ frequencyPenalty?: number;
2529
3410
  /**
2530
3411
  * Maximum allowed NaN gradient occurrences before stopping training (default: 100)
2531
3412
  * When exceeded, training will stop with an error to prevent model corruption.
@@ -2659,6 +3540,46 @@ export interface GrpoLossConfig {
2659
3540
  vocabChunkSize?: number;
2660
3541
  }
2661
3542
 
3543
+ /**
3544
+ * Configuration for Harrier embedding model (Qwen3 backbone).
3545
+ *
3546
+ * Only includes backbone dimensions needed for encoding.
3547
+ * No generation fields (token IDs, paged attention, etc.).
3548
+ */
3549
+ export interface HarrierConfig {
3550
+ hiddenSize: number;
3551
+ numLayers: number;
3552
+ numHeads: number;
3553
+ numKeyValueHeads: number;
3554
+ intermediateSize: number;
3555
+ rmsNormEps: number;
3556
+ ropeTheta: number;
3557
+ maxPositionEmbeddings: number;
3558
+ headDim: number;
3559
+ /**
3560
+ * Qwen3 always uses QK normalization. Omit to use the default (true).
3561
+ * Explicitly passing false is allowed but produces a model incompatible
3562
+ * with published Harrier weights.
3563
+ */
3564
+ useQkNorm?: boolean;
3565
+ vocabSize: number;
3566
+ }
3567
+
3568
+ /** InternViT vision encoder configuration */
3569
+ export interface InternVisionConfig {
3570
+ hiddenSize: number;
3571
+ intermediateSize: number;
3572
+ numHiddenLayers: number;
3573
+ numAttentionHeads: number;
3574
+ numChannels: number;
3575
+ imageSize: number;
3576
+ patchSize: number;
3577
+ layerNormEps: number;
3578
+ qkvBias: boolean;
3579
+ /** Drop path rate (inference only, always 0) */
3580
+ dropPathRate: number;
3581
+ }
3582
+
2662
3583
  /** Check whether profiling is currently enabled. */
2663
3584
  export declare function isProfilingEnabled(): boolean;
2664
3585
 
@@ -2676,6 +3597,98 @@ export interface LayoutElement {
2676
3597
  order: number;
2677
3598
  }
2678
3599
 
3600
+ /**
3601
+ * LFM2 model configuration.
3602
+ *
3603
+ * Supports LiquidAI's LFM2.5 hybrid conv+attention architecture.
3604
+ * 16 layers total: 10 conv + 6 full_attention, defined by `layer_types` array.
3605
+ */
3606
+ export interface Lfm2Config {
3607
+ vocabSize: number;
3608
+ hiddenSize: number;
3609
+ numHiddenLayers: number;
3610
+ numAttentionHeads: number;
3611
+ numKeyValueHeads: number;
3612
+ maxPositionEmbeddings: number;
3613
+ normEps: number;
3614
+ convBias: boolean;
3615
+ convLCache: number;
3616
+ blockDim: number;
3617
+ blockFfDim: number;
3618
+ blockMultipleOf: number;
3619
+ blockFfnDimMultiplier: number;
3620
+ blockAutoAdjustFfDim: boolean;
3621
+ ropeTheta: number;
3622
+ layerTypes: Array<string>;
3623
+ tieEmbedding: boolean;
3624
+ eosTokenId: number;
3625
+ bosTokenId: number;
3626
+ padTokenId: number;
3627
+ /**
3628
+ * GPU memory budget for paged KV cache in megabytes.
3629
+ * Only used when `use_block_paged_cache` is true.
3630
+ * Default: 2048 (2GB).
3631
+ */
3632
+ pagedCacheMemoryMb?: number | undefined;
3633
+ /**
3634
+ * Block size for paged attention (tokens per block).
3635
+ * Only used when `use_block_paged_cache` is true.
3636
+ * Default: 16.
3637
+ */
3638
+ pagedBlockSize?: number | undefined;
3639
+ /**
3640
+ * Use the new block-paged KV cache adapter (`PagedKVCacheAdapter`).
3641
+ *
3642
+ * Default: `true` since 2026-04-28 (parity-verified via
3643
+ * `crates/mlx-core/tests/lfm2_paged_vs_flat_parity.rs` against real
3644
+ * LFM2.5-1.2B weights: byte-equal greedy decode + prefix-reuse
3645
+ * byte-equal at BF16). Wired through
3646
+ * `Lfm2DecoderLayer::forward_paged_or_flat`.
3647
+ *
3648
+ * Per-layer routing: LFM2's hybrid architecture means only
3649
+ * `full_attention` layers go through the paged adapter; conv layers
3650
+ * stay on the existing flat `Lfm2LayerCache::Conv(ArraysCache)`
3651
+ * storage regardless of this flag. The `LayerKVPool` is sized to
3652
+ * the count of `full_attention` layers and indexed by
3653
+ * attention-ordinal (via `config.full_attn_idxs()`), not by absolute
3654
+ * layer index.
3655
+ *
3656
+ * Opt out with `use_block_paged_cache: Some(false)` to revert to the
3657
+ * fully flat `Lfm2LayerCache` path on all layers.
3658
+ */
3659
+ useBlockPagedCache?: boolean | undefined;
3660
+ /**
3661
+ * MLP intermediate size for the DENSE-in-MoE layers (`layer_idx <
3662
+ * num_dense_layers`). Used DIRECTLY (no 2/3 `computed_ff_dim()` shrink).
3663
+ * Only present on MoE checkpoints.
3664
+ */
3665
+ intermediateSize?: number | undefined;
3666
+ /** Per-expert MLP intermediate size for the sparse MoE layers. */
3667
+ moeIntermediateSize?: number | undefined;
3668
+ /** Total number of routed experts. */
3669
+ numExperts?: number | undefined;
3670
+ /** Top-k experts selected per token. */
3671
+ numExpertsPerTok?: number | undefined;
3672
+ /** Number of leading DENSE layers before MoE layers begin. */
3673
+ numDenseLayers?: number | undefined;
3674
+ /**
3675
+ * Renormalize the top-k routing weights to sum to 1 (`/(sum+1e-20)`).
3676
+ *
3677
+ * `Option<bool>` so TS callers may omit it (napi renders bare `bool` as
3678
+ * required). Absent (None) is read as `true` everywhere via
3679
+ * `.unwrap_or(true)`, matching the prior `default = "default_true"`.
3680
+ */
3681
+ normTopkProb?: boolean | undefined;
3682
+ /**
3683
+ * Add the learned per-expert bias to the post-softmax gates BEFORE top-k.
3684
+ *
3685
+ * `Option<bool>` so TS callers may omit it (napi renders bare `bool` as
3686
+ * required). Absent (None) is read as `true` everywhere via
3687
+ * `.unwrap_or(true)`, matching the prior `default = "default_true"`.
3688
+ */
3689
+ useExpertBias?: boolean | undefined;
3690
+ }
3691
+
2679
3692
  export interface MemorySnapshot {
2680
3693
  /** Active (non-cached) memory in bytes. */
2681
3694
  activeBytes: number;
@@ -2685,6 +3698,28 @@ export interface MemorySnapshot {
2685
3698
  cacheBytes: number;
2686
3699
  }
2687
3700
 
3701
+ /**
3702
+ * Return a snapshot of the MLX allocator's memory counters. Primarily
3703
+ * useful for dashboards and for debugging the `MLX_CACHE_LIMIT_GB`
3704
+ * override. Read-only — does not mutate allocator state.
3705
+ */
3706
+ export declare function memoryStats(): MemoryStats;
3707
+
3708
+ /**
3709
+ * Snapshot of the MLX Metal allocator's memory state. All values are in
3710
+ * bytes and returned as `f64` to avoid forcing BigInt round-trips in JS.
3711
+ */
3712
+ export interface MemoryStats {
3713
+ /** Actively-used memory (excludes the cached free-pool). */
3714
+ active: number;
3715
+ /** Peak memory usage since load / the last `resetPeakMemory`. */
3716
+ peak: number;
3717
+ /** Cache / free-pool memory currently held by the allocator. */
3718
+ cache: number;
3719
+ /** Metal `max_recommended_working_set_size` snapshot (0 on non-Metal). */
3720
+ wiredLimit: number;
3721
+ }
3722
+
2688
3723
  /** Full model configuration */
2689
3724
  export interface ModelConfig {
2690
3725
  visionConfig: VisionConfig;
@@ -2728,56 +3763,6 @@ export interface OutputStoreConfig {
2728
3763
  localPath: string;
2729
3764
  }
2730
3765
 
2731
- /** Paged attention memory statistics (NAPI-compatible) */
2732
- export interface PagedCacheStats {
2733
- /** Total number of blocks in the pool */
2734
- totalBlocks: number;
2735
- /** Number of free blocks */
2736
- freeBlocks: number;
2737
- /** Number of allocated blocks */
2738
- allocatedBlocks: number;
2739
- /** Total memory in MB */
2740
- totalMemoryMb: number;
2741
- /** Used memory in MB */
2742
- usedMemoryMb: number;
2743
- /** Utilization percentage */
2744
- utilizationPercent: number;
2745
- }
2746
-
2747
- /** A completed sequence from paged generation */
2748
- export interface PagedCompletedSequence {
2749
- /** Original request ID */
2750
- requestId: string;
2751
- /** All generated tokens (excluding prompt) */
2752
- tokens: Array<number>;
2753
- /** Reason for completion ("stop", "length", "repetition", "tool_calls") */
2754
- finishReason: string;
2755
- }
2756
-
2757
- /** Result of a paged generation step */
2758
- export interface PagedGenerationStep {
2759
- /** Token outputs for each sequence in the batch */
2760
- outputs: Array<PagedTokenOutput>;
2761
- /** Number of sequences that were in prefill phase */
2762
- numPrefill: number;
2763
- /** Number of sequences that were in decode phase */
2764
- numDecode: number;
2765
- }
2766
-
2767
- /** Output from a single token generation step in paged attention */
2768
- export interface PagedTokenOutput {
2769
- /** Sequence ID in the scheduler */
2770
- seqId: number;
2771
- /** Request ID for this sequence */
2772
- requestId: string;
2773
- /** Generated token ID */
2774
- token: number;
2775
- /** Log probability of the token (f64 for NAPI compatibility) */
2776
- logprob: number;
2777
- /** Whether this sequence has finished */
2778
- isFinished: boolean;
2779
- }
2780
-
2781
3766
  /** A text paragraph */
2782
3767
  export interface Paragraph {
2783
3768
  content: string;
@@ -2859,6 +3844,52 @@ export interface PerformanceMetrics {
2859
3844
  * Excludes the first token (counted as prefill).
2860
3845
  */
2861
3846
  decodeTokensPerSecond: number;
3847
+ /**
3848
+ * MTP speculative decode: mean accepted *draft* tokens per cycle
3849
+ * (range `[0, depth]`). EXCLUDES the always-verified token each cycle
3850
+ * commits. `None` on plain autoregressive runs where no MTP cycle
3851
+ * executed. This is the historical drafts-only metric; for the
3852
+ * mlx-vlm-comparable headline see [`Self::mtp_mean_accepted_tokens_total`].
3853
+ */
3854
+ mtpMeanAcceptedTokens?: number;
3855
+ /**
3856
+ * MTP speculative decode: mean *committed* tokens per cycle, INCLUDING
3857
+ * the single always-verified token each cycle emits — i.e.
3858
+ * `mtp_accepted_drafts_total / mtp_cycles + 1.0`. This is the
3859
+ * mlx-vlm-comparable headline accept rate: it equals mlx-vlm's
3860
+ * `mean_accepted_tokens = (accepted_drafts + rounds) / rounds`
3861
+ * (`mlx-vlm/mlx_vlm/speculative/common.py:247`), where our
3862
+ * `mtp_cycles` is the 1:1 analog of mlx-vlm's `rounds` (one
3863
+ * draft+verify iteration; `record_mtp_cycle` is called exactly once
3864
+ * per cycle). The per-cycle `+1.0` matches mlx-vlm's `+rounds`
3865
+ * assumption — every round commits exactly one verified token
3866
+ * (the residual on partial-accept, the bonus on full-accept), even
3867
+ * when the final cycle's tail is EOS/length-truncated downstream
3868
+ * (mlx-vlm makes the same assumption: it appends to `accept_lens`
3869
+ * once per round regardless of truncation). `None` on plain
3870
+ * autoregressive runs.
3871
+ */
3872
+ mtpMeanAcceptedTokensTotal?: number;
3873
+ /**
3874
+ * MTP speculative decode: per-draft-position acceptance rate
3875
+ * (index = draft position). `None` on plain autoregressive runs.
3876
+ */
3877
+ mtpAcceptanceByPosition?: Array<number>;
3878
+ /**
3879
+ * MTP speculative decode: number of draft+verify cycles executed.
3880
+ * `None` on plain autoregressive runs.
3881
+ */
3882
+ mtpCycles?: number;
3883
+ /**
3884
+ * MTP speculative decode: mean attempted draft depth per cycle.
3885
+ * `None` on plain autoregressive runs.
3886
+ */
3887
+ mtpMeanDepth?: number;
3888
+ /**
3889
+ * Optional decode phase breakdown. Present when decode profiling
3890
+ * is enabled via `MLX_PROFILE_DECODE=1` or `setProfilingEnabled(true)`.
3891
+ */
3892
+ profilePhases?: Array<PhaseProfile>;
2862
3893
  }
2863
3894
 
2864
3895
  export interface PhaseProfile {
@@ -2872,6 +3903,76 @@ export interface PhaseProfile {
2872
3903
  count: number;
2873
3904
  }
2874
3905
 
3906
+ /**
3907
+ * Per-call Viterbi calibration overrides.
3908
+ *
3909
+ * Any field set to `Some(_)` overrides the corresponding bias from the
3910
+ * model's default calibration (loaded from `viterbi_calibration.json`
3911
+ * at load time). Missing fields fall back to the default.
3912
+ */
3913
+ export interface PrivacyCalibration {
3914
+ transitionBiasBackgroundStay?: number;
3915
+ transitionBiasBackgroundToStart?: number;
3916
+ transitionBiasEndToBackground?: number;
3917
+ transitionBiasEndToStart?: number;
3918
+ transitionBiasInsideToContinue?: number;
3919
+ transitionBiasInsideToEnd?: number;
3920
+ }
3921
+
3922
+ /**
3923
+ * Options for [`PrivacyFilterModelJs::classify`].
3924
+ *
3925
+ * - `threshold` (default `0.5`): minimum mean per-token probability for
3926
+ * an extracted span to be returned.
3927
+ * - `calibration`: per-call overrides on top of the checkpoint default.
3928
+ * - `return_tokens` (default `false`): when `true`, the result includes
3929
+ * a `tokens` array with one entry per input token.
3930
+ */
3931
+ export interface PrivacyClassifyOptions {
3932
+ threshold?: number;
3933
+ calibration?: PrivacyCalibration;
3934
+ returnTokens?: boolean;
3935
+ }
3936
+
3937
+ /** Result of [`PrivacyFilterModelJs::classify`]. */
3938
+ export interface PrivacyClassifyResult {
3939
+ entities: Array<PrivacyEntity>;
3940
+ tokens?: Array<PrivacyToken>;
3941
+ }
3942
+
3943
+ /**
3944
+ * A privacy entity detected by [`PrivacyFilterModelJs::classify`].
3945
+ *
3946
+ * `start`/`end` are byte offsets into the input string (Hugging Face
3947
+ * `tokenizers` convention). `label` is the privacy class without the
3948
+ * BIOES prefix (e.g. `"private_email"`). `score` is the mean — across
3949
+ * the span's tokens — of the softmax probability of the Viterbi-emitted
3950
+ * tag at each token.
3951
+ */
3952
+ export interface PrivacyEntity {
3953
+ label: string;
3954
+ start: number;
3955
+ end: number;
3956
+ score: number;
3957
+ text: string;
3958
+ }
3959
+
3960
+ /**
3961
+ * Per-token output emitted when [`PrivacyClassifyOptions::return_tokens`]
3962
+ * is `true`. `tag` is the full BIOES tag (`"O"` or `"B-..."`/`"I-..."`/
3963
+ * `"E-..."`/`"S-..."`) chosen by the Viterbi decoder. `score` is the
3964
+ * softmax probability of that emitted tag at this token, so `tag` and
3965
+ * `score` always share decoders (at boundary tokens the Viterbi tag can
3966
+ * differ from the local argmax).
3967
+ */
3968
+ export interface PrivacyToken {
3969
+ text: string;
3970
+ tag: string;
3971
+ score: number;
3972
+ start: number;
3973
+ end: number;
3974
+ }
3975
+
2875
3976
  export interface ProfilingSession {
2876
3977
  /** GPU hardware info. */
2877
3978
  gpuInfo: GpuInfo;
@@ -2896,6 +3997,55 @@ export interface ProfilingSummary {
2896
3997
  avgPrefillMs: number;
2897
3998
  }
2898
3999
 
4000
+ /** Full Qianfan-OCR model configuration */
4001
+ export interface QianfanOcrConfig {
4002
+ visionConfig: InternVisionConfig;
4003
+ llmConfig: Qwen3LmConfig;
4004
+ modelType: string;
4005
+ imgContextTokenId: number;
4006
+ /** `<img>` token ID */
4007
+ imgStartTokenId: number;
4008
+ /** `</img>` token ID */
4009
+ imgEndTokenId: number;
4010
+ /** `<|im_end|>` token ID */
4011
+ eosTokenId: number;
4012
+ /** Which vision encoder layer to extract features from */
4013
+ selectLayer: number;
4014
+ /** Pixel shuffle version */
4015
+ psVersion: string;
4016
+ downsampleRatio: number;
4017
+ dynamicImageSize: boolean;
4018
+ useThumbnail: boolean;
4019
+ maxDynamicPatch: number;
4020
+ minDynamicPatch: number;
4021
+ }
4022
+
4023
+ /** Microbench result for the production quantized qmv dispatch path. */
4024
+ export interface QmvQuantizedMicrobenchResult {
4025
+ /** Median `quantized_matmul` wall-clock per call, in nanoseconds. */
4026
+ medianNs: number;
4027
+ /** A tiny materialized checksum of the final output, used to keep the call live. */
4028
+ checksum: number;
4029
+ }
4030
+
4031
+ /**
4032
+ * Run the production quantized-qmv microbench in the current process.
4033
+ *
4034
+ * To compare `MLX_MTP_SMALL_M_QMV=0` versus `1`, call this from separate
4035
+ * processes. MLX caches the env-backed dispatch predicate statically.
4036
+ */
4037
+ export declare function quantizedQmvMicrobench(
4038
+ k: number,
4039
+ n: number,
4040
+ m: number,
4041
+ groupSize: number,
4042
+ bits: number,
4043
+ mode: string,
4044
+ dtype: DType,
4045
+ warmup?: number | undefined | null,
4046
+ iters?: number | undefined | null,
4047
+ ): QmvQuantizedMicrobenchResult;
4048
+
2899
4049
  /**
2900
4050
  * Qwen3.5 model configuration (dense variant).
2901
4051
  *
@@ -2924,6 +4074,72 @@ export interface Qwen35Config {
2924
4074
  fullAttentionInterval: number;
2925
4075
  partialRotaryFactor: number;
2926
4076
  ropeTheta: number;
4077
+ /**
4078
+ * GPU memory budget for paged KV cache in megabytes.
4079
+ * Only used when `use_block_paged_cache` is true.
4080
+ * Default: automatically sized for one full-context sequence.
4081
+ */
4082
+ pagedCacheMemoryMb?: number | undefined;
4083
+ /**
4084
+ * Block size for paged attention (tokens per block).
4085
+ * Only used when `use_block_paged_cache` is true.
4086
+ * Default: 16.
4087
+ */
4088
+ pagedBlockSize?: number | undefined;
4089
+ /**
4090
+ * Use the block-paged KV cache adapter (`PagedKVCacheAdapter`) for
4091
+ * full-attention layers.
4092
+ *
4093
+ * **OPT-IN — experimental.** When `Some(true)`, `Qwen35Inner`
4094
+ * allocates a `BlockAllocator` + `LayerKVPool` pair sized for the
4095
+ * model's full-attention layer count and constructs a
4096
+ * `PagedKVCacheAdapter`. The chat-session forward dispatch routes
4097
+ * full-attention layers through this adapter while linear-attention
4098
+ * (GatedDeltaNet / GDN) layers continue to use the existing
4099
+ * `Qwen3_5LayerCache::Linear(ArraysCache)` path with no
4100
+ * cross-request prefix reuse — vLLM's `MambaManager`-style "no
4101
+ * prefix reuse for recurrent layers" stance.
4102
+ *
4103
+ * **Paged vs flat eager**: this flag selects the eager paged decode
4104
+ * over the eager flat decode. When `Some(true)`, full-attention
4105
+ * layers run through the paged adapter (cross-request prefix reuse);
4106
+ * when unset, they run the eager flat decode. Either way the forward
4107
+ * is pure-Rust eager.
4108
+ *
4109
+ * **VLM under paged**: a VLM checkpoint defaults this flag ON at load, so
4110
+ * dense image turns ONLY run on the paged-vision core. A fresh single-turn
4111
+ * image-bearing prompt prefills through the paged adapter (M-RoPE positions
4112
+ * feed the rotary; the merged vision embeddings feed the forward) and
4113
+ * decodes plain AR — MTP weights are ignored on image turns. Warm
4114
+ * image-bearing session continues / cache-hit reuse are still rejected at
4115
+ * runtime (the GDN two-pass warm prefix is not byte-exact). A vision turn
4116
+ * that reaches a None adapter (explicit `Some(false)`, non-Metal build, or
4117
+ * a sym8 checkpoint) errors at dispatch.
4118
+ *
4119
+ * Default: `None` for text-only checkpoints (eager flat decode);
4120
+ * `Some(true)` for VLM checkpoints (block-paged, set in `parse_config`).
4121
+ */
4122
+ useBlockPagedCache?: boolean | undefined;
4123
+ /**
4124
+ * Number of MTP (Multi-Token Prediction) head layers shipped with the
4125
+ * checkpoint. Populated from `mtp_num_hidden_layers` /
4126
+ * `num_nextn_predict_layers` in `config.json`. `0` means the
4127
+ * checkpoint has no MTP heads and the speculative-decode path is
4128
+ * unavailable.
4129
+ */
4130
+ nMtpLayers: number;
4131
+ }
4132
+
4133
+ /**
4134
+ * Trained and physically available active-context limits for one loaded
4135
+ * Qwen3.5 model. Values are snapshots because the physical pool is fixed for
4136
+ * the lifetime of the resident model.
4137
+ */
4138
+ export interface Qwen35ContextLimits {
4139
+ trainedWindowTokens: number;
4140
+ effectiveWindowTokens: number;
4141
+ pagedBlockCapacity: number;
4142
+ pagedBlockSize: number;
2927
4143
  }
2928
4144
 
2929
4145
  /** Generation configuration for Qwen3.5 */
@@ -2978,6 +4194,47 @@ export interface Qwen35MoeConfig {
2978
4194
  moeIntermediateSize?: number | undefined;
2979
4195
  normTopkProb: boolean;
2980
4196
  mlpOnlyLayers?: number[] | undefined;
4197
+ /**
4198
+ * GPU memory budget for paged KV cache in megabytes.
4199
+ * Only used when `use_block_paged_cache` is true.
4200
+ * Default: automatically sized for one full-context sequence.
4201
+ */
4202
+ pagedCacheMemoryMb?: number | undefined;
4203
+ /**
4204
+ * Block size for paged attention (tokens per block).
4205
+ * Only used when `use_block_paged_cache` is true.
4206
+ * Default: 16.
4207
+ */
4208
+ pagedBlockSize?: number | undefined;
4209
+ /**
4210
+ * Use the block-paged KV cache adapter for full-attention layers.
4211
+ *
4212
+ * **OPT-IN — experimental.** Same semantics as the dense
4213
+ * `Qwen3_5Config::use_block_paged_cache` field. Selects the eager
4214
+ * paged decode over the eager flat decode: routes full-attention
4215
+ * layers through `PagedKVCacheAdapter` (cross-request prefix reuse);
4216
+ * GDN linear-attention layers stay on `Qwen3_5LayerCache::Linear`
4217
+ * either way. When disabled, full-attention layers run the eager flat
4218
+ * decode instead.
4219
+ *
4220
+ * **VLM under paged**: a VLM checkpoint loads with this flag set, and a
4221
+ * fresh single-turn image-bearing prompt prefills through the paged
4222
+ * adapter (M-RoPE positions feed the rotary; the merged vision embeddings
4223
+ * feed the forward). Image-bearing MTP turns are still rejected at
4224
+ * runtime; warm image-bearing session continues / cache-hit reuse are
4225
+ * cold-started (no warm GDN two-pass prefix).
4226
+ *
4227
+ * Default: `None` / `false`.
4228
+ */
4229
+ useBlockPagedCache?: boolean | undefined;
4230
+ /**
4231
+ * Number of MTP (Multi-Token Prediction) head layers shipped with
4232
+ * the checkpoint. Populated from `mtp_num_hidden_layers` /
4233
+ * `num_nextn_predict_layers` in `config.json`. `0` means the
4234
+ * checkpoint has no MTP heads and the speculative-decode path is
4235
+ * unavailable.
4236
+ */
4237
+ nMtpLayers: number;
2981
4238
  }
2982
4239
 
2983
4240
  /** Generation configuration for Qwen3.5 MoE */
@@ -3014,29 +4271,45 @@ export interface Qwen3Config {
3014
4271
  padTokenId: number;
3015
4272
  eosTokenId: number;
3016
4273
  bosTokenId: number;
3017
- /**
3018
- * Enable paged attention for memory-efficient inference.
3019
- * Default: false (use standard KVCache)
3020
- */
3021
- usePagedAttention?: boolean | undefined;
3022
4274
  /**
3023
4275
  * GPU memory budget for paged KV cache in megabytes.
3024
- * Only used when use_paged_attention is true.
3025
4276
  * Default: 2048 (2GB)
3026
4277
  */
3027
4278
  pagedCacheMemoryMb?: number | undefined;
3028
4279
  /**
3029
4280
  * Block size for paged attention (tokens per block).
3030
- * Only used when use_paged_attention is true.
3031
4281
  * Default: 16
3032
4282
  */
3033
4283
  pagedBlockSize?: number | undefined;
3034
4284
  /**
3035
- * Use FP8 cache for 2x memory reduction (experimental).
3036
- * Only used when use_paged_attention is true.
3037
- * Default: false
4285
+ * Use the block-paged KV cache adapter (`PagedKVCacheAdapter`).
4286
+ *
4287
+ * When `Some(true)` (the default for Qwen3), `Qwen3Inner` allocates a
4288
+ * `BlockAllocator` + `LayerKVPool` pair and constructs a
4289
+ * `PagedKVCacheAdapter` for cross-request KV prefix reuse (vLLM-style
4290
+ * block-paged storage with refcounted prefix caching). When
4291
+ * `Some(false)`, the flat (non-paged) `Vec<KVCache>` cache path is
4292
+ * used instead.
4293
+ *
4294
+ * Default: true.
3038
4295
  */
3039
- useFp8Cache?: boolean | undefined;
4296
+ useBlockPagedCache?: boolean | undefined;
4297
+ }
4298
+
4299
+ /** Qwen3 language model configuration */
4300
+ export interface Qwen3LmConfig {
4301
+ hiddenSize: number;
4302
+ numHiddenLayers: number;
4303
+ intermediateSize: number;
4304
+ numAttentionHeads: number;
4305
+ numKeyValueHeads: number;
4306
+ headDim: number;
4307
+ rmsNormEps: number;
4308
+ vocabSize: number;
4309
+ maxPositionEmbeddings: number;
4310
+ ropeTheta: number;
4311
+ useQkNorm: boolean;
4312
+ tieWordEmbeddings: boolean;
3040
4313
  }
3041
4314
 
3042
4315
  /** Result of text recognition. */
@@ -3047,6 +4320,14 @@ export interface RecResult {
3047
4320
  score: number;
3048
4321
  }
3049
4322
 
4323
+ /**
4324
+ * Reset MLX's peak-memory counter to the current active level.
4325
+ * Useful for measuring per-request peak memory in a long-running
4326
+ * process — call before a request, sample
4327
+ * `getMemorySnapshot().peakBytes` after.
4328
+ */
4329
+ export declare function resetPeakMemory(): void;
4330
+
3050
4331
  /** Clear all collected profiling data and reset session timer. */
3051
4332
  export declare function resetProfilingData(): void;
3052
4333
 
@@ -3136,22 +4417,6 @@ export interface SamplingConfig {
3136
4417
  */
3137
4418
  export declare function saveToXlsx(text: string, filePath: string): void;
3138
4419
 
3139
- /** Scheduler statistics (NAPI-compatible) */
3140
- export interface SchedulerStatsNapi {
3141
- /** Number of requests waiting to be scheduled */
3142
- numWaiting: number;
3143
- /** Number of sequences currently running */
3144
- numRunning: number;
3145
- /** Number of completed sequences */
3146
- numCompleted: number;
3147
- /** Number of sequences in prefill phase */
3148
- numPrefill: number;
3149
- /** Number of sequences in decode phase */
3150
- numDecode: number;
3151
- /** Total tokens across all running sequences */
3152
- totalRunningTokens: number;
3153
- }
3154
-
3155
4420
  /** Enable or disable profiling globally. */
3156
4421
  export declare function setProfilingEnabled(enabled: boolean): void;
3157
4422
 
@@ -3272,6 +4537,22 @@ export interface StepSummary {
3272
4537
  lengthCount: number;
3273
4538
  }
3274
4539
 
4540
+ /** A stored response record exposed to JavaScript. */
4541
+ export interface StoredResponseRecord {
4542
+ id: string;
4543
+ createdAt: number;
4544
+ model: string;
4545
+ status: string;
4546
+ instructions?: string;
4547
+ inputJson: string;
4548
+ outputJson: string;
4549
+ outputText: string;
4550
+ usageJson: string;
4551
+ previousResponseId?: string;
4552
+ configJson?: string;
4553
+ expiresAt?: number;
4554
+ }
4555
+
3275
4556
  /** A table structure */
3276
4557
  export interface Table {
3277
4558
  rows: Array<TableRow>;
@@ -3418,6 +4699,35 @@ export interface TrainStepResultWithOutputs {
3418
4699
  completionLengths: Array<number>;
3419
4700
  }
3420
4701
 
4702
+ /**
4703
+ * Encoder-free vision configuration for the Gemma 4 unified multimodal model.
4704
+ *
4705
+ * Parsed from the `vision_config` sub-dict of a `gemma4_unified` checkpoint
4706
+ * (`model_type == "gemma4_unified_vision"`). This is a different shape from the
4707
+ * SigLIP-style [`super::vision_config::Gemma4VisionConfig`] used by the dense
4708
+ * gemma4 family: the unified vision path has no transformer encoder, only a
4709
+ * patch embedder (LayerNorm + Linear + 2D positional embedding) feeding the
4710
+ * multimodal projection.
4711
+ */
4712
+ export interface UnifiedVisionConfig {
4713
+ /** Pixel side length of a single image patch (48 = patch_size 16 × pooling 3). */
4714
+ modelPatchSize: number;
4715
+ /** Embedding width inside the vision embedder (3840, == text hidden_size). */
4716
+ mmEmbedDim: number;
4717
+ /** Number of rows in the 2D positional-embedding table (1120). */
4718
+ mmPosembSize: number;
4719
+ /** Maximum soft tokens (patches) per image after resize (280). */
4720
+ numSoftTokens: number;
4721
+ /** Output projection width of `embed_vision` (3840, == text hidden_size). */
4722
+ outputProjDims: number;
4723
+ /** Pixel-grid patch size used by the resize math (16). */
4724
+ patchSize: number;
4725
+ /** Pooling kernel size used by the resize math (3). */
4726
+ poolingKernelSize: number;
4727
+ /** Epsilon for the embedder LayerNorms and the projection RMSNorm. */
4728
+ rmsNormEps: number;
4729
+ }
4730
+
3421
4731
  /** Result from document unwarping. */
3422
4732
  export interface UnwarpResult {
3423
4733
  /** Unwarped image as PNG bytes */
@@ -3462,6 +4772,20 @@ export interface VlmChatConfig {
3462
4772
  topP?: number;
3463
4773
  /** Repetition penalty (default: 1.5) */
3464
4774
  repetitionPenalty?: number;
4775
+ /**
4776
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
4777
+ * token that appeared at least once in context. Matches OpenAI API semantics.
4778
+ */
4779
+ presencePenalty?: number;
4780
+ /** Number of recent tokens to consider for presence penalty (default: 20) */
4781
+ presenceContextSize?: number;
4782
+ /**
4783
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
4784
+ * logits of each token in context. Matches OpenAI API semantics.
4785
+ */
4786
+ frequencyPenalty?: number;
4787
+ /** Number of recent tokens to consider for frequency penalty (default: 20) */
4788
+ frequencyContextSize?: number;
3465
4789
  /** Whether to return log probabilities (default: false) */
3466
4790
  returnLogprobs?: boolean;
3467
4791
  }
@@ -3473,3 +4797,27 @@ export interface VlmChatMessage {
3473
4797
  /** Text content of the message */
3474
4798
  content: string;
3475
4799
  }
4800
+
4801
+ export declare namespace __internal__ {
4802
+ /**
4803
+ * Drain the MLX allocator's free-pool.
4804
+ *
4805
+ * @internal
4806
+ *
4807
+ * This is a process-wide drain routed through MLX's default-stream
4808
+ * `mlx_synchronize()`, which does NOT wait on the custom generation
4809
+ * streams that the per-model threads run on. Calling this from user
4810
+ * code while a decode is in flight can race live Metal command buffers
4811
+ * and risk use-after-free. The only safe caller today is
4812
+ * `@mlx-node/server`'s idle sweeper, which only triggers after the
4813
+ * in-flight request counter has returned to zero.
4814
+ *
4815
+ * Exposed under the `__internal__` NAPI namespace — reachable as
4816
+ * `require('@mlx-node/core').__internal__.clearCache()` and NOT on
4817
+ * the root `require('@mlx-node/core')` object. The namespace prefix
4818
+ * is a deliberate speed-bump that forces any caller to acknowledge
4819
+ * this is a private drain with custom-stream caveats; the root
4820
+ * surface stays clean of the footgun.
4821
+ */
4822
+ export function clearCache(): void;
4823
+ }