---
title: "Video generation"
canonical: https://docs.qvac.tether.io/sdk/v0.18/ai-capabilities/video-generation/
collection: "SDK"
package: "@qvac/sdk"
line: v0.18
current_line: false
---

# Video generation (/sdk/v0.18/ai-capabilities/video-generation)



## Overview

Video generation runs on a **customized Diffusion engine** ([`qvac-ext-stable-diffusion.cpp`](https://github.com/tetherto/qvac-ext-stable-diffusion.cpp)). Load a supported model using `modelType: "diffusion"` with `modelConfig.mode: "video"`. Then call `video()` with a `mode` and `prompt`.

It supports two modes — `txt2vid` (from a text prompt) and `img2vid` (animate a still image) — across the WAN 2.1, Wan 2.2, LTX-2, and MiniMax-H3 model families. `video()` returns `{ progressStream, outputs, stats }`: `outputs` resolves to the generated video(s) as `Uint8Array` buffers (AVI), `progressStream` streams step-by-step progress, and `stats` carries per-run metadata.

<Callout type="warn">
  Video generation is hardware-intensive: it requires at least **16 GB of video memory** or **20 GB of unified memory**.
</Callout>

## Functions

Use the following sequence of function calls:

1. [`loadModel()`](/sdk/v0.18/reference/api#loadmodel)
2. [`video()`](/sdk/v0.18/reference/api#video)
3. [`unloadModel()`](/sdk/v0.18/reference/api#unloadmodel)

For how to use each function, see [SDK — API reference](/sdk/v0.18/reference/api/).

### `video()`

Generates a video from a `prompt` in one of two modes:

#### Modes

* **`txt2vid`** — generate a video from a text prompt alone. Supported by all families.
* **`img2vid`** — animate a still image. Supported by WAN (load the model with `clipVisionModelSrc`, an OpenCLIP ViT-H/14 encoder) and LTX-2 (no CLIP-vision encoder needed).

`img2vid` takes two extra params:

* `init_image` — PNG or JPEG bytes (`Uint8Array`) of the first frame. Required.
* `strength` — a value in `0–1`. Use higher values when you want the output to diverge more from the first frame.

#### Generation knobs

Common to all families:

* `video_frames` — number of frames to generate. Wan models use `4k + 1` (e.g. `17`, `33`, `49`, `81`). LTX-2 uses `8k + 1` (e.g. `25`, `121`, `241`). MiniMax-H3 uses `17k + 5` (e.g. `5`, `22`, `39`, `56`, `73`, `90`, `107`, `124`).
* `fps` — output frame rate.
* `cfg_scale` — classifier-free guidance strength. Higher values follow the prompt more closely.
* `flow_shift` — flow-matching shift. For Wan 2.1 T2V, use `3.0`; higher values can produce near-static frames.

LTX-2 adds:

* `temporal_tiling` — set to `true` to cap peak VRAM on HD or long clips. No effect on WAN.

#### LTX-2 reference conditioning (IC-LoRA)

Available in `txt2vid` only. Pass a composite reference sheet together with an Ingredients LoRA so the generated video inherits a specific character or style:

* `scheduler` — set to `'ltx2'`.
* `reference_images` — one composite reference sheet as a `Uint8Array`. Required to enable reference conditioning.
* `lora` — worker-local absolute path to an Ingredients LoRA adapter. Required alongside `reference_images`.
* `lora_strength` — controls how strongly the LoRA steers appearance.
* `reference_attention_strength` — controls how closely the video follows the reference.
* `stg_scale`, `stg_block` — optional; tune them to refine output.
* `reference_downscale_factor` — must be `1`.

#### Wan 2.2 A14B dual-expert knobs

Wan 2.2 A14B routes a second set of knobs to its high-noise expert. They require a model loaded with `modelConfig.highNoiseDiffusionModelSrc` and are rejected on single-expert layouts (Wan 2.1, Wan 2.2 TI2V-5B) with `PluginRequestValidationFailedError`:

* `high_noise_steps` — steps for the high-noise expert.
* `high_noise_sample_method` — sampling method for the high-noise expert.
* `high_noise_scheduler` — scheduler for the high-noise expert.
* `high_noise_cfg_scale` — classifier-free guidance strength for the high-noise expert.
* `high_noise_flow_shift` — flow-matching shift for the high-noise expert.
* `moe_boundary` — boundary at which sampling switches from the high-noise expert to the low-noise expert.

<Callout type="warn">
  Size and frame constraints:

  * `width` and `height` must be positive multiples of **16**.
  * On **LTX-2** and **Wan 2.2 TI2V-5B**, `width` and `height` must be multiples of **32**.
  * On **LTX-2**, `video_frames` must satisfy `8*k + 1` (e.g. `25`, `121`, `241`), with a maximum of **257**.
  * On **LTX-2** with `reference_images`, the minimum `video_frames` rises to **121**.
  * On **MiniMax-H3**, `width` and `height` must be multiples of **32**, `video_frames` must satisfy `17*k + 5`, and `fps` must be **24**. H3 produces synchronized audio at **32 kHz**.
</Callout>

The returned `stats` includes `hasAudio` (`true` when the output AVI carries a muxed audio track) and `audioSampleRate` (Hz of that track, `0` when there is no audio).

## Models

Four model families are supported — **WAN 2.1**, **Wan 2.2**, **LTX-2**, and **MiniMax-H3** — and the file layout is auto-selected at load time from the model sources you provide:

* **WAN 2.1 T2V** (txt2vid): split layout — diffusion model + UMT5-XXL text encoder (via `t5XxlModelSrc`) + VAE (via `vaeModelSrc`). Available constants: `WAN2_1_T2V_1_3B_FP16`, `UMT5_XXL_FP16`, `WAN_2_1_COMFYUI_REPACKAGED_VAE`.
* **WAN 2.1 I2V** (img2vid): same split layout as T2V, plus an OpenCLIP ViT-H/14 vision encoder (via `clipVisionModelSrc`). Available constants: `WAN2_1_I2V_14B_Q4_K_M`, `CLIP_VISION_H`, `UMT5_XXL_FP16`, `WAN_2_1_COMFYUI_REPACKAGED_VAE`.
* **Wan 2.2 TI2V-5B** (txt2vid, single-expert): split layout — diffusion model + UMT5-XXL text encoder (via `t5XxlModelSrc`) + Wan 2.2 VAE (via `vaeModelSrc`). The Wan 2.2 VAE's 16× spatial compression is what gives TI2V its 32-pixel grid; the Wan 2.1 VAE is not interchangeable. The UMT5-XXL text encoder is byte-identical to the Wan 2.1 repackage, so both generations share the one registry entry. Available constants: `WAN2_2_TI2V_5B_Q5_K_S`, `UMT5_XXL_FP16`, `WAN_2_2_COMFYUI_REPACKAGED_VAE`.
* **Wan 2.2 A14B** (txt2vid, dual-expert / mixture-of-experts): split layout — low-noise diffusion model + high-noise diffusion model (via `highNoiseDiffusionModelSrc`, whose presence selects this layout) + UMT5-XXL text encoder + Wan 2.2 VAE. The A14B-only `high_noise_*` and `moe_boundary` request fields route to the high-noise expert and are rejected on single-expert layouts.
* **LTX-2** (txt2vid and img2vid): split layout — diffusion model + Gemma text encoder (via `llmModelSrc`) + video VAE (via `vaeModelSrc`) + text-embedding connectors (via `embeddingsConnectorsModelSrc`, whose presence selects this layout), plus an optional audio VAE (via `audioVaeModelSrc`) for the synchronized 48 kHz audio track. Its img2vid path needs no CLIP-vision encoder.
* **MiniMax-H3** (txt2vid): split layout — diffusion model + Qwen3-VL text encoder (via `llmModelSrc`) + video VAE (via `vaeModelSrc`) + audio VAE (via `audioVaeModelSrc`). H3 uses a 32-pixel spatial grid, `17*k + 5` frames at 24 FPS, and produces synchronized 32 kHz audio. It does not support `img2vid`, Wan's T5 encoder, or CLIP vision encoders.

<Callout type="warn">
  LTX-2 is large (\~21B) — budget at least **20 GB** of video / unified memory.
</Callout>

For models available as constants, see [SDK — Models](/sdk/v0.18/#models).

## Examples

### Text-to-video (WAN 2.1)

The following script shows text-to-video generation using Wan 2.1 T2V 1.3B with its split-layout model (separate diffusion model, UMT5-XXL text encoder, and VAE):

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-txt2vid.js title="diffusion-txt2vid.js" lineNumbers
      import { loadModel, unloadModel, video, WAN2_1_T2V_1_3B_FP16, UMT5_XXL_FP16, WAN_2_1_COMFYUI_REPACKAGED_VAE } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      // Text-to-video with Wan 2.1 T2V 1.3B. Wan uses a split layout:
      // a diffusion model + a UMT5-XXL text encoder + a VAE.
      // This example needs powerful hardware: at least 16 GB of video memory or
      // 20 GB of unified memory.
      const diffusionModelSrc = process.argv[2] || WAN2_1_T2V_1_3B_FP16;
      const t5XxlModelSrc = process.argv[3] || UMT5_XXL_FP16;
      const vaeModelSrc = process.argv[4] || WAN_2_1_COMFYUI_REPACKAGED_VAE;
      // Prompt tip: Wan 1.3B is small and has weak temporal priors. Use motion-
      // explicit verbs and avoid static framing words like "standing", "still",
      // or "portrait" in the positive prompt.
      const prompt = process.argv[5] || 'a colorful bird flapping its wings';
      const outputDir = process.argv[6] || '.';
      try {
          console.log('▸ Loading Wan 2.1 T2V model (diffusion + UMT5-XXL + VAE)...');
          const modelId = await loadModel({
              modelSrc: diffusionModelSrc,
              modelType: 'sdcpp-generation',
              modelConfig: {
                  mode: 'video',
                  device: 'gpu',
                  threads: 4,
                  t5XxlModelSrc,
                  vaeModelSrc,
                  diffusion_fa: true,
                  offload_to_cpu: true,
                  params_backend: 'vae=cpu',
                  vae_tiling: true
              },
              onProgress: (p) => {
                  const mb = (n) => (n / 1e6).toFixed(1);
                  const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
                  process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
                  if (p.percentage >= 100)
                      process.stderr.write('\n');
              }
          });
          console.log(`▸ Model loaded: ${modelId}`);
          console.log(`\n▸ Generating video for: "${prompt}"`);
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'txt2vid',
              prompt,
              negative_prompt: 'blurry, low quality, static, jittery, watermark',
              width: 480,
              height: 832,
              // Frame count must satisfy (4*k + 1), k >= 1. Common values at 16 fps:
              // 17 frames ~= 1.06s (very fast, ~6 min on M3 Ultra Metal)
              // 33 frames ~= 2.06s (default in this example, ~11 min)
              // 49 frames ~= 3.06s (~17 min)
              // 65 frames ~= 4.06s (~22 min)
              // 81 frames ~= 5.06s (Wan 1.3B native training length, best motion
              // quality, ~28 min)
              // Going beyond 81 can degrade quality because it exceeds the model's
              // positional embeddings.
              video_frames: 33,
              fps: 16,
              steps: 30,
              cfg_scale: 6.0,
              // Wan 2.1 T2V needs flow_shift=3.0 for visible motion. Higher values can
              // make consecutive frames near-identical, which looks like a frozen video.
              flow_shift: 3.0,
              seed: 42,
              vae_tiling: true
          });
          for await (const { step, totalSteps } of progressStream) {
              console.log(`▸ step ${step}/${totalSteps}`);
          }
          const buffers = await outputs;
          for (let i = 0; i < buffers.length; i++) {
              const outputPath = path.join(outputDir, `wan_t2v_${i}.avi`);
              fs.writeFileSync(outputPath, buffers[i]);
              console.log(`▸ Saved ${outputPath}`);
          }
          console.log('\n▸ Stats:', await stats);
          await unloadModel({ modelId, clearStorage: false });
          console.log('▸ Done.');
          process.exit(0);
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-txt2vid.ts title="diffusion-txt2vid.ts" lineNumbers
      import {
        loadModel,
        unloadModel,
        video,
        WAN2_1_T2V_1_3B_FP16,
        UMT5_XXL_FP16,
        WAN_2_1_COMFYUI_REPACKAGED_VAE
      } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      // Text-to-video with Wan 2.1 T2V 1.3B. Wan uses a split layout:
      // a diffusion model + a UMT5-XXL text encoder + a VAE.
      // This example needs powerful hardware: at least 16 GB of video memory or
      // 20 GB of unified memory.
      const diffusionModelSrc = process.argv[2] || WAN2_1_T2V_1_3B_FP16
      const t5XxlModelSrc = process.argv[3] || UMT5_XXL_FP16
      const vaeModelSrc = process.argv[4] || WAN_2_1_COMFYUI_REPACKAGED_VAE

      // Prompt tip: Wan 1.3B is small and has weak temporal priors. Use motion-
      // explicit verbs and avoid static framing words like "standing", "still",
      // or "portrait" in the positive prompt.
      const prompt = process.argv[5] || 'a colorful bird flapping its wings'
      const outputDir = process.argv[6] || '.'

      try {
        console.log('▸ Loading Wan 2.1 T2V model (diffusion + UMT5-XXL + VAE)...')
        const modelId = await loadModel({
          modelSrc: diffusionModelSrc,
          modelType: 'sdcpp-generation',
          modelConfig: {
            mode: 'video',
            device: 'gpu',
            threads: 4,
            t5XxlModelSrc,
            vaeModelSrc,
            diffusion_fa: true,
            offload_to_cpu: true,
            params_backend: 'vae=cpu',
            vae_tiling: true
          },
          onProgress: (p) => {
            const mb = (n: number) => (n / 1e6).toFixed(1)
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`)
            if (p.percentage >= 100) process.stderr.write('\n')
          }
        })
        console.log(`▸ Model loaded: ${modelId}`)

        console.log(`\n▸ Generating video for: "${prompt}"`)

        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'txt2vid',
          prompt,
          negative_prompt: 'blurry, low quality, static, jittery, watermark',
          width: 480,
          height: 832,
          // Frame count must satisfy (4*k + 1), k >= 1. Common values at 16 fps:
          // 17 frames ~= 1.06s (very fast, ~6 min on M3 Ultra Metal)
          // 33 frames ~= 2.06s (default in this example, ~11 min)
          // 49 frames ~= 3.06s (~17 min)
          // 65 frames ~= 4.06s (~22 min)
          // 81 frames ~= 5.06s (Wan 1.3B native training length, best motion
          // quality, ~28 min)
          // Going beyond 81 can degrade quality because it exceeds the model's
          // positional embeddings.
          video_frames: 33,
          fps: 16,
          steps: 30,
          cfg_scale: 6.0,
          // Wan 2.1 T2V needs flow_shift=3.0 for visible motion. Higher values can
          // make consecutive frames near-identical, which looks like a frozen video.
          flow_shift: 3.0,
          seed: 42,
          vae_tiling: true
        })

        for await (const { step, totalSteps } of progressStream) {
          console.log(`▸ step ${step}/${totalSteps}`)
        }

        const buffers = await outputs
        for (let i = 0; i < buffers.length; i++) {
          const outputPath = path.join(outputDir, `wan_t2v_${i}.avi`)
          fs.writeFileSync(outputPath, buffers[i]!)
          console.log(`▸ Saved ${outputPath}`)
        }

        console.log('\n▸ Stats:', await stats)
        await unloadModel({ modelId, clearStorage: false })
        console.log('▸ Done.')
        process.exit(0)
      } catch (error) {
        console.error('✖', error)
        process.exit(1)
      }
      ```
    </WrapCode>
  </Tab>
</Tabs>

### Image-to-video (WAN 2.1)

The following script shows image-to-video generation using Wan 2.1 I2V with its split-layout model (separate diffusion model, UMT5-XXL text encoder, VAE, and CLIP vision encoder). It animates a first-frame `init_image` guided by a motion prompt:

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-img2vid.js title="diffusion-img2vid.js" lineNumbers
      import { loadModel, unloadModel, video, WAN2_1_I2V_14B_Q4_K_M, CLIP_VISION_H, UMT5_XXL_FP16, WAN_2_1_COMFYUI_REPACKAGED_VAE } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      // Image-to-video with Wan 2.1 I2V. Requires a Wan I2V diffusion checkpoint (GGUF
      // recommended), plus UMT5-XXL, Wan VAE, and CLIP vision weights. The model
      // sources default to the bundled registry constants, so the common case is just
      // an init image path.
      const initImagePath = process.argv[2];
      const prompt = process.argv[3] || 'the subject slowly turns and smiles, soft natural lighting, cinematic';
      const outputDir = process.argv[4] || '.';
      const diffusionModelSrc = process.argv[5] || WAN2_1_I2V_14B_Q4_K_M;
      const t5XxlModelSrc = process.argv[6] || UMT5_XXL_FP16;
      const vaeModelSrc = process.argv[7] || WAN_2_1_COMFYUI_REPACKAGED_VAE;
      const clipVisionModelSrc = process.argv[8] || CLIP_VISION_H;
      if (!initImagePath) {
          console.error('✖ init image path is required');
          console.error('Usage: bun run bare:example dist/examples/diffusion-img2vid.js ' +
              '<initImagePath> [prompt] [outputDir] ' +
              '[i2vModelSrc] [t5XxlModelSrc] [vaeModelSrc] [clipVisionModelSrc]');
          process.exit(1);
      }
      try {
          console.log('▸ Loading Wan 2.1 I2V model (diffusion + UMT5-XXL + VAE + CLIP vision)...');
          const modelId = await loadModel({
              modelSrc: diffusionModelSrc,
              modelType: 'sdcpp-generation',
              modelConfig: {
                  mode: 'video',
                  device: 'gpu',
                  threads: 4,
                  t5XxlModelSrc,
                  vaeModelSrc,
                  clipVisionModelSrc,
                  diffusion_fa: true,
                  offload_to_cpu: true,
                  params_backend: 'vae=cpu',
                  vae_tiling: true
              },
              onProgress: (p) => {
                  const mb = (n) => (n / 1e6).toFixed(1);
                  const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
                  process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
                  if (p.percentage >= 100)
                      process.stderr.write('\n');
              }
          });
          console.log(`▸ Model loaded: ${modelId}`);
          const init_image = new Uint8Array(fs.readFileSync(initImagePath));
          console.log(`▸ Generating video for: "${prompt}"`);
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'img2vid',
              prompt,
              init_image,
              negative_prompt: 'blurry, distorted, low quality, jittery, static, frozen',
              strength: 0.85,
              flow_shift: 3.0,
              video_frames: 33,
              fps: 16,
              steps: 30,
              cfg_scale: 6.0,
              seed: 42,
              vae_tiling: true
          });
          for await (const { step, totalSteps } of progressStream) {
              console.log(`▸ step ${step}/${totalSteps}`);
          }
          const buffers = await outputs;
          for (let i = 0; i < buffers.length; i++) {
              const outputPath = path.join(outputDir, `wan_i2v_${i}.avi`);
              fs.writeFileSync(outputPath, buffers[i]);
              console.log(`▸ Saved ${outputPath}`);
          }
          console.log('▸ Stats:', await stats);
          await unloadModel({ modelId, clearStorage: false });
          console.log('▸ Done');
          process.exit(0);
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-img2vid.ts title="diffusion-img2vid.ts" lineNumbers
      import {
        loadModel,
        unloadModel,
        video,
        WAN2_1_I2V_14B_Q4_K_M,
        CLIP_VISION_H,
        UMT5_XXL_FP16,
        WAN_2_1_COMFYUI_REPACKAGED_VAE
      } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      // Image-to-video with Wan 2.1 I2V. Requires a Wan I2V diffusion checkpoint (GGUF
      // recommended), plus UMT5-XXL, Wan VAE, and CLIP vision weights. The model
      // sources default to the bundled registry constants, so the common case is just
      // an init image path.
      const initImagePath = process.argv[2]
      const prompt =
        process.argv[3] || 'the subject slowly turns and smiles, soft natural lighting, cinematic'
      const outputDir = process.argv[4] || '.'
      const diffusionModelSrc = process.argv[5] || WAN2_1_I2V_14B_Q4_K_M
      const t5XxlModelSrc = process.argv[6] || UMT5_XXL_FP16
      const vaeModelSrc = process.argv[7] || WAN_2_1_COMFYUI_REPACKAGED_VAE
      const clipVisionModelSrc = process.argv[8] || CLIP_VISION_H

      if (!initImagePath) {
        console.error('✖ init image path is required')
        console.error(
          'Usage: bun run bare:example dist/examples/diffusion-img2vid.js ' +
            '<initImagePath> [prompt] [outputDir] ' +
            '[i2vModelSrc] [t5XxlModelSrc] [vaeModelSrc] [clipVisionModelSrc]'
        )
        process.exit(1)
      }

      try {
        console.log('▸ Loading Wan 2.1 I2V model (diffusion + UMT5-XXL + VAE + CLIP vision)...')
        const modelId = await loadModel({
          modelSrc: diffusionModelSrc,
          modelType: 'sdcpp-generation',
          modelConfig: {
            mode: 'video',
            device: 'gpu',
            threads: 4,
            t5XxlModelSrc,
            vaeModelSrc,
            clipVisionModelSrc,
            diffusion_fa: true,
            offload_to_cpu: true,
            params_backend: 'vae=cpu',
            vae_tiling: true
          },
          onProgress: (p) => {
            const mb = (n: number) => (n / 1e6).toFixed(1)
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`)
            if (p.percentage >= 100) process.stderr.write('\n')
          }
        })
        console.log(`▸ Model loaded: ${modelId}`)

        const init_image = new Uint8Array(fs.readFileSync(initImagePath))
        console.log(`▸ Generating video for: "${prompt}"`)

        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'img2vid',
          prompt,
          init_image,
          negative_prompt: 'blurry, distorted, low quality, jittery, static, frozen',
          strength: 0.85,
          flow_shift: 3.0,
          video_frames: 33,
          fps: 16,
          steps: 30,
          cfg_scale: 6.0,
          seed: 42,
          vae_tiling: true
        })

        for await (const { step, totalSteps } of progressStream) {
          console.log(`▸ step ${step}/${totalSteps}`)
        }

        const buffers = await outputs
        for (let i = 0; i < buffers.length; i++) {
          const outputPath = path.join(outputDir, `wan_i2v_${i}.avi`)
          fs.writeFileSync(outputPath, buffers[i]!)
          console.log(`▸ Saved ${outputPath}`)
        }

        console.log('▸ Stats:', await stats)
        await unloadModel({ modelId, clearStorage: false })
        console.log('▸ Done')
        process.exit(0)
      } catch (error) {
        console.error('✖', error)
        process.exit(1)
      }
      ```
    </WrapCode>
  </Tab>
</Tabs>

### Text-to-video (Wan 2.2 TI2V-5B)

The following script shows text-to-video generation with Wan 2.2 TI2V-5B Turbo (single-expert). It loads the same split layout as Wan 2.1 T2V — diffusion model + UMT5-XXL text encoder + VAE — but with the Wan 2.2 VAE, whose 16× spatial compression enforces the TI2V 32-pixel grid. `high_noise_*` and `moe_boundary` are A14B-only and therefore omitted here:

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-txt2vid-wan22.js title="diffusion-txt2vid-wan22.js" lineNumbers
      import { loadModel, unloadModel, video, WAN2_2_TI2V_5B_Q5_K_S, UMT5_XXL_FP16, WAN_2_2_COMFYUI_REPACKAGED_VAE } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      // Text-to-video with Wan 2.2 TI2V-5B Turbo (Q5_K_S). Like Wan 2.1 this is a
      // split layout — diffusion model + UMT5-XXL text encoder + VAE — but it needs
      // the Wan 2.2 VAE specifically: its 16x spatial compression is what gives
      // TI2V its 32-pixel grid, and the Wan 2.1 VAE is not interchangeable.
      //
      // TI2V-5B is a single-expert model. The high_noise_* / moe_boundary fields
      // belong to the two-expert Wan 2.2 A14B layout and are rejected here.
      //
      // Budget at least 16 GB of video memory or 20 GB of unified memory. The
      // artifacts total roughly 16.4 GB, dominated by the fp16 text encoder.
      //
      // The text encoder is byte-identical to the Wan 2.1 repackage, so both
      // generations share the one UMT5_XXL_FP16 registry entry.
      const diffusionModelSrc = process.argv[2] || WAN2_2_TI2V_5B_Q5_K_S;
      const t5XxlModelSrc = process.argv[3] || UMT5_XXL_FP16;
      const vaeModelSrc = process.argv[4] || WAN_2_2_COMFYUI_REPACKAGED_VAE;
      // Prompt tip: Turbo responds well to explicit camera and lighting direction.
      // Describe continuous motion rather than a pose, or the clip reads as a still.
      const prompt = process.argv[5] ||
          'A single white porcelain espresso cup on a dark walnut table beside a sunlit window, ' +
              'delicate steam curling upward, slow circular camera move, warm morning light, ' +
              'sharp ceramic texture, realistic continuous motion';
      const outputDir = process.argv[6] || '.';
      try {
          console.log('▸ Loading Wan 2.2 TI2V-5B Turbo model (diffusion + UMT5-XXL + Wan 2.2 VAE)...');
          const modelId = await loadModel({
              modelSrc: diffusionModelSrc,
              modelType: 'sdcpp-generation',
              modelConfig: {
                  mode: 'video',
                  device: 'gpu',
                  threads: 4,
                  t5XxlModelSrc,
                  vaeModelSrc,
                  diffusion_fa: true,
                  offload_to_cpu: true,
                  vae_tiling: true
              },
              onProgress: (p) => {
                  const mb = (n) => (n / 1e6).toFixed(1);
                  const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
                  process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
                  if (p.percentage >= 100)
                      process.stderr.write('\n');
              }
          });
          console.log(`▸ Model loaded: ${modelId}`);
          console.log(`\n▸ Generating video for: "${prompt}"`);
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'txt2vid',
              prompt,
              negative_prompt: 'flickering, temporal jitter, morphing, duplicated subject, warped geometry, ' +
                  'distorted anatomy, blurry details, low resolution, text, watermark, logo',
              // TI2V-5B requires width and height to be multiples of 32 — stricter than
              // the multiple of 16 the schema enforces for Wan generally. Native
              // validation derives this from the loaded GGUF, so a non-conforming size
              // fails at generation time rather than at load.
              // Turbo was trained on this 720p, 24 fps, five-second shape; 640x352 with
              // 49 frames is a much cheaper way to smoke-test the pipeline.
              width: 1280,
              height: 704,
              // Frame count must satisfy (4*k + 1), k >= 1. At 24 fps: 49 ≈ 2s,
              // 121 ≈ 5s (the trained length).
              video_frames: 121,
              fps: 24,
              // Turbo is distilled for very short schedules: 4 steps with guidance
              // effectively disabled. The non-distilled TI2V-5B wants steps >= 30 and
              // cfg ~5.0 instead.
              steps: 4,
              cfg_scale: 1.0,
              flow_shift: 5.0,
              seed: 42,
              vae_tiling: true
          });
          for await (const { step, totalSteps } of progressStream) {
              console.log(`▸ step ${step}/${totalSteps}`);
          }
          const buffers = await outputs;
          for (let i = 0; i < buffers.length; i++) {
              const outputPath = path.join(outputDir, `wan22_ti2v_t2v_${i}.avi`);
              fs.writeFileSync(outputPath, buffers[i]);
              console.log(`▸ Saved ${outputPath}`);
          }
          console.log('\n▸ Stats:', await stats);
          await unloadModel({ modelId, clearStorage: false });
          console.log('▸ Done.');
          process.exit(0);
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-txt2vid-wan22.ts title="diffusion-txt2vid-wan22.ts" lineNumbers
      import {
        loadModel,
        unloadModel,
        video,
        WAN2_2_TI2V_5B_Q5_K_S,
        UMT5_XXL_FP16,
        WAN_2_2_COMFYUI_REPACKAGED_VAE
      } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      // Text-to-video with Wan 2.2 TI2V-5B Turbo (Q5_K_S). Like Wan 2.1 this is a
      // split layout — diffusion model + UMT5-XXL text encoder + VAE — but it needs
      // the Wan 2.2 VAE specifically: its 16x spatial compression is what gives
      // TI2V its 32-pixel grid, and the Wan 2.1 VAE is not interchangeable.
      //
      // TI2V-5B is a single-expert model. The high_noise_* / moe_boundary fields
      // belong to the two-expert Wan 2.2 A14B layout and are rejected here.
      //
      // Budget at least 16 GB of video memory or 20 GB of unified memory. The
      // artifacts total roughly 16.4 GB, dominated by the fp16 text encoder.
      //
      // The text encoder is byte-identical to the Wan 2.1 repackage, so both
      // generations share the one UMT5_XXL_FP16 registry entry.
      const diffusionModelSrc = process.argv[2] || WAN2_2_TI2V_5B_Q5_K_S
      const t5XxlModelSrc = process.argv[3] || UMT5_XXL_FP16
      const vaeModelSrc = process.argv[4] || WAN_2_2_COMFYUI_REPACKAGED_VAE

      // Prompt tip: Turbo responds well to explicit camera and lighting direction.
      // Describe continuous motion rather than a pose, or the clip reads as a still.
      const prompt =
        process.argv[5] ||
        'A single white porcelain espresso cup on a dark walnut table beside a sunlit window, ' +
          'delicate steam curling upward, slow circular camera move, warm morning light, ' +
          'sharp ceramic texture, realistic continuous motion'
      const outputDir = process.argv[6] || '.'

      try {
        console.log('▸ Loading Wan 2.2 TI2V-5B Turbo model (diffusion + UMT5-XXL + Wan 2.2 VAE)...')
        const modelId = await loadModel({
          modelSrc: diffusionModelSrc,
          modelType: 'sdcpp-generation',
          modelConfig: {
            mode: 'video',
            device: 'gpu',
            threads: 4,
            t5XxlModelSrc,
            vaeModelSrc,
            diffusion_fa: true,
            offload_to_cpu: true,
            vae_tiling: true
          },
          onProgress: (p) => {
            const mb = (n: number) => (n / 1e6).toFixed(1)
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`)
            if (p.percentage >= 100) process.stderr.write('\n')
          }
        })
        console.log(`▸ Model loaded: ${modelId}`)

        console.log(`\n▸ Generating video for: "${prompt}"`)

        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'txt2vid',
          prompt,
          negative_prompt:
            'flickering, temporal jitter, morphing, duplicated subject, warped geometry, ' +
            'distorted anatomy, blurry details, low resolution, text, watermark, logo',
          // TI2V-5B requires width and height to be multiples of 32 — stricter than
          // the multiple of 16 the schema enforces for Wan generally. Native
          // validation derives this from the loaded GGUF, so a non-conforming size
          // fails at generation time rather than at load.
          // Turbo was trained on this 720p, 24 fps, five-second shape; 640x352 with
          // 49 frames is a much cheaper way to smoke-test the pipeline.
          width: 1280,
          height: 704,
          // Frame count must satisfy (4*k + 1), k >= 1. At 24 fps: 49 ≈ 2s,
          // 121 ≈ 5s (the trained length).
          video_frames: 121,
          fps: 24,
          // Turbo is distilled for very short schedules: 4 steps with guidance
          // effectively disabled. The non-distilled TI2V-5B wants steps >= 30 and
          // cfg ~5.0 instead.
          steps: 4,
          cfg_scale: 1.0,
          flow_shift: 5.0,
          seed: 42,
          vae_tiling: true
        })

        for await (const { step, totalSteps } of progressStream) {
          console.log(`▸ step ${step}/${totalSteps}`)
        }

        const buffers = await outputs
        for (let i = 0; i < buffers.length; i++) {
          const outputPath = path.join(outputDir, `wan22_ti2v_t2v_${i}.avi`)
          fs.writeFileSync(outputPath, buffers[i]!)
          console.log(`▸ Saved ${outputPath}`)
        }

        console.log('\n▸ Stats:', await stats)
        await unloadModel({ modelId, clearStorage: false })
        console.log('▸ Done.')
        process.exit(0)
      } catch (error) {
        console.error('✖', error)
        process.exit(1)
      }
      ```
    </WrapCode>
  </Tab>
</Tabs>

### Text-to-video with audio (LTX-2)

The following script loads LTX-2 with its split layout (diffusion model + Gemma text encoder + video VAE + embedding connectors + optional audio VAE) and generates a clip with a synchronized 48 kHz audio track. It uses the LTX-2 dimension/frame constraints (multiples of 32, `8*k + 1` frames) and enables `temporal_tiling` to cap peak VRAM:

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-txt2vid-ltx.js title="diffusion-txt2vid-ltx.js" lineNumbers
      import { loadModel, unloadModel, video } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      // Text-to-video (+ synchronized audio) with LTX-2. Unlike Wan, LTX-2 uses a
      // Gemma text encoder (llmModelSrc), a video VAE (vaeModelSrc), a set of
      // text-embedding connectors (embeddingsConnectorsModelSrc — this is what
      // selects the LTX-2 layout), and an optional audio VAE (audioVaeModelSrc)
      // that produces a 48 kHz audio track muxed into the output AVI.
      //
      // This example needs powerful hardware: LTX-2.3 is a ~21B model, budget at
      // least 20+ GB of video / unified memory.
      //
      // No LTX-2 model constants ship in the SDK registry, so the model sources
      // default to public HuggingFace URLs (the SDK downloads http(s) sources
      // directly). Pass your own paths / URLs via argv to override.
      const HF = 'https://huggingface.co';
      const diffusionModelSrc = process.argv[2] ||
          `${HF}/QuantStack/LTX-2.3-GGUF/resolve/main/LTX-2.3-distilled-1.1/LTX-2.3-22B-distilled-1.1-Q5_K_M.gguf`;
      const llmModelSrc = process.argv[3] || `${HF}/unsloth/gemma-3-12b-it-GGUF/resolve/main/gemma-3-12b-it-UD-Q4_K_XL.gguf`;
      const vaeModelSrc = process.argv[4] ||
          `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_video_vae.safetensors`;
      const audioVaeModelSrc = process.argv[5] ||
          `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_audio_vae.safetensors`;
      const embeddingsConnectorsModelSrc = process.argv[6] ||
          `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/text_encoders/ltx-2.3-22b-distilled_embeddings_connectors.safetensors`;
      const prompt = process.argv[7] || 'a claymation cat playing jazz on a piano';
      const outputDir = process.argv[8] || '.';
      try {
          console.log('▸ Loading LTX-2 model (diffusion + Gemma + video VAE + audio VAE + connectors)...');
          const modelId = await loadModel({
              modelSrc: diffusionModelSrc,
              modelType: 'sdcpp-generation',
              modelConfig: {
                  mode: 'video',
                  device: 'gpu',
                  threads: 4,
                  // Supplying embeddingsConnectorsModelSrc selects the LTX-2 layout.
                  llmModelSrc,
                  vaeModelSrc,
                  audioVaeModelSrc,
                  embeddingsConnectorsModelSrc,
                  diffusion_fa: true,
                  vae_tiling: true
              },
              onProgress: (p) => {
                  const mb = (n) => (n / 1e6).toFixed(1);
                  const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
                  process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
                  if (p.percentage >= 100)
                      process.stderr.write('\n');
              }
          });
          console.log(`▸ Model loaded: ${modelId}`);
          console.log(`\n▸ Generating video for: "${prompt}"`);
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'txt2vid',
              prompt,
              negative_prompt: 'blurry, low quality, static, jittery, watermark, distorted audio',
              // LTX-2 requires width/height as multiples of 32.
              width: 512,
              height: 320,
              // LTX-2 requires (8*k + 1) frames, max 257. At 24 fps: 25≈1s, 121≈5s, 241≈10s.
              video_frames: 121,
              fps: 24,
              // Distilled variants run in 4-8 steps with cfg ~1.0; the full --dev model
              // wants steps >= 20 and cfg ~7.0.
              steps: 10,
              cfg_scale: 1.0,
              seed: 42,
              // Tile the video VAE decode along the time axis to cap peak VRAM on long
              // / HD clips (LTX-2 only).
              temporal_tiling: true
          });
          for await (const { step, totalSteps } of progressStream) {
              console.log(`▸ step ${step}/${totalSteps}`);
          }
          const buffers = await outputs;
          for (let i = 0; i < buffers.length; i++) {
              const outputPath = path.join(outputDir, `ltx_t2v_${i}.avi`);
              fs.writeFileSync(outputPath, buffers[i]);
              console.log(`▸ Saved ${outputPath} (play in VLC to hear the muxed audio track)`);
          }
          console.log('\n▸ Stats:', await stats);
          await unloadModel({ modelId, clearStorage: false });
          console.log('▸ Done.');
          process.exit(0);
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-txt2vid-ltx.ts title="diffusion-txt2vid-ltx.ts" lineNumbers
      import { loadModel, unloadModel, video } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      // Text-to-video (+ synchronized audio) with LTX-2. Unlike Wan, LTX-2 uses a
      // Gemma text encoder (llmModelSrc), a video VAE (vaeModelSrc), a set of
      // text-embedding connectors (embeddingsConnectorsModelSrc — this is what
      // selects the LTX-2 layout), and an optional audio VAE (audioVaeModelSrc)
      // that produces a 48 kHz audio track muxed into the output AVI.
      //
      // This example needs powerful hardware: LTX-2.3 is a ~21B model, budget at
      // least 20+ GB of video / unified memory.
      //
      // No LTX-2 model constants ship in the SDK registry, so the model sources
      // default to public HuggingFace URLs (the SDK downloads http(s) sources
      // directly). Pass your own paths / URLs via argv to override.
      const HF = 'https://huggingface.co'
      const diffusionModelSrc =
        process.argv[2] ||
        `${HF}/QuantStack/LTX-2.3-GGUF/resolve/main/LTX-2.3-distilled-1.1/LTX-2.3-22B-distilled-1.1-Q5_K_M.gguf`
      const llmModelSrc =
        process.argv[3] || `${HF}/unsloth/gemma-3-12b-it-GGUF/resolve/main/gemma-3-12b-it-UD-Q4_K_XL.gguf`
      const vaeModelSrc =
        process.argv[4] ||
        `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_video_vae.safetensors`
      const audioVaeModelSrc =
        process.argv[5] ||
        `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_audio_vae.safetensors`
      const embeddingsConnectorsModelSrc =
        process.argv[6] ||
        `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/text_encoders/ltx-2.3-22b-distilled_embeddings_connectors.safetensors`

      const prompt = process.argv[7] || 'a claymation cat playing jazz on a piano'
      const outputDir = process.argv[8] || '.'

      try {
        console.log('▸ Loading LTX-2 model (diffusion + Gemma + video VAE + audio VAE + connectors)...')
        const modelId = await loadModel({
          modelSrc: diffusionModelSrc,
          modelType: 'sdcpp-generation',
          modelConfig: {
            mode: 'video',
            device: 'gpu',
            threads: 4,
            // Supplying embeddingsConnectorsModelSrc selects the LTX-2 layout.
            llmModelSrc,
            vaeModelSrc,
            audioVaeModelSrc,
            embeddingsConnectorsModelSrc,
            diffusion_fa: true,
            vae_tiling: true
          },
          onProgress: (p) => {
            const mb = (n: number) => (n / 1e6).toFixed(1)
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`)
            if (p.percentage >= 100) process.stderr.write('\n')
          }
        })
        console.log(`▸ Model loaded: ${modelId}`)

        console.log(`\n▸ Generating video for: "${prompt}"`)

        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'txt2vid',
          prompt,
          negative_prompt: 'blurry, low quality, static, jittery, watermark, distorted audio',
          // LTX-2 requires width/height as multiples of 32.
          width: 512,
          height: 320,
          // LTX-2 requires (8*k + 1) frames, max 257. At 24 fps: 25≈1s, 121≈5s, 241≈10s.
          video_frames: 121,
          fps: 24,
          // Distilled variants run in 4-8 steps with cfg ~1.0; the full --dev model
          // wants steps >= 20 and cfg ~7.0.
          steps: 10,
          cfg_scale: 1.0,
          seed: 42,
          // Tile the video VAE decode along the time axis to cap peak VRAM on long
          // / HD clips (LTX-2 only).
          temporal_tiling: true
        })

        for await (const { step, totalSteps } of progressStream) {
          console.log(`▸ step ${step}/${totalSteps}`)
        }

        const buffers = await outputs
        for (let i = 0; i < buffers.length; i++) {
          const outputPath = path.join(outputDir, `ltx_t2v_${i}.avi`)
          fs.writeFileSync(outputPath, buffers[i]!)
          console.log(`▸ Saved ${outputPath} (play in VLC to hear the muxed audio track)`)
        }

        console.log('\n▸ Stats:', await stats)
        await unloadModel({ modelId, clearStorage: false })
        console.log('▸ Done.')
        process.exit(0)
      } catch (error) {
        console.error('✖', error)
        process.exit(1)
      }
      ```
    </WrapCode>
  </Tab>
</Tabs>

### Image-to-video (LTX-2)

The following script animates a first-frame `init_image` with LTX-2. Unlike WAN I2V, the LTX-2 img2vid path needs no CLIP-vision encoder:

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-img2vid-ltx.js title="diffusion-img2vid-ltx.js" lineNumbers
      import { loadModel, unloadModel, video } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      // Image-to-video (+ synchronized audio) with LTX-2. Unlike Wan I2V, LTX-2
      // conditions on the first frame through its video VAE, so it needs NO CLIP
      // vision weights (clipVisionModelSrc) — the same LTX-2 layout used for
      // txt2vid works for img2vid, you just add an init_image at generation time.
      //
      // This example needs powerful hardware: LTX-2.3 is a ~21B model, budget at
      // least 20+ GB of video / unified memory.
      //
      // No LTX-2 model constants ship in the SDK registry, so the model sources
      // default to public HuggingFace URLs (the SDK downloads http(s) sources
      // directly). Pass your own paths / URLs via argv to override.
      const HF = 'https://huggingface.co';
      const initImagePath = process.argv[2];
      const prompt = process.argv[3] || 'the subject slowly turns and smiles, soft natural lighting, cinematic';
      const outputDir = process.argv[4] || '.';
      const diffusionModelSrc = process.argv[5] ||
          `${HF}/QuantStack/LTX-2.3-GGUF/resolve/main/LTX-2.3-distilled-1.1/LTX-2.3-22B-distilled-1.1-Q5_K_M.gguf`;
      const llmModelSrc = process.argv[6] || `${HF}/unsloth/gemma-3-12b-it-GGUF/resolve/main/gemma-3-12b-it-UD-Q4_K_XL.gguf`;
      const vaeModelSrc = process.argv[7] ||
          `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_video_vae.safetensors`;
      const audioVaeModelSrc = process.argv[8] ||
          `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_audio_vae.safetensors`;
      const embeddingsConnectorsModelSrc = process.argv[9] ||
          `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/text_encoders/ltx-2.3-22b-distilled_embeddings_connectors.safetensors`;
      if (!initImagePath) {
          console.error('✖ init image path is required');
          console.error('Usage: bun run bare:example dist/examples/diffusion-img2vid-ltx.js ' +
              '<initImagePath> [prompt] [outputDir] ' +
              '[diffusionModelSrc] [llmModelSrc] [vaeModelSrc] [audioVaeModelSrc] [embeddingsConnectorsModelSrc]');
          process.exit(1);
      }
      try {
          console.log('▸ Loading LTX-2 model (diffusion + Gemma + video VAE + audio VAE + connectors)...');
          // no clipVisionModelSrc — LTX-2 img2vid does not use CLIP vision.
          const modelId = await loadModel({
              modelSrc: diffusionModelSrc,
              modelType: 'sdcpp-generation',
              modelConfig: {
                  mode: 'video',
                  device: 'gpu',
                  threads: 4,
                  // Supplying embeddingsConnectorsModelSrc selects the LTX-2 layout.
                  llmModelSrc,
                  vaeModelSrc,
                  audioVaeModelSrc,
                  embeddingsConnectorsModelSrc,
                  diffusion_fa: true,
                  vae_tiling: true
              },
              onProgress: (p) => {
                  const mb = (n) => (n / 1e6).toFixed(1);
                  const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
                  process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
                  if (p.percentage >= 100)
                      process.stderr.write('\n');
              }
          });
          console.log(`▸ Model loaded: ${modelId}`);
          const init_image = new Uint8Array(fs.readFileSync(initImagePath));
          console.log(`▸ Generating video for: "${prompt}"`);
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'img2vid',
              prompt,
              init_image,
              negative_prompt: 'blurry, distorted, low quality, jittery, static, frozen, distorted audio',
              strength: 0.85,
              // LTX-2 requires width/height as multiples of 32 and (8*k + 1) frames (max 257).
              width: 512,
              height: 320,
              video_frames: 121,
              fps: 24,
              steps: 10,
              cfg_scale: 1.0,
              seed: 42,
              temporal_tiling: true
          });
          for await (const { step, totalSteps } of progressStream) {
              console.log(`▸ step ${step}/${totalSteps}`);
          }
          const buffers = await outputs;
          for (let i = 0; i < buffers.length; i++) {
              const outputPath = path.join(outputDir, `ltx_i2v_${i}.avi`);
              fs.writeFileSync(outputPath, buffers[i]);
              console.log(`▸ Saved ${outputPath} (play in VLC to hear the muxed audio track)`);
          }
          console.log('▸ Stats:', await stats);
          await unloadModel({ modelId, clearStorage: false });
          console.log('▸ Done');
          process.exit(0);
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-img2vid-ltx.ts title="diffusion-img2vid-ltx.ts" lineNumbers
      import { loadModel, unloadModel, video } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      // Image-to-video (+ synchronized audio) with LTX-2. Unlike Wan I2V, LTX-2
      // conditions on the first frame through its video VAE, so it needs NO CLIP
      // vision weights (clipVisionModelSrc) — the same LTX-2 layout used for
      // txt2vid works for img2vid, you just add an init_image at generation time.
      //
      // This example needs powerful hardware: LTX-2.3 is a ~21B model, budget at
      // least 20+ GB of video / unified memory.
      //
      // No LTX-2 model constants ship in the SDK registry, so the model sources
      // default to public HuggingFace URLs (the SDK downloads http(s) sources
      // directly). Pass your own paths / URLs via argv to override.
      const HF = 'https://huggingface.co'
      const initImagePath = process.argv[2]
      const prompt =
        process.argv[3] || 'the subject slowly turns and smiles, soft natural lighting, cinematic'
      const outputDir = process.argv[4] || '.'
      const diffusionModelSrc =
        process.argv[5] ||
        `${HF}/QuantStack/LTX-2.3-GGUF/resolve/main/LTX-2.3-distilled-1.1/LTX-2.3-22B-distilled-1.1-Q5_K_M.gguf`
      const llmModelSrc =
        process.argv[6] || `${HF}/unsloth/gemma-3-12b-it-GGUF/resolve/main/gemma-3-12b-it-UD-Q4_K_XL.gguf`
      const vaeModelSrc =
        process.argv[7] ||
        `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_video_vae.safetensors`
      const audioVaeModelSrc =
        process.argv[8] ||
        `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/vae/ltx-2.3-22b-distilled_audio_vae.safetensors`
      const embeddingsConnectorsModelSrc =
        process.argv[9] ||
        `${HF}/unsloth/LTX-2.3-GGUF/resolve/main/text_encoders/ltx-2.3-22b-distilled_embeddings_connectors.safetensors`

      if (!initImagePath) {
        console.error('✖ init image path is required')
        console.error(
          'Usage: bun run bare:example dist/examples/diffusion-img2vid-ltx.js ' +
            '<initImagePath> [prompt] [outputDir] ' +
            '[diffusionModelSrc] [llmModelSrc] [vaeModelSrc] [audioVaeModelSrc] [embeddingsConnectorsModelSrc]'
        )
        process.exit(1)
      }

      try {
        console.log('▸ Loading LTX-2 model (diffusion + Gemma + video VAE + audio VAE + connectors)...')
        // no clipVisionModelSrc — LTX-2 img2vid does not use CLIP vision.
        const modelId = await loadModel({
          modelSrc: diffusionModelSrc,
          modelType: 'sdcpp-generation',
          modelConfig: {
            mode: 'video',
            device: 'gpu',
            threads: 4,
            // Supplying embeddingsConnectorsModelSrc selects the LTX-2 layout.
            llmModelSrc,
            vaeModelSrc,
            audioVaeModelSrc,
            embeddingsConnectorsModelSrc,
            diffusion_fa: true,
            vae_tiling: true
          },
          onProgress: (p) => {
            const mb = (n: number) => (n / 1e6).toFixed(1)
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`)
            if (p.percentage >= 100) process.stderr.write('\n')
          }
        })
        console.log(`▸ Model loaded: ${modelId}`)

        const init_image = new Uint8Array(fs.readFileSync(initImagePath))
        console.log(`▸ Generating video for: "${prompt}"`)

        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'img2vid',
          prompt,
          init_image,
          negative_prompt: 'blurry, distorted, low quality, jittery, static, frozen, distorted audio',
          strength: 0.85,
          // LTX-2 requires width/height as multiples of 32 and (8*k + 1) frames (max 257).
          width: 512,
          height: 320,
          video_frames: 121,
          fps: 24,
          steps: 10,
          cfg_scale: 1.0,
          seed: 42,
          temporal_tiling: true
        })

        for await (const { step, totalSteps } of progressStream) {
          console.log(`▸ step ${step}/${totalSteps}`)
        }

        const buffers = await outputs
        for (let i = 0; i < buffers.length; i++) {
          const outputPath = path.join(outputDir, `ltx_i2v_${i}.avi`)
          fs.writeFileSync(outputPath, buffers[i]!)
          console.log(`▸ Saved ${outputPath} (play in VLC to hear the muxed audio track)`)
        }

        console.log('▸ Stats:', await stats)
        await unloadModel({ modelId, clearStorage: false })
        console.log('▸ Done')
        process.exit(0)
      } catch (error) {
        console.error('✖', error)
        process.exit(1)
      }
      ```
    </WrapCode>
  </Tab>
</Tabs>

### Reference-conditioned text-to-video (LTX-2 IC-LoRA)

The following script conditions LTX-2 on a composite reference sheet so the generated video inherits a specific character or style. Load LTX-2 with `lora_apply_mode: 'at_runtime'`, then pass the reference sheet in `reference_images` alongside an Ingredients LoRA in `lora`. Reference conditioning requires at least **121** frames, so `video_frames` is set to `217`:

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-ic-lora-ltx.js title="diffusion-ic-lora-ltx.js" lineNumbers
      import { GEMMA_3_12B_Q4_K_XL, loadModel, LTX_2_3_22B_DISTILLED_EMBEDDINGS_CONNECTORS, LTX_2_3_22B_Q5_K_M, LTX_2_3_AUDIO_VAE, LTX_2_3_VIDEO_VAE, unloadModel, video } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      const loraPath = process.argv[2];
      const referencePath = process.argv[3];
      const prompt = process.argv[4] ||
          'Reference sheet: a red-haired explorer in a blue parka. Generated video: the explorer crosses a snowy ridge at sunrise.';
      const outputDir = process.argv[5] || '.';
      if (!loraPath || !referencePath) {
          console.error('Usage: bare:example examples/diffusion-ic-lora-ltx.ts <absolute-lora-path> <reference-sheet-path> [prompt] [output-dir]');
          process.exit(1);
      }
      if (!path.isAbsolute(loraPath)) {
          console.error('The LoRA adapter path must be absolute on the inference worker.');
          process.exit(1);
      }
      const referenceSheet = fs.readFileSync(referencePath);
      try {
          const modelId = await loadModel({
              modelSrc: LTX_2_3_22B_Q5_K_M,
              modelType: 'sdcpp-generation',
              modelConfig: {
                  mode: 'video',
                  device: 'gpu',
                  threads: 4,
                  llmModelSrc: GEMMA_3_12B_Q4_K_XL,
                  vaeModelSrc: LTX_2_3_VIDEO_VAE,
                  audioVaeModelSrc: LTX_2_3_AUDIO_VAE,
                  embeddingsConnectorsModelSrc: LTX_2_3_22B_DISTILLED_EMBEDDINGS_CONNECTORS,
                  diffusion_fa: true,
                  offload_to_cpu: true,
                  vae_tiling: true,
                  lora_apply_mode: 'at_runtime'
              }
          });
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'txt2vid',
              prompt,
              negative_prompt: 'blurry, low quality, static, jittery, watermark',
              width: 768,
              height: 448,
              video_frames: 217,
              fps: 24,
              steps: 8,
              scheduler: 'ltx2',
              cfg_scale: 1,
              seed: 84,
              lora: loraPath,
              lora_strength: 1.37,
              stg_scale: 1,
              stg_block: 29,
              reference_images: [referenceSheet],
              reference_attention_strength: 1,
              reference_downscale_factor: 1,
              temporal_tiling: true,
              vae_tile_size: 4
          });
          for await (const { step, totalSteps } of progressStream) {
              console.log(`▸ step ${step}/${totalSteps}`);
          }
          fs.mkdirSync(outputDir, { recursive: true });
          const buffers = await outputs;
          for (let i = 0; i < buffers.length; i++) {
              const outputPath = path.join(outputDir, `ltx_ic_lora_${i}.avi`);
              fs.writeFileSync(outputPath, buffers[i]);
              console.log(`▸ Saved ${outputPath}`);
          }
          console.log('▸ Stats:', await stats);
          await unloadModel({ modelId, clearStorage: false });
          process.exit(0);
      }
      catch (error) {
          console.error('✖', error);
          process.exit(1);
      }
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-ic-lora-ltx.ts title="diffusion-ic-lora-ltx.ts" lineNumbers
      import {
        GEMMA_3_12B_Q4_K_XL,
        loadModel,
        LTX_2_3_22B_DISTILLED_EMBEDDINGS_CONNECTORS,
        LTX_2_3_22B_Q5_K_M,
        LTX_2_3_AUDIO_VAE,
        LTX_2_3_VIDEO_VAE,
        unloadModel,
        video
      } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      const loraPath = process.argv[2]
      const referencePath = process.argv[3]
      const prompt =
        process.argv[4] ||
        'Reference sheet: a red-haired explorer in a blue parka. Generated video: the explorer crosses a snowy ridge at sunrise.'
      const outputDir = process.argv[5] || '.'

      if (!loraPath || !referencePath) {
        console.error(
          'Usage: bare:example examples/diffusion-ic-lora-ltx.ts <absolute-lora-path> <reference-sheet-path> [prompt] [output-dir]'
        )
        process.exit(1)
      }
      if (!path.isAbsolute(loraPath)) {
        console.error('The LoRA adapter path must be absolute on the inference worker.')
        process.exit(1)
      }

      const referenceSheet = fs.readFileSync(referencePath)

      try {
        const modelId = await loadModel({
          modelSrc: LTX_2_3_22B_Q5_K_M,
          modelType: 'sdcpp-generation',
          modelConfig: {
            mode: 'video',
            device: 'gpu',
            threads: 4,
            llmModelSrc: GEMMA_3_12B_Q4_K_XL,
            vaeModelSrc: LTX_2_3_VIDEO_VAE,
            audioVaeModelSrc: LTX_2_3_AUDIO_VAE,
            embeddingsConnectorsModelSrc: LTX_2_3_22B_DISTILLED_EMBEDDINGS_CONNECTORS,
            diffusion_fa: true,
            offload_to_cpu: true,
            vae_tiling: true,
            lora_apply_mode: 'at_runtime'
          }
        })

        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'txt2vid',
          prompt,
          negative_prompt: 'blurry, low quality, static, jittery, watermark',
          width: 768,
          height: 448,
          video_frames: 217,
          fps: 24,
          steps: 8,
          scheduler: 'ltx2',
          cfg_scale: 1,
          seed: 84,
          lora: loraPath,
          lora_strength: 1.37,
          stg_scale: 1,
          stg_block: 29,
          reference_images: [referenceSheet],
          reference_attention_strength: 1,
          reference_downscale_factor: 1,
          temporal_tiling: true,
          vae_tile_size: 4
        })

        for await (const { step, totalSteps } of progressStream) {
          console.log(`▸ step ${step}/${totalSteps}`)
        }

        fs.mkdirSync(outputDir, { recursive: true })
        const buffers = await outputs
        for (let i = 0; i < buffers.length; i++) {
          const outputPath = path.join(outputDir, `ltx_ic_lora_${i}.avi`)
          fs.writeFileSync(outputPath, buffers[i]!)
          console.log(`▸ Saved ${outputPath}`)
        }

        console.log('▸ Stats:', await stats)
        await unloadModel({ modelId, clearStorage: false })
        process.exit(0)
      } catch (error) {
        console.error('✖', error)
        process.exit(1)
      }
      ```
    </WrapCode>
  </Tab>
</Tabs>

### Text-to-video with audio (MiniMax-H3)

The following script loads MiniMax-H3 with its split layout (diffusion model + Qwen3-VL text encoder + video VAE + audio VAE) and generates a clip with a synchronized 32 kHz audio track. It uses the H3 constraints (dimensions in multiples of 32, `17*k + 5` frames, 24 FPS) and leaves `offload_to_cpu` on so the run fits on a single GPU:

<Tabs>
  <Tab value="js" label="JavaScript" default>
    <WrapCode>
      ```js file=<rootDir>/packages/sdk/dist/examples/diffusion-txt2vid-minimax-h3.js title="diffusion-txt2vid-minimax-h3.js" lineNumbers
      import { loadModel, unloadModel, video } from '@qvac/sdk';
      import fs from 'fs';
      import path from 'path';
      // Pass a directory containing the four MiniMax-H3 files, followed by an output path.
      const modelsDir = process.argv[2];
      if (!modelsDir) {
          throw new Error('Usage: diffusion-txt2vid-minimax-h3.ts <models-directory> [output.avi]');
      }
      const outputPath = process.argv[3] || 'minimax-h3.avi';
      const modelSrc = path.resolve(modelsDir, process.env['H3_MODEL'] || 'minimax_h3_fl2va_pruned-Q4_K.gguf');
      const llmModelSrc = path.resolve(modelsDir, process.env['H3_LLM'] || 'qwen3vl_32b_minimax_h3-Q4_K_M.gguf');
      const vaeModelSrc = path.resolve(modelsDir, 'vae/minimax_h3_video_vae_fp16.safetensors');
      const audioVaeModelSrc = path.resolve(modelsDir, 'vae/minimax_h3_audio_vae_fp32.safetensors');
      for (const file of [modelSrc, llmModelSrc, vaeModelSrc, audioVaeModelSrc])
          fs.accessSync(file);
      let modelId;
      try {
          modelId = await loadModel({
              modelType: 'sdcpp-generation',
              modelSrc,
              modelConfig: {
                  mode: 'video',
                  llmModelSrc,
                  vaeModelSrc,
                  audioVaeModelSrc,
                  device: 'gpu',
                  diffusion_fa: true,
                  offload_to_cpu: process.env['H3_OFFLOAD_TO_CPU'] !== '0',
                  stream_layers: process.env['H3_STREAM_LAYERS'] === '1',
                  ...(process.env['H3_BACKEND'] && { backend: process.env['H3_BACKEND'] }),
                  ...(process.env['H3_PARAMS_BACKEND'] && { params_backend: process.env['H3_PARAMS_BACKEND'] }),
                  ...(process.env['H3_MAX_VRAM'] && { max_vram: process.env['H3_MAX_VRAM'] })
              }
          });
          const { progressStream, outputs, stats } = video({
              modelId,
              mode: 'txt2vid',
              prompt: process.env['PROMPT'] ||
                  'Premium cinematic coffee commercial. A confident adult sits at a small café table at sunrise, slowly lifts one matte black coffee cup, takes a relaxed sip, and smiles. Warm golden rim light, drifting steam, realistic skin, natural hands, shallow depth of field, subtle slow camera push-in, restrained natural motion, polished live-action advertising, no dialogue, no text overlay.',
              negative_prompt: 'extra people, duplicate cup, malformed hands, cup fused to hand, text, subtitles, watermark, cartoon, CGI, blur, flicker, jitter, camera shake',
              width: 960,
              height: 544,
              // H3 uses 17*k+5 frames; 124 frames at 24 FPS is approximately 5.17 seconds.
              video_frames: Number(process.env['FRAMES'] || 124),
              fps: 24,
              steps: 8,
              cfg_scale: 1,
              seed: 11
          });
          const progress = (async () => {
              for await (const tick of progressStream)
                  console.log(`Step ${tick.step}/${tick.totalSteps}`);
          })();
          const [buffers, runtimeStats] = await Promise.all([outputs, stats, progress]);
          if (!buffers[0])
              throw new Error('Generation finished without an AVI result');
          fs.writeFileSync(outputPath, buffers[0]);
          console.log(`Saved ${outputPath}`, runtimeStats);
      }
      finally {
          if (modelId)
              await unloadModel({ modelId, clearStorage: false });
      }
      process.exit(0);
      ```
    </WrapCode>
  </Tab>

  <Tab value="ts" label="TypeScript">
    <WrapCode>
      ```ts file=<rootDir>/packages/sdk/examples/diffusion-txt2vid-minimax-h3.ts title="diffusion-txt2vid-minimax-h3.ts" lineNumbers
      import { loadModel, unloadModel, video } from '@qvac/sdk'
      import fs from 'fs'
      import path from 'path'

      // Pass a directory containing the four MiniMax-H3 files, followed by an output path.
      const modelsDir = process.argv[2]
      if (!modelsDir) {
        throw new Error('Usage: diffusion-txt2vid-minimax-h3.ts <models-directory> [output.avi]')
      }
      const outputPath = process.argv[3] || 'minimax-h3.avi'
      const modelSrc = path.resolve(
        modelsDir,
        process.env['H3_MODEL'] || 'minimax_h3_fl2va_pruned-Q4_K.gguf'
      )
      const llmModelSrc = path.resolve(
        modelsDir,
        process.env['H3_LLM'] || 'qwen3vl_32b_minimax_h3-Q4_K_M.gguf'
      )
      const vaeModelSrc = path.resolve(modelsDir, 'vae/minimax_h3_video_vae_fp16.safetensors')
      const audioVaeModelSrc = path.resolve(modelsDir, 'vae/minimax_h3_audio_vae_fp32.safetensors')
      for (const file of [modelSrc, llmModelSrc, vaeModelSrc, audioVaeModelSrc]) fs.accessSync(file)

      let modelId: string | undefined
      try {
        modelId = await loadModel({
          modelType: 'sdcpp-generation',
          modelSrc,
          modelConfig: {
            mode: 'video',
            llmModelSrc,
            vaeModelSrc,
            audioVaeModelSrc,
            device: 'gpu',
            diffusion_fa: true,
            offload_to_cpu: process.env['H3_OFFLOAD_TO_CPU'] !== '0',
            stream_layers: process.env['H3_STREAM_LAYERS'] === '1',
            ...(process.env['H3_BACKEND'] && { backend: process.env['H3_BACKEND'] }),
            ...(process.env['H3_PARAMS_BACKEND'] && { params_backend: process.env['H3_PARAMS_BACKEND'] }),
            ...(process.env['H3_MAX_VRAM'] && { max_vram: process.env['H3_MAX_VRAM'] })
          }
        })
        const { progressStream, outputs, stats } = video({
          modelId,
          mode: 'txt2vid',
          prompt:
            process.env['PROMPT'] ||
            'Premium cinematic coffee commercial. A confident adult sits at a small café table at sunrise, slowly lifts one matte black coffee cup, takes a relaxed sip, and smiles. Warm golden rim light, drifting steam, realistic skin, natural hands, shallow depth of field, subtle slow camera push-in, restrained natural motion, polished live-action advertising, no dialogue, no text overlay.',
          negative_prompt:
            'extra people, duplicate cup, malformed hands, cup fused to hand, text, subtitles, watermark, cartoon, CGI, blur, flicker, jitter, camera shake',
          width: 960,
          height: 544,
          // H3 uses 17*k+5 frames; 124 frames at 24 FPS is approximately 5.17 seconds.
          video_frames: Number(process.env['FRAMES'] || 124),
          fps: 24,
          steps: 8,
          cfg_scale: 1,
          seed: 11
        })
        const progress = (async () => {
          for await (const tick of progressStream) console.log(`Step ${tick.step}/${tick.totalSteps}`)
        })()
        const [buffers, runtimeStats] = await Promise.all([outputs, stats, progress])
        if (!buffers[0]) throw new Error('Generation finished without an AVI result')
        fs.writeFileSync(outputPath, buffers[0])
        console.log(`Saved ${outputPath}`, runtimeStats)
      } finally {
        if (modelId) await unloadModel({ modelId, clearStorage: false })
      }
      process.exit(0)
      ```
    </WrapCode>
  </Tab>
</Tabs>

<Callout type="info">
  The Python client supports this capability through the same worker. A dedicated Python example is not yet published — see the [Python SDK](/sdk/v0.18/python-sdk) for the API surface.
</Callout>

<Callout type="success">
  **Tip:** all examples throughout this documentation are self-contained and runnable. For instructions on how to run them, see the [JS/TS quickstart](/sdk/v0.18/js-ts-sdk#quickstart) or the [Python quickstart](/sdk/v0.18/python-sdk#quickstart).
</Callout>
