@bendyline/gezel-inference 1.0.5 → 1.0.7
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/cache/controller.d.ts +2 -0
- package/dist/cache/controller.js +38 -5
- package/dist/catalog/chat-models/index.json +1 -1
- package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/manifest.json +114 -0
- package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/raw-index.json +1 -1
- package/dist/catalog/image-loras/index.json +1 -0
- package/dist/catalog/image-loras/kr/krea-2-ink-wash/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-ink-wash/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-isometric-3d/manifest.json +32 -0
- package/dist/catalog/image-loras/kr/krea-2-isometric-3d/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-kids-drawing/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-kids-drawing/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-pixel-art/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-pixel-art/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/kr/krea-2-turbo-4step/manifest.json +31 -0
- package/dist/catalog/image-loras/kr/krea-2-turbo-4step/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/mo/moss-image-gouache/manifest.json +29 -0
- package/dist/catalog/image-loras/mo/moss-image-gouache/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/mo/moss-image-sft-4step/manifest.json +30 -0
- package/dist/catalog/image-loras/mo/moss-image-sft-4step/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/manifest.json +31 -0
- package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/raw-index.json +1 -0
- package/dist/catalog/image-loras/sd/sdxl-3d-render/manifest.json +29 -0
- package/dist/catalog/image-loras/sd/sdxl-3d-render/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/sd/sdxl-coloring-book/manifest.json +30 -0
- package/dist/catalog/image-loras/sd/sdxl-coloring-book/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/sd/sdxl-papercut/manifest.json +29 -0
- package/dist/catalog/image-loras/sd/sdxl-papercut/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-loras/sd/sdxl-pixel-art/manifest.json +29 -0
- package/dist/catalog/image-loras/sd/sdxl-pixel-art/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/manifest.json +3 -0
- package/dist/catalog/image-models/index.json +1 -1
- package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/manifest.json +44 -0
- package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/versions/1.0.0/manifest.json +36 -0
- package/dist/catalog/image-models/raw-index.json +1 -1
- package/dist/catalog/knowledge-catalogs/index.json +1 -1
- package/dist/catalog/knowledge-catalogs/raw-index.json +1 -1
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.10.2/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/manifest.json +55 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/versions/2026.10.2/manifest.json +165 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/versions/2026.10.2/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/versions/2026.10.2/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.10.2/manifest.json +181 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.10.2/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/versions/2026.10.2/manifest.json +209 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/versions/2026.10.2/manifest.json +201 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/versions/2026.10.2/manifest.json +165 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/versions/2026.10.2/manifest.json +201 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/versions/2026.10.2/manifest.json +205 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/versions/2026.10.2/manifest.json +237 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.10.2/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.10.2/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.10.2/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.10.2/manifest.json +189 -0
- package/dist/catalog/provenance.json +2 -1
- package/dist/engine-components.js +2 -2
- package/dist/engines/electron-native-reuse.d.ts +7 -1
- package/dist/engines/electron-native-reuse.js +12 -4
- package/dist/engines/native-file-manifest.json +546 -406
- package/dist/engines/native-manifest.d.ts +4 -4
- package/dist/engines/native-manifest.js +36 -36
- package/dist/engines/resolver.d.ts +1 -1
- package/dist/engines/resolver.js +48 -51
- package/dist/http/auth-context.d.ts +12 -0
- package/dist/http/routes/image-ensure-routes.d.ts +4 -0
- package/dist/http/routes/image-ensure-routes.js +102 -0
- package/dist/http/routes/v1-image-styles.d.ts +5 -0
- package/dist/http/routes/v1-image-styles.js +91 -0
- package/dist/http/routes/v1-images.d.ts +1 -0
- package/dist/http/routes/v1-images.js +15 -93
- package/dist/inference-service.js +2 -1
- package/dist/knowledge/worker-host.js +2 -0
- package/dist/machine-engine/inspect-runtime.js +12 -3
- package/dist/memory/embed-core.js +3 -0
- package/dist/memory/embeddings.js +2 -0
- package/dist/models/image-fit.js +2 -2
- package/dist/models/weight-facts.d.ts +5 -0
- package/dist/models/weight-facts.js +5 -2
- package/dist/providers/ds4/build-provider.js +1 -1
- package/dist/providers/ds4/residency.js +3 -3
- package/dist/providers/image/img2img-support.d.ts +36 -1
- package/dist/providers/image/img2img-support.js +30 -1
- package/dist/providers/image/local-engine.d.ts +1 -0
- package/dist/providers/image/local-engine.js +8 -2
- package/dist/providers/image/local-factory.d.ts +10 -1
- package/dist/providers/image/local-factory.js +33 -6
- package/dist/providers/image/mock.d.ts +8 -1
- package/dist/providers/image/mock.js +46 -1
- package/dist/providers/image/pull-registry.d.ts +25 -3
- package/dist/providers/image/pull-registry.js +96 -34
- package/dist/providers/image/sd-cpp.d.ts +63 -4
- package/dist/providers/image/sd-cpp.js +361 -38
- package/dist/providers/image/types.d.ts +76 -4
- package/dist/providers/image/types.js +8 -0
- package/dist/providers/llama-cpp/build-provider.js +3 -2
- package/dist/providers/llama-cpp/cache-adapter.d.ts +3 -0
- package/dist/providers/llama-cpp/cache-adapter.js +18 -4
- package/dist/providers/llama-cpp/devices.js +2 -2
- package/dist/providers/llama-cpp/engine-flags.d.ts +5 -0
- package/dist/providers/llama-cpp/engine-flags.js +5 -2
- package/dist/providers/llama-cpp/provider.js +20 -4
- package/dist/providers/mlx/provider.js +13 -4
- package/dist/providers/mlx/python/gezel_mlx_server.py +9 -2
- package/dist/providers/mlx/python/spec_decode.py +44 -5
- package/dist/providers/native/capacity-broker.d.ts +1 -1
- package/dist/providers/native/capacity-broker.js +8 -8
- package/dist/providers/native/capacity-budget.js +5 -4
- package/dist/providers/native/device-capacity-ledger.d.ts +2 -0
- package/dist/providers/native/device-capacity-ledger.js +36 -5
- package/dist/providers/native/device-capacity.js +4 -3
- package/dist/providers/native/local-engine-runtime.js +4 -4
- package/dist/providers/native/process-identity.d.ts +2 -0
- package/dist/providers/native/process-identity.js +70 -0
- package/dist/relevance/relevance-core.js +2 -0
- package/dist/relevance/relevance-model.js +2 -0
- package/dist/relevance/settings.js +2 -2
- package/dist/runtime-lock.d.ts +43 -5
- package/dist/runtime-lock.js +189 -26
- package/dist/system/host-memory.d.ts +46 -0
- package/dist/system/host-memory.js +89 -0
- package/dist/system/memory.js +5 -4
- package/dist/transformers-cache.js +2 -0
- package/dist/utils/download-common.d.ts +1 -1
- package/package.json +8 -8
|
@@ -6,6 +6,8 @@ import { imageCatalogFits, imageModelFit, wontRunRefusal } from '../../models/im
|
|
|
6
6
|
import { ReadOnlyModelError, modelExistsOnlyReadOnly, modelStorageRoots, } from '../../models/storage-roots.js';
|
|
7
7
|
import { NoLocalImageModelError } from '../../providers/image/local-factory.js';
|
|
8
8
|
import { ImageCapacityError, ImageGenerationCancelledError, } from '../../providers/image/types.js';
|
|
9
|
+
import { registerImageEnsureRoutes } from './image-ensure-routes.js';
|
|
10
|
+
import { v1ImageStylesRoutes } from './v1-image-styles.js';
|
|
9
11
|
const log = createLogger('v1-images');
|
|
10
12
|
/** The one local image engine this host serves. */
|
|
11
13
|
const ENGINE = 'sd-cpp';
|
|
@@ -36,6 +38,7 @@ function describeManifest(manifest) {
|
|
|
36
38
|
description: manifest.description,
|
|
37
39
|
download_bytes: downloadBytes(manifest),
|
|
38
40
|
...(license ? { license } : {}),
|
|
41
|
+
...(manifest.licenseUrl ? { license_url: manifest.licenseUrl } : {}),
|
|
39
42
|
commercial_use: manifest.commercialUse,
|
|
40
43
|
...(manifest.rendersText !== undefined ? { renders_text: manifest.rendersText } : {}),
|
|
41
44
|
...(manifest.promptStyle ? { prompt_style: manifest.promptStyle } : {}),
|
|
@@ -49,36 +52,6 @@ function describeManifest(manifest) {
|
|
|
49
52
|
: {}),
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
|
-
function ensureEvent(jobId, event, totalBytes) {
|
|
53
|
-
const job = { jobId, modelId: qualified(jobId) };
|
|
54
|
-
switch (event.type) {
|
|
55
|
-
case 'progress':
|
|
56
|
-
return {
|
|
57
|
-
...job,
|
|
58
|
-
type: 'progress',
|
|
59
|
-
bytesWritten: event.bytesWritten,
|
|
60
|
-
totalBytes: event.totalBytes ?? totalBytes,
|
|
61
|
-
};
|
|
62
|
-
case 'retrying':
|
|
63
|
-
return {
|
|
64
|
-
...job,
|
|
65
|
-
type: 'retrying',
|
|
66
|
-
attempt: event.attempt,
|
|
67
|
-
maxAttempts: event.maxAttempts,
|
|
68
|
-
delayMs: event.delayMs,
|
|
69
|
-
reason: event.reason,
|
|
70
|
-
};
|
|
71
|
-
case 'error':
|
|
72
|
-
return { ...job, type: 'error', error: event.error };
|
|
73
|
-
case 'done':
|
|
74
|
-
return { ...job, type: 'done' };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function pullStatus(pull) {
|
|
78
|
-
if (!pull.finished)
|
|
79
|
-
return 'running';
|
|
80
|
-
return pull.error ? 'error' : 'done';
|
|
81
|
-
}
|
|
82
55
|
function generationError(err) {
|
|
83
56
|
const message = err instanceof Error ? err.message : String(err);
|
|
84
57
|
if (err instanceof ImageGenerationCancelledError)
|
|
@@ -117,6 +90,8 @@ function generationError(err) {
|
|
|
117
90
|
*/
|
|
118
91
|
export function v1ImagesRoutes(ctx) {
|
|
119
92
|
const app = new Hono();
|
|
93
|
+
if (ctx.images.loraPulls)
|
|
94
|
+
app.route('/styles', v1ImageStylesRoutes(ctx, ctx.images.loraPulls));
|
|
120
95
|
const fitContext = { catalog: ctx.catalog, readConfig: () => ctx.store.readConfig() };
|
|
121
96
|
app.get('/models', async (c) => {
|
|
122
97
|
const provider = await ctx.images.current();
|
|
@@ -200,69 +175,7 @@ export function v1ImagesRoutes(ctx) {
|
|
|
200
175
|
}
|
|
201
176
|
return c.json({ status: 'downloading', model_id: qualified(id), job_id: id }, 202);
|
|
202
177
|
});
|
|
203
|
-
app
|
|
204
|
-
const pull = ctx.images.pulls.get(c.req.param('jobId'));
|
|
205
|
-
if (!pull)
|
|
206
|
-
return c.json(apiError('job_not_found', 'job not found'), 404);
|
|
207
|
-
return c.json({
|
|
208
|
-
job_id: pull.id,
|
|
209
|
-
model_id: qualified(pull.id),
|
|
210
|
-
status: pullStatus(pull),
|
|
211
|
-
started_at: Date.parse(pull.startedAt),
|
|
212
|
-
bytes_written: pull.bytesWritten,
|
|
213
|
-
total_bytes: pull.totalBytes,
|
|
214
|
-
...(pull.error ? { error: pull.error } : {}),
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
app.get('/models/ensure/:jobId/events', (c) => {
|
|
218
|
-
const jobId = c.req.param('jobId');
|
|
219
|
-
const snapshot = ctx.images.pulls.get(jobId);
|
|
220
|
-
if (!snapshot)
|
|
221
|
-
return c.json(apiError('job_not_found', 'job not found'), 404);
|
|
222
|
-
return streamSSE(c, async (stream) => {
|
|
223
|
-
let closed = false;
|
|
224
|
-
let sawError = false;
|
|
225
|
-
const unsubscribe = ctx.images.pulls.subscribe(jobId, (event) => {
|
|
226
|
-
// A failed pull ends with an error and then its done; the error is terminal.
|
|
227
|
-
if (sawError)
|
|
228
|
-
return;
|
|
229
|
-
if (event.type === 'error')
|
|
230
|
-
sawError = true;
|
|
231
|
-
const terminal = event.type === 'done' || event.type === 'error';
|
|
232
|
-
void stream
|
|
233
|
-
.writeSSE({ data: JSON.stringify(ensureEvent(jobId, event, snapshot.totalBytes)) })
|
|
234
|
-
.then(() => {
|
|
235
|
-
if (terminal)
|
|
236
|
-
closed = true;
|
|
237
|
-
})
|
|
238
|
-
.catch(() => {
|
|
239
|
-
closed = true;
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
if (!unsubscribe)
|
|
243
|
-
return;
|
|
244
|
-
stream.onAbort(() => {
|
|
245
|
-
closed = true;
|
|
246
|
-
unsubscribe();
|
|
247
|
-
});
|
|
248
|
-
while (!closed) {
|
|
249
|
-
await stream.sleep(1000);
|
|
250
|
-
try {
|
|
251
|
-
await stream.writeSSE({ event: 'ping', data: '' });
|
|
252
|
-
}
|
|
253
|
-
catch {
|
|
254
|
-
closed = true;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
unsubscribe();
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
app.delete('/models/ensure/:jobId', (c) => {
|
|
261
|
-
if (!ctx.images.pulls.cancel(c.req.param('jobId'))) {
|
|
262
|
-
return c.json(apiError('job_not_found', 'No download in progress for that job'), 404);
|
|
263
|
-
}
|
|
264
|
-
return c.json({ cancelled: true });
|
|
265
|
-
});
|
|
178
|
+
registerImageEnsureRoutes(app, '/models/ensure', ctx.images.pulls, qualified);
|
|
266
179
|
app.delete('/models/:id', async (c) => {
|
|
267
180
|
const id = catalogIdOf(decodeURIComponent(c.req.param('id')));
|
|
268
181
|
if (!id)
|
|
@@ -302,6 +215,14 @@ export function v1ImagesRoutes(ctx) {
|
|
|
302
215
|
: apiError('no_local_image_model', new NoLocalImageModelError().message);
|
|
303
216
|
return c.json(error, 409);
|
|
304
217
|
}
|
|
218
|
+
const style = request.style
|
|
219
|
+
? installed
|
|
220
|
+
.find((model) => model.id === modelId)
|
|
221
|
+
?.loras?.find((lora) => lora.id === request.style && lora.purpose === 'style')
|
|
222
|
+
: undefined;
|
|
223
|
+
if (request.style && !style) {
|
|
224
|
+
return c.json(apiError('style_not_available', 'That style is not installed for this image model.'), 409);
|
|
225
|
+
}
|
|
305
226
|
const item = await ctx.catalog.get('image-model', modelId).catch(() => null);
|
|
306
227
|
const manifest = item?.manifest.kind === 'image-model' ? item.manifest : undefined;
|
|
307
228
|
const { width, height } = resolveImageSize({
|
|
@@ -338,6 +259,7 @@ export function v1ImagesRoutes(ctx) {
|
|
|
338
259
|
prompt: request.prompt,
|
|
339
260
|
...(request.negative_prompt ? { negativePrompt: request.negative_prompt } : {}),
|
|
340
261
|
model: modelId,
|
|
262
|
+
...(style ? { loras: [{ id: style.id }] } : {}),
|
|
341
263
|
width,
|
|
342
264
|
height,
|
|
343
265
|
...(request.seed !== undefined ? { seed: request.seed } : {}),
|
|
@@ -2,7 +2,7 @@ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { GEZEL_API_GENERATION, GEZEL_API_GENERATION_FLOOR, GEZEL_VERSION, GezelConfigSchema, createLogger, getLogOutput, nowIso, setLogOutput, startSuspendMonitor, stopSuspendMonitor, } from '@bendyline/gezel';
|
|
4
4
|
import { BundledSource, CatalogService } from '@bendyline/gezel-catalog';
|
|
5
|
-
import { discoverNativeBinaries } from '@bendyline/gezel/native';
|
|
5
|
+
import { disableOnnxRuntimeTelemetry, discoverNativeBinaries } from '@bendyline/gezel/native';
|
|
6
6
|
import { gezelHome } from '@bendyline/gezel/paths';
|
|
7
7
|
import { Hono } from 'hono';
|
|
8
8
|
import { bodyLimit } from 'hono/body-limit';
|
|
@@ -37,6 +37,7 @@ import './http/auth-context.js';
|
|
|
37
37
|
const log = createLogger('inference');
|
|
38
38
|
/** Direct Fetch host: no listener, product store, background downloads or daemon discovery. */
|
|
39
39
|
export async function startService(options = {}) {
|
|
40
|
+
disableOnnxRuntimeTelemetry();
|
|
40
41
|
const previousLogOutput = getLogOutput();
|
|
41
42
|
const restoreLogOutput = () => setLogOutput(previousLogOutput);
|
|
42
43
|
if (options.logOutput)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Worker } from 'node:worker_threads';
|
|
9
9
|
import { createLogger } from '@bendyline/gezel';
|
|
10
|
+
import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
|
|
10
11
|
import { findServiceWorkerEntry } from '../utils/service-worker-entry.js';
|
|
11
12
|
import { createInProcessCatalogHost } from './catalog-host.js';
|
|
12
13
|
const log = createLogger('knowledge');
|
|
@@ -63,6 +64,7 @@ export function createWorkerCatalogHost() {
|
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
65
66
|
try {
|
|
67
|
+
disableOnnxRuntimeTelemetry();
|
|
66
68
|
const w = new Worker(entry);
|
|
67
69
|
w.on('message', (msg) => {
|
|
68
70
|
const p = pending.get(msg.id);
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
import { innermostErrorReason } from '@bendyline/gezel';
|
|
1
2
|
import { IdentityResponseSchema, certFingerprintFromPem, fingerprintOfPublicKeyPem, verifyIdentitySignature, } from '../remotes/identity.js';
|
|
2
3
|
import { createPinnedFetch } from '../remotes/pinned-fetch.js';
|
|
3
4
|
const MACHINE_REMOTE_ID = 'this-machine';
|
|
4
5
|
export async function inspectMachineRuntime(runtime) {
|
|
5
6
|
const fetchImpl = runtime.cert ? createPinnedFetch(runtime.cert) : fetch;
|
|
6
7
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
let response;
|
|
9
|
+
try {
|
|
10
|
+
response = await fetchImpl(`${runtime.baseUrl}/v1/identity`, {
|
|
11
|
+
signal: AbortSignal.timeout(5_000),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// Bare, this surfaced as `fetch failed` in chat bubbles that never
|
|
16
|
+
// mentioned a machine engine at all.
|
|
17
|
+
throw new Error(`Gezel's machine engine at ${runtime.baseUrl} isn't answering (${innermostErrorReason(error)})`, { cause: error });
|
|
18
|
+
}
|
|
10
19
|
if (!response.ok)
|
|
11
20
|
throw new Error(`identity returned HTTP ${response.status}`);
|
|
12
21
|
const identity = IdentityResponseSchema.parse(await response.json());
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { createLogger, embeddingProfileArtifacts, } from '@bendyline/gezel';
|
|
17
17
|
import { EmbedderUnavailableError, KNOWLEDGE_EMBEDDING_PROFILES, createProfileEmbedder, resolveTransformersModelOptions, verifyProfileArtifacts, } from '@bendyline/gezel-knowledge';
|
|
18
|
+
import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
|
|
18
19
|
import { HF_CACHE_DIR_ENV, ModelFileDownloadError, TRANSFORMERS_MODULE, ensureVerifiedModelFile, isCorruptTransformersCacheFailure, isMissingModule, loadTransformersModelWithCacheRecovery, pinTransformersCacheDir, } from '../transformers-cache.js';
|
|
19
20
|
export const log = createLogger('memory');
|
|
20
21
|
/**
|
|
@@ -236,6 +237,7 @@ export async function loadPipeline() {
|
|
|
236
237
|
// and throws "Unable to get model file path or buffer". The dir is
|
|
237
238
|
// passed via env so this works in the embed worker thread too, which
|
|
238
239
|
// inherits `process.env` but has no other view of the gezel home.
|
|
240
|
+
disableOnnxRuntimeTelemetry();
|
|
239
241
|
const cacheDir = process.env[HF_CACHE_DIR_ENV];
|
|
240
242
|
if (cacheDir)
|
|
241
243
|
await pinTransformersCacheDir(cacheDir);
|
|
@@ -348,6 +350,7 @@ async function verifyDaemonPipeline() {
|
|
|
348
350
|
}
|
|
349
351
|
}
|
|
350
352
|
async function defaultTransformersCacheDir() {
|
|
353
|
+
disableOnnxRuntimeTelemetry();
|
|
351
354
|
const { env } = await import('@huggingface/transformers');
|
|
352
355
|
// 4.x types it nullable: null only where there is no filesystem cache.
|
|
353
356
|
return env.cacheDir ?? '';
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { Worker } from 'node:worker_threads';
|
|
21
21
|
import { createLogger, sameVectorSpace } from '@bendyline/gezel';
|
|
22
|
+
import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
|
|
22
23
|
import { retireModelWorker } from '../utils/retire-model-worker.js';
|
|
23
24
|
import { findServiceWorkerEntry } from '../utils/service-worker-entry.js';
|
|
24
25
|
import { ModelWorkCancelledError, PipelineLoadError, cancelInFlightModelWork, daemonEmbedderPin, daemonEmbedderVerified, passageInstruction, queryInstruction, runEmbed, runProfileQueryEmbed, } from './embed-core.js';
|
|
@@ -124,6 +125,7 @@ function ensureWorker() {
|
|
|
124
125
|
return null;
|
|
125
126
|
}
|
|
126
127
|
try {
|
|
128
|
+
disableOnnxRuntimeTelemetry();
|
|
127
129
|
const w = new Worker(entry);
|
|
128
130
|
w.on('message', onMessage);
|
|
129
131
|
w.on('error', (err) => onWorkerDown(err instanceof Error ? err.message : String(err)));
|
package/dist/models/image-fit.js
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* The video half lives with the video engine in the service, which shares
|
|
12
12
|
* the helpers here.
|
|
13
13
|
*/
|
|
14
|
-
import { totalmem } from 'node:os';
|
|
15
14
|
import { MEDIA_MODEL_WONT_RUN_CODE, assessMediaFit, createLogger, imageMemoryRequirement, imageModelDefaultSide, resolveImageSize, } from '@bendyline/gezel';
|
|
16
15
|
import { isCudaArchivePath } from '@bendyline/gezel/native';
|
|
17
16
|
import { sdServerLoadProfile } from '../providers/image/sd-cpp.js';
|
|
18
17
|
import { configuredEngineMemoryBytes, staticCapacityCeilings, } from '../providers/native/device-capacity.js';
|
|
19
18
|
import { measuredCapacityBudget } from '../providers/native/measured-budget.js';
|
|
19
|
+
import { totalMemoryBytes } from '../system/host-memory.js';
|
|
20
20
|
import { detectMemoryProfileCached } from '../system/memory.js';
|
|
21
21
|
import { imageCatalogWeightFiles, sdCppMainComponent } from './weight-facts.js';
|
|
22
22
|
const log = createLogger('media-fit');
|
|
@@ -63,7 +63,7 @@ export function imageMediaPool(kind, host = {
|
|
|
63
63
|
/** The ceilings the device ledger applies on this machine, automatic and under the person's limit. */
|
|
64
64
|
export async function mediaCapacity(pool, readConfig) {
|
|
65
65
|
const budget = await measuredCapacityBudget();
|
|
66
|
-
const ram =
|
|
66
|
+
const ram = totalMemoryBytes();
|
|
67
67
|
const auto = staticCapacityCeilings(budget, ram);
|
|
68
68
|
const configured = configuredEngineMemoryBytes(await readConfig().catch(() => ({})));
|
|
69
69
|
const limited = configured === undefined ? undefined : staticCapacityCeilings(budget, ram, configured);
|
|
@@ -34,3 +34,8 @@ export declare function localSdCppWeightFiles(model: {
|
|
|
34
34
|
path: string;
|
|
35
35
|
}>;
|
|
36
36
|
}): Promise<MediaWeightFile[]>;
|
|
37
|
+
/** Weight files on disk, priced from their headers where they are safetensors. */
|
|
38
|
+
export declare function localWeightFiles(entries: ReadonlyArray<{
|
|
39
|
+
component: string;
|
|
40
|
+
path: string;
|
|
41
|
+
}>): Promise<MediaWeightFile[]>;
|
|
@@ -148,10 +148,13 @@ export function imageCatalogWeightFiles(manifest) {
|
|
|
148
148
|
}
|
|
149
149
|
/** The weight files of an installed sd-cpp model, headers read from disk. */
|
|
150
150
|
export async function localSdCppWeightFiles(model) {
|
|
151
|
-
|
|
151
|
+
return localWeightFiles([
|
|
152
152
|
{ component: sdCppMainComponent(model.weightsKind), path: model.weightsPath },
|
|
153
153
|
...model.auxiliaryFiles.map((a) => ({ component: a.role, path: a.path })),
|
|
154
|
-
];
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
/** Weight files on disk, priced from their headers where they are safetensors. */
|
|
157
|
+
export async function localWeightFiles(entries) {
|
|
155
158
|
const out = [];
|
|
156
159
|
for (const entry of entries) {
|
|
157
160
|
const format = weightFileFormat(entry.path);
|
|
@@ -123,7 +123,7 @@ export async function buildDs4Provider(opts) {
|
|
|
123
123
|
// This tier assumes DeepSeek-V4's small resident footprint. A model whose
|
|
124
124
|
// non-routed weights are much larger caps it further via the catalog's
|
|
125
125
|
// `ds4.maxLaunchCtx`; see `resolveDs4LaunchCtx` below.
|
|
126
|
-
const totalRamBytes = (await import('
|
|
126
|
+
const totalRamBytes = (await import('../../system/host-memory.js')).totalMemoryBytes();
|
|
127
127
|
const totalRamGb = totalRamBytes / 1024 ** 3;
|
|
128
128
|
const ramTieredCtx = ds4RamTieredContext(totalRamBytes);
|
|
129
129
|
const ds4ConstrainedToolNoSignalMs = (() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { totalmem } from 'node:os';
|
|
2
1
|
import { DS4_FULL_RESIDENCY_HEADROOM_BYTES, LLAMA_CPP_VISION_COMPUTE_BYTES, LLAMA_CPP_WEIGHTS_MULTIPLIER, ds4FitsFullResidency, } from '@bendyline/gezel';
|
|
2
|
+
import { totalMemoryBytes } from '../../system/host-memory.js';
|
|
3
3
|
const GB = 1024 ** 3;
|
|
4
4
|
export { DS4_FULL_RESIDENCY_HEADROOM_BYTES };
|
|
5
5
|
/**
|
|
@@ -43,7 +43,7 @@ export function ds4BaseResidentBytes(opts) {
|
|
|
43
43
|
export function canUseDs4FullResidency(opts = {}) {
|
|
44
44
|
return ds4FitsFullResidency({
|
|
45
45
|
modelSizeBytes: opts.modelSizeBytes,
|
|
46
|
-
totalRamBytes: opts.totalRamBytes ??
|
|
46
|
+
totalRamBytes: opts.totalRamBytes ?? totalMemoryBytes(),
|
|
47
47
|
companionBytes: opts.companionBytes,
|
|
48
48
|
platform: opts.platform ?? process.platform,
|
|
49
49
|
arch: opts.arch ?? process.arch,
|
|
@@ -82,7 +82,7 @@ export function shouldUseDs4SsdStreaming(opts = {}) {
|
|
|
82
82
|
* state + the cache cannot consume the runtime/OS reserve.
|
|
83
83
|
*/
|
|
84
84
|
export function planDs4ExpertCache(opts) {
|
|
85
|
-
const totalRamBytes = opts.totalRamBytes ??
|
|
85
|
+
const totalRamBytes = opts.totalRamBytes ?? totalMemoryBytes();
|
|
86
86
|
const catalogCacheBytes = opts.catalogCacheBytes ?? 0;
|
|
87
87
|
const fixedResidentBytes = Math.max(0, (opts.catalogResidentBytes ?? 0) - catalogCacheBytes);
|
|
88
88
|
const fallbackGb = totalRamBytes >= 120 * GB ? 64 : totalRamBytes >= 88 * GB ? 48 : 32;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-model
|
|
2
|
+
* Per-model image-editing capability for the local sd-cpp engine: whether a
|
|
3
|
+
* model re-noises a source (img2img), reads sources as references beside an
|
|
4
|
+
* instruction, or cannot use one. The img2img half first.
|
|
3
5
|
*
|
|
4
6
|
* stable-diffusion.cpp's `/sdapi/v1/img2img` endpoint accepts
|
|
5
7
|
* `init_images` for any loaded model, but whether the *architecture*
|
|
@@ -37,4 +39,37 @@ export interface Img2ImgSupportVerdict {
|
|
|
37
39
|
/** Human-readable reason, set only when unsupported. */
|
|
38
40
|
reason?: string;
|
|
39
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Models that edit by reading source pictures as references beside an
|
|
44
|
+
* instruction (sd-server's `extra_images`), rather than re-noising one. The
|
|
45
|
+
* same ladder as above: a catalog `referenceImages` declaration persisted at
|
|
46
|
+
* pull time wins, then this map, which is how installs that predate the
|
|
47
|
+
* field learn it.
|
|
48
|
+
*
|
|
49
|
+
* FLUX.2 Klein: verified on the pinned engine (native sd-cpp master-975) on
|
|
50
|
+
* an RTX Spark, 2026-10-27, recoloring, adding an object and composing two
|
|
51
|
+
* sources at 4 steps in 12 s.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MODEL_REFERENCE_EDIT_SUPPORT: Map<string, {
|
|
54
|
+
maxImages: number;
|
|
55
|
+
editOnly?: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
export interface ImageEditCapabilityInput extends Img2ImgSupportInput {
|
|
58
|
+
/** Explicit `referenceImages` from catalog/installed metadata. */
|
|
59
|
+
referenceImages?: {
|
|
60
|
+
max: number;
|
|
61
|
+
} | undefined;
|
|
62
|
+
/** Explicit `editOnly` from catalog/installed metadata. */
|
|
63
|
+
editOnly?: boolean | undefined;
|
|
64
|
+
}
|
|
65
|
+
export interface ImageEditCapability {
|
|
66
|
+
/** Absent when the model cannot use a source picture at all. */
|
|
67
|
+
editMode?: 'reference' | 'img2img';
|
|
68
|
+
/** Sources one edit takes: the reference limit, or 1 for img2img. */
|
|
69
|
+
maxSourceImages?: number;
|
|
70
|
+
editOnly: boolean;
|
|
71
|
+
/** Why the model cannot edit; set only when `editMode` is absent. */
|
|
72
|
+
reason?: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function resolveImageEditCapability(input: ImageEditCapabilityInput): ImageEditCapability;
|
|
40
75
|
export declare function resolveImg2ImgSupport(input: Img2ImgSupportInput): Img2ImgSupportVerdict;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-model
|
|
2
|
+
* Per-model image-editing capability for the local sd-cpp engine: whether a
|
|
3
|
+
* model re-noises a source (img2img), reads sources as references beside an
|
|
4
|
+
* instruction, or cannot use one. The img2img half first.
|
|
3
5
|
*
|
|
4
6
|
* stable-diffusion.cpp's `/sdapi/v1/img2img` endpoint accepts
|
|
5
7
|
* `init_images` for any loaded model, but whether the *architecture*
|
|
@@ -44,6 +46,33 @@ export const MODEL_IMG2IMG_SUPPORT = new Map([
|
|
|
44
46
|
['krea-2-turbo-q6', false],
|
|
45
47
|
['krea-2-turbo-q8', false],
|
|
46
48
|
]);
|
|
49
|
+
/**
|
|
50
|
+
* Models that edit by reading source pictures as references beside an
|
|
51
|
+
* instruction (sd-server's `extra_images`), rather than re-noising one. The
|
|
52
|
+
* same ladder as above: a catalog `referenceImages` declaration persisted at
|
|
53
|
+
* pull time wins, then this map, which is how installs that predate the
|
|
54
|
+
* field learn it.
|
|
55
|
+
*
|
|
56
|
+
* FLUX.2 Klein: verified on the pinned engine (native sd-cpp master-975) on
|
|
57
|
+
* an RTX Spark, 2026-10-27, recoloring, adding an object and composing two
|
|
58
|
+
* sources at 4 steps in 12 s.
|
|
59
|
+
*/
|
|
60
|
+
export const MODEL_REFERENCE_EDIT_SUPPORT = new Map([
|
|
61
|
+
['flux-2-klein-4b-q4', { maxImages: 3 }],
|
|
62
|
+
['qwen-image-edit-2511-q4', { maxImages: 3, editOnly: true }],
|
|
63
|
+
]);
|
|
64
|
+
export function resolveImageEditCapability(input) {
|
|
65
|
+
const assessed = input.modelId ? MODEL_REFERENCE_EDIT_SUPPORT.get(input.modelId) : undefined;
|
|
66
|
+
const editOnly = input.editOnly ?? assessed?.editOnly ?? false;
|
|
67
|
+
const maxImages = input.referenceImages?.max ?? assessed?.maxImages;
|
|
68
|
+
if (maxImages !== undefined) {
|
|
69
|
+
return { editMode: 'reference', maxSourceImages: maxImages, editOnly };
|
|
70
|
+
}
|
|
71
|
+
const img2img = resolveImg2ImgSupport(input);
|
|
72
|
+
if (img2img.supported)
|
|
73
|
+
return { editMode: 'img2img', maxSourceImages: 1, editOnly };
|
|
74
|
+
return { editOnly, ...(img2img.reason ? { reason: img2img.reason } : {}) };
|
|
75
|
+
}
|
|
47
76
|
export function resolveImg2ImgSupport(input) {
|
|
48
77
|
if (input.explicit !== undefined) {
|
|
49
78
|
return input.explicit
|
|
@@ -21,6 +21,7 @@ export interface LocalImageEngineOptions {
|
|
|
21
21
|
export declare class LocalImageEngine {
|
|
22
22
|
private readonly opts;
|
|
23
23
|
readonly pulls: ImageModelPullRegistry;
|
|
24
|
+
readonly loraPulls: ImageModelPullRegistry;
|
|
24
25
|
private readonly lifecycle;
|
|
25
26
|
constructor(opts: LocalImageEngineOptions);
|
|
26
27
|
/** Whether this host can render at all: an engine binary, a server URL, or the mock. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ProviderLifecycle } from '../provider-lifecycle.js';
|
|
2
2
|
import { createLocalImageProvider } from './local-factory.js';
|
|
3
|
-
import { ImageModelPullRegistry } from './pull-registry.js';
|
|
3
|
+
import { ImageModelPullRegistry, imageLoraPullResolver } from './pull-registry.js';
|
|
4
4
|
/**
|
|
5
5
|
* Hosted inference's image engine: the local sd-server provider, built on
|
|
6
6
|
* first use, plus the pulls that install its models. Reset drains in-flight
|
|
@@ -9,12 +9,17 @@ import { ImageModelPullRegistry } from './pull-registry.js';
|
|
|
9
9
|
export class LocalImageEngine {
|
|
10
10
|
opts;
|
|
11
11
|
pulls;
|
|
12
|
+
loraPulls;
|
|
12
13
|
// Only promise-returning operations can be tracked; a pull is an async
|
|
13
14
|
// iterable and is cancelled through its own registry instead.
|
|
14
|
-
lifecycle = new ProviderLifecycle(new Set(['generate', 'deleteModel']));
|
|
15
|
+
lifecycle = new ProviderLifecycle(new Set(['generate', 'deleteModel', 'deleteLora']));
|
|
15
16
|
constructor(opts) {
|
|
16
17
|
this.opts = opts;
|
|
17
18
|
this.pulls = new ImageModelPullRegistry({ imageProvider: this, catalog: opts.catalog });
|
|
19
|
+
this.loraPulls = new ImageModelPullRegistry({
|
|
20
|
+
resolve: imageLoraPullResolver(this, opts.catalog),
|
|
21
|
+
kind: 'image-lora',
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
/** Whether this host can render at all: an engine binary, a server URL, or the mock. */
|
|
20
25
|
available() {
|
|
@@ -42,6 +47,7 @@ export class LocalImageEngine {
|
|
|
42
47
|
}
|
|
43
48
|
async shutdown() {
|
|
44
49
|
this.pulls.clear();
|
|
50
|
+
this.loraPulls.clear();
|
|
45
51
|
await this.lifecycle.reset();
|
|
46
52
|
}
|
|
47
53
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* base URL (127.0.0.1:9081) and no supervisor. `health()` reports
|
|
16
16
|
* `not-configured` so the UI can say the engine is not installed.
|
|
17
17
|
*/
|
|
18
|
+
import type { MediaWeightFile } from '@bendyline/gezel';
|
|
18
19
|
import type { GpuArbiter } from '../gpu-arbiter.js';
|
|
19
20
|
import type { NativeCapacityOptions } from '../native/device-capacity.js';
|
|
20
21
|
import type { ImageProvider, InstalledImageModelInfo } from './types.js';
|
|
@@ -92,4 +93,12 @@ export interface ResolvedInstalledModel extends InstalledImageModelInfo {
|
|
|
92
93
|
* encoder runs once per image, so GPUs lose nothing. `--diffusion-fa` means
|
|
93
94
|
* `--flash-attn off,diffusion=on`, so that branch already leaves it off.
|
|
94
95
|
*/
|
|
95
|
-
export declare function buildSdServerArgs(model: ResolvedInstalledModel, port: number
|
|
96
|
+
export declare function buildSdServerArgs(model: ResolvedInstalledModel, port: number, opts?: {
|
|
97
|
+
loraDir?: string;
|
|
98
|
+
}): string[];
|
|
99
|
+
/**
|
|
100
|
+
* The LoRA files a render of `modelId` can hold at once (its fast mode and
|
|
101
|
+
* its largest style), priced like weights. Read when the engine launches, so
|
|
102
|
+
* a LoRA installed later counts from the next launch.
|
|
103
|
+
*/
|
|
104
|
+
export declare function launchedLoraWeightFiles(loraRoot: string, modelId: string): Promise<MediaWeightFile[]>;
|
|
@@ -15,18 +15,18 @@
|
|
|
15
15
|
* base URL (127.0.0.1:9081) and no supervisor. `health()` reports
|
|
16
16
|
* `not-configured` so the UI can say the engine is not installed.
|
|
17
17
|
*/
|
|
18
|
-
import { readFile } from 'node:fs/promises';
|
|
18
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { imageMediaPool, sdServerLedgerClaim, sdServerRequirement, } from '../../models/image-fit.js';
|
|
21
21
|
import { findModelRoot, listOverlayModelIds, modelStorageRoots, verifyReadOnlyModelPayload, } from '../../models/storage-roots.js';
|
|
22
|
-
import { localSdCppWeightFiles } from '../../models/weight-facts.js';
|
|
22
|
+
import { localSdCppWeightFiles, localWeightFiles } from '../../models/weight-facts.js';
|
|
23
23
|
import { pressureIdleGraceMs } from '../native/capacity-broker.js';
|
|
24
24
|
import { measuredCapacityBudget } from '../native/measured-budget.js';
|
|
25
25
|
import { pickFreePort } from '../native/port.js';
|
|
26
26
|
import { NativeEngineSupervisor } from '../native/supervisor.js';
|
|
27
27
|
import { patientFetch } from '../patient-fetch.js';
|
|
28
28
|
import { MockImageProvider } from './mock.js';
|
|
29
|
-
import { MODEL_SAMPLE_STEP_DEFAULTS, StableDiffusionCppProvider, sdServerLoadProfile, } from './sd-cpp.js';
|
|
29
|
+
import { MODEL_SAMPLE_STEP_DEFAULTS, StableDiffusionCppProvider, readInstalledLoras, sdServerLoadProfile, } from './sd-cpp.js';
|
|
30
30
|
/**
|
|
31
31
|
* Nothing to generate with: the person has to download a model first. Its
|
|
32
32
|
* own type so a route can answer with the fix, not a bare 500, which reads
|
|
@@ -80,6 +80,8 @@ export async function createLocalImageProvider(opts) {
|
|
|
80
80
|
// when it changes; resolveLaunch reads it on the next start.
|
|
81
81
|
const launchState = { modelId: undefined };
|
|
82
82
|
let launchedModel;
|
|
83
|
+
const loraRoots = modelStorageRoots({ home: opts.home, engine: 'sd-cpp-loras', env });
|
|
84
|
+
const loraDir = loraRoots.writableRoot;
|
|
83
85
|
const supervisor = new NativeEngineSupervisor({
|
|
84
86
|
capacity: {
|
|
85
87
|
...sdServerCapacity(opts.home),
|
|
@@ -93,7 +95,10 @@ export async function createLocalImageProvider(opts) {
|
|
|
93
95
|
const requirement = sdServerRequirement({
|
|
94
96
|
modelId: launchedModel.id,
|
|
95
97
|
weightsKind: launchedModel.weightsKind,
|
|
96
|
-
files:
|
|
98
|
+
files: [
|
|
99
|
+
...(await localSdCppWeightFiles(launchedModel)),
|
|
100
|
+
...(await launchedLoraWeightFiles(loraDir, launchedModel.id)),
|
|
101
|
+
],
|
|
97
102
|
pool: imageMediaPool(budget.kind),
|
|
98
103
|
});
|
|
99
104
|
return sdServerLedgerClaim(requirement, budget.fastBytes);
|
|
@@ -131,9 +136,11 @@ export async function createLocalImageProvider(opts) {
|
|
|
131
136
|
if (!model)
|
|
132
137
|
throw new NoLocalImageModelError();
|
|
133
138
|
launchedModel = model;
|
|
139
|
+
// sd-server ignores a LoRA folder that does not exist yet, then lists none.
|
|
140
|
+
await mkdir(loraDir, { recursive: true });
|
|
134
141
|
return {
|
|
135
142
|
command: binary,
|
|
136
|
-
args: buildSdServerArgs(model, port),
|
|
143
|
+
args: buildSdServerArgs(model, port, { loraDir }),
|
|
137
144
|
baseUrl: `http://127.0.0.1:${port}`,
|
|
138
145
|
};
|
|
139
146
|
},
|
|
@@ -146,6 +153,7 @@ export async function createLocalImageProvider(opts) {
|
|
|
146
153
|
storageRoots,
|
|
147
154
|
supervisor,
|
|
148
155
|
launchState,
|
|
156
|
+
loraRoots,
|
|
149
157
|
fetchImpl: patientFetch(),
|
|
150
158
|
...(defaultLocalModelId ? { defaultModelId: defaultLocalModelId } : {}),
|
|
151
159
|
...(opts.arbiter ? { arbiter: opts.arbiter } : {}),
|
|
@@ -239,7 +247,7 @@ async function findFirstInstalledModel(storageRoots) {
|
|
|
239
247
|
* encoder runs once per image, so GPUs lose nothing. `--diffusion-fa` means
|
|
240
248
|
* `--flash-attn off,diffusion=on`, so that branch already leaves it off.
|
|
241
249
|
*/
|
|
242
|
-
export function buildSdServerArgs(model, port) {
|
|
250
|
+
export function buildSdServerArgs(model, port, opts = {}) {
|
|
243
251
|
const args = [];
|
|
244
252
|
if (model.weightsKind === 'diffusion-model') {
|
|
245
253
|
args.push('--diffusion-model', model.weightsPath);
|
|
@@ -265,6 +273,25 @@ export function buildSdServerArgs(model, port) {
|
|
|
265
273
|
args.push('--vae-tiling');
|
|
266
274
|
if (profile.stepCache)
|
|
267
275
|
args.push('--cache-mode', profile.stepCache);
|
|
276
|
+
if (profile.modelArgs)
|
|
277
|
+
args.push('--model-args', profile.modelArgs);
|
|
278
|
+
// At runtime for every model: a LoRA then changes per request without
|
|
279
|
+
// touching the weights. In auto mode an unquantized checkpoint (SD, SDXL)
|
|
280
|
+
// would merge it into the weights instead.
|
|
281
|
+
if (opts.loraDir)
|
|
282
|
+
args.push('--lora-model-dir', opts.loraDir, '--lora-apply-mode', 'at_runtime');
|
|
268
283
|
args.push('--listen-ip', '127.0.0.1', '--listen-port', String(port));
|
|
269
284
|
return args;
|
|
270
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* The LoRA files a render of `modelId` can hold at once (its fast mode and
|
|
288
|
+
* its largest style), priced like weights. Read when the engine launches, so
|
|
289
|
+
* a LoRA installed later counts from the next launch.
|
|
290
|
+
*/
|
|
291
|
+
export async function launchedLoraWeightFiles(loraRoot, modelId) {
|
|
292
|
+
const fitting = (await readInstalledLoras(loraRoot)).filter((l) => l.baseModels.includes(modelId));
|
|
293
|
+
const largest = (purpose) => fitting
|
|
294
|
+
.filter((l) => l.purpose === purpose)
|
|
295
|
+
.sort((a, b) => b.approxSizeBytes - a.approxSizeBytes)[0];
|
|
296
|
+
return localWeightFiles([largest('speed'), largest('style')].flatMap((l) => l ? [{ component: 'lora', path: join(loraRoot, l.id, l.filename) }] : []));
|
|
297
|
+
}
|
|
@@ -4,19 +4,26 @@
|
|
|
4
4
|
* colour is derived from a hash of the prompt so different prompts yield
|
|
5
5
|
* visually different (but still trivially small) outputs.
|
|
6
6
|
*/
|
|
7
|
-
import type { ImageEngineHealth, ImageGenerationInput, ImageGenerationOutput, ImageModelPullEvent, ImageModelPullSpec, ImageProvider, InstalledImageModelInfo } from './types.js';
|
|
7
|
+
import type { ImageEngineHealth, ImageGenerationInput, ImageGenerationOutput, ImageLoraPullSpec, ImageModelPullEvent, ImageModelPullSpec, ImageProvider, InstalledImageLoraInfo, InstalledImageModelInfo } from './types.js';
|
|
8
8
|
export declare class MockImageProvider implements ImageProvider {
|
|
9
9
|
readonly name = "mock";
|
|
10
10
|
private readonly installed;
|
|
11
|
+
private readonly loras;
|
|
11
12
|
/** Internal counter driving deterministic seed generation on "random" seeds. */
|
|
12
13
|
private nextSeed;
|
|
13
14
|
constructor(opts?: {
|
|
14
15
|
preinstalled?: InstalledImageModelInfo[];
|
|
16
|
+
loras?: InstalledImageLoraInfo[];
|
|
15
17
|
});
|
|
16
18
|
/** Number of input images received on the most recent generate(). Tests use this. */
|
|
17
19
|
lastInputImageCount: number;
|
|
20
|
+
/** The most recent generate() input. Tests use this. */
|
|
21
|
+
lastInput: ImageGenerationInput | undefined;
|
|
18
22
|
generate(input: ImageGenerationInput): Promise<ImageGenerationOutput>;
|
|
19
23
|
listInstalledModels(): Promise<InstalledImageModelInfo[]>;
|
|
24
|
+
listLoras(): Promise<InstalledImageLoraInfo[]>;
|
|
25
|
+
pullLora(id: string, spec: ImageLoraPullSpec): AsyncIterable<ImageModelPullEvent>;
|
|
26
|
+
deleteLora(id: string): Promise<void>;
|
|
20
27
|
pullModel(id: string, spec: ImageModelPullSpec, signal?: AbortSignal): AsyncIterable<ImageModelPullEvent>;
|
|
21
28
|
deleteModel(id: string): Promise<void>;
|
|
22
29
|
health(): Promise<ImageEngineHealth>;
|