@gjsify/image-generation 0.64.28 → 0.64.30
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 +12 -0
- package/lib/esm/sdcpp-engine.js +1 -1
- package/lib/types/sdcpp-engine.d.ts +39 -6
- package/lib/types/sdcpp.d.ts +8 -4
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -72,6 +72,18 @@ The model entry extends `ModelEntry` with sd.cpp-specific roles:
|
|
|
72
72
|
|
|
73
73
|
`ImageRequest` carries `prompt`, optional `negativePrompt`, `width`, `height`, `steps`, `guidance`, `seed`, `sampler`, `scheduler`, `loras` and an `init` image for img2img.
|
|
74
74
|
|
|
75
|
+
### Files the person already has
|
|
76
|
+
|
|
77
|
+
For a checkpoint the person downloaded themselves, pass `local` instead of `store` and `model`. Nothing is fetched or verified:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const engine = sdCppImageGeneration({
|
|
81
|
+
local: { modelId: 'my-checkpoint', diffusionModel: '/models/sdxl.safetensors', vae: '/models/vae.safetensors' },
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`availability()` is `available` when every named file exists and `unavailable` otherwise, never `downloadable`. `create()` throws and names the missing files. `LocalImageModel` takes `modelId` and `diffusionModel` (both required), `vae`, `llm`, `backend`, `defaultWidth` / `defaultHeight` (512 when absent) and `footprintBytes`. The memory check runs only when `footprintBytes` is given.
|
|
86
|
+
|
|
75
87
|
**The GPU guard.** A Vulkan driver can hang a process instead of failing. Before going to the GPU, the engine leaves a pin in `gpuGuardDir` and removes it when the work finishes. A pin left over from a crash keeps the next run on the CPU and reports it through `onGpuPinned`. `gpu: 'on'` removes the pin.
|
|
76
88
|
|
|
77
89
|
### Bundling for GJS
|
package/lib/esm/sdcpp-engine.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{encodePng as e}from"./encode.js";import{currentTarget as t,hasPrebuiltBins as n,runningOnGjs as r}from"./sdcpp-bin.js";import{checkFit as
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{encodePng as e}from"./encode.js";import{currentTarget as t,hasPrebuiltBins as n,runningOnGjs as r}from"./sdcpp-bin.js";import{existsSync as i}from"node:fs";import{checkFit as a,createGpuGuard as o,defaultGuardDir as s,onBattery as c,readMachineMemory as l,systemProbe as u}from"@gjsify/machine-fit";const d={name:`sd.cpp`,locality:`local`};var GenerationAborted=class extends Error{constructor(){super(`Aborted`),this.name=`AbortError`}};function toNativeOptions(e,t,n){let r={prompt:e.prompt,width:t,height:n,seed:e.seed??Math.floor(Math.random()*2**32),sampleParams:{sampleSteps:e.steps??20,guidance:{txtCfg:e.guidance??7.5}}};return e.negativePrompt&&(r.negativePrompt=e.negativePrompt),e.sampler&&(r.sampleParams.sampleMethod=e.sampler),e.scheduler&&(r.sampleParams.scheduler=e.scheduler),e.loras?.length&&(r.loras=e.loras.map(e=>({path:e.path,multiplier:e.multiplier}))),e.init&&(r.initImage={width:e.init.width,height:e.init.height,channel:3,data:e.init.rgb},r.strength=e.init.strength),r}function contextOptions(e){let t={diffusionModelPath:e.diffusionModel};return e.vae&&(t.vaePath=e.vae),e.llm&&(t.llmPath=e.llm),e.backend&&(t.backend=e.backend),t}function toNativeContext(e,t){return contextOptions({diffusionModel:t[e.diffusionModel],vae:e.vae?t[e.vae]:void 0,llm:e.llm?t[e.llm]:void 0,backend:e.backend})}function storeSource(e){let{store:t}=e,n=t.require(e.model);return assertModelFiles(n),{modelId:n.modelId,backend:n.backend,footprintBytes:n.footprintBytes,width:n.defaultWidth,height:n.defaultHeight,availability:async()=>t.availability(n.id),async prepare(e){let r=await t.install(n.id,{monitor:e.monitor,signal:e.signal});return{diffusionModel:r.files[n.diffusionModel],vae:n.vae?r.files[n.vae]:void 0,llm:n.llm?r.files[n.llm]:void 0,backend:n.backend}}}}function missingFiles(e){return[e.diffusionModel,e.vae,e.llm].filter(e=>e!==void 0&&e!==``&&!i(e))}function localSource(e){if(!e.modelId)throw Error(`sd.cpp local model needs a modelId`);if(!e.diffusionModel)throw Error(`sd.cpp local model '${e.modelId}' needs a diffusionModel path`);return{modelId:e.modelId,backend:e.backend,footprintBytes:e.footprintBytes,width:e.defaultWidth??512,height:e.defaultHeight??512,availability:async()=>missingFiles(e).length===0?`available`:`unavailable`,async prepare(){let t=missingFiles(e);if(t.length>0)throw Error(`sd.cpp model '${e.modelId}' has missing files: ${t.join(`, `)}`);return{diffusionModel:e.diffusionModel,vae:e.vae,llm:e.llm,backend:e.backend}}}}function resolveSize(e,t){return{width:e.width??t.width,height:e.height??t.height}}let f=!1;function defaultOnGpuPinned(e){console.error(`The GPU is switched off for image generation: process ${e.pid} died while using it (${e.at}). Turn the GPU setting on to try it again.`)}function assertModelFiles(e){let t=new Set(e.files.map(e=>e.file));if(!t.has(e.diffusionModel))throw Error(`sd.cpp model '${e.id}' names '${e.diffusionModel}' as diffusionModel, but it is not in files`);if(e.vae&&!t.has(e.vae))throw Error(`sd.cpp model '${e.id}' names '${e.vae}' as vae, but it is not in files`);if(e.llm&&!t.has(e.llm))throw Error(`sd.cpp model '${e.id}' names '${e.llm}' as llm, but it is not in files`)}function isCpuOnly(e){return e===void 0?!1:e.split(`,`).every(e=>e.trim().toLowerCase()===`cpu`)}function createSdCppEngine(i,p){let m=`local`in p?localSource(p.local):storeSource(p),h=p.gpu??`auto`,g,gpuGuard=()=>g??=o({dir:p.gpuGuardDir??s(`gjsify`)}),gpuBarred=()=>{if(isCpuOnly(m.backend)||h===`off`)return!0;if(h===`on`)return gpuGuard().clear(),!1;let e=gpuGuard().pin();return e===void 0?!1:(f||(f=!0,(p.onGpuPinned??defaultOnGpuPinned)(e)),!0)};return{info:d,async availability(){let e=p.probe??u();if(c(e)||!n(t(e),r()))return`unavailable`;if(m.footprintBytes!==void 0){let t=l(e);if(a(m.footprintBytes,t).placement===`none`)return`unavailable`}return m.availability()},async create(t={}){let n=await m.prepare(t);t.signal?.throwIfAborted();let r,a,o=!1,open=async()=>{r??=await i();let e=gpuBarred(),t=contextOptions(n);e&&!t.backend&&(t.backend=`cpu`),a??=await r.StableDiffusionContext.create(t),o=!e};if(await open(),!a)throw Error(`sd.cpp context did not open`);let generate=async t=>{let{width:n,height:r}=resolveSize(t,m),i=toNativeOptions(t,n,r),o=(await a.generateImage(i))[0];if(!o)throw Error(`The engine returned no image.`);if(o.channel!==3)throw Error(`sd.cpp returned ${o.channel} channels, expected 3`);let s=new Uint8Array(o.data);return{blob:e({width:o.width,height:o.height,rgb:s}),width:o.width,height:o.height,seed:i.seed,modelId:m.modelId}};return{info:d,modelId:m.modelId,get backend(){return o?`gpu`:`cpu`},async generate(e,t={}){let{signal:n,onProgress:i}=t;if(n?.aborted)throw new GenerationAborted;let onAbort=()=>a.abort();n?.addEventListener(`abort`,onAbort,{once:!0});let work=async()=>(r.setProgressCallback(i?({step:e,steps:t,time:n})=>i({step:e,steps:t,seconds:n}):null),await generate(e));try{return o?await gpuGuard().during(work):await work()}catch(e){throw n?.aborted?new GenerationAborted:e}finally{n?.removeEventListener(`abort`,onAbort),r.setProgressCallback(null)}},async destroy(){a?.close(),a=void 0}}}}}export{GenerationAborted,d as SDCPP_ENGINE,createSdCppEngine,toNativeContext,toNativeOptions};
|
|
@@ -21,11 +21,8 @@ export interface ImageModelEntry extends ModelEntry {
|
|
|
21
21
|
/** Working set at defaultWidth/defaultHeight, measured, not derived from file size. */
|
|
22
22
|
readonly footprintBytes: number;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly store: ModelStore<E>;
|
|
27
|
-
/** Model id from the store; `create()` requires one. */
|
|
28
|
-
readonly model: string;
|
|
24
|
+
/** Fields every sd.cpp engine takes, whatever supplies the model files. */
|
|
25
|
+
export interface SdCppCommonOptions {
|
|
29
26
|
/** The person's GPU switch, e.g. `parseGpuSetting(env.MYAPP_GPU)`. `on` also lifts a pin. */
|
|
30
27
|
readonly gpu?: GpuSetting;
|
|
31
28
|
/**
|
|
@@ -38,6 +35,42 @@ export interface SdCppEngineOptions<E extends ImageModelEntry = ImageModelEntry>
|
|
|
38
35
|
/** The machine `availability()` asks. */
|
|
39
36
|
readonly probe?: MachineProbe;
|
|
40
37
|
}
|
|
38
|
+
/** The model comes from a {@link ModelStore}: fetched, verified and placed by the store. */
|
|
39
|
+
export interface SdCppEngineOptions<E extends ImageModelEntry = ImageModelEntry> extends SdCppCommonOptions {
|
|
40
|
+
/** A store that contains the model manifest. */
|
|
41
|
+
readonly store: ModelStore<E>;
|
|
42
|
+
/** Model id from the store; `create()` requires one. */
|
|
43
|
+
readonly model: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A model that already sits on disk, chosen by the person (a checkpoint they downloaded
|
|
47
|
+
* themselves). Nothing is fetched or verified: the files either exist or the engine is
|
|
48
|
+
* `unavailable`.
|
|
49
|
+
*/
|
|
50
|
+
export interface LocalImageModel {
|
|
51
|
+
/** What a gallery records next to the image (`ImageGenerator.modelId`). */
|
|
52
|
+
readonly modelId: string;
|
|
53
|
+
/** Path of the diffusion checkpoint. */
|
|
54
|
+
readonly diffusionModel: string;
|
|
55
|
+
/** Path of the VAE, if the model needs a separate one. */
|
|
56
|
+
readonly vae?: string;
|
|
57
|
+
/** Path of the text encoder / LLM, if the model family needs one. */
|
|
58
|
+
readonly llm?: string;
|
|
59
|
+
/** Per-module device assignment, e.g. `te=cpu` — see sd.cpp's docs/backend.md. */
|
|
60
|
+
readonly backend?: string;
|
|
61
|
+
/** Size used when a request names none. Default 512x512. */
|
|
62
|
+
readonly defaultWidth?: number;
|
|
63
|
+
readonly defaultHeight?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Working set at the default size. Optional: without it `availability()` cannot say the
|
|
66
|
+
* machine is too small, and the model simply tries to load.
|
|
67
|
+
*/
|
|
68
|
+
readonly footprintBytes?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface SdCppLocalOptions extends SdCppCommonOptions {
|
|
71
|
+
readonly local: LocalImageModel;
|
|
72
|
+
}
|
|
73
|
+
export type SdCppOptions<E extends ImageModelEntry = ImageModelEntry> = SdCppEngineOptions<E> | SdCppLocalOptions;
|
|
41
74
|
export interface NativeGenerateOptions {
|
|
42
75
|
prompt: string;
|
|
43
76
|
negativePrompt?: string;
|
|
@@ -94,4 +127,4 @@ export declare class GenerationAborted extends Error {
|
|
|
94
127
|
export declare function toNativeOptions(request: ImageRequest, width: number, height: number): NativeGenerateOptions;
|
|
95
128
|
export declare function toNativeContext(model: ImageModelEntry, files: Record<string, string>): Record<string, unknown>;
|
|
96
129
|
/** {@link sdCppImageGeneration} with the addon loader passed in. */
|
|
97
|
-
export declare function createSdCppEngine<E extends ImageModelEntry = ImageModelEntry>(loadAddon: () => Promise<SdAddon>, options:
|
|
130
|
+
export declare function createSdCppEngine<E extends ImageModelEntry = ImageModelEntry>(loadAddon: () => Promise<SdAddon>, options: SdCppOptions<E>): ImageEngineFactory;
|
package/lib/types/sdcpp.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ImageEngineFactory } from './engine.js';
|
|
2
|
-
import { type ImageModelEntry, type
|
|
3
|
-
export { SDCPP_ENGINE, type ImageModelEntry, type NativeGenerateOptions, type NativeImage, type SdAddon, type SdCppEngineOptions, toNativeContext, toNativeOptions, } from './sdcpp-engine.js';
|
|
2
|
+
import { type ImageModelEntry, type SdCppOptions } from './sdcpp-engine.js';
|
|
3
|
+
export { SDCPP_ENGINE, type ImageModelEntry, type LocalImageModel, type NativeGenerateOptions, type NativeImage, type SdAddon, type SdCppCommonOptions, type SdCppEngineOptions, type SdCppLocalOptions, type SdCppOptions, toNativeContext, toNativeOptions, } from './sdcpp-engine.js';
|
|
4
4
|
export { addonDir, addonPath, AddonMissingError, backendCandidates, currentTarget, findAddon, hasPrebuiltBins, runningOnGjs, } from './sdcpp-bin.js';
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The sd.cpp image-generation engine factory, for `ImageGenerator.availability()` and
|
|
7
|
+
* `ImageGenerator.create()`. Pass `{ store, model }` for a fetched model or `{ local }` for
|
|
8
|
+
* files the person already has.
|
|
9
|
+
*/
|
|
10
|
+
export declare function sdCppImageGeneration<E extends ImageModelEntry = ImageModelEntry>(options: SdCppOptions<E>): ImageEngineFactory;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/image-generation",
|
|
3
|
-
"version": "0.64.
|
|
3
|
+
"version": "0.64.30",
|
|
4
4
|
"description": "Image generation in the WebML CG lifecycle shape: an ImageGenerator with availability(), create({ monitor, signal }) and destroy(), and a local stable-diffusion.cpp engine for diffusion models on Node and GJS.",
|
|
5
5
|
"module": "lib/esm/index.js",
|
|
6
6
|
"types": "lib/types/index.d.ts",
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
"webml"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@gjsify/buffer": "^0.64.
|
|
50
|
-
"@gjsify/fs": "^0.64.
|
|
51
|
-
"@gjsify/machine-fit": "^0.64.
|
|
52
|
-
"@gjsify/model-store": "^0.64.
|
|
53
|
-
"@gjsify/path": "^0.64.
|
|
54
|
-
"@gjsify/process": "^0.64.
|
|
55
|
-
"@gjsify/zlib": "^0.64.
|
|
49
|
+
"@gjsify/buffer": "^0.64.30",
|
|
50
|
+
"@gjsify/fs": "^0.64.30",
|
|
51
|
+
"@gjsify/machine-fit": "^0.64.30",
|
|
52
|
+
"@gjsify/model-store": "^0.64.30",
|
|
53
|
+
"@gjsify/path": "^0.64.30",
|
|
54
|
+
"@gjsify/process": "^0.64.30",
|
|
55
|
+
"@gjsify/zlib": "^0.64.30"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@gjsify/napi": "^0.64.19"
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@gjsify/cli": "^0.64.
|
|
67
|
-
"@gjsify/unit": "^0.64.
|
|
66
|
+
"@gjsify/cli": "^0.64.30",
|
|
67
|
+
"@gjsify/unit": "^0.64.30",
|
|
68
68
|
"@types/node": "^25.9.2",
|
|
69
69
|
"typescript": "^6.0.3"
|
|
70
70
|
},
|