@qvac/harness 0.5.0 → 0.6.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/CHANGELOG.md +52 -1
- package/README.md +46 -0
- package/dist/harness.bundle +6 -5
- package/dist-lib/index.d.ts +14 -1
- package/dist-lib/index.js +173 -51
- package/dist-lib/lib/connect.d.ts +3 -1
- package/dist-lib/lib/connect.js +18 -0
- package/dist-lib/lib/contract-types.d.ts +13 -0
- package/dist-lib/lib/models/model-catalog.d.ts +2 -0
- package/dist-lib/lib/models/model-catalog.js +7 -0
- package/dist-lib/lib/models/model-pool.d.ts +6 -2
- package/dist-lib/lib/models/model-pool.js +67 -11
- package/dist-lib/lib/serve.js +12 -0
- package/dist-lib/schema/harness.js +25 -0
- package/dist-lib/spec/bind.js +6 -0
- package/dist-lib/spec/calls.js +2 -0
- package/dist-lib/spec/hrpc/hrpc.json +26 -0
- package/dist-lib/spec/hrpc/index.js +27 -3
- package/dist-lib/spec/hrpc/messages.js +68 -2
- package/dist-lib/spec/hyperschema/index.js +68 -2
- package/dist-lib/spec/hyperschema/schema.json +48 -1
- package/dist-lib/spec/hyperschema/types.d.ts +17 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,56 @@ While the major version is `0`, a breaking change bumps the minor version.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.6.0] - 2026-09-28
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `loadModel({ model, agent })` loads a downloaded chat, embedding, speech or
|
|
16
|
+
transcription model ahead of use, under the key a request for it would load
|
|
17
|
+
under, so the first request finds it loaded. `unloadModel({ model })` unloads
|
|
18
|
+
every copy of a model once the requests holding it finish, and says whether it
|
|
19
|
+
could. Image and music models load for each generation and are refused. A load
|
|
20
|
+
or an unload counts as a call in flight for `setModelIdleTimeout`, and one that
|
|
21
|
+
lands during an idle unload waits for it. Also on the wire as `load-model` and
|
|
22
|
+
`unload-model`. (#133)
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
const { loaded } = await harness.loadModel({ model: QWEN_35_4B, agent: { ctxSize: 8192 } })
|
|
26
|
+
const { unloaded } = await harness.unloadModel({ model: QWEN_35_4B })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- A chat model loaded with `loadModel` is pinned: it does not count against
|
|
30
|
+
`completionModelCap`, so several stay loaded at once and a request for
|
|
31
|
+
another model leaves them in place. A manual load never unloads another model
|
|
32
|
+
to fit; it returns `{ loaded: false, reason: 'memory' }` when the SDK's
|
|
33
|
+
`assessModelFit` says it would not fit beside what is loaded. A request always
|
|
34
|
+
gets its model: pinned models no reply is using give way oldest first when it would not fit,
|
|
35
|
+
and a request or a manual load for a model under other settings replaces its idle copy. Image and
|
|
36
|
+
music generation and an idle unload still clear every idle chat model. (#134)
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
const { loaded, reason } = await harness.loadModel({ model: QWEN_35_9B })
|
|
40
|
+
if (reason === 'memory') showNotice('Unload a model first')
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- A model loading or unloading in any pool emits `model` with `catalogChanged`,
|
|
46
|
+
not only a chat model, so `list()` reports a fresh `isLoaded` for embedding,
|
|
47
|
+
speech and transcription models too. (#133)
|
|
48
|
+
- Speech, transcription and embedding requests key their pool entry by the
|
|
49
|
+
model's registry src however the caller names it, so a model named once by
|
|
50
|
+
its catalog name and once by its src is one copy in memory. (#133)
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- A chat model loads when the host passes no `llmDevice`. 0.5.0 sent `device`,
|
|
55
|
+
`gpu_layers`, `main-gpu` and `openclCacheDir` to the SDK as `undefined`,
|
|
56
|
+
which the SDK spreads over its defaults: with no device the load failed with
|
|
57
|
+
"must specify a device: 'gpu' or 'cpu'", and with a device but no
|
|
58
|
+
`llmGpuLayers` the default of 99 GPU layers was lost. Unset fields are left
|
|
59
|
+
out again. (#137)
|
|
60
|
+
|
|
11
61
|
## [0.5.0] - 2026-09-25
|
|
12
62
|
|
|
13
63
|
### Added
|
|
@@ -840,7 +890,8 @@ Tagged but never published to npm; these changes first shipped in 0.2.12.
|
|
|
840
890
|
- GPU stats are read through `bare-gpu-info`, so every dependency installs from
|
|
841
891
|
the public npm registry. (#12)
|
|
842
892
|
|
|
843
|
-
[Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.
|
|
893
|
+
[Unreleased]: https://github.com/tetherto/qvac-harness/compare/v0.6.0...HEAD
|
|
894
|
+
[0.6.0]: https://github.com/tetherto/qvac-harness/compare/v0.5.0...v0.6.0
|
|
844
895
|
[0.5.0]: https://github.com/tetherto/qvac-harness/compare/v0.4.0...v0.5.0
|
|
845
896
|
[0.4.0]: https://github.com/tetherto/qvac-harness/compare/v0.3.3...v0.4.0
|
|
846
897
|
[0.3.3]: https://github.com/tetherto/qvac-harness/compare/v0.3.2...v0.3.3
|
package/README.md
CHANGED
|
@@ -193,6 +193,52 @@ user's files. The model stays registered and
|
|
|
193
193
|
lists as not cached, ready to download again. Like a catalog delete, it does not
|
|
194
194
|
unload a model that is running.
|
|
195
195
|
|
|
196
|
+
### Load and unload a model
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
import { QWEN_35_4B } from '@qvac/harness'
|
|
200
|
+
|
|
201
|
+
const { loaded } = await harness.loadModel({ model: QWEN_35_4B, agent: { ctxSize: 8192 } })
|
|
202
|
+
const row = (await harness.list({ cachedOnly: true })).find((model) => model.name === QWEN_35_4B)
|
|
203
|
+
// row.isLoaded === true, and the next completion with the same agent reuses it
|
|
204
|
+
|
|
205
|
+
const { unloaded } = await harness.unloadModel({ model: QWEN_35_4B })
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`loadModel` loads a downloaded model ahead of use, under the key a request for it
|
|
209
|
+
would load under, so the first request does not load it again. Pass the agent a
|
|
210
|
+
chat model will run for, since its context size and KV cache types are part of
|
|
211
|
+
that key. Chat, embedding, speech and transcription models stay loaded; image and
|
|
212
|
+
music models load for each generation and are refused.
|
|
213
|
+
|
|
214
|
+
A chat model loaded this way is pinned. It does not count against
|
|
215
|
+
`completionModelCap`, so several can be loaded at once, and a request loading
|
|
216
|
+
another model leaves it in place. A request or a manual load for the same model
|
|
217
|
+
under other settings replaces its idle copy rather than loading a second one. A
|
|
218
|
+
manual load never unloads another model to fit: `{ loaded: false, reason: 'memory' }` means the
|
|
219
|
+
SDK's `assessModelFit` estimate says it would not fit beside what is loaded, so
|
|
220
|
+
unload one first. A request always gets its model, though: when that would not
|
|
221
|
+
fit, pinned models no reply is using give way, oldest first. Image or music generation and an idle
|
|
222
|
+
unload still clear every idle chat model, pinned or not.
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
const big = await harness.loadModel({ model: QWEN_36_27B, agent: { ctxSize: 262144 } })
|
|
226
|
+
if (big.reason === 'memory') showNotice('Unload a model first')
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`unloadModel` waits for any request holding the model to finish, then unloads
|
|
230
|
+
every copy of it. `unloaded` is false when a request still held it after the
|
|
231
|
+
wait, so a caller that wants the model gone at once stops those requests first.
|
|
232
|
+
Both change `isLoaded` in the next `list()`, and emit a `model` event with
|
|
233
|
+
`catalogChanged` set. A load or an unload counts as a call in flight for
|
|
234
|
+
[`setModelIdleTimeout`](#freeing-memory-when-idle), and one that lands during an
|
|
235
|
+
idle unload waits for it, so a load never loads a second copy and an unload
|
|
236
|
+
answers once the memory is free.
|
|
237
|
+
|
|
238
|
+
`bun run model-residency` (`bare examples/model-residency/run.ts`, with
|
|
239
|
+
`QVAC_MODEL_CACHE_PATH` pointing at the models) runs these calls against the
|
|
240
|
+
models already on disk and prints what each step changed. It never downloads.
|
|
241
|
+
|
|
196
242
|
## Freeing memory when idle
|
|
197
243
|
|
|
198
244
|
```js
|