@qvac/diffusion-cpp
v0.35.1
Published
stable-diffusion.cpp addon for qvac image/video generation
Readme
diffusion-cpp
Native C++ addon for image, video, interactive world-walk, and ESRGAN inference through qvac-ext-stable-diffusion.cpp, built for the Bare Runtime.
The package exposes four JS entry points:
| API | Entry point | Use case |
| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------- |
| ImgStableDiffusion | @qvac/diffusion-cpp | Text-to-image, image-to-image, FLUX.2 reference fusion, optional ESRGAN upscale |
| VideoStableDiffusion | @qvac/diffusion-cpp/video or named export | Wan, LTX, and MiniMax-H3 text-to-video / image-to-video |
| WorldStableDiffusion | @qvac/diffusion-cpp/world | ABot-World interactive walk: block-by-block generation under keyboard input |
| EsrganUpscaler | named export from @qvac/diffusion-cpp | Standalone PNG/JPEG upscaling |
Table of Contents
- Supported Models
- Supported Platforms
- Platform packages
- Building From Source
- Downloading Models
- Examples
- Image API
- Image-to-Image and FLUX.2 Fusion
- Video API
- MiniMax-H3 Text-to-Video and Image-to-Video
- LTX-2 Text-to-Video With Audio
- ABot-World Interactive Walk
- ESRGAN Upscaler
- Response Streams and Stats
- Cancellation and Unload
- Assessing fit
- Operational Notes
- Credits
- License
Supported Models
| Family | Mode | Notes |
| ---------------------------- | -------------------------------------- | --------------------------------------------------------------------------------- |
| SD1.x / SD2.x | image | All-in-one checkpoints through files.model |
| SDXL | image | All-in-one checkpoints or split encoders when required |
| SD3 | image | Supports split CLIP-L / CLIP-G / T5-XXL inputs |
| FLUX.2 [klein] | image, img2img, multi-reference fusion | Split diffusion + Qwen3 LLM + VAE |
| FLUX.3 Image Klein | image, reference editing | Split diffusion + Qwen3-VL 4B + FLUX.2 VAE; see FLUX.3 setup |
| Wan 2.1 | text-to-video, image-to-video | Single diffusion expert; I2V requires CLIP vision |
| Wan 2.2 TI2V-5B Turbo Q5_K_S | text-to-video | Community-distilled GGUF with Wan 2.2 VAE; use scripts/download-model-wan2.2.sh |
| LTX-2 / LTXAV | text-to-video + audio | Gemma text encoder, video VAE, audio VAE, embedding connectors |
| MiniMax-H3 FL2VA ConvRot | text-to-video, image-to-video + audio | Qwen3-VL text encoder, video VAE, audio VAE; see H3 setup |
| ABot-World | interactive world walk | Causal block-by-block generation under keyboard input; see ABot-World guide |
| ESRGAN | upscale | Standalone or post-generation image upscale |
Qwen-Image-2.1 integration
See Qwen-Image-2.1 for the native registry port, pinned downloads, text-to-image, ordered reference editing, and RGBA handling.
FLUX.3 setup
See FLUX.3 Image Klein for pinned, checksum-verified downloads, the shared MOSS registry baseline, and text-to-image and reference-editing examples.
MOSS-Image setup
Download the pinned SFT diffusion model, Qwen3.5 encoder, small FLUX.2 VAE, and external tokenizer with checksum verification:
bash scripts/download-model-moss.shThe default directory is models/moss-image; override it with
MOSS_MODELS_DIR. Pass absolute paths to the image constructor:
const files = {
model: '/absolute/models/moss-image/moss_image_sft_bf16.safetensors',
llm: '/absolute/models/moss-image/Qwen3.5-9B-Q4_K_M.gguf',
vae: '/absolute/models/moss-image/flux2-vae_small.safetensors',
tokenizer: '/absolute/models/moss-image/tokenizer.json'
}files.tokenizer is passed to the native engine for both loading and fit
assessment. Use Euler sampling, the discrete scheduler, CFG 3, and 50 steps
as the initial SFT settings. Layout prompts can be supplied as a JSON string.
This requires a native addon rebuilt with the 2026-11-01 registry ports;
previously published native binaries do not expose the tokenizer option.
Supported Platforms
| Platform | Architecture | Status | GPU backend | | -------- | ------------ | ------ | -------------- | | macOS | arm64, x64 | Tier 1 | Metal | | Linux | arm64 | Tier 1 | Vulkan, CUDA | | Linux | x64 | Tier 1 | Vulkan, CUDA | | Android | arm64 | Tier 1 | Vulkan, OpenCL | | iOS | arm64 | Tier 1 | Metal | | Windows | x64 | Tier 1 | Vulkan |
Dependencies:
qvac-ext-stable-diffusion.cppggml- Bare Runtime >= 1.24.0
- CMake >= 3.25 and a C++20-capable compiler
Platform packages
@qvac/diffusion-cpp is a meta package that ships the JavaScript wrapper only.
The native prebuild for each desktop host lives in a version-locked platform
package selected at install time through os/cpu filtered
optionalDependencies:
| Host | Package |
| --- | --- |
| linux-x64 (glibc) | @qvac/diffusion-cpp-linux-x64 |
| linux-arm64 (glibc) | @qvac/diffusion-cpp-linux-arm64 |
| darwin-arm64 | @qvac/diffusion-cpp-darwin-arm64 |
| darwin-x64 | @qvac/diffusion-cpp-darwin-x64 |
| win32-x64 | @qvac/diffusion-cpp-win32-x64 |
Do not depend on desktop platform packages directly. Supported installers are
npm 7+, pnpm, bun, and Yarn Berry. Yarn v1 and --omit=optional installs skip
the platform package and fail at require time, and at bare-pack time, naming
the missing package. Each platform package is an ordinary Bare addon whose
prebuilds/<host>/ holds the native module and, next to it, the dynamically
loaded ggml backends the module finds on its own. A source build becomes
loadable once npm run link:platform (run by npm run build, build:vulkan,
and build:cuda) stages it as the host's platform package in node_modules/.
Mobile targets are cross-built, so no install host ever matches their os,
and optionalDependencies filtering can never select them. Mobile
applications must declare the target's platform package as a direct
dependency, pinned to the exact @qvac/diffusion-cpp version:
| Target | Package |
| --- | --- |
| android-arm64 | @qvac/diffusion-cpp-android-arm64 |
| ios (device + simulators) | @qvac/diffusion-cpp-ios |
Building From Source
See build.md for prerequisites, platform setup, cross-compilation, and troubleshooting.
npm install -g bare bare-make
npm install
npm run buildCUDA is opt-in for source builds. Ordinary Linux and Windows builds use Vulkan and CPU
without requiring a CUDA toolkit. To include CUDA, install the toolkit and run
the explicit build command below. Linux and Windows release prebuilds enable SD_CUDA
through platformCmakeDefines. The QVAC_CUDA_TOOLKIT marker exported by CI
setup also enables CUDA for Linux builds without tests or fuzzing, so automatic
PR builds work before the updated build metadata reaches main. An explicit
-D SD_CUDA=OFF overrides this default. CI provisions the toolkit with
includeCuda, includeCudaArm64, and includeCudaWin32.
npm run build:cudaLinux ARM64/x64 and Windows x64 platform packages include dynamically loaded CUDA modules.
Installing @qvac/diffusion-cpp selects @qvac/diffusion-cpp-linux-arm64
on DGX Spark or @qvac/diffusion-cpp-linux-x64 on an x64 PC. Each platform
package carries its host addon and the CUDA and Vulkan backend modules.
CUDA execution requires the NVIDIA driver and CUDA 13 runtime/cuBLAS
libraries on the host; the addon itself retains CPU-only load compatibility.
Windows uses @qvac/diffusion-cpp-win32-x64, with
qvac-diffusion-ggml-cuda.dll alongside the Vulkan DLL. CUDA 13 runtime
and cuBLAS DLLs must be discoverable on PATH (for example, from the CUDA
toolkit installation); they are not bundled in the platform package.
The server CUDA build uses CUDA 13.0.3 (nvcc 13.0.88) and matches qvac-fabric's packaged architectures for each platform:
| Platform package | CUDA architectures |
| --- | --- |
| Linux/Windows x64 | 75-virtual;80-real;86-real;89-real;90-virtual;120a-real |
| Linux ARM64 | 121a-real |
virtual targets carry PTX that the driver compiles on first use; real
targets carry native GPU code. The x64 module includes T4 PTX, native A100
code, and RTX 5090 code. The ARM64 CUDA module ships DGX Spark code only to
keep the npm platform package within its size limit. The host addon must
also match the CPU architecture. This package does not ship a separate
Jetson CUDA module.
Any non-empty QVAC_CUDA_TOOLKIT marker requires nvcc 13.0.88; its text
is hashed into the build's cache identity. Unmarked source builds with another
compiler version warn and continue; nvcc rejects unsupported architectures.
CUDACXX selects the compiler explicitly. Otherwise discovery follows Fabric:
CUDA_PATH hints, then the default search including PATH, then
/usr/local/cuda. Compiler paths are kept through VCPKG_KEEP_ENV_VARS,
which the shared setup-cuda action exports.
With automatic backend selection, CUDA devices whose code cannot run are
skipped, allowing Vulkan or ultimately CPU to be selected. Auto-fit prefers
a dedicated GPU with a positive memory budget, falling back to integrated
GPUs when none is available. Within each class, the largest available budget
wins; shared system memory does not make an integrated GPU outrank a dedicated
GPU. Explicit backend assignments remain unchanged. Set
GGML_DISABLE_CUDA=1 before starting the process to disable CUDA, or use
backend: 'vulkan0' to select Vulkan explicitly. An explicit
backend: 'cuda0' requires that CUDA device and fails if it is unavailable.
Diffusion's comma-separated backend syntax assigns modules (for example,
diffusion=cuda0,te=cpu); it is not an ordered fallback list.
To verify automatic selection on a CUDA-capable host, run these from this package directory after building the addon. Each command starts a fresh process, generates an SD2 image, and checks the engine's resolved backend:
QVAC_EXPECT_BACKEND=CUDA bare test/integration/backend-fallback.js --exit
GGML_DISABLE_CUDA=1 QVAC_EXPECT_BACKEND=Vulkan bare test/integration/backend-fallback.js --exit
GGML_DISABLE_CUDA=1 GGML_DISABLE_VULKAN=1 QVAC_EXPECT_BACKEND=CPU bare test/integration/backend-fallback.js --exitSet SD_TEST_MODEL_PATH to an existing SD2 Q8_0 GGUF to avoid downloading it.
Set QVAC_TEST_BACKENDS_DIR to test modules from a separate build, including
a directory containing only Vulkan or a CUDA module incompatible with the GPU.
Downloading Models
Download scripts populate packages/diffusion-cpp/models/ with the files used
by the examples.
| Script | Model set |
| ------------------------------------- | ----------------------------- |
| ./scripts/download-model.sh | FLUX.2 [klein] 4B image model |
| ./scripts/download-model-sd2.sh | SD2.x example model |
| ./scripts/download-model-sd3.sh | SD3 example files |
| ./scripts/download-model-sdxl.sh | SDXL example files |
| ./scripts/download-model-wan.sh | Wan 2.1 T2V 1.3B |
| ./scripts/download-model-wan-14b.sh | Wan larger T2V variant |
| ./scripts/download-model-wan-i2v.sh | Wan 2.1 I2V 14B + CLIP vision |
| ./scripts/download-model-ltx.sh | LTX-2.3 video + audio files |
| ./scripts/download-minimax-h3-convrot.sh | MiniMax-H3 FL2VA ConvRot T2V/I2V files |
| ./scripts/download-model-abot.sh | ABot-World set into test/model/abot (from the P2P model registry, no credentials) |
The FLUX.2 [klein] default image example uses:
| Role | File |
| --------------- | --------------------------- |
| Diffusion model | flux-2-klein-4b-Q8_0.gguf |
| Text encoder | Qwen3-4B-Q4_K_M.gguf |
| VAE | flux2-vae.safetensors |
LTX-2.3 requires more companion files:
| Role | Default file |
| -------------------- | --------------------------------------------------------- |
| Diffusion model | LTX-2.3-22B-distilled-1.1-Q5_K_M.gguf |
| Text encoder | gemma-3-12b-it-UD-Q4_K_XL.gguf |
| Video VAE | ltx-2.3-22b-distilled_video_vae.safetensors |
| Audio VAE | ltx-2.3-22b-distilled_audio_vae.safetensors |
| Embedding connectors | ltx-2.3-22b-distilled_embeddings_connectors.safetensors |
Downloads are resumable where supported by the script.
Examples
| Command | Description |
| -------------------------------------------- | ------------------------------------------- |
| npm run example | Load/unload the default FLUX.2 model |
| npm run generate | FLUX.2 text-to-image |
| npm run generate:flux2-cuda | Distilled Klein 4B on Spark / RTX 5090 CUDA |
| npm run generate:sd2 | SD2.x text-to-image |
| npm run generate:sd3 | SD3 text-to-image |
| npm run generate:sdxl | SDXL text-to-image |
| bare examples/generate-fusion.js | FLUX.2 multi-reference fusion |
| bare examples/img2img-flux2.js | FLUX.2 single-reference img2img |
| bare examples/img2img-sd3.js | SD3 SDEdit img2img |
| bare examples/generate-video-wan.js | Wan 2.1 text-to-video |
| bare examples/img2vid-wan.js | Wan 2.1 image-to-video |
| npm run generate:video | Wan text-to-video |
| npm run generate:ltx | LTX-2.3 text-to-video with audio |
| npm run generate:ltx-coffee | 9-second LTX Ingredients coffee example |
| bare examples/generate-video-minimax-h3-comfy-t2v.js | MiniMax-H3 text-to-video with audio |
| bare examples/generate-video-minimax-h3-comfy-i2v.js | MiniMax-H3 image-to-video with audio |
| npm run generate:esrgan | Image generation followed by ESRGAN upscale |
| bare examples/standalone-esrgan-upscale.js | Standalone ESRGAN upscale |
| npm run walk:world | ABot-World browser demo (generate + walk a world) |
Outputs are written to packages/diffusion-cpp/output/.
Distilled FLUX.2 Klein 4B on DGX Spark and RTX 5090
npm run generate:flux2-cuda uses four Euler steps, cfg_scale: 1,
guidance: 1, and the engine's automatic FLUX.2 noise schedule. The fields
have separate meanings: cfg_scale controls text classifier-free guidance,
while guidance supplies the model's optional guidance embedding. Klein 4B
has no guidance embedding, so text CFG is the field that must be set to 1.
The existing generic image example's 20-step settings are not the distilled
four-step recipe.
The CUDA example selects cuda0, keeps weights resident there, enables
diffusion flash attention, and sets vae_conv_direct: false. On both GPUs, the
existing im2col/matrix-multiplication path substantially reduces VAE decode
time compared with direct convolution. This trades additional graph memory
for lower latency; use the measured recipe when memory permits.
With four steps, diffusion Q8_0 and Qwen3 Q4_K_M, measured mean request-to-returned-PNG latency was:
| GPU | Native output size | Direct VAE, mean ± SD | Im2col VAE, mean ± SD | Speedup | | --- | --- | ---: | ---: | ---: | | DGX Spark GB10 | 864×480 | 12.574 ± 0.055 s | 2.865 ± 0.005 s | 4.39× | | DGX Spark GB10 | 1024×1024 | 32.981 ± 0.075 s | 7.392 ± 0.010 s | 4.46× | | RTX 5090 | 864×480 | 2.835 ± 0.006 s | 0.590 ± 0.002 s | 4.81× | | RTX 5090 | 1024×1024 | 7.383 ± 0.035 s | 1.404 ± 0.003 s | 5.26× |
Each cell summarizes 12 formal requests: three prompts, seeds 0/43, two
repeats, following a separate warmup at each resolution. Loading and
warmups are excluded. Timers include fresh prompt encoding through returned
PNG, and exclude subsequent sample-file writes. Both variants use the same
settings and model files on each machine, changing only vae_conv_direct.
The Spark measurements used diffusion-cpp 0.28.1, engine commit
8b3efc68c64befbf01ccc2ac9816ee1cde4dda4f, ggml commit
9a7d2b36e96a198c1c67c013cafcde4e4c2c60eb, LLVM 22, CUDA 13.3, and driver
580.178.04 on one DGX Spark.
The RTX 5090 measurements used one 32 GB GPU, diffusion-cpp 0.29.2, engine
commit 886c66f60aca623145da6ec6261b02e5728ac109, ggml commit
9f94f330ac196e0a2e3176e798b36226b22091a5, Clang 20.1.8, statically linked
libc++ 19, CUDA 13.3.73, and driver 595.71.05. The Linux x64 source build
contains native SM120a cubins. A second 5090 was idle and was not used.
At 1024×1024, mean VAE decode dropped from 6.253 s to 0.276 s while
denoising stayed approximately 0.95 s.
The published build uses the pinned CI toolchain. These measurements use different source/toolchain revisions across machines, so they establish the within-machine VAE speedup rather than a controlled hardware ranking or a guarantee for every release or prompt.
Each machine produced 24 matched image pairs. Worst RGB RMSE on the 0–255 scale was 0.295 on Spark and 0.298 on RTX 5090.
Download the revision-pinned bundle with
./scripts/download-model-flux2-ingredients.sh; that script requires an
authorized HF_TOKEN for the VAE. Set FLUX_MODELS_DIR when assets are in a
different directory. H3's Qwen3-VL encoder is not the FLUX.2 Qwen3-4B encoder.
FLUX_WIDTH=864 FLUX_HEIGHT=480 npm run generate:flux2-cuda
FLUX_WIDTH=1024 FLUX_HEIGHT=1024 FLUX_IMAGES=3 npm run generate:flux2-cudaThe same commands work on either machine; generate:flux2-spark remains
an alias. On a multi-GPU host, CUDA_VISIBLE_DEVICES=0 limits the example
to the first card. FLUX_PROMPT, FLUX_SEED, and FLUX_OUTPUT_DIR
customize the requests.
FLUX_IMAGES issues sequential requests through one loaded model; every
request freshly encodes its prompt. The example logs model loading,
request-to-returned-PNG latency, progress, and native phase statistics, and
unloads in finally. conditionerMs includes pre-sampler setup;
postProcessMs includes PNG encoding and output callbacks. Neither is an
isolated encoder timer. Cancellation uses model.cancel() and rejects the
active response; a cancelled request supplies no partial PNG.
Image API
const path = require('bare-path')
const ImgStableDiffusion = require('@qvac/diffusion-cpp')
const MODELS_DIR = path.resolve(__dirname, './models')
const model = new ImgStableDiffusion({
files: {
model: path.join(MODELS_DIR, 'flux-2-klein-4b-Q8_0.gguf'),
llm: path.join(MODELS_DIR, 'Qwen3-4B-Q4_K_M.gguf'),
vae: path.join(MODELS_DIR, 'flux2-vae.safetensors')
},
config: {
threads: 4,
device: 'gpu',
diffusion_fa: true
},
opts: { stats: true },
logger: console
})
await model.load()
const response = await model.run({
prompt: 'a majestic red fox in a snowy forest, golden light, photorealistic',
width: 512,
height: 512,
steps: 20,
guidance: 3.5,
seed: 42
})
const images = []
await response
.onUpdate((data) => {
if (data instanceof Uint8Array) images.push(data) // PNG bytes
})
.await()
require('bare-fs').writeFileSync('output.png', images[0])
await model.unload()Constructor Files
All file paths must be absolute.
| Key | Required | Description |
| ------------------------------- | -------: | --------------------------------------------------------------------------- |
| files.model | yes | Main model. All-in-one checkpoint for SD, diffusion model for split layouts |
| files.clipL | no | CLIP-L text encoder for SD3 / split layouts |
| files.clipG | no | CLIP-G text encoder for SDXL / SD3 |
| files.t5Xxl | no | T5-XXL text encoder for SD3 |
| files.llm | no | Qwen3 LLM text encoder for FLUX.2 [klein] |
| files.vae | no | Separate VAE |
| files.esrgan | no | ESRGAN model for post-generation upscale |
| files.highNoiseDiffusionModel | no | Wan 2.2 high-noise expert path; normally used by VideoStableDiffusion |
Passing any separate text encoder (llm, t5Xxl, clipL, clipG) makes the
wrapper route files.model to stable-diffusion.cpp's diffusion_model_path.
All-in-one checkpoints are routed to model_path.
Image Config
config is part of the constructor object. There is no second constructor
argument.
| Key | Type | Default | Description |
| ----------------------- | ----------------------------------------- | ----------------- | ------------------------------------------------------------------------- |
| threads | number | auto | CPU threads for loading / CPU ops |
| device | 'gpu' | 'cpu' | 'gpu' | Prefer GPU backends or force CPU |
| main-gpu | number | 'integrated' | 'dedicated' | unset | Pin the GPU selected by stable-diffusion.cpp |
| type | weight type | auto | Override weight quantization |
| rng | 'cpu' | 'cuda' | 'std_default' | 'cuda' | Context RNG; cuda means Philox and is not GPU-specific |
| sampler_rng | RNG type | auto | Sampler RNG override |
| vae_decode_only | boolean | false | Load only VAE decoder weights; leave false for img2img/fusion/hires paths |
| vae_tiling | boolean | false | Tile VAE decode to reduce peak VRAM |
| flash_attn | boolean | false | Enable flash attention globally |
| diffusion_fa | boolean | true | Enable diffusion-model flash attention; important for FLUX/LTX memory use |
| mmap | boolean | backend default | Memory-map weights when supported |
| offload_to_cpu | boolean | backend default | Keep weights on CPU/offload as supported by backend |
| backend | string | auto | Runtime backend for all modules or per-module assignments |
| params_backend | string | runtime backend | Parameter residency on a backend, CPU RAM, or disk |
| max_vram | number | string | 0 | VRAM budget in GiB for graph-cut segmented execution |
| stream_layers | boolean | false | Stream diffusion layers from CPU RAM when graph cutting is active |
| prediction | prediction type | auto | Recommended for FLUX.2 sizing and prompt warnings: 'flux2_flow'; leave automatic for Qwen |
| flow_shift | number | model default | Flow-matching noise schedule shift |
| diffusion_conv_direct | boolean | true | Use direct convolution in diffusion model |
| vae_conv_direct | boolean | true | Use direct convolution in VAE |
| backendsDir | string | beside the loaded module | Directory of ggml backend modules, scanned as given |
| lora_apply_mode | string | auto | LoRA application mode |
| upscaler_tile_size | number | 128 | ESRGAN tile size |
| max_image_pixels | positive integer | 67108864 | Maximum decoded input or ESRGAN output pixels per image |
| max_job_pixels | positive integer | 134217728 | Maximum decoded input pixels retained by a multi-image or video job |
main-gpu is resolved against the addon's own ggml device enumeration and then
pinned through sd_ctx_params_t.backend. If an explicit request cannot be
satisfied ('integrated' with no integrated GPU, 'dedicated' with no discrete
GPU, or an out-of-range index), the addon falls back to CPU instead of silently
choosing another GPU. Mobile targets reject main-gpu because they are
single-GPU devices. An explicit backend assignment takes precedence over
main-gpu.
backend controls where graphs execute. params_backend controls where model
weights remain between uses. For example, backend: 'diffusion=cuda0,te=cpu'
runs the diffusion model on CUDA and the text encoder on CPU.
params_backend: 'diffusion=cpu' keeps diffusion weights in CPU RAM and stages
them to the runtime backend. params_backend: 'diffusion=disk' reloads those
weights from the model file on demand and releases them after use — on every
job, not only the first — so a disk-backed module also disables eager weight
loading for the whole context. Disk is not selected automatically.
offload_to_cpu: true supplies a *=cpu default. An explicit params_backend
entry written in module=backend form overrides that default for that module
only: params_backend: 'te=disk' with offload_to_cpu: true keeps TE weights
on disk while other parameters remain in CPU RAM.
A bare entry or an assignment to *, all, or default sets the whole-spec
default rather than a per-module override, and the last default wins. For
example, params_backend: 'cuda0' with offload_to_cpu: true puts every module
on cuda0 and offloads nothing. The addon logs when the final whole-spec default
differs from CPU. Equivalent defaults such as all=cpu do not produce an error.
Write params_backend: 'diffusion=cuda0' to move one module and leave the rest
offloaded.
A nonzero max_vram enables graph-cut segmentation even without
stream_layers. Positive values cap the VRAM budget in GiB. Negative values
use detected free VRAM while reserving the absolute value as headroom, and 0
disables graph cutting. Backend assignments such as
max_vram: 'cuda0=6,vulkan0=4' apply one budget per device.
stream_layers adds diffusion-layer prefetch and eviction only when graph
cutting is active and the diffusion parameter backend is CPU, for example:
config: {
backend: 'cuda0',
params_backend: 'diffusion=cpu',
max_vram: -1,
stream_layers: true
}It does not stream from disk. Use params_backend: 'diffusion=disk' for
on-demand reads from the model file.
stream_layers is forwarded to the engine as configured; the engine itself
skips streaming when its prerequisites are unmet. The addon reports the cases
it can prove before engine initialization: max_vram is unset, 0, or an
all-zero assignment such as 'cuda0=0'. Per-backend and automatic negative
budgets are resolved by the engine using the selected runtime backend and its
free memory. That message, and the main-gpu and params_backend notices above,
are the only diagnostics emitted at the default verbosity: 0; set
verbosity: 2 to also see the effective backend, params_backend and
max_vram assignments the addon passes to the engine.
The 16-case Linux hardware matrix is available in
scripts/validate-layer-streaming.sh. It expects the MiniMax-H3 files under
/home/shared/models/minimax-h3-q2 by default. Override that location with
H3_MODELS_DIR. The runtime backend defaults to vulkan0; override it with
BACKEND, for example BACKEND=cuda0. Then run the script from the package
directory. Each case requires a non-empty AVI and checks the engine log for the
expected graph-cut, streaming, CPU RAM, or disk behavior.
Image Generation Parameters
| Key | Type | Description |
| ----------------------------- | ----------------- | ------------------------------------------------------------------- |
| prompt | string | Required non-empty prompt |
| negative_prompt | string | Negative prompt |
| width, height | number | Positive multiples of 8; FLUX img2img defaults omitted axes to 1024 |
| steps | number | Diffusion step count |
| cfg_scale | number | Classifier-free guidance for SD/SDXL/SD3 and FLUX img2img examples |
| guidance | number | Distilled guidance for FLUX.2 |
| sampling_method / sampler | string | Sampler name; omit for auto-selection |
| scheduler | string | Scheduler name; omit for auto-selection |
| seed | number | -1 for random |
| batch_count | number | Number of images |
| lora | string | Absolute path to a LoRA adapter |
| upscale | boolean | object | Post-generation ESRGAN upscale; requires files.esrgan |
| vae_tiling | boolean | Per-job VAE tiling |
| vae_tile_size | number | string | VAE tile size in latent pixels, e.g. 64 or '64x64' |
| vae_tile_overlap | number | Tile overlap fraction |
| cache_mode | string | Step-caching algorithm |
| cache_preset | string | Cache preset: slow, medium, fast, ultra |
| cache_threshold | number | Cache reuse threshold |
| eta | number | DDIM/TCD stochasticity |
| clip_skip | number | Skip last N CLIP layers |
Do not force sampling_method: 'euler_a' for FLUX.2 models. Leave the sampler
unset unless you know the model family requires an override.
Image-to-Image and FLUX.2 Fusion
init_image accepts PNG/JPEG bytes and selects image-to-image mode.
- FLUX.2 uses in-context conditioning: the reference image is VAE-encoded into separate latent tokens and the target starts from pure noise.
- SD1.x / SD2.x / SDXL / SD3 use SDEdit: the input is noised according to
strengthand denoised with the prompt.
For FLUX.2 img2img, load the context with files.llm and
keep config.prediction: 'flux2_flow' for the full FLUX.2 convenience behavior.
Native model detection selects reference conditioning even when prediction is
omitted or 'auto'. However, the single-reference size fallback (replacing
512×512 with the reference dimensions) still requires 'flux2_flow', and the
JavaScript @imageN prompt warnings require both files.llm and that setting.
Without it, pass explicit output dimensions and check reference tags yourself.
Leave prediction automatic for Qwen-Image-2.1.
Reference-validation compatibility: init_images on SD1/SD2/SDXL is now
rejected by the loaded native model after job submission, rather than by the
JavaScript file/prediction checks. Await the response's .await() promise to
handle the error. The native message is
img2img: init_images requires a model supporting reference editing (FLUX.2 or Qwen-Image-2.1).
Callers matching the former JavaScript error text must update their handling.
The former rejection of init_image with files.llm but no explicit
'flux2_flow' prediction is also removed; native model capabilities determine
the conditioning path. JavaScript still validates input types, empty arrays,
and mutual exclusion of init_image and init_images before submission.
const inputImage = require('bare-fs').readFileSync('assets/source.jpg')
const response = await model.run({
prompt: 'a cinematic portrait of the same person, professional lighting',
init_image: inputImage,
width: 1024,
height: 1024,
cfg_scale: 1.0,
guidance: 3.5,
steps: 20,
seed: 42
})FLUX.2 multi-reference fusion uses init_images, an array of PNG/JPEG buffers.
It is mutually exclusive with init_image. Keep
config.prediction: 'flux2_flow' for the convenience behavior described above.
const response = await model.run({
prompt: 'blend @image1 and @image2 into one scientist in a black studio',
init_images: [image1Bytes, image2Bytes],
width: 624,
height: 624,
cfg_scale: 1.0,
guidance: 3.5,
steps: 10,
seed: 10
})@image1, @image2, etc. are prose anchors for the text encoder. The FLUX.2
Qwen3 text encoder does not receive vision tokens; visual fusion happens in the
DiT through attention over reference latents. Keep increase_ref_index unset or
false for FLUX.2-klein fusion.
Video API
const path = require('bare-path')
const fs = require('bare-fs')
const VideoStableDiffusion = require('@qvac/diffusion-cpp/video')
const MODELS_DIR = path.resolve(__dirname, './models')
const model = new VideoStableDiffusion({
files: {
model: path.join(MODELS_DIR, 'wan2.1_t2v_1.3B_fp16.safetensors'),
t5Xxl: path.join(MODELS_DIR, 'umt5_xxl_fp16.safetensors'),
vae: path.join(MODELS_DIR, 'wan_2.1_vae.safetensors')
},
config: {
threads: 4,
device: 'gpu',
diffusion_fa: true,
offload_to_cpu: true,
vae_tiling: true
},
logger: console
})
await model.load()
const response = await model.run({
mode: 'txt2vid',
prompt: 'a colorful bird flapping its wings',
negative_prompt: 'blurry, low quality, static, jittery, watermark',
width: 480,
height: 832,
video_frames: 81,
fps: 16,
steps: 30,
cfg_scale: 6.0,
flow_shift: 3.0,
seed: 42
})
let avi = null
await response
.onUpdate((data) => {
if (data instanceof Uint8Array) avi = data // MJPG AVI bytes
})
.await()
fs.writeFileSync('wan_t2v_seed42.avi', avi)
await model.unload()The default export from @qvac/diffusion-cpp/video and the named
VideoStableDiffusion export from @qvac/diffusion-cpp are the same class.
Video Files
| Key | Model family | Description |
| ------------------------------- | ------------ | --------------------------------------------------------------- |
| files.model | all video | Wan, LTX, or MiniMax-H3 diffusion transformer |
| files.t5Xxl | Wan | UMT5-XXL text encoder |
| files.vae | all video | Matching video VAE |
| files.clipVision | Wan I2V | OpenCLIP ViT-H/14; required for mode: 'img2vid' on Wan |
| files.llm | LTX / H3 | Gemma or Qwen3-VL text encoder |
| files.audioVae | LTX / H3 | Audio VAE for synchronized audio |
| files.embeddingsConnectors | LTX | Text-embedding connector weights; also marks the context as LTX |
Video Parameters
| Key | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------------ |
| mode | Required: 'txt2vid' or 'img2vid' |
| prompt, negative_prompt | Text conditioning |
| width, height | Wan 2.1: multiples of 16. LTX, Wan TI2V, H3: multiples of 32 |
| video_frames | Wan: (4*k + 1). LTX: (8*k + 1), max 257. H3: (17*k + 5) |
| fps | Generation rate and AVI rate; H3 is fixed at 24 FPS |
| steps, cfg_scale, sampling_method, scheduler, seed | Sampling controls |
| flow_shift | Per-job flow-shift override; Wan 2.1 T2V 1.3B works well at 3.0 |
| init_image | First frame for img2vid; required by that mode |
| control_frames, vace_strength | Optional VACE guidance |
| temporal_tiling | LTX-only temporal VAE tiling to reduce peak VRAM |
| cache_mode, cache_preset, cache_threshold | Step-cache controls |
Decoded init_image, control_frames, and reference_images together are
limited to 128 Mi pixels per job by default; each image is limited to 64 Mi
pixels by default. At 1920×1080, up to 64 control frames fit when there are
no other input images.
An initial or reference image reduces the available control-frame budget.
Set config.max_job_pixels and config.max_image_pixels to positive pixel
counts when the target device can support a different budget. The fixed
16,384-pixel edge and compressed-source safeguards still apply.
Video output is a single MJPG AVI Uint8Array. For LTX-2 and H3 models loaded
with audioVae, the AVI also contains a second IEEE-float PCM audio stream:
48 kHz for LTX-2 and 32 kHz for H3.
VLC handles these files well.
Wan 2.2
The supported Wan 2.2 workflow is the community-distilled TI2V-5B Turbo Q5_K_S GGUF for text-to-video. The downloader does not include the CLIP vision encoder required by the wrapper's image-to-video path. Download its complete text-to-video model layout before running the example:
# TI2V-5B Turbo Q5_K_S (4 steps, CFG 1)
./scripts/download-model-wan2.2.sh
npm run generate:wan22The download is approximately 16.4 GB: 3.56 GB for the Q5_K_S diffusion model, 11.4 GB for the fp16 UMT5-XXL encoder, and 1.41 GB for the Wan 2.2 VAE. The backend runs one process on one device with optional CPU offload. Use dimensions that are multiples of 32 for this TI2V model so the emitted AVI dimensions match the requested dimensions.
MiniMax-H3 Text-to-Video and Image-to-Video
The Comfy-Org FL2VA ConvRot checkpoint supports both modes through
VideoStableDiffusion.run(). Its init_image is a PNG/JPEG first frame, copied
into the asynchronous native job and encoded into H3's keyframe conditioning.
H3 does not use Wan's CLIP vision encoder. The video VAE must include encoder
weights for I2V (vae_decode_only: false, the default).
From the repository root, install the Hugging Face CLI, read the MiniMax-H3
license, and download the four
matching files. If access requires authentication, run hf auth login first.
--dry-run lists the files and sizes without fetching the weights. The
downloader defaults to the validated model repository commit
e5eb578a89295337b8ff433a035929ce0279e0b6; set HF_REVISION to use
another commit.
python -m pip install -U huggingface_hub
bash packages/diffusion-cpp/scripts/download-minimax-h3-convrot.sh --dry-run
bash packages/diffusion-cpp/scripts/download-minimax-h3-convrot.shThe four files total about 55 GB. The QVAC vcpkg registry pins
stable-diffusion-cpp 2026-08-11#4 to
qvac-ext-stable-diffusion.cpp commit
107121df3ee9664cd47f80b19add495002d0232f (merged PR #44) and
ggml 2026-08-11#1 to
qvac-ext-ggml commit 9a7d2b36e96a198c1c67c013cafcde4e4c2c60eb.
At the validated model revision, the four checkpoint SHA-256 hashes are:
| File | SHA-256 |
| --- | --- |
| diffusion_models/minimax_h3_fl2va_pruned_int8_convrot.safetensors | e889202c41dafb67b10d67b97f0d8541508036a6090af23425a5c2615d03c47a |
| text_encoders/qwen3vl_32b_minimax_h3_int8_convrot.safetensors | bc2ced0fbea64757fa9acddccfc0b3f4819d1dcf1da6c124d690d368be283923 |
| vae/minimax_h3_video_vae_fp16.safetensors | 7c1f131492e7eddacaac9069a61b81bdd39de5cc96561e677c5eab1cdce5e522 |
| vae/minimax_h3_audio_vae_fp32.safetensors | 8e505d95dd1561d47abd43d4238fd40d9bb1ae9e147ed0a4cba778d76ae4db48 |
From packages/diffusion-cpp, run the separate examples. Set H3_MODELS_DIR
if the checkpoint is elsewhere, H3_BACKEND to select a backend, and
H3_OUTPUT for the AVI path. The I2V example defaults to the FLUX.2-generated
960×544 assets/h3-keyframe-boat.png; set H3_INPUT_IMAGE to a different
PNG/JPEG of the same dimensions. Use H3_FRAMES=22 for a short clip or 124
for the longer reference workload.
bare examples/generate-video-minimax-h3-comfy-t2v.js
bare examples/generate-video-minimax-h3-comfy-i2v.jsexample-h3-minimax-comfy.js first generates a commercial keyframe with
FLUX.2 [klein] 4B and then passes that saved PNG to H3 I2V. Set
H3_SKIP_FLUX=1 and H3_INPUT_IMAGE=/path/to/keyframe.png to rerun H3 with an
existing image. FLUX_MODELS_DIR, FLUX_MODEL, FLUX_LLM, FLUX_VAE,
FLUX_SEED, FLUX_STEPS, and FLUX_IMAGE_PROMPT customize the first stage.
bare examples/example-h3-minimax-comfy.jsH3 requires dimensions on a 32-pixel grid and frame counts of 17*k + 5
(5, 22, 39, …). Its distilled guidance uses cfg_scale: 1.0, and output is
always 24 FPS. The addon writes MJPG AVI with the decoded audio stream when
audioVae is provided. Control frames, Ref2VA references, Wan MoE settings,
and image-conditioning strength are rejected for this FL2VA path. The existing
Unsloth GGUF T2V example remains available as npm run generate:h3-coffee.
LTX-2 Text-to-Video With Audio
const model = new VideoStableDiffusion({
files: {
model: path.join(MODELS_DIR, 'LTX-2.3-22B-distilled-1.1-Q5_K_M.gguf'),
llm: path.join(MODELS_DIR, 'gemma-3-12b-it-UD-Q4_K_XL.gguf'),
vae: path.join(MODELS_DIR, 'ltx-2.3-22b-distilled_video_vae.safetensors'),
audioVae: path.join(MODELS_DIR, 'ltx-2.3-22b-distilled_audio_vae.safetensors'),
embeddingsConnectors: path.join(
MODELS_DIR,
'ltx-2.3-22b-distilled_embeddings_connectors.safetensors'
)
},
config: {
threads: 4,
device: 'gpu',
diffusion_fa: true,
vae_tiling: true,
vae_conv_direct: true
},
opts: { stats: true },
logger: console
})
const response = await model.run({
mode: 'txt2vid',
prompt: 'a claymation cat playing jazz on a piano',
negative_prompt: 'blurry, low quality, static, jittery, watermark, distorted audio',
width: 512,
height: 320,
video_frames: 241,
fps: 24,
steps: 10,
cfg_scale: 1.0,
temporal_tiling: true,
seed: 42
})LTX distilled variants are designed for low step counts and low CFG values. For full dev weights, use higher steps and a larger CFG.
ABot-World Interactive Walk
ABot-World is a causal world model: it generates video block-by-block under
live keyboard input instead of one batch call, exposed via
@qvac/diffusion-cpp/world (WorldStableDiffusion). Worlds are created
natively from a prompt + first-frame image (createScene()), then walked with
WASD/IJKL (step()), streaming decoded PNG/JPEG frames. The four model files
ship in the QVAC P2P registry.
# models (P2P registry, no credentials) + browser demo
npm install -g @qvac/registry-client # then see docs/abot-world.md for the 4 downloads
export ABOT_MODELS_DIR=~/abot-models ABOT_KV_CACHE=1
npm run walk:world # open http://127.0.0.1:8787const WorldStableDiffusion = require('@qvac/diffusion-cpp/world')
const world = new WorldStableDiffusion({
files: { model: ditGguf, taehv: taehvGguf, scene: scenePack },
config: { seed: 42, kvCache: true }
})
await world.load()
const response = await world.step({ W: true }) // one generated block forward
await response
.onUpdate((data) => {
if (data instanceof Uint8Array) frames.push(data) // PNG/JPEG frames
})
.await()
await world.unload()Hardware requirements (interactive walk, Q8 DiT + KV cache):
| tier | GPU | host RAM | disk | measured |
|---|---|---|---|---|
| minimum @ 832x480 | 24 GB card, >= 20 GB VRAM free (16.3 GB steady + ~2.7 GB transient at block 0) | 8 GB | 14 GB | RTX 5090: 1.78 s/block |
| low-VRAM @ 448x256 | ~6 GB | 8 GB | 14 GB | laptop RTX 4050 |
| optimal | RTX 5090-class, dedicated (no co-tenant VRAM); optional dual-GPU split ABOT_BACKEND="diffusion=cuda0,vae=cuda1" | 16 GB | NVMe | 6.7-6.8 fps generation |
Performance vs the PyTorch reference (same 5090, same walk): the QVAC build generates 6.7 fps vs ~28.5 fps for the reference's fp8 Triton pipeline — about 4.2x slower per block, the main known trade-off of this implementation today. In exchange it starts 36x faster (first frame in 1.5 s vs ~54 s), uses 14x less host RAM (3.1 GB vs 43.6 GB peak), one CPU core instead of two-plus, 14 GB on disk instead of ~38 GB, and needs no Python runtime.
Model fidelity: the weights are converted to GGUF (DiT and umT5 at Q8_0, VAE and taehv at F16), so outputs are not bit-exact vs the PyTorch reference — parity is held by cosine-similarity gates instead (golden walk replays 0.993-0.99995, scene packs >= 0.997, ~32-38 dB walk-level PSNR), and walks are visually indistinguishable in validation.
Full guide — building, the demo server (local and over-SSH playback), the world API for app developers, resolutions, performance details and troubleshooting: docs/abot-world.md.
ESRGAN Upscaler
const { EsrganUpscaler } = require('@qvac/diffusion-cpp')
const fs = require('bare-fs')
const upscaler = new EsrganUpscaler({
files: {
esrgan: '/absolute/path/to/RealESRGAN_x4plus_anime_6B.pth'
},
config: {
device: 'gpu',
upscaler_tile_size: 128
},
logger: console
})
await upscaler.load()
const response = await upscaler.upscale(fs.readFileSync('input.png'), {
repeats: 1
})
const images = []
await response
.onUpdate((data) => {
if (data instanceof Uint8Array) images.push(data)
})
.await()
fs.writeFileSync('upscaled.png', images[0])
await upscaler.unload()repeats controls how many ESRGAN passes are applied. One pass typically scales
by 4x; two passes scale by 16x.
Response Streams and Stats
All three wrappers return a QvacResponse.
- Progress updates are JSON strings like
{"step":1,"total":20,"elapsed_ms":...}. The stream is multi-phase: sampler sequences (one per image batch item / video expert,total= its step count) and, whenvae_tilingis enabled, VAE tile passes (total= tile count). Each sequence restarts atstep: 0, so a bar renderer should key ontotalchanges rather than assume a single monotonic sequence. Model weights normally load eagerly atload(), so generation emits no loader ticks. Two configurations load lazily instead and do emit loader ticks inside generation: mobile targets, and anyparams_backendnamingdiskfor one or more modules — the latter on every job, since disk-backed weights are released after each phase. In those cases the first job'sconditionerMsalso absorbs weight-load time. - Image generation and ESRGAN emit PNG
Uint8Arrayvalues. - Video generation emits one MJPG AVI
Uint8Array. - If
opts.statsis enabled, astatsevent is emitted before completion.
Image stats include load time, generation time, cumulative steps/images/pixels,
dimensions, and seed. Video stats additionally include cumulative videos,
frames, fps, hasAudio, and audioSampleRate. ESRGAN stats include upscale
timing, output dimensions, repeats, and the backend device actually used.
Cancellation and Unload
Each wrapper has cancel() and unload() methods. Only one job may run per
model instance at a time; overlapping run() calls fail with a busy error.
const response = await model.run({ prompt: '...', steps: 30 })
await model.cancel()
await response.await().catch(() => {})
await model.unload()During ESRGAN upscale, cancellation is honored between repeat passes.
Assessing fit
assessFit projects a load against the memory free right now. It reads model metadata and never weight data, so GGUF and safetensors files can be the registry's header-only copies and the projection can run before weights are downloaded. It is a module export, not an instance method — nothing is loaded to call it.
const { assessFit } = require('@qvac/diffusion-cpp')
const fit = await assessFit({
files: { model: '/models/model.gguf' },
config: { backendsDir: '/opt/backends' },
workload: { prompt: 'a lighthouse at dusk', width: 1024, height: 1024 }
})
fit.status // 'fits' | 'does-not-fit' | 'unknown'
fit.devices // [{ id, name, unified, usedBytes, freeBytes, totalBytes }]
fit.stats // {}devices lists the GPU devices the placement uses, then the host. id is the device's PCI bus ID when the backend reports one, and its name otherwise. unified marks memory the host owns: the CPU, an integrated GPU, the Metal device on Apple silicon, Android's OpenCL GPU, and a device that reports no memory of its own, which carries the host's figures. A unified row's free memory is capped at the host's, which on iOS is the app's own limit. usedBytes is the engine's measured projection for that device, and freeBytes and totalBytes are read while planning. A shared-memory GPU's use is in its own row and is not repeated in the host row. A load on the CPU is one host row, and unknown has no rows.
files and config are what createInstance takes, plus files.clipVision, files.audioVae and files.embeddingsConnectors. Paths must be absolute. workload describes the generation the projection is sized for, and its defaults are the generation defaults:
| Field | Default | Description |
| --- | --- | --- |
| prompt | a stand-in prompt | Token count drives the text-encoder memory. |
| width, height | 512 | Output dimensions. |
| videoFrames | 1 | 1 or less projects image generation. A video-only model is projected at one frame unless this is passed, while run() defaults to 33. |
| vaeTiling | false | Tiled decoding trades speed for a much smaller VAE arena. |
| vaeTileSizeX, vaeTileSizeY | 512 | The tile geometry, in pixels. |
| vaeTileOverlap | 0.5 | Fraction of a tile used as the overlap seam. |
| upscaleRepeats | 1 | ESRGAN passes, each applying the checkpoint's scale factor. |
Every number must be finite, whole and in range; anything else rejects.
The engine chooses the placement, so it has to be given an unpinned configuration. A config.mainGpu, a config.device of cpu, a paramsBackend naming individual modules, or an OpenCL preference that does not name the first GPU is status: "unknown".
For standalone ESRGAN, use mode: 'upscale', files: { esrgan } and the same config as EsrganUpscaler. This mode supports device: 'cpu'. workload.width and height describe the input image. The estimate includes weights, the tiled graph and full-image RAM buffers; smaller tiles do not remove full-image allocations.
For post-generation upscaling, include files.esrgan and the upscaler_* config fields in the diffusion request. The estimate includes ESRGAN weights retained alongside diffusion, including GPU staging when its parameters are offloaded to CPU. Set workload.upscaleRepeats to the number of passes to assess.
For ABot, use mode: 'world', the same files: { model, taehv, scene } and
config as WorldSession, and workload: { walkSteps: 100 }. The default is
100 walk steps; an explicit value must be a whole number from 1 to 1,000,000.
The scene header supplies the resolution and reference slots. The check sizes
the configured placement, including CPU offload, graph cuts, KV caches, the
decoder and retained latent history. It does not change generation settings
or limit the walk. Scene creation with umT5 and the Wan VAE is outside this
session estimate.
A load the engine can place only by moving modules between backends is status: "does-not-fit", since the configuration as given does not fit, and its rows describe the placement the engine found. A paramsBackend of *=cpu is the exception: the engine replans every module for it, so the plan is compared against the placement that load already uses, and an equal one is fits.
A malformed request, a model the engine cannot read, or an ESRGAN output above the pixel limit rejects. A world placement naming a device the fitter cannot plan for is status: "unknown".
assessFit returns a Promise. Concurrent calls run one at a time within the addon.
The projection runs in the calling process and shares its engine state: the GPU backend modules load once per process, so the first config.backendsDir any call supplies is the one every later load uses, and the native log callback is process-global like everywhere else.
Operational Notes
- Native C++ logs are process-global. Configure them once with
require('@qvac/diffusion-cpp/addonLogging').setLogger(...). - Leave sampler and scheduler unset for normal use; the addon preserves stable-diffusion.cpp auto-detection for model-specific defaults.
diffusion_fadefaults to true and is important for FLUX/LTX memory use.- For FLUX.2 img2img/fusion, set
config.prediction: 'flux2_flow'so the JS prompt warnings and single-reference size fallback remain enabled. Native model detection selects the conditioning path independently of this setting. - Wan I2V requires
files.clipVision; LTX and MiniMax-H3 img2vid do not. - Wan 2.1 dimensions must be multiples of 16; LTX and H3 dimensions must be multiples of 32.
- Wan frame counts use
(4*k + 1); LTX uses(8*k + 1)and H3 uses(17*k + 5). - LTX and H3 audio is muxed into AVI as IEEE-float PCM.
- On Linux the shipped prebuild must not hard-link any GPU loader
(
libvulkan/libOpenCL/libcuda/libcudart) — an integration test asserts this. GPU dependencies belong to the dynamically loaded backend modules. Linux ARM64 and x64 prebuilds must ship both CUDA and Vulkan modules. A custom source build using a different backend layout can opt out of this shipped layout check withQVAC_SKIP_PREBUILD_LINK_CHECK=true.
Credits
Test Images
assets/h3-keyframe-boat.png and assets/h3-keyframe-balloon.png are 960×544
FLUX.2 [klein] 4B images generated by examples/generate-h3-keyframes-flux2.js.
Run bare examples/generate-h3-keyframes-flux2.js from this package to recreate
them with the FLUX.2 models listed above. Set FLUX_MODELS_DIR if the models
are elsewhere, or H3_KEYFRAME=boat to generate only the boat. They are included
under this package's Apache-2.0 license for I2V examples and fixed-seed
conditioning comparisons.
assets/von-neumann.jpg — John von Neumann (1956).
Source: U.S. Department of Energy, File ID: HD.3F.191.
This image is in the Public Domain as a work of the U.S. Federal Government.
assets/claude-shannon.jpg — Claude Shannon.
Source: Bell Labs / Wikimedia Commons.
Licensed under Creative Commons Attribution-ShareAlike (CC BY-SA).
Attribution must be preserved; any redistribution of this image or a derivative
must be released under a compatible CC BY-SA license.
License
Apache-2.0 — see LICENSE for details.
