@mlx-node/lm 0.0.10 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-session.d.ts +175 -68
- package/dist/chat-session.d.ts.map +1 -1
- package/dist/chat-session.js +271 -122
- package/dist/family-data.d.ts +407 -0
- package/dist/family-data.d.ts.map +1 -0
- package/dist/family-data.js +381 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/models/model-loader.d.ts +91 -135
- package/dist/models/model-loader.d.ts.map +1 -1
- package/dist/models/model-loader.js +74 -179
- package/dist/models/paged-config-override.d.ts +0 -2
- package/dist/models/paged-config-override.d.ts.map +1 -1
- package/dist/models/paged-config-override.js +29 -6
- package/dist/models/qwen3_5-configs.d.ts +1 -3
- package/dist/models/qwen3_5-configs.d.ts.map +1 -1
- package/dist/stream.d.ts +29 -5
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +127 -24
- package/package.json +7 -3
- package/dist/interfaces.d.ts +0 -3
- package/dist/interfaces.d.ts.map +0 -1
- package/dist/interfaces.js +0 -1
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
import { mkdir, mkdtemp, readFile, readdir, rm, stat, symlink, writeFile } from 'node:fs/promises';
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
14
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
15
|
-
|
|
16
|
-
export const AGENT_PAGED_MODEL_TYPES = ['qwen3', 'qwen3_5', 'qwen3_5_moe', 'gemma4', 'lfm2', 'lfm2_moe'];
|
|
15
|
+
import { CHAT_FAMILY_IDS } from '../family-data.js';
|
|
17
16
|
/** Families historically forced paged by `mlx launch claude`. */
|
|
18
17
|
export const QWEN35_PAGED_MODEL_TYPES = ['qwen3_5', 'qwen3_5_moe'];
|
|
19
18
|
const QWEN35_CACHE_FLOOR_MODEL_TYPES = new Set(QWEN35_PAGED_MODEL_TYPES);
|
|
20
19
|
const DEFAULT_QWEN35_PAGED_CACHE_MB = 16_384;
|
|
20
|
+
const DEFAULT_QWEN35_PAGED_CACHE_INITIAL_MB = 2_048;
|
|
21
21
|
const QWEN35_MTP_DRAFTER_DIR = 'mtp-drafter';
|
|
22
22
|
const QWEN35_DENSE_NESTED_MTP_SIDECAR = 'mtp/weights.safetensors';
|
|
23
23
|
/**
|
|
@@ -38,7 +38,7 @@ export class PagedConfigOverrideManager {
|
|
|
38
38
|
cleanupPromise;
|
|
39
39
|
disposed = false;
|
|
40
40
|
constructor(options = {}) {
|
|
41
|
-
this.modelTypes = new Set(options.modelTypes ??
|
|
41
|
+
this.modelTypes = new Set(options.modelTypes ?? CHAT_FAMILY_IDS);
|
|
42
42
|
this.tempDirPrefix = options.tempDirPrefix ?? 'mlx-paged-overrides-';
|
|
43
43
|
this.preserveEmbeddedGemmaDraft = options.preserveEmbeddedGemmaDraft ?? false;
|
|
44
44
|
}
|
|
@@ -108,12 +108,20 @@ export class PagedConfigOverrideManager {
|
|
|
108
108
|
const pagedEnabled = config.use_block_paged_cache === true;
|
|
109
109
|
const configuredMemoryMb = positiveNumber(config.paged_cache_memory_mb);
|
|
110
110
|
const memorySatisfied = cacheFloorMb === undefined || (configuredMemoryMb ?? 0) >= cacheFloorMb;
|
|
111
|
+
// The clone writes `paged_cache_initial_memory_mb = min(initialMb, maxMb)`.
|
|
112
|
+
// A source whose field already equals that value needs no clone; an absent
|
|
113
|
+
// or different field does, or the new start-small default would silently
|
|
114
|
+
// skip every already-paged Qwen3.5 checkpoint (they lack the field).
|
|
115
|
+
const initialMb = QWEN35_CACHE_FLOOR_MODEL_TYPES.has(modelType) ? resolveQwen35InitialMb() : undefined;
|
|
116
|
+
const configuredInitialMb = positiveNumber(config.paged_cache_initial_memory_mb);
|
|
117
|
+
const initialSatisfied = initialMb === undefined ||
|
|
118
|
+
configuredInitialMb === Math.min(initialMb, Math.max(configuredMemoryMb ?? 0, cacheFloorMb ?? 0));
|
|
111
119
|
// Even an already-paged Gemma config must be cloned when `draft/` exists:
|
|
112
120
|
// returning the source would expose the draft to native auto-discovery and
|
|
113
121
|
// trigger the flat-speculation/paged-cache conflict. An authoritative
|
|
114
122
|
// persist directive that disagrees with the source likewise blocks the
|
|
115
123
|
// pass-through so the resolved value reaches the loader.
|
|
116
|
-
if (pagedEnabled && memorySatisfied && !hasEmbeddedGemmaDraft && !persistOverrideNeeded) {
|
|
124
|
+
if (pagedEnabled && memorySatisfied && initialSatisfied && !hasEmbeddedGemmaDraft && !persistOverrideNeeded) {
|
|
117
125
|
return modelPath;
|
|
118
126
|
}
|
|
119
127
|
// Memoize per (source, resolved family, persist directive): the same
|
|
@@ -125,7 +133,7 @@ export class PagedConfigOverrideManager {
|
|
|
125
133
|
const existing = this.overrides.get(cacheKey);
|
|
126
134
|
if (existing !== undefined)
|
|
127
135
|
return existing;
|
|
128
|
-
const pending = this.createOverride(sourcePath, config, cacheFloorMb, modelType, persistPagedCache);
|
|
136
|
+
const pending = this.createOverride(sourcePath, config, cacheFloorMb, initialMb, modelType, persistPagedCache);
|
|
129
137
|
this.overrides.set(cacheKey, pending);
|
|
130
138
|
try {
|
|
131
139
|
return await pending;
|
|
@@ -155,7 +163,7 @@ export class PagedConfigOverrideManager {
|
|
|
155
163
|
await rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
|
-
async createOverride(sourcePath, sourceConfig, cacheFloorMb, modelType, persistPagedCache) {
|
|
166
|
+
async createOverride(sourcePath, sourceConfig, cacheFloorMb, initialMb, modelType, persistPagedCache) {
|
|
159
167
|
const root = await this.getRoot();
|
|
160
168
|
const overrideDir = await mkdtemp(join(root, 'model-'));
|
|
161
169
|
const config = {
|
|
@@ -173,6 +181,14 @@ export class PagedConfigOverrideManager {
|
|
|
173
181
|
if (cacheFloorMb !== undefined) {
|
|
174
182
|
config.paged_cache_memory_mb = Math.max(positiveNumber(sourceConfig.paged_cache_memory_mb) ?? 0, cacheFloorMb);
|
|
175
183
|
}
|
|
184
|
+
if (initialMb !== undefined) {
|
|
185
|
+
// Authoritative initial (grow-on-demand) budget, clamped to the max just
|
|
186
|
+
// written above so it can never exceed the pool ceiling. `initialMb` is
|
|
187
|
+
// always >= 1 and the max is floored by `cacheFloorMb` (>= 1) for the
|
|
188
|
+
// same families, so this never writes 0 — which the native loader rejects.
|
|
189
|
+
const maxMb = positiveNumber(config.paged_cache_memory_mb) ?? 0;
|
|
190
|
+
config.paged_cache_initial_memory_mb = Math.min(initialMb, maxMb);
|
|
191
|
+
}
|
|
176
192
|
await writeFile(join(overrideDir, 'config.json'), JSON.stringify(config, null, 2), 'utf-8');
|
|
177
193
|
const sourceEntries = await readdir(sourcePath);
|
|
178
194
|
for (const name of sourceEntries) {
|
|
@@ -289,3 +305,10 @@ function resolveQwen35CacheFloorMb() {
|
|
|
289
305
|
const parsed = Number.parseInt(raw, 10);
|
|
290
306
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_QWEN35_PAGED_CACHE_MB;
|
|
291
307
|
}
|
|
308
|
+
function resolveQwen35InitialMb() {
|
|
309
|
+
const raw = process.env.MLX_PAGED_CACHE_INITIAL_MB;
|
|
310
|
+
if (raw == null || raw === '')
|
|
311
|
+
return DEFAULT_QWEN35_PAGED_CACHE_INITIAL_MB;
|
|
312
|
+
const parsed = Number.parseInt(raw, 10);
|
|
313
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_QWEN35_PAGED_CACHE_INITIAL_MB;
|
|
314
|
+
}
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* Supports both dense and MoE variants. MoE fields are optional -
|
|
5
5
|
* when `numExperts` is undefined, the model uses dense MLP layers.
|
|
6
6
|
*/
|
|
7
|
-
import type { Qwen35Config as RustQwen35Config
|
|
7
|
+
import type { Qwen35Config as RustQwen35Config } from '@mlx-node/core';
|
|
8
8
|
export type Qwen35Config = RustQwen35Config;
|
|
9
|
-
export type Qwen35GenerationConfig = RustQwen35GenerationConfig;
|
|
10
|
-
export type Qwen35GenerationResult = RustQwen35GenerationResult;
|
|
11
9
|
/**
|
|
12
10
|
* Default configurations for common Qwen3.5 models
|
|
13
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qwen3_5-configs.d.ts","sourceRoot":"","sources":["../../src/models/qwen3_5-configs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"qwen3_5-configs.d.ts","sourceRoot":"","sources":["../../src/models/qwen3_5-configs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,MAAM,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;CA8BzD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAM1D"}
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from "@mlx-node/core";
|
|
1
|
+
import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, MuseGlimmerModel as MuseGlimmerModelNative, NemotronHModel as NemotronHModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from "@mlx-node/core";
|
|
2
2
|
import type { ChatStreamChunk, ChatStreamHandle, PerformanceMetrics, ToolCallResult } from "@mlx-node/core";
|
|
3
3
|
import type { SessionCapableModel } from "./chat-session.js";
|
|
4
4
|
export interface ChatStreamDelta {
|
|
@@ -184,12 +184,14 @@ type ResolvedApplyTemplate<O extends StreamingModelOptions> = O extends {
|
|
|
184
184
|
} ? O["applyTemplate"] : O["recordModelPath"];
|
|
185
185
|
/** @internal Method names whose callback ABI is replaced by the generator wrapper. */
|
|
186
186
|
export type NativeStreamingMethod = keyof NativeStreamingInstance;
|
|
187
|
-
type
|
|
187
|
+
type NativeSessionReplacementMethod = "chatSessionStart" | "chatSessionContinue" | "chatSessionContinueTool" | "beginChatSessionStart" | "beginChatSessionContinue" | "beginChatSessionContinueTool";
|
|
188
|
+
type StreamingReplacementMethod<O extends StreamingModelOptions> = NativeStreamingMethod | NativeSessionReplacementMethod | (ResolvedApplyTemplate<O> extends true ? "applyChatTemplate" : never);
|
|
188
189
|
/**
|
|
189
190
|
* Instance surface of a generated streaming wrapper. Only methods replaced at
|
|
190
191
|
* runtime are removed from the native instance: the three callback streaming
|
|
191
|
-
* methods, plus `applyChatTemplate` when the
|
|
192
|
-
* implementation. Intersecting the remaining
|
|
192
|
+
* methods, the internal operation methods, plus `applyChatTemplate` when the
|
|
193
|
+
* wrapper installs its path-backed implementation. Intersecting the remaining
|
|
194
|
+
* native surface with
|
|
193
195
|
* `SessionCapableModel` preserves required native capabilities such as
|
|
194
196
|
* `hasBlockPagedCache()` while exposing the generator streaming signatures.
|
|
195
197
|
*
|
|
@@ -225,7 +227,7 @@ declare const Qwen35Model_base: {
|
|
|
225
227
|
new (): StreamingInstance<typeof Qwen35ModelNative, {
|
|
226
228
|
readonly recordModelPath: true;
|
|
227
229
|
}>;
|
|
228
|
-
load(path: string): Promise<StreamingInstance<typeof Qwen35ModelNative, {
|
|
230
|
+
load(path: string, options?: import("@mlx-node/core").Qwen35LoadOptions | null | undefined): Promise<StreamingInstance<typeof Qwen35ModelNative, {
|
|
229
231
|
readonly recordModelPath: true;
|
|
230
232
|
}>>;
|
|
231
233
|
};
|
|
@@ -263,6 +265,17 @@ declare const Lfm2Model_base: {
|
|
|
263
265
|
/** LFM2 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
264
266
|
export declare class Lfm2Model extends Lfm2Model_base {
|
|
265
267
|
}
|
|
268
|
+
declare const NemotronHModel_base: {
|
|
269
|
+
new (): StreamingInstance<typeof NemotronHModelNative, {
|
|
270
|
+
readonly recordModelPath: true;
|
|
271
|
+
}>;
|
|
272
|
+
load(modelPath: string): Promise<StreamingInstance<typeof NemotronHModelNative, {
|
|
273
|
+
readonly recordModelPath: true;
|
|
274
|
+
}>>;
|
|
275
|
+
};
|
|
276
|
+
/** Nemotron 3.5 Lightning (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
277
|
+
export declare class NemotronHModel extends NemotronHModel_base {
|
|
278
|
+
}
|
|
266
279
|
declare const Gemma4Model_base: {
|
|
267
280
|
new (config: import("@mlx-node/core").Gemma4Config): StreamingInstance<typeof Gemma4ModelNative, {
|
|
268
281
|
readonly recordModelPath: true;
|
|
@@ -274,6 +287,17 @@ declare const Gemma4Model_base: {
|
|
|
274
287
|
/** Gemma4 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
275
288
|
export declare class Gemma4Model extends Gemma4Model_base {
|
|
276
289
|
}
|
|
290
|
+
declare const MuseGlimmerModel_base: {
|
|
291
|
+
new (): StreamingInstance<typeof MuseGlimmerModelNative, {
|
|
292
|
+
readonly recordModelPath: true;
|
|
293
|
+
}>;
|
|
294
|
+
load(modelPath: string): Promise<StreamingInstance<typeof MuseGlimmerModelNative, {
|
|
295
|
+
readonly recordModelPath: true;
|
|
296
|
+
}>>;
|
|
297
|
+
};
|
|
298
|
+
/** Muse-Glimmer text model with embedded DFlash speculative decoding. */
|
|
299
|
+
export declare class MuseGlimmerModel extends MuseGlimmerModel_base {
|
|
300
|
+
}
|
|
277
301
|
declare const Qwen3Model_base: {
|
|
278
302
|
new (): StreamingInstance<typeof Qwen3ModelNative, {
|
|
279
303
|
readonly recordModelPath: true;
|
package/dist/stream.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,IAAI,iBAAiB,EAChC,SAAS,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
|
package/dist/stream.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Tokenizer, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from "@mlx-node/core";
|
|
2
|
+
import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, MuseGlimmerModel as MuseGlimmerModelNative, NemotronHModel as NemotronHModelNative, Qwen3Tokenizer, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from "@mlx-node/core";
|
|
3
3
|
const modelPathsForTokenizers = new WeakMap();
|
|
4
4
|
const tokenizerPromises = new WeakMap();
|
|
5
5
|
function getNativeIsReasoning(chunk) {
|
|
6
6
|
return typeof chunk.isReasoning === "boolean" ? chunk.isReasoning : undefined;
|
|
7
7
|
}
|
|
8
|
-
function rememberModelPath(model, modelPath) {
|
|
9
|
-
modelPathsForTokenizers.set(model, modelPath);
|
|
10
|
-
}
|
|
11
8
|
async function applyChatTemplateFromModelPath(model, messages, addGenerationPrompt, tools, enableThinking, contentPolicy) {
|
|
12
9
|
const modelPath = modelPathsForTokenizers.get(model);
|
|
13
10
|
if (modelPath == null) {
|
|
@@ -62,8 +59,31 @@ async function applyChatTemplateFromModelPath(model, messages, addGenerationProm
|
|
|
62
59
|
* public API — may change without notice.
|
|
63
60
|
*/
|
|
64
61
|
export async function* _runChatStream(startCall, signal) {
|
|
62
|
+
// The native ThreadsafeFunction uses the same fixed ceiling. This JS-side
|
|
63
|
+
// guard also protects handwritten/test adapters that bypass the Rust glue.
|
|
64
|
+
const maxBufferedEvents = 64;
|
|
65
65
|
const queue = [];
|
|
66
66
|
let resolve = null;
|
|
67
|
+
let handle = null;
|
|
68
|
+
let cancelRequested = false;
|
|
69
|
+
let cancelled = false;
|
|
70
|
+
let overflowed = false;
|
|
71
|
+
const cancelOnce = () => {
|
|
72
|
+
if (cancelled)
|
|
73
|
+
return;
|
|
74
|
+
if (handle === null) {
|
|
75
|
+
cancelRequested = true;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
cancelled = true;
|
|
79
|
+
try {
|
|
80
|
+
handle.cancel();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Cancellation is best-effort. The distinguished backlog error below
|
|
84
|
+
// remains authoritative for the consumer even if a backend throws.
|
|
85
|
+
}
|
|
86
|
+
};
|
|
67
87
|
const waitForItem = () => queue.length > 0
|
|
68
88
|
? Promise.resolve()
|
|
69
89
|
: new Promise((r) => {
|
|
@@ -77,31 +97,31 @@ export async function* _runChatStream(startCall, signal) {
|
|
|
77
97
|
}
|
|
78
98
|
};
|
|
79
99
|
const callback = (err, chunk) => {
|
|
100
|
+
if (overflowed)
|
|
101
|
+
return;
|
|
102
|
+
if (queue.length >= maxBufferedEvents) {
|
|
103
|
+
overflowed = true;
|
|
104
|
+
cancelOnce();
|
|
105
|
+
// Keep the queue at its fixed ceiling while guaranteeing the consumer
|
|
106
|
+
// eventually observes why the stream was cancelled.
|
|
107
|
+
queue[queue.length - 1] = {
|
|
108
|
+
error: new Error(`Native chat stream backlog exceeded ${maxBufferedEvents} buffered events`),
|
|
109
|
+
};
|
|
110
|
+
notify();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
80
113
|
queue.push(err ? { error: err } : { chunk });
|
|
81
114
|
notify();
|
|
82
115
|
};
|
|
83
|
-
|
|
116
|
+
handle = await startCall(callback);
|
|
117
|
+
if (cancelRequested)
|
|
118
|
+
cancelOnce();
|
|
84
119
|
// Guard against double-cancel. Some native backends throw on a
|
|
85
120
|
// second `cancel()`; we route every cancel site through this
|
|
86
121
|
// helper so the abort path (via `triggerAbort`) and the unwind
|
|
87
122
|
// path (via the `finally` block) don't cancel twice and so any
|
|
88
123
|
// backend that does throw is swallowed rather than escaping as
|
|
89
124
|
// an error out of an otherwise-clean early termination.
|
|
90
|
-
let cancelled = false;
|
|
91
|
-
const cancelOnce = () => {
|
|
92
|
-
if (cancelled)
|
|
93
|
-
return;
|
|
94
|
-
cancelled = true;
|
|
95
|
-
try {
|
|
96
|
-
handle.cancel();
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Native backend threw on cancel — nothing actionable here.
|
|
100
|
-
// Swallow so aborted streams still surface as a clean early
|
|
101
|
-
// termination via the synthetic `aborted` marker rather than
|
|
102
|
-
// as an unexpected error out of the generator.
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
125
|
// Signal-driven fast-abort. If the signal is already aborted at
|
|
106
126
|
// attach time we still arm the listener so the synchronous abort
|
|
107
127
|
// dispatch path runs below (calling `handle.cancel()` after the
|
|
@@ -211,6 +231,32 @@ export async function* _runChatStream(startCall, signal) {
|
|
|
211
231
|
cancelOnce();
|
|
212
232
|
}
|
|
213
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Translate the public AbortSignal API to the native two-phase cancellation
|
|
236
|
+
* operation. Callers receive one ordinary Promise and use the same
|
|
237
|
+
* AbortController as fetch and the streaming APIs.
|
|
238
|
+
*/
|
|
239
|
+
async function runChatSessionCall(startCall, signal) {
|
|
240
|
+
if (signal.aborted)
|
|
241
|
+
throw new Error("chat session cancelled");
|
|
242
|
+
const call = await startCall();
|
|
243
|
+
let cancelled = false;
|
|
244
|
+
const cancelOnce = () => {
|
|
245
|
+
if (cancelled)
|
|
246
|
+
return;
|
|
247
|
+
cancelled = true;
|
|
248
|
+
call.cancel();
|
|
249
|
+
};
|
|
250
|
+
signal.addEventListener("abort", cancelOnce, { once: true });
|
|
251
|
+
if (signal.aborted)
|
|
252
|
+
cancelOnce();
|
|
253
|
+
try {
|
|
254
|
+
return await call.result();
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
signal.removeEventListener("abort", cancelOnce);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
214
260
|
/**
|
|
215
261
|
* Build the streaming-model subclass for a native chat model class.
|
|
216
262
|
*
|
|
@@ -238,6 +284,7 @@ export function makeStreamingModel(NativeClass, opts) {
|
|
|
238
284
|
const nativeStart = NativeClass.prototype.chatStreamSessionStart;
|
|
239
285
|
const nativeContinue = NativeClass.prototype.chatStreamSessionContinue;
|
|
240
286
|
const nativeContinueTool = NativeClass.prototype.chatStreamSessionContinueTool;
|
|
287
|
+
const nativeChat = NativeClass.prototype;
|
|
241
288
|
// `NativeClass` is structurally a constructor; cast to a concrete
|
|
242
289
|
// constructor type so `class extends` accepts it. Runtime behavior is
|
|
243
290
|
// unchanged — we extend the real native class.
|
|
@@ -251,7 +298,7 @@ export function makeStreamingModel(NativeClass, opts) {
|
|
|
251
298
|
}
|
|
252
299
|
static async load(modelPath, ...rest) {
|
|
253
300
|
// Forward any trailing family-specific load options verbatim (e.g.
|
|
254
|
-
// Gemma4
|
|
301
|
+
// Gemma4/Qwen3.5 `draftModelPath`); families whose
|
|
255
302
|
// native `load` takes only the path receive no extras. The public
|
|
256
303
|
// signature is re-narrowed per family via `Parameters<C['load']>` in
|
|
257
304
|
// the factory return type below.
|
|
@@ -260,10 +307,48 @@ export function makeStreamingModel(NativeClass, opts) {
|
|
|
260
307
|
// concrete subclass declared per family supplies the prototype and
|
|
261
308
|
// `instanceof ConcreteSubclass` holds.
|
|
262
309
|
Object.setPrototypeOf(instance, this.prototype);
|
|
263
|
-
if (recordPath)
|
|
264
|
-
|
|
310
|
+
if (recordPath) {
|
|
311
|
+
const resolvedAssetsPath = instance.modelAssetsPath?.();
|
|
312
|
+
modelPathsForTokenizers.set(instance, resolvedAssetsPath ?? modelPath);
|
|
313
|
+
}
|
|
265
314
|
return instance;
|
|
266
315
|
}
|
|
316
|
+
async chatSessionStart(messages, config, signal) {
|
|
317
|
+
if (signal == null) {
|
|
318
|
+
if (nativeChat.chatSessionStart == null) {
|
|
319
|
+
throw new Error("Native model does not implement chatSessionStart");
|
|
320
|
+
}
|
|
321
|
+
return await nativeChat.chatSessionStart.call(this, messages, config);
|
|
322
|
+
}
|
|
323
|
+
if (nativeChat.beginChatSessionStart == null) {
|
|
324
|
+
throw new Error("Native model does not implement beginChatSessionStart");
|
|
325
|
+
}
|
|
326
|
+
return await runChatSessionCall(() => nativeChat.beginChatSessionStart.call(this, messages, config), signal);
|
|
327
|
+
}
|
|
328
|
+
async chatSessionContinue(messages, config, signal) {
|
|
329
|
+
if (signal == null) {
|
|
330
|
+
if (nativeChat.chatSessionContinue == null) {
|
|
331
|
+
throw new Error("Native model does not implement chatSessionContinue");
|
|
332
|
+
}
|
|
333
|
+
return await nativeChat.chatSessionContinue.call(this, messages, config);
|
|
334
|
+
}
|
|
335
|
+
if (nativeChat.beginChatSessionContinue == null) {
|
|
336
|
+
throw new Error("Native model does not implement beginChatSessionContinue");
|
|
337
|
+
}
|
|
338
|
+
return await runChatSessionCall(() => nativeChat.beginChatSessionContinue.call(this, messages, config), signal);
|
|
339
|
+
}
|
|
340
|
+
async chatSessionContinueTool(messages, config, signal) {
|
|
341
|
+
if (signal == null) {
|
|
342
|
+
if (nativeChat.chatSessionContinueTool == null) {
|
|
343
|
+
throw new Error("Native model does not implement chatSessionContinueTool");
|
|
344
|
+
}
|
|
345
|
+
return await nativeChat.chatSessionContinueTool.call(this, messages, config);
|
|
346
|
+
}
|
|
347
|
+
if (nativeChat.beginChatSessionContinueTool == null) {
|
|
348
|
+
throw new Error("Native model does not implement beginChatSessionContinueTool");
|
|
349
|
+
}
|
|
350
|
+
return await runChatSessionCall(() => nativeChat.beginChatSessionContinueTool.call(this, messages, config), signal);
|
|
351
|
+
}
|
|
267
352
|
// The native methods are callback-based, but `Base` is typed as a
|
|
268
353
|
// `SessionCapableModel` constructor (whose streaming methods already
|
|
269
354
|
// return `AsyncGenerator<ChatStreamEvent>`), so these overrides are
|
|
@@ -313,11 +398,21 @@ export class Lfm2Model extends makeStreamingModel(Lfm2ModelNative, {
|
|
|
313
398
|
replayAssistantRawText: true,
|
|
314
399
|
}) {
|
|
315
400
|
}
|
|
401
|
+
/** Nemotron 3.5 Lightning (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
402
|
+
export class NemotronHModel extends makeStreamingModel(NemotronHModelNative, {
|
|
403
|
+
recordModelPath: true,
|
|
404
|
+
}) {
|
|
405
|
+
}
|
|
316
406
|
/** Gemma4 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
317
407
|
export class Gemma4Model extends makeStreamingModel(Gemma4ModelNative, {
|
|
318
408
|
recordModelPath: true,
|
|
319
409
|
}) {
|
|
320
410
|
}
|
|
411
|
+
/** Muse-Glimmer text model with embedded DFlash speculative decoding. */
|
|
412
|
+
export class MuseGlimmerModel extends makeStreamingModel(MuseGlimmerModelNative, {
|
|
413
|
+
recordModelPath: true,
|
|
414
|
+
}) {
|
|
415
|
+
}
|
|
321
416
|
/**
|
|
322
417
|
* Qwen3 (first-gen, text-only) model.
|
|
323
418
|
*
|
|
@@ -344,12 +439,16 @@ function _assertSessionCapable() {
|
|
|
344
439
|
const _moe = null;
|
|
345
440
|
const _lfm2 = null;
|
|
346
441
|
const _gemma4 = null;
|
|
442
|
+
const _museGlimmer = null;
|
|
347
443
|
const _qwen3 = null;
|
|
444
|
+
const _nemotronH = null;
|
|
348
445
|
void _qwen35;
|
|
349
446
|
void _moe;
|
|
350
447
|
void _lfm2;
|
|
351
448
|
void _gemma4;
|
|
449
|
+
void _museGlimmer;
|
|
352
450
|
void _qwen3;
|
|
451
|
+
void _nemotronH;
|
|
353
452
|
}
|
|
354
453
|
void _assertSessionCapable;
|
|
355
454
|
/** Compile-time guard that both Qwen3.5 native classes and wrappers retain the exact media planner. */
|
|
@@ -364,17 +463,21 @@ function _assertExpandedPromptPlannerSurfaces() {
|
|
|
364
463
|
void _wrappedMoe;
|
|
365
464
|
}
|
|
366
465
|
void _assertExpandedPromptPlannerSurfaces;
|
|
367
|
-
/** Compile-time guard
|
|
466
|
+
/** Compile-time guard for every native member the factory does not replace. */
|
|
368
467
|
function _assertPreservedNativeSurfaces() {
|
|
369
468
|
const _qwen3 = null;
|
|
370
469
|
const _qwen35 = null;
|
|
371
470
|
const _moe = null;
|
|
372
471
|
const _lfm2 = null;
|
|
373
472
|
const _gemma4 = null;
|
|
473
|
+
const _museGlimmer = null;
|
|
474
|
+
const _nemotronH = null;
|
|
374
475
|
void _qwen3;
|
|
375
476
|
void _qwen35;
|
|
376
477
|
void _moe;
|
|
377
478
|
void _lfm2;
|
|
378
479
|
void _gemma4;
|
|
480
|
+
void _museGlimmer;
|
|
481
|
+
void _nemotronH;
|
|
379
482
|
}
|
|
380
483
|
void _assertPreservedNativeSurfaces;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlx-node/lm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"homepage": "https://github.com/mlx-node/mlx-node",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/mlx-node/mlx-node/issues"
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
".": {
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./family-data": {
|
|
26
|
+
"types": "./dist/family-data.d.ts",
|
|
27
|
+
"import": "./dist/family-data.js"
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
"scripts": {
|
|
@@ -28,9 +32,9 @@
|
|
|
28
32
|
"test": "vite test run"
|
|
29
33
|
},
|
|
30
34
|
"dependencies": {
|
|
31
|
-
"@mlx-node/core": "0.0.
|
|
35
|
+
"@mlx-node/core": "0.0.13"
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^26.
|
|
38
|
+
"@types/node": "^26.4.0"
|
|
35
39
|
}
|
|
36
40
|
}
|
package/dist/interfaces.d.ts
DELETED
package/dist/interfaces.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/interfaces.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|