dsh-local-models 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README-es.md +138 -0
- package/README-hi.md +119 -0
- package/README-pt.md +138 -0
- package/README-zh.md +138 -0
- package/README.md +137 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +1917 -0
- package/lib/index.js +2536 -0
- package/package.json +63 -4
- package/skills/dsh-local-models-ops/SKILL.md +123 -0
- package/skills/dsh-local-models-ops/scripts/audit-profiles.mjs +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-local-models",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "dsh addon: a Local Models settings tab that starts and stops a llama-server child process living with the dsh host process.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
9
|
+
},
|
|
10
|
+
"packageManager": "pnpm@11.7.0",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node --test test/",
|
|
13
|
+
"audit": "node skills/dsh-local-models-ops/scripts/audit-profiles.mjs"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"dsh",
|
|
17
|
+
"dsh-plugin",
|
|
18
|
+
"deepseek-harness",
|
|
19
|
+
"llama.cpp",
|
|
20
|
+
"llama-server",
|
|
21
|
+
"gguf",
|
|
22
|
+
"local-models"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Vmarcelo49/dsh-local-models.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/Vmarcelo49/dsh-local-models#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/Vmarcelo49/dsh-local-models/issues"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"default": "./lib/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./client": {
|
|
37
|
+
"default": "./lib/client.js"
|
|
38
|
+
},
|
|
39
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"dsh": {
|
|
43
|
+
"bundle": {
|
|
44
|
+
"patch": "./cordis.patch.yml"
|
|
45
|
+
},
|
|
46
|
+
"client": {
|
|
47
|
+
"platform": "web",
|
|
48
|
+
"inject": [
|
|
49
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"lib",
|
|
56
|
+
"skills",
|
|
57
|
+
"cordis.patch.yml",
|
|
58
|
+
"README.md",
|
|
59
|
+
"README-pt.md",
|
|
60
|
+
"README-es.md",
|
|
61
|
+
"README-zh.md",
|
|
62
|
+
"README-hi.md"
|
|
63
|
+
],
|
|
64
|
+
"license": "MIT"
|
|
65
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-local-models-ops
|
|
3
|
+
description: Operate and maintain the dsh-local-models plugin that spawns llama-server from the dsh Web GUI. Verify the spawned args match the tuned upstream llama-server config (fixed MTP depth, KV cache K/V types from the tab, mmproj on CPU), audit and fix ~/.dsh/local-models/profiles.json (model/mmproj paths, MTP per profile, KV pairs), restart the dsh web after node-half changes, and diagnose a spawned server (cmdline, log, DRM VRAM, /slots fill). Use when the daily server is run via the Local Models tab or when plugin config/profiles diverge from the tuning. Only useful inside this project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsh-local-models-ops
|
|
7
|
+
|
|
8
|
+
Operational skill for the dsh Local Models plugin (node half: `lib/index.js`,
|
|
9
|
+
client half: `lib/client.js`). It spawns upstream llama-server from the Web
|
|
10
|
+
GUI and provides the "Register in dsh" provider wiring.
|
|
11
|
+
|
|
12
|
+
## 1. Spawn parity checklist
|
|
13
|
+
|
|
14
|
+
The plugin spawn must match the tuned daily config. Verify against
|
|
15
|
+
`lib/index.js` (the `args` array in `run()`):
|
|
16
|
+
|
|
17
|
+
| Setting | Expected value |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `-ngl 999 -c <ctx>` | full offload, ctx from the tab slider |
|
|
20
|
+
| `--flash-attn on --kv-unified` | always |
|
|
21
|
+
| `--cache-type-k <K> --cache-type-v <V>` | from the tab's K/V selectors (`kvTypeK`/`kvTypeV` in profiles), default `q5_0`/`q4_1`; accepted ids: `f32 f16 bf16 q8_0 q5_1 q5_0 q4_1 iq4_nl q4_0`. MLA models (DeepSeek-style latent KV, `isMla` in the GGUF) must use the same type for both |
|
|
22
|
+
| `--spec-draft-type-k q4_0 --spec-draft-type-v q4_0` | fixed (MTP draft KV is not user-selectable) |
|
|
23
|
+
| reasoning chain | `--reasoning auto --reasoning-format deepseek --reasoning-preserve/--no-reasoning-preserve --reasoning-effort medium` (preserve toggle in the tab, `preserveThinking` in profiles; default off = `--no-reasoning-preserve`, matching the plugin's historical behavior — upstream defaults to preserve ON) |
|
|
24
|
+
| MTP | `--spec-type draft-mtp --spec-draft-n-max N --spec-draft-p-min 0` whenever `mtp > 0` — no ctx ceiling any more (the old `ignoreCtxCap` checkbox is gone), depth 0-7 with 3 the tuned sweet spot (deeper collapses at large ctx) |
|
|
25
|
+
| idle eviction | `--sleep-idle-seconds <autoUnloadMins * 60>` when the setting is > 0 (default 30 min; omitted when 0). Sleeping server keeps `/health` + `/models` and reloads on next request |
|
|
26
|
+
| mmproj | `--mmproj <file> --image-min-tokens 1024` + `--no-mmproj-offload` when the tab checkbox is on (default) |
|
|
27
|
+
| spawn env | `RADV_PERFTEST=nogttspill` |
|
|
28
|
+
|
|
29
|
+
## 2. Profiles audit (`~/.dsh/local-models/profiles.json`)
|
|
30
|
+
|
|
31
|
+
Schema per profile: `{ id, name, modelPath, ctx, mtpHeads, mmprojPath,
|
|
32
|
+
mmprojCpu, effort, preserveThinking, kvTypeK, kvTypeV, splitMode, tensorSplit,
|
|
33
|
+
cpuMoe, nCpuMoe, expertUsed, arch, updatedAt }`
|
|
34
|
+
(`preserve_thinking` is accepted as an alias on read; router presets emit
|
|
35
|
+
`reasoning-preserve = 1/0`, a per-model `cache-type-k/-v` pair, and
|
|
36
|
+
`no-mmproj-offload = 1` unless the profile sets `mmprojCpu: false`). Known audit items:
|
|
37
|
+
|
|
38
|
+
- **Paths must exist and live on the fast mount**. Referencing the failing/
|
|
39
|
+
legacy `/mnt/disco1` is a red flag - targets are under `/mnt/raid0/GGUF/`.
|
|
40
|
+
A path outside the tab's roots (home + the Runtime card's Model folders + the
|
|
41
|
+
folders saved profiles already declare) is refused on save and on launch, so
|
|
42
|
+
a profile pointing somewhere else needs its folder added in the tab first.
|
|
43
|
+
- **MTP depth must be <= 7** (the tab's ceiling; upstream clamps the
|
|
44
|
+
effective depth to the model's nextn depth) and **3 is the tuned value** —
|
|
45
|
+
deeper collapses at large ctx. Old profiles may still carry 4-6 from the
|
|
46
|
+
fork era; the tab keeps them now, so fix the stored value.
|
|
47
|
+
- **Daily qwen35 profile** should use `mtpHeads: 3` at `ctx <= 131072`
|
|
48
|
+
(no KV streaming upstream, so 256K ctx is out of reach on 16 GB VRAM).
|
|
49
|
+
- **KV pair**: profiles without `kvTypeK`/`kvTypeV` run the default
|
|
50
|
+
`q5_0`/`q4_1`; an unknown id is normalized to that default on save/launch.
|
|
51
|
+
MLA (DeepSeek-style) profiles must have `kvTypeK === kvTypeV` or the launch
|
|
52
|
+
is refused.
|
|
53
|
+
- A helper ships with this skill: `node scripts/audit-profiles.mjs`.
|
|
54
|
+
|
|
55
|
+
## 3. Tab / client defaults (`lib/client.js`)
|
|
56
|
+
|
|
57
|
+
- `_mtp = useState(2)` (fixed-MTP head default; the daily tune wants 3)
|
|
58
|
+
- `_kvTypeK = useState("q5_0")`, `_kvTypeV = useState("q4_1")` (the tuned
|
|
59
|
+
16 GiB pair; the two selects live in the Model card and feed the estimate)
|
|
60
|
+
- `_mmCpu = useState(true)` (mmproj on CPU, frees ~0.87 GiB VRAM)
|
|
61
|
+
- no MTP softcap control: the draft is unconditional, and `ignoreCtxCap` in
|
|
62
|
+
old profiles is ignored
|
|
63
|
+
- `_splitMode = useState("layer")` / `_tensorSplit = useState("")` — the
|
|
64
|
+
multi-GPU row only renders with more than one detected GPU (or a non-default
|
|
65
|
+
value carried by a profile), and the default emits no flags at all
|
|
66
|
+
|
|
67
|
+
## 4. Diagnostics
|
|
68
|
+
|
|
69
|
+
- Spawned server PID: `pgrep -x llama-server`; confirm the real cmdline with
|
|
70
|
+
`ps -o args= -p <pid>` (two processes may exist: the router on :8080 with
|
|
71
|
+
`--models-preset` + the model server on a random port).
|
|
72
|
+
- Server log: `~/.dsh/local-models/llama-server.log` — also viewable live in
|
|
73
|
+
the GUI: Local Models tab → **Open terminal** (overlay tail of the same
|
|
74
|
+
file, backed by `GET /local-models/logs?offset=<nextOffset>`). Each launch
|
|
75
|
+
writes a header line (`starting <model> (ctx …, mtp …, effort …,
|
|
76
|
+
kv <K>/<V>, experts …, mmproj …)`) — the fastest way to confirm what the
|
|
77
|
+
tab actually asked for.
|
|
78
|
+
- VRAM: `/sys/class/drm/card*/device/mem_info_vram_used|total` (used/total).
|
|
79
|
+
- Current fill: `curl -s http://<port>/slots` -> `n_past` / slot ctx.
|
|
80
|
+
|
|
81
|
+
## 5. Restart flows
|
|
82
|
+
|
|
83
|
+
- **Node-half changes** (lib/index.js, inject list, new routes): restart the
|
|
84
|
+
dsh web process - `pkill -f 'dsh web'` (or the profile's launcher) and
|
|
85
|
+
relaunch; bundle composition picks up only at boot.
|
|
86
|
+
- **Client-half changes** (lib/client.js): page refresh is enough (the served
|
|
87
|
+
bundle rev updates automatically).
|
|
88
|
+
- **Router autostart** (`settings.json: { autostartRouter: true }`, toggled
|
|
89
|
+
from the tab's Router card): dsh boot launches the router from saved
|
|
90
|
+
profiles and registers `local-router` once healthy. Watch
|
|
91
|
+
`~/.dsh/local-models/llama-server.log` (`[autostart]` lines) after a
|
|
92
|
+
restart; needs at least one saved profile, otherwise it logs and skips.
|
|
93
|
+
|
|
94
|
+
## 6. Register in dsh + opencode wiring
|
|
95
|
+
|
|
96
|
+
`POST /local-models/register` adds the ready server as an `llm-pi-ai`
|
|
97
|
+
provider route on the dsh webserver. Single-model mode needs the manual
|
|
98
|
+
**Register in dsh** press; router mode has no Register button — starting
|
|
99
|
+
the router auto-registers the `local-router` route once the server is
|
|
100
|
+
ready (a refresh poll fires it; stopping first cancels a pending one).
|
|
101
|
+
The registered model advertises `maxTokens: min(131072, ctx)` — the full
|
|
102
|
+
131K window for the daily full-window qwen3.x profile (heavy-thinking
|
|
103
|
+
models at xhigh emit thinking blocks past the old 32K default and get
|
|
104
|
+
truncated), and the launched ctx for smaller windows.
|
|
105
|
+
For opencode against the spawned server:
|
|
106
|
+
- the opencode config resolves per directory (project `opencode.json` beats
|
|
107
|
+
nothing; the global `~/.config/opencode/opencode.json(c)` is authoritative
|
|
108
|
+
from arbitrary cwds),
|
|
109
|
+
- `-m local/<model>` needs the model entry **in the config opencode loads**
|
|
110
|
+
("Model not found: ... Did you mean: <other model>?" = wrong config won),
|
|
111
|
+
- `opencode run ... -f <image>` consumes the NEXT argument as the file - put
|
|
112
|
+
the message BEFORE `-f`,
|
|
113
|
+
- model must declare `"attachment": true` (+ image modality) for vision.
|
|
114
|
+
|
|
115
|
+
## 7. Pitfalls learned in the field
|
|
116
|
+
|
|
117
|
+
- `profiles.json` is overwritten wholesale by the plugin's profile save
|
|
118
|
+
(merge is by id) - edit carefully, keep `updatedAt`.
|
|
119
|
+
- Legacy `disco1` references: the disk is failing; always resolve to
|
|
120
|
+
`/mnt/raid0/GGUF/<author>/<model>/` and verify with `test -f`.
|
|
121
|
+
- Old profiles may carry fork-era fields (`kvStreamMib`, `ignoreCtxCap`,
|
|
122
|
+
`mtpHeads` 4-6): harmless leftovers (the tab keeps depths up to 7 and ignores
|
|
123
|
+
`ignoreCtxCap`), but clean them when auditing.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// dsh-local-models-ops: profiles.json audit.
|
|
3
|
+
// Checks: file exists/parses, model/mmproj paths exist and are not on the
|
|
4
|
+
// legacy /mnt/disco1, MTP depth is within the upstream fixed-MTP range
|
|
5
|
+
// (0-3), KV cache ids are known, and the K/V pair matches on MLA-style
|
|
6
|
+
// profiles where it matters. Exit 0 = clean (warnings allowed),
|
|
7
|
+
// exit 1 = hard errors found.
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
const PROFILES = process.env.DSH_LOCAL_MODELS_PROFILES || join(homedir(), ".dsh/local-models/profiles.json");
|
|
13
|
+
const LEGACY = "/mnt/disco1";
|
|
14
|
+
const MTP_MAX = 7; // tab ceiling (mirrors MTP_MAX in lib/index.js); 3 is the tuned sweet spot
|
|
15
|
+
// Accepted --cache-type-k/v ids (mirrors KV_CACHE_TYPES in lib/index.js);
|
|
16
|
+
// unknown ids are normalized to the q5_0/q4_1 default on save/launch.
|
|
17
|
+
const KV_TYPES = ["f32", "f16", "bf16", "q8_0", "q5_1", "q5_0", "q4_1", "iq4_nl", "q4_0"];
|
|
18
|
+
let errors = 0;
|
|
19
|
+
let warnings = 0;
|
|
20
|
+
|
|
21
|
+
const fail = (msg) => { errors++; console.log(` [FAIL] ${msg}`); };
|
|
22
|
+
const warn = (msg) => { warnings++; console.log(` [warn] ${msg}`); };
|
|
23
|
+
|
|
24
|
+
let profiles;
|
|
25
|
+
try {
|
|
26
|
+
profiles = JSON.parse(readFileSync(PROFILES, "utf8"));
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (e?.code === "ENOENT") {
|
|
29
|
+
console.log("[ok] no profiles saved (store does not exist yet)");
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
console.error(`[FAIL] cannot read/parse ${PROFILES}: ${e.message}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (!Array.isArray(profiles) || profiles.length === 0) {
|
|
36
|
+
console.log("[ok] no profiles saved (or empty list)");
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const p of profiles) {
|
|
41
|
+
const id = p.id || p.name || "?";
|
|
42
|
+
const ok = (f) => (f ? (existsSync(f) ? "ok" : "MISSING") : "none");
|
|
43
|
+
const legacy = [p.modelPath, p.mmprojPath].some((f) => f && f.includes(LEGACY));
|
|
44
|
+
const preserve = p.preserveThinking ?? p.preserve_thinking ?? false;
|
|
45
|
+
const metrics = `ctx=${p.ctx} mtp=${p.mtpHeads} effort=${p.effort ?? "?"} preserve=${preserve === true ? "on" : "off"}${p.splitMode && p.splitMode !== "layer" ? " split=" + p.splitMode : ""}`;
|
|
46
|
+
console.log(`- ${id}: model=${ok(p.modelPath)} mmproj=${ok(p.mmprojPath)} | ${metrics}${legacy ? " | !!legacy disco1" : ""}`);
|
|
47
|
+
if (legacy) warn(`legacy ${LEGACY} path (disk is failing; move to /mnt/raid0/GGUF/...)`);
|
|
48
|
+
if (!p.modelPath) fail("missing modelPath");
|
|
49
|
+
else if (!existsSync(p.modelPath)) fail(`model file missing: ${p.modelPath}`);
|
|
50
|
+
if (p.mmprojPath && !existsSync(p.mmprojPath)) fail(`mmproj file missing: ${p.mmprojPath}`);
|
|
51
|
+
if (Number.isInteger(p.mtpHeads) && p.mtpHeads > MTP_MAX) {
|
|
52
|
+
warn(`mtpHeads=${p.mtpHeads} exceeds the tab's MTP ceiling (${MTP_MAX}) - left over from the fork era`);
|
|
53
|
+
}
|
|
54
|
+
if (p.kvStreamMib !== undefined) {
|
|
55
|
+
warn(`stale fork-era kvStreamMib=${p.kvStreamMib} field (ignored by the plugin)`);
|
|
56
|
+
}
|
|
57
|
+
for (const [field, value] of [["kvTypeK", p.kvTypeK], ["kvTypeV", p.kvTypeV]]) {
|
|
58
|
+
if (value !== undefined && value !== null && !KV_TYPES.includes(value)) {
|
|
59
|
+
warn(`${field}=${value} is not a known cache type (normalized to the default on save/launch)`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// The plugin refuses mixed K/V only on MLA (latent-KV) models, which the
|
|
63
|
+
// audit cannot detect from profiles.json alone — so a mismatch is a heads-up,
|
|
64
|
+
// not a failure. Verify against the GGUF (isMla / deepseek4 arch) if unsure.
|
|
65
|
+
if (p.kvTypeK && p.kvTypeV && p.kvTypeK !== p.kvTypeV) {
|
|
66
|
+
console.log(` [info] mixed KV pair ${p.kvTypeK}/${p.kvTypeV} (refused at launch only on MLA models)`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (errors > 0) {
|
|
71
|
+
console.log(`[FAIL] ${errors} error(s), ${warnings} warning(s)`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
console.log(warnings === 0 ? "[ok] profiles look healthy" : `[ok] profiles usable with ${warnings} warning(s)`);
|
|
75
|
+
process.exit(0);
|