dsh-plugin-local-infer 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/docs/zh/README.md +4 -1
- package/dsh/client.js +627 -133
- package/package.json +11 -10
- package/packages/core/lib/adapter.d.ts +75 -4
- package/packages/core/lib/index.d.ts +2 -1
- package/packages/core/lib/index.js +1 -0
- package/packages/core/lib/launch-command.d.ts +13 -0
- package/packages/core/lib/launch-command.js +64 -0
- package/packages/core/lib/models.d.ts +2 -1
- package/packages/core/lib/models.js +10 -1
- package/packages/core/lib/status.d.ts +9 -1
- package/packages/core/lib/status.js +6 -1
- package/packages/providers/lib/acquire.d.ts +125 -0
- package/packages/providers/lib/acquire.js +325 -0
- package/packages/providers/lib/adapters.d.ts +42 -5
- package/packages/providers/lib/adapters.js +385 -57
- package/packages/providers/lib/deps.d.ts +4 -2
- package/packages/providers/lib/deps.js +16 -2
- package/packages/providers/lib/engines.d.ts +4 -3
- package/packages/providers/lib/engines.js +11 -3
- package/packages/providers/lib/guidelines.js +16 -0
- package/packages/providers/lib/index.d.ts +9 -3
- package/packages/providers/lib/index.js +8 -4
- package/packages/providers/lib/infernix.d.ts +170 -0
- package/packages/providers/lib/infernix.js +671 -0
- package/packages/providers/lib/model-defaults.d.ts +5 -3
- package/packages/providers/lib/model-defaults.js +27 -3
- package/packages/providers/lib/ninfer.d.ts +7 -0
- package/packages/providers/lib/ninfer.js +9 -2
- package/packages/providers/lib/platform.d.ts +31 -0
- package/packages/providers/lib/platform.js +58 -0
- package/packages/service/lib/index.d.ts +61 -18
- package/packages/service/lib/index.js +278 -42
- package/packages/service/lib/types.d.ts +44 -2
- package/packages/ui/lib/route.d.ts +83 -12
- package/packages/ui/lib/route.js +495 -55
- package/packages/ui/package.json +1 -1
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infernix parameter surface. The declarative `ParameterSpec` data for the
|
|
3
|
+
* Infernix engine (https://github.com/wallawalla47/infernix), a C++/CUDA
|
|
4
|
+
* fork of NInfer tuned for Qwen models on a single RTX 50-series GPU,
|
|
5
|
+
* sourced from the project's `docs/cli.md` and `docs/serving.md`. The
|
|
6
|
+
* spelling is an `infernix-serve` CLI flag the `InfernixAdapter` passes onto
|
|
7
|
+
* the process.
|
|
8
|
+
*
|
|
9
|
+
* The surface carries the flags the NInfer surface shares (KV format,
|
|
10
|
+
* context, speculation, sampling) plus the Infernix-only fields: the
|
|
11
|
+
* `--ngram-volume` PLE table for Qwen3.8-Flash-Next, the `--draft-model`
|
|
12
|
+
* DFlash2 companion drafter, the n-gram copy proposal set, the offloaded-
|
|
13
|
+
* expert memory tiers, and the quality routes. Those fields live only on
|
|
14
|
+
* this surface, so the form renderer shows them only while the Infernix
|
|
15
|
+
* engine is selected, and the adapters of the other engines never spell
|
|
16
|
+
* them. Adding a flag here is a data edit, never a core rewire.
|
|
17
|
+
* @module dsh-plugin-local-infer/providers/infernix
|
|
18
|
+
*/
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { CHAT_TEMPLATE_OPTIONS, LM_HEAD_DRAFT_GATE_MESSAGE } from "./ninfer.js";
|
|
21
|
+
/**
|
|
22
|
+
* The KV cache quantization formats Infernix serves, in the order the form
|
|
23
|
+
* dropdown lists them. The set differs from NInfer's: the 2-bit and 4-bit-
|
|
24
|
+
* K vector-quantized tiers (vq2, k4v2) replace the KVarN rk tiers, and both
|
|
25
|
+
* hold three to four times the context of INT8 in the same VRAM.
|
|
26
|
+
*/
|
|
27
|
+
export const INFERNIX_KV_DTYPE_OPTIONS = [
|
|
28
|
+
{ value: 'bf16', label: 'bf16', note: 'full precision; the widest KV pool' },
|
|
29
|
+
{ value: 'int8', label: 'int8', note: 'half the bytes of bf16; the everyday choice' },
|
|
30
|
+
{ value: 'fp8', label: 'fp8', note: 'half the bytes of bf16; the long-context tier' },
|
|
31
|
+
{ value: 'nvfp4', label: 'nvfp4', note: 'NVFP4; the smallest KV tier, needs SM 89 or newer' },
|
|
32
|
+
{ value: 'k8v4', label: 'k8v4', note: '8-bit keys, 4-bit values; between int8 and vq2' },
|
|
33
|
+
{ value: 'vq2', label: 'vq2', note: '2-bit vector-quantized K and V; the longest context tier' },
|
|
34
|
+
{ value: 'k4v2', label: 'k4v2', note: '4-bit K with 2-bit V; between k8v4 and vq2' },
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* The speculative-decoding backends. `none` loads no speculative backend at
|
|
38
|
+
* all, which is the smallest resident profile.
|
|
39
|
+
*/
|
|
40
|
+
export const INFERNIX_SPEC_OPTIONS = [
|
|
41
|
+
{ value: 'none', label: 'none', note: 'no speculative backend; the smallest resident profile' },
|
|
42
|
+
{ value: 'mtp', label: 'mtp', note: 'multi-token-prediction head; the 27B and 35B-A3B choice' },
|
|
43
|
+
{ value: 'dflash', label: 'dflash', note: 'DFlash; the 35B-A3B companion-weight backend' },
|
|
44
|
+
{
|
|
45
|
+
value: 'dflash2',
|
|
46
|
+
label: 'dflash2',
|
|
47
|
+
note: 'DFlash2; the 27B and Flash-Next backend, the Flash-Next drafter rides --draft-model',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* The PLE n-gram table residency modes for Qwen3.8-Flash-Next. `auto` takes
|
|
52
|
+
* pinned RAM only when the RAM ledger keeps every expert in RAM beside it.
|
|
53
|
+
*/
|
|
54
|
+
export const INFERNIX_NGRAM_RESIDENCY_OPTIONS = [
|
|
55
|
+
{
|
|
56
|
+
value: 'auto',
|
|
57
|
+
label: 'auto',
|
|
58
|
+
note: 'reads the table into RAM when the ledger allows, else from the volume',
|
|
59
|
+
},
|
|
60
|
+
{ value: 'ram', label: 'ram', note: 'reads the whole table into pinned host RAM at startup' },
|
|
61
|
+
{ value: 'volume', label: 'volume', note: 'reads rows on demand from the n-gram volume' },
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* The reasoning-loop guard modes. The guard ends a reply, or closes the
|
|
65
|
+
* thinking, when the open thinking keeps repeating whole passages.
|
|
66
|
+
*/
|
|
67
|
+
export const INFERNIX_REASONING_LOOP_OPTIONS = [
|
|
68
|
+
{ value: 'off', label: 'off', note: 'no guard; the engine default' },
|
|
69
|
+
{ value: 'stop', label: 'stop', note: 'ends the reply when the thinking repeats' },
|
|
70
|
+
{ value: 'conclude', label: 'conclude', note: 'closes the thinking and lets the model answer' },
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* The gate message the form shows while the speculation fields are greyed
|
|
74
|
+
* out. It names the fix (pick a backend) and the backends that unlock the
|
|
75
|
+
* fields, so a first-time user knows the next click, not just the constraint.
|
|
76
|
+
*/
|
|
77
|
+
export const SPEC_GATE_MESSAGE = 'Pick a speculation backend first (mtp, dflash, or dflash2)';
|
|
78
|
+
/**
|
|
79
|
+
* The gate message the form shows while the dflash2-only fields are greyed
|
|
80
|
+
* out. It names the fix (pick dflash2) and the reason, so a first-time user
|
|
81
|
+
* knows the next click, not just the constraint.
|
|
82
|
+
*/
|
|
83
|
+
export const DFLASH2_GATE_MESSAGE = 'Pick the dflash2 speculation backend first; the DFlash2 drafter loads with dflash2';
|
|
84
|
+
/**
|
|
85
|
+
* The Infernix model artifacts, keyed by the config model key. The 27B
|
|
86
|
+
* NVIDIA NVFP4 entry is the default the DEFAULT_MODELS list names; an id no
|
|
87
|
+
* entry names falls back to it, so a bare config always renders a plan. The
|
|
88
|
+
* Flash-Next entry is the recommended Dense8 conversion: its download pulls
|
|
89
|
+
* the whole repo, which carries the FP8 n-gram volume beside the shards, so
|
|
90
|
+
* the `--ngram-volume` flag points at a file the same download left on disk.
|
|
91
|
+
*/
|
|
92
|
+
export const INFERNIX_MODEL_ARTIFACTS = {
|
|
93
|
+
'qwen3-27b': {
|
|
94
|
+
file: 'qwen3_8_27b_nvfp4-nvidia.ninfer',
|
|
95
|
+
repo: 'Wallawalla47/Qwen3.8-27B-NVIDIA-NVFP4-NInferV3',
|
|
96
|
+
modelId: 'Qwen3.8-27B',
|
|
97
|
+
},
|
|
98
|
+
'qwen3-27b-uncensored': {
|
|
99
|
+
file: 'qwen3_8_27b_nvfp4-uncensored.ninfer',
|
|
100
|
+
repo: 'Wallawalla47/Qwen3.8-27B-Uncensored-NVFP4-NInferV3',
|
|
101
|
+
modelId: 'Qwen3.8-27B',
|
|
102
|
+
},
|
|
103
|
+
'qwen38-flash-next': {
|
|
104
|
+
file: 'Qwen3.8-Flash-Next-NVIDIA-NVFP4-Dense8-Infernix-00001-of-00003.infernix',
|
|
105
|
+
files: [
|
|
106
|
+
'Qwen3.8-Flash-Next-NVIDIA-NVFP4-Dense8-Infernix-00001-of-00003.infernix',
|
|
107
|
+
'Qwen3.8-Flash-Next-NVIDIA-NVFP4-Dense8-Infernix-00002-of-00003.infernix',
|
|
108
|
+
'Qwen3.8-Flash-Next-NVIDIA-NVFP4-Dense8-Infernix-00003-of-00003.infernix',
|
|
109
|
+
],
|
|
110
|
+
repo: 'Wallawalla47/Qwen3.8-Flash-Next-NVIDIA-NVFP4-Dense8-Infernix',
|
|
111
|
+
modelId: 'Qwen3.8-Flash-Next',
|
|
112
|
+
},
|
|
113
|
+
'qwen38-flash-next-dflash2': {
|
|
114
|
+
file: 'Qwen3.8-Flash-Next-DFlash2-Infernix.infernix',
|
|
115
|
+
repo: 'Wallawalla47/Qwen3.8-Flash-Next-DFlash2-Infernix',
|
|
116
|
+
modelId: 'Qwen3.8-Flash-Next-DFlash2',
|
|
117
|
+
},
|
|
118
|
+
'qwen36-35b': {
|
|
119
|
+
file: 'qwen3_6_35b_a3b.ninfer',
|
|
120
|
+
repo: 'neroued/Qwen3.6-35B-A3B-NInfer',
|
|
121
|
+
modelId: 'Qwen3.6-35B-A3B',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The common DFlash2 drafters the Infernix DFlash2 model dropdown offers, in
|
|
126
|
+
* dropdown order. The Flash-Next DFlash2 is Inco AI's drafter (the 776 MB
|
|
127
|
+
* `.infernix` artifact) that the Infernix README recommends beside any
|
|
128
|
+
* Flash-Next conversion. The list is the single source the dropdown, the
|
|
129
|
+
* `--draft-model` path resolver, and the Get download step read.
|
|
130
|
+
*/
|
|
131
|
+
export const INFERNIX_DFLASH2_MODELS = [
|
|
132
|
+
{
|
|
133
|
+
file: 'Qwen3.8-Flash-Next-DFlash2-Infernix.infernix',
|
|
134
|
+
repo: 'Wallawalla47/Qwen3.8-Flash-Next-DFlash2-Infernix',
|
|
135
|
+
label: 'Qwen3.8-Flash-Next DFlash2',
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
/**
|
|
139
|
+
* The dropdown value the DFlash2 model field's Custom entry carries. A
|
|
140
|
+
* selection of this value (or no selection) makes the effective `--draft-model`
|
|
141
|
+
* path fall through to the Draft model path field, so a drafter no list entry
|
|
142
|
+
* names still loads.
|
|
143
|
+
*/
|
|
144
|
+
export const DFLASH2_CUSTOM_VALUE = 'custom';
|
|
145
|
+
/**
|
|
146
|
+
* dflash2PresetOptions renders the DFlash2 model dropdown options: one entry
|
|
147
|
+
* per common drafter (the label, the repo as the note) plus the Custom entry
|
|
148
|
+
* last. The option value is the drafter's artifact file, so the selection
|
|
149
|
+
* resolves back to the model data by file name.
|
|
150
|
+
*/
|
|
151
|
+
export function dflash2PresetOptions() {
|
|
152
|
+
return [
|
|
153
|
+
...INFERNIX_DFLASH2_MODELS.map((model) => ({
|
|
154
|
+
value: model.file,
|
|
155
|
+
label: model.label,
|
|
156
|
+
note: model.repo,
|
|
157
|
+
})),
|
|
158
|
+
{
|
|
159
|
+
value: DFLASH2_CUSTOM_VALUE,
|
|
160
|
+
label: 'Custom',
|
|
161
|
+
note: 'use the Draft model path field',
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The PLE n-gram volumes, keyed by the config model key. The Flash-Next entry
|
|
167
|
+
* is the FP8 volume (the recommended one), which ships beside the Dense8 model
|
|
168
|
+
* shards in the same repo, so a Get of the Flash-Next model pulls the table
|
|
169
|
+
* into the model directory.
|
|
170
|
+
*/
|
|
171
|
+
export const INFERNIX_NGRAM_VOLUMES = {
|
|
172
|
+
'qwen38-flash-next': {
|
|
173
|
+
file: 'Qwen3.8-Flash-Next-NVIDIA-NVFP4-Infernix.ngram',
|
|
174
|
+
repo: 'Wallawalla47/Qwen3.8-Flash-Next-NVIDIA-NVFP4-Dense8-Infernix',
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* infernixDraftModelPath resolves the effective `--draft-model` path a config
|
|
179
|
+
* carries: the selected DFlash2 preset's artifact (the model directory plus the
|
|
180
|
+
* drafter file, the layout the hf CLI download leaves) when a preset is
|
|
181
|
+
* selected, else the Custom Draft model path field. Returns null when neither
|
|
182
|
+
* names a path, which is the misconfiguration the dflash2 guideline names.
|
|
183
|
+
*/
|
|
184
|
+
export function infernixDraftModelPath(config) {
|
|
185
|
+
const preset = typeof config.draftModelPreset === 'string' ? config.draftModelPreset : '';
|
|
186
|
+
if (preset !== '' && preset !== DFLASH2_CUSTOM_VALUE) {
|
|
187
|
+
const model = INFERNIX_DFLASH2_MODELS.find((entry) => entry.file === preset);
|
|
188
|
+
const modelDir = typeof config.modelDirectory === 'string' && config.modelDirectory !== ''
|
|
189
|
+
? config.modelDirectory
|
|
190
|
+
: '';
|
|
191
|
+
if (model !== undefined && modelDir !== '') {
|
|
192
|
+
return join(modelDir, model.file, model.file);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const custom = typeof config.draftModel === 'string' ? config.draftModel : '';
|
|
196
|
+
return custom !== '' ? custom : null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* infernixGetDownloads renders the hf CLI download commands a Get run pulls
|
|
200
|
+
* for a config: the selected DFlash2 drafter (into the model directory, the
|
|
201
|
+
* layout the `--draft-model` path expects) when a preset is selected, and the
|
|
202
|
+
* model key's PLE n-gram volume (into the model directory) when the key names
|
|
203
|
+
* one. The commands are the single source the Get route streams, so a Get
|
|
204
|
+
* leaves the model directory ready to launch.
|
|
205
|
+
*/
|
|
206
|
+
export function infernixGetDownloads(config, modelDir) {
|
|
207
|
+
const commands = [];
|
|
208
|
+
const preset = typeof config.draftModelPreset === 'string' ? config.draftModelPreset : '';
|
|
209
|
+
if (preset !== '' && preset !== DFLASH2_CUSTOM_VALUE) {
|
|
210
|
+
const model = INFERNIX_DFLASH2_MODELS.find((entry) => entry.file === preset);
|
|
211
|
+
if (model !== undefined) {
|
|
212
|
+
commands.push(`hf download ${model.repo} ${model.file} --local-dir ${join(modelDir, model.file)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const volume = INFERNIX_NGRAM_VOLUMES[infernixModelKey(config)];
|
|
216
|
+
if (volume !== undefined) {
|
|
217
|
+
commands.push(`hf download ${volume.repo} ${volume.file} --local-dir ${modelDir}`);
|
|
218
|
+
}
|
|
219
|
+
return commands;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The Infernix `ParameterSpec` surface, in form order. The `group` field is
|
|
223
|
+
* the form section; specs without a group render under `General`. The
|
|
224
|
+
* Infernix-only fields (ngramVolume, draftModel, the n-gram proposal set,
|
|
225
|
+
* the expert memory tiers, the quality routes) appear only on this surface,
|
|
226
|
+
* so the form shows them only while the Infernix engine is selected.
|
|
227
|
+
*/
|
|
228
|
+
export const INFERNIX_PARAMETERS = [
|
|
229
|
+
{
|
|
230
|
+
key: 'port',
|
|
231
|
+
label: 'Port',
|
|
232
|
+
description: 'The loopback port the engine serves its OpenAI endpoint and health check on. The registrar publishes it as the model base URL.',
|
|
233
|
+
// The port is a typed number, not a range strip: a 1..65535 drag slider is
|
|
234
|
+
// noise, so `slider: false` renders the plain number entry only.
|
|
235
|
+
form: { kind: 'number', min: 1, max: 65535, step: 1, slider: false },
|
|
236
|
+
// 8080 is the port the infernix-serve CLI binds by default, so the form
|
|
237
|
+
// seeds the value the adapter's own fallback uses when a config names no
|
|
238
|
+
// port.
|
|
239
|
+
defaultValue: 8080,
|
|
240
|
+
group: 'Engine',
|
|
241
|
+
// The port contributes no flag of its own: renderLaunch spells the local
|
|
242
|
+
// `--port` from this value, and the health URL derives from it too.
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
key: 'kvDtype',
|
|
246
|
+
label: 'KV cache format',
|
|
247
|
+
description: 'Quantizes the KV cache. Smaller formats fit a longer context in the same VRAM at a small quality cost.',
|
|
248
|
+
form: { kind: 'enum', options: [...INFERNIX_KV_DTYPE_OPTIONS] },
|
|
249
|
+
defaultValue: 'int8',
|
|
250
|
+
group: 'Memory',
|
|
251
|
+
hardwareConstraint: (value, hw) => {
|
|
252
|
+
// nvfp4 KV runs on SM 89 (Ada) and newer; a 30-series card (sm86) cannot
|
|
253
|
+
// serve it, so the warning names the fix before the engine starts.
|
|
254
|
+
if (value === 'nvfp4' && hw.gpu && hw.gpu.sm < 89) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
severity: 'error',
|
|
258
|
+
message: `nvfp4 KV needs SM 89 or newer; this card reports SM ${hw.gpu.sm}. Pick k8v4 or vq2.`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return { ok: true };
|
|
262
|
+
},
|
|
263
|
+
toArgv: (value) => ['--kv-dtype', String(value)],
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
key: 'maxContext',
|
|
267
|
+
label: 'Max context',
|
|
268
|
+
description: 'The longest conversation the engine accepts. Raising it reserves more VRAM for the KV cache.',
|
|
269
|
+
form: { kind: 'number', min: 256, max: 262144, step: 256, unit: 'tokens' },
|
|
270
|
+
defaultValue: 32768,
|
|
271
|
+
group: 'Memory',
|
|
272
|
+
toArgv: (value) => ['--max-context', String(value)],
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
key: 'kvCapacity',
|
|
276
|
+
label: 'KV capacity',
|
|
277
|
+
description: 'The shared KV pool across concurrent requests. Leave unset to let the engine maximize the pool from the remaining GPU memory.',
|
|
278
|
+
form: {
|
|
279
|
+
kind: 'number',
|
|
280
|
+
min: 256,
|
|
281
|
+
step: 256,
|
|
282
|
+
unit: 'tokens',
|
|
283
|
+
// The range strip caps at the live max-context value (the pool cannot
|
|
284
|
+
// outgrow the context it serves), re-priced on every form render so a
|
|
285
|
+
// max-context edit moves the cap without touching the current value.
|
|
286
|
+
// The number entry stays uncapped, so a typed value is never hard-limited.
|
|
287
|
+
sliderMax: (_hw, config) => (typeof config.maxContext === 'number' ? config.maxContext : 0),
|
|
288
|
+
},
|
|
289
|
+
group: 'Memory',
|
|
290
|
+
toArgv: (value) => ['--kv-capacity', String(value)],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
key: 'hostContextMib',
|
|
294
|
+
label: 'Host context MiB',
|
|
295
|
+
description: 'Pinned host RAM for the hybrid prefix cache: the slab pool the KV blocks and state snapshots share. Retains inactive conversations when device memory is under pressure.',
|
|
296
|
+
form: {
|
|
297
|
+
kind: 'number',
|
|
298
|
+
min: 0,
|
|
299
|
+
step: 1024,
|
|
300
|
+
unit: 'MiB',
|
|
301
|
+
// The range strip caps at half the host RAM (the pool cannot outgrow the
|
|
302
|
+
// machine it pins), while the number entry stays uncapped so a typed
|
|
303
|
+
// value is never hard-limited by the slider.
|
|
304
|
+
sliderMax: (hw) => Math.max(0, Math.floor((hw.hostRamGiB ?? 0) * 0.5 * 1024)),
|
|
305
|
+
},
|
|
306
|
+
group: 'Memory',
|
|
307
|
+
// 16384 MiB is the engine default the form seeds; a value of 0 keeps the
|
|
308
|
+
// cache on the device only, which the engine still accepts.
|
|
309
|
+
defaultValue: 16384,
|
|
310
|
+
toArgv: (value) => ['--host-context-mib', String(value)],
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
key: 'mediaLiveMib',
|
|
314
|
+
label: 'Media live MiB',
|
|
315
|
+
description: 'Pinned host RAM reserved for live media (vision) decode. Retains the in-flight image and video buffers outside the GPU workspace.',
|
|
316
|
+
form: { kind: 'number', min: 0, step: 1024, unit: 'MiB' },
|
|
317
|
+
group: 'Memory',
|
|
318
|
+
toArgv: (value) => ['--media-live-mib', String(value)],
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
key: 'ramHeadroomMib',
|
|
322
|
+
label: 'RAM headroom MiB',
|
|
323
|
+
description: 'Host RAM the pinned weights and caches leave free for the rest of the machine. The startup refuses a pin that would take it.',
|
|
324
|
+
form: { kind: 'number', min: 0, step: 256, unit: 'MiB' },
|
|
325
|
+
defaultValue: 8192,
|
|
326
|
+
group: 'Memory',
|
|
327
|
+
toArgv: (value) => ['--ram-headroom-mib', String(value)],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
key: 'vramHeadroomMib',
|
|
331
|
+
label: 'VRAM headroom MiB',
|
|
332
|
+
description: 'VRAM the startup sizing leaves free for the display and other programs. Leave unset for the engine automatic headroom.',
|
|
333
|
+
form: { kind: 'number', min: 0, step: 256, unit: 'MiB' },
|
|
334
|
+
group: 'Memory',
|
|
335
|
+
// The engine defaults to auto, so a bare value spells no flag.
|
|
336
|
+
toArgv: (value) => ['--vram-headroom-mib', String(value)],
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
key: 'expertRamMib',
|
|
340
|
+
label: 'Expert RAM MiB',
|
|
341
|
+
description: 'Host RAM for the routed experts of offloaded models. Less than the full set keeps that many in RAM and reads the rest from the artifact on an SSD tier.',
|
|
342
|
+
form: { kind: 'number', min: 0, step: 1024, unit: 'MiB' },
|
|
343
|
+
group: 'Memory',
|
|
344
|
+
// The engine defaults to auto (what the RAM ledger leaves), so a bare
|
|
345
|
+
// value spells no flag.
|
|
346
|
+
toArgv: (value) => ['--expert-ram-mib', String(value)],
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
key: 'expertPinMib',
|
|
350
|
+
label: 'Expert pin MiB',
|
|
351
|
+
description: 'How much of the expert RAM the GPU page-locks. The rest stays unpinned and the CPU serves it, which the page-locking limit on the OS bounds.',
|
|
352
|
+
form: { kind: 'number', min: 0, step: 1024, unit: 'MiB' },
|
|
353
|
+
group: 'Memory',
|
|
354
|
+
// The engine defaults to auto (what the OS lets the GPU page-lock), so a
|
|
355
|
+
// bare value spells no flag.
|
|
356
|
+
toArgv: (value) => ['--expert-pin-mib', String(value)],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
key: 'ngramResidency',
|
|
360
|
+
label: 'N-gram residency',
|
|
361
|
+
description: 'Chooses where the PLE n-gram table of Qwen3.8-Flash-Next lives: pinned RAM, the volume, or the engine automatic rule.',
|
|
362
|
+
form: { kind: 'enum', options: [...INFERNIX_NGRAM_RESIDENCY_OPTIONS] },
|
|
363
|
+
defaultValue: 'auto',
|
|
364
|
+
group: 'Memory',
|
|
365
|
+
// auto is the engine default, so the bare value spells no flag.
|
|
366
|
+
toArgv: (value) => (value === 'auto' ? [] : ['--ngram-residency', String(value)]),
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
key: 'maxConcurrency',
|
|
370
|
+
label: 'Max concurrency',
|
|
371
|
+
description: 'How many requests decode at once. More lanes share the KV pool and cut per-request throughput.',
|
|
372
|
+
form: { kind: 'number', min: 1, max: 8, step: 1, unit: 'lanes' },
|
|
373
|
+
defaultValue: 1,
|
|
374
|
+
group: 'Concurrency',
|
|
375
|
+
toArgv: (value) => ['--max-concurrency', String(value)],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
key: 'maxPendingRequests',
|
|
379
|
+
label: 'Max pending requests',
|
|
380
|
+
description: 'How many extra requests may wait for an admission slot. Raises the queue for bursty agent traffic at the cost of longer waits.',
|
|
381
|
+
form: { kind: 'number', min: 0, step: 1, unit: 'requests' },
|
|
382
|
+
group: 'Concurrency',
|
|
383
|
+
toArgv: (value) => ['--max-pending-requests', String(value)],
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
key: 'spec',
|
|
387
|
+
label: 'Speculation backend',
|
|
388
|
+
description: 'The speculative-decoding backend. Speeds decode when drafts are accepted; adds draft weights and CUDA graphs to resident memory.',
|
|
389
|
+
form: { kind: 'enum', options: [...INFERNIX_SPEC_OPTIONS] },
|
|
390
|
+
defaultValue: 'none',
|
|
391
|
+
group: 'Speculation',
|
|
392
|
+
toArgv: (value) => (value === 'none' ? [] : ['--spec', String(value)]),
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
key: 'draftTokens',
|
|
396
|
+
label: 'Draft tokens',
|
|
397
|
+
description: 'How many draft tokens a speculative round verifies. More drafts speed decode but cost more per round; auto verifies the count the drafter probabilities make fastest.',
|
|
398
|
+
form: { kind: 'number', min: 1, max: 15, step: 1, unit: 'tokens' },
|
|
399
|
+
defaultValue: 3,
|
|
400
|
+
group: 'Speculation',
|
|
401
|
+
requires: (config) => config.spec && config.spec !== 'none' ? null : SPEC_GATE_MESSAGE,
|
|
402
|
+
toArgv: (value) => ['--draft-tokens', String(value)],
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
key: 'lmHeadDraft',
|
|
406
|
+
label: 'LM-head draft',
|
|
407
|
+
description: 'Drafts from the small proposal head instead of the full output head, cutting the per-draft projection cost. An MTP-backend feature, so the field unlocks only while MTP is selected.',
|
|
408
|
+
form: { kind: 'boolean' },
|
|
409
|
+
defaultValue: false,
|
|
410
|
+
group: 'Speculation',
|
|
411
|
+
requires: (config) => config.spec === 'mtp' ? null : LM_HEAD_DRAFT_GATE_MESSAGE,
|
|
412
|
+
toArgv: (value) => (value === true ? ['--lm-head-draft'] : []),
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: 'draftModelPreset',
|
|
416
|
+
label: 'DFlash2 model',
|
|
417
|
+
description: 'The DFlash2 drafter to download into the model directory. Pick a common model, or Custom to use the Draft model path. A Get run pulls the selected drafter through the hf CLI.',
|
|
418
|
+
form: { kind: 'enum', options: [...dflash2PresetOptions()] },
|
|
419
|
+
// The first common drafter is the selection a fresh config carries, so a
|
|
420
|
+
// switch to dflash2 always has a drafter path to load.
|
|
421
|
+
defaultValue: INFERNIX_DFLASH2_MODELS[0]?.file,
|
|
422
|
+
group: 'Speculation',
|
|
423
|
+
// The drafter only loads with the dflash2 backend, so the field stays
|
|
424
|
+
// greyed out until the user picks it.
|
|
425
|
+
requires: (config) => config.spec === 'dflash2' ? null : DFLASH2_GATE_MESSAGE,
|
|
426
|
+
// No toArgv: the adapter resolves the effective --draft-model path from
|
|
427
|
+
// this selection (or the Custom Draft model field) and spells the flag.
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
key: 'draftModel',
|
|
431
|
+
label: 'Draft model',
|
|
432
|
+
description: 'The DFlash2 drafter companion artifact for Qwen3.8-Flash-Next. Downloads separately from the model, and the engine loads it beside the main artifact. Used while the DFlash2 model dropdown is Custom.',
|
|
433
|
+
form: { kind: 'path' },
|
|
434
|
+
group: 'Speculation',
|
|
435
|
+
// The drafter only loads with the dflash2 backend, and the Custom path
|
|
436
|
+
// field is only live while the DFlash2 model dropdown is Custom (or
|
|
437
|
+
// cleared), so a selected preset never leaves a stale custom path behind.
|
|
438
|
+
requires: (config) => {
|
|
439
|
+
if (config.spec !== 'dflash2')
|
|
440
|
+
return DFLASH2_GATE_MESSAGE;
|
|
441
|
+
const preset = typeof config.draftModelPreset === 'string' ? config.draftModelPreset : '';
|
|
442
|
+
if (preset !== '' && preset !== DFLASH2_CUSTOM_VALUE) {
|
|
443
|
+
return 'A DFlash2 model is selected; switch to Custom to use a custom Draft model path';
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
},
|
|
447
|
+
// No toArgv: the adapter resolves the effective --draft-model path and
|
|
448
|
+
// spells the flag, so the preset and the custom path share one source.
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
key: 'ngramDraftTokens',
|
|
452
|
+
label: 'N-gram draft tokens',
|
|
453
|
+
description: 'Verified n-gram copy proposals per round beside the spec drafter. Speeds decode on repetitive text; 0 disables the proposals.',
|
|
454
|
+
form: { kind: 'number', min: 0, max: 63, step: 1, unit: 'proposals' },
|
|
455
|
+
group: 'Speculation',
|
|
456
|
+
toArgv: (value) => (value === 0 ? [] : ['--ngram-draft-tokens', String(value)]),
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
key: 'ngramMinMatch',
|
|
460
|
+
label: 'N-gram min match',
|
|
461
|
+
description: 'The minimum matched tokens an n-gram copy proposal must reach. Raises it to verify longer copies at a lower proposal rate.',
|
|
462
|
+
form: { kind: 'number', min: 4, max: 64, step: 1, unit: 'tokens' },
|
|
463
|
+
defaultValue: 12,
|
|
464
|
+
group: 'Speculation',
|
|
465
|
+
// The minimum match only prices a proposal the user enabled, so the
|
|
466
|
+
// field stays greyed out while n-gram drafting is off.
|
|
467
|
+
requires: (config) => typeof config.ngramDraftTokens === 'number' && config.ngramDraftTokens > 0
|
|
468
|
+
? null
|
|
469
|
+
: 'Enable n-gram copy proposals first (n-gram draft tokens above 0)',
|
|
470
|
+
toArgv: (value) => ['--ngram-min-match', String(value)],
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
key: 'ngramVolume',
|
|
474
|
+
label: 'N-gram volume',
|
|
475
|
+
description: 'The PLE n-gram table file for Qwen3.8-Flash-Next, kept apart from the model shards and read from an NVMe drive. The recommended FP8 table downloads with the Dense8 model repo.',
|
|
476
|
+
form: { kind: 'path' },
|
|
477
|
+
group: 'Artifacts',
|
|
478
|
+
toArgv: (value) => (value ? ['--ngram-volume', String(value)] : []),
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
key: 'expertState',
|
|
482
|
+
label: 'Expert state',
|
|
483
|
+
description: 'The saved expert-cache state file. The engine seeds the VRAM expert cache from it at startup and rewrites it on stop, so warm experts survive a restart.',
|
|
484
|
+
form: { kind: 'path' },
|
|
485
|
+
group: 'Artifacts',
|
|
486
|
+
// The engine defaults to the artifact-named state file, so a bare value
|
|
487
|
+
// spells no flag.
|
|
488
|
+
toArgv: (value) => (value ? ['--expert-state', String(value)] : []),
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
key: 'prefixCacheFile',
|
|
492
|
+
label: 'Prefix cache file',
|
|
493
|
+
description: 'The file the hybrid prefix cache saves to on stop and reloads on start, so cached KV blocks survive a restart.',
|
|
494
|
+
form: { kind: 'path' },
|
|
495
|
+
group: 'Artifacts',
|
|
496
|
+
toArgv: (value) => (value ? ['--prefix-cache-file', String(value)] : []),
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
key: 'prefillChunk',
|
|
500
|
+
label: 'Prefill chunk',
|
|
501
|
+
description: 'The prompt-chunk size for prefill, in multiples of 128. The engine rounds it to whole attention waves. The form seeds 2048; a lower value trades prefill speed for smaller per-chunk memory.',
|
|
502
|
+
form: {
|
|
503
|
+
kind: 'number',
|
|
504
|
+
min: 128,
|
|
505
|
+
step: 128,
|
|
506
|
+
unit: 'tokens',
|
|
507
|
+
// The range strip caps at the live max-context value (a chunk cannot
|
|
508
|
+
// outgrow the context it serves), re-priced on every form render. The
|
|
509
|
+
// number entry stays uncapped, so a typed value is never hard-limited.
|
|
510
|
+
sliderMax: (_hw, config) => (typeof config.maxContext === 'number' ? config.maxContext : 0),
|
|
511
|
+
},
|
|
512
|
+
// 2048 tokens is the engine default the form seeds.
|
|
513
|
+
defaultValue: 2048,
|
|
514
|
+
group: 'Engine',
|
|
515
|
+
toArgv: (value) => ['--prefill-chunk', String(value)],
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
key: 'vision',
|
|
519
|
+
label: 'Vision input',
|
|
520
|
+
description: 'Accepts image and video input. Adds the vision tower and its encode workspace to resident memory.',
|
|
521
|
+
form: { kind: 'boolean' },
|
|
522
|
+
defaultValue: false,
|
|
523
|
+
group: 'Engine',
|
|
524
|
+
toArgv: (value) => (value === true ? ['--vision'] : []),
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
key: 'noCudaGraph',
|
|
528
|
+
label: 'Disable CUDA graphs',
|
|
529
|
+
description: 'Turns off CUDA Graph decode. Costs decode speed, which helps a debug build that cannot instantiate graphs.',
|
|
530
|
+
form: { kind: 'boolean' },
|
|
531
|
+
defaultValue: false,
|
|
532
|
+
group: 'Engine',
|
|
533
|
+
toArgv: (value) => (value === true ? ['--no-cuda-graph'] : []),
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
key: 'reproducible',
|
|
537
|
+
label: 'Reproducible route',
|
|
538
|
+
description: 'Runs the bit-exact reproducible route: request-invariant output from release to release, at the cost of the fast paths.',
|
|
539
|
+
form: { kind: 'boolean' },
|
|
540
|
+
defaultValue: false,
|
|
541
|
+
group: 'Engine',
|
|
542
|
+
toArgv: (value) => (value === true ? ['--reproducible'] : []),
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
key: 'lossless',
|
|
546
|
+
label: 'Lossless route',
|
|
547
|
+
description: 'Computes the model forward pass exactly, rounded once to its dtype. The output depends on the request and artifact alone, at the slowest speed.',
|
|
548
|
+
form: { kind: 'boolean' },
|
|
549
|
+
defaultValue: false,
|
|
550
|
+
group: 'Engine',
|
|
551
|
+
toArgv: (value) => (value === true ? ['--lossless'] : []),
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
key: 'ropeYarnFactor',
|
|
555
|
+
label: 'YaRN factor',
|
|
556
|
+
description: 'Extends the context ceiling by the YaRN scaling factor, from the native limit up to 4x. Long-context extrapolation, not a quality guarantee.',
|
|
557
|
+
form: { kind: 'number', min: 1, max: 4, step: 0.05 },
|
|
558
|
+
defaultValue: 1,
|
|
559
|
+
group: 'Engine',
|
|
560
|
+
// 1 leaves the native RoPE unchanged, so the bare value spells no flag.
|
|
561
|
+
toArgv: (value) => (value === 1 ? [] : ['--rope-yarn-factor', String(value)]),
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
key: 'chatTemplate',
|
|
565
|
+
label: 'Chat template',
|
|
566
|
+
description: 'Overrides the artifact built-in Jinja chat template. Pick a preset from the dropdown, or choose Custom and type a local path or HF repo reference. Changes take effect after a restart.',
|
|
567
|
+
form: { kind: 'combo', options: [...CHAT_TEMPLATE_OPTIONS] },
|
|
568
|
+
group: 'Engine',
|
|
569
|
+
toArgv: (value) => (value ? ['--chat-template', String(value)] : []),
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
key: 'temperature',
|
|
573
|
+
label: 'Temperature',
|
|
574
|
+
description: 'Sampling randomness. 0 decodes deterministically; higher values draw from a wider token distribution.',
|
|
575
|
+
form: { kind: 'number', min: 0, max: 2, step: 0.05 },
|
|
576
|
+
defaultValue: 0.9,
|
|
577
|
+
group: 'Sampling',
|
|
578
|
+
toArgv: (value) => ['--temperature', String(value)],
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
key: 'minP',
|
|
582
|
+
label: 'Min-p',
|
|
583
|
+
description: 'Drops tokens whose probability falls under this fraction of the top token, trimming the sampling tail.',
|
|
584
|
+
form: { kind: 'number', min: 0, max: 1, step: 0.01 },
|
|
585
|
+
defaultValue: 0.05,
|
|
586
|
+
group: 'Sampling',
|
|
587
|
+
toArgv: (value) => ['--min-p', String(value)],
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
key: 'defaultMaxTokens',
|
|
591
|
+
label: 'Default max tokens',
|
|
592
|
+
description: 'Caps the response length when a request names no max. Raises it for long answers; the engine still honours a per-request cap.',
|
|
593
|
+
form: {
|
|
594
|
+
kind: 'number',
|
|
595
|
+
// A response under 1024 tokens is too short to be useful, so the floor
|
|
596
|
+
// is 1024, not 1.
|
|
597
|
+
min: 1024,
|
|
598
|
+
step: 1,
|
|
599
|
+
unit: 'tokens',
|
|
600
|
+
// The range strip caps at 12.5% of the live max-context value (the
|
|
601
|
+
// default response cannot outgrow the quarter of context it is priced
|
|
602
|
+
// against), re-priced on every form render. The number entry stays
|
|
603
|
+
// uncapped, so a typed value is never hard-limited by the slider.
|
|
604
|
+
sliderMax: (_hw, config) => {
|
|
605
|
+
const ctx = typeof config.maxContext === 'number' ? config.maxContext : 0;
|
|
606
|
+
return Math.max(0, Math.floor(ctx * 0.125));
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
group: 'Sampling',
|
|
610
|
+
toArgv: (value) => ['--default-max-tokens', String(value)],
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
key: 'defaultThinkingBudget',
|
|
614
|
+
label: 'Default thinking budget',
|
|
615
|
+
description: 'Reserves reasoning tokens for a thinking model when a request names no budget. Higher budgets spend more time reasoning per turn.',
|
|
616
|
+
form: { kind: 'number', min: 0, step: 1, unit: 'tokens' },
|
|
617
|
+
group: 'Sampling',
|
|
618
|
+
toArgv: (value) => ['--default-thinking-budget', String(value)],
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
key: 'preserveThinking',
|
|
622
|
+
label: 'Preserve thinking',
|
|
623
|
+
description: 'Keeps the reasoning trace in the context across turns instead of dropping it, so a multi-turn conversation builds on its own prior reasoning.',
|
|
624
|
+
form: { kind: 'boolean' },
|
|
625
|
+
defaultValue: false,
|
|
626
|
+
group: 'Sampling',
|
|
627
|
+
toArgv: (value) => (value === true ? ['--preserve-thinking'] : []),
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
key: 'tolerantToolCalls',
|
|
631
|
+
label: 'Tolerant tool calls',
|
|
632
|
+
description: 'Recovers tool calls the model truncates or malforms: a suffix after a complete call, a cut closing tag, or a call stranded in unclosed thinking.',
|
|
633
|
+
form: { kind: 'boolean' },
|
|
634
|
+
defaultValue: false,
|
|
635
|
+
group: 'Sampling',
|
|
636
|
+
toArgv: (value) => (value === true ? ['--tolerant-tool-calls'] : []),
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
key: 'reasoningLoop',
|
|
640
|
+
label: 'Reasoning loop guard',
|
|
641
|
+
description: 'Ends the reply, or closes the thinking, when the open thinking keeps repeating whole passages. Guards long thinking runs that stall.',
|
|
642
|
+
form: { kind: 'enum', options: [...INFERNIX_REASONING_LOOP_OPTIONS] },
|
|
643
|
+
defaultValue: 'off',
|
|
644
|
+
group: 'Sampling',
|
|
645
|
+
// off is the engine default, so the bare value spells no flag.
|
|
646
|
+
toArgv: (value) => (value === 'off' ? [] : ['--reasoning-loop', String(value)]),
|
|
647
|
+
},
|
|
648
|
+
];
|
|
649
|
+
/**
|
|
650
|
+
* The Infernix model key a config carries: the string model value, falling
|
|
651
|
+
* back to the 27B artifact key so a bare config always renders a plan. A
|
|
652
|
+
* value that names a local artifact file (a directory-scanned model, a
|
|
653
|
+
* `.ninfer` or `.infernix` name) passes through unchanged.
|
|
654
|
+
*/
|
|
655
|
+
export function infernixModelKey(config) {
|
|
656
|
+
return typeof config.model === 'string' && config.model !== '' ? config.model : 'qwen3-27b';
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* infernixArtifactFor resolves the artifact a model key deploys. A key that
|
|
660
|
+
* names a local artifact file passes through the artifact map's 27B entry
|
|
661
|
+
* for the model id, and an id no entry names falls back to the 27B entry, so
|
|
662
|
+
* a bare config always renders a plan.
|
|
663
|
+
*/
|
|
664
|
+
export function infernixArtifactFor(modelKey) {
|
|
665
|
+
if (modelKey.endsWith('.ninfer') || modelKey.endsWith('.infernix')) {
|
|
666
|
+
return INFERNIX_MODEL_ARTIFACTS['qwen3-27b'];
|
|
667
|
+
}
|
|
668
|
+
const artifact = INFERNIX_MODEL_ARTIFACTS[modelKey];
|
|
669
|
+
return artifact ?? INFERNIX_MODEL_ARTIFACTS['qwen3-27b'];
|
|
670
|
+
}
|
|
671
|
+
//# sourceMappingURL=infernix.js.map
|