songyiren's picture
Initial upload: LTX-V2V reference-conditioned video-to-video (code + configs, no weights)
be7e4b7 verified
|
Raw
History Blame Contribute Delete
10.1 kB
# LTX-V2V Reference-Conditioned Video-to-Video
Video-to-video generation on top of [LTX-2](https://github.com/Lightricks/LTX-2)
(Lightricks' DiT-based video+audio diffusion model): given a **source video**
(structure/motion signal) and a **first-frame reference image** (target
identity/appearance), generate an **output video** that follows the source
video's motion while adopting the reference image's look.
This repo is a trimmed-down, sharable fork containing:
- `packages/ltx-core`, `packages/ltx-pipelines` β€” the LTX-2 inference stack,
with a small local patch to the reference-conditioning position logic (see
below) that is **not** in upstream `Lightricks/LTX-2`.
- `packages/ltx-trainer` β€” LTX-2's LoRA/IC-LoRA training stack, with a matching
patch on the training side, plus a `reference_image` dataset column so you
can train on (video, reference_image, caption) triples.
- `scripts/infer_v2v.py` β€” new inference script for the video+ref-image β†’
video workflow described above.
- `configs/*.yaml`, `scripts/train_ic_lora.sh` β€” training configs and launcher
for the two custom IC-LoRA variants this workflow needs.
- `data/` β€” dataset format documentation and example JSONL rows.
**Not included:** pretrained IC-LoRA checkpoints. The ones we trained
internally were trained on licensed movie footage, so we're not redistributing
the weights β€” train your own with `scripts/train_ic_lora.sh` on your own data
(see `data/README.md`).
## Quick start
```bash
./setup.sh # uv sync + download ~90GB of base-model weights
source .venv/bin/activate
# 1. Prepare your data (see data/README.md for the JSONL schema)
scripts/preprocess_dataset.sh data/dataset.jsonl "1920x1024x233"
# 2. Train an IC-LoRA (defaults to configs/v2v_reference_ic_lora.yaml)
scripts/train_ic_lora.sh --config configs/v2v_reference_ic_lora.yaml
# 3. Run inference with your trained checkpoint
python scripts/infer_v2v.py \
--input-video my_source_video.mp4 \
--ref-image my_reference.png \
--prompt "A woman in a red dress walks through a garden, camera tracking steadily." \
--structure-lora packages/ltx-trainer/outputs/v2v_reference_ic_lora/checkpoints/lora_weights_step_08000.safetensors \
--output out.mp4
```
`setup.sh` downloads gated/large weights from HuggingFace β€” if a download
401s, run `huggingface-cli login` (or set `HF_TOKEN`) first.
## How it works
### Input video β†’ output video
The source video is never fed to the transformer as raw pixels through a
special "V2V" code path β€” there isn't one. It goes through the exact same
`video_conditioning` mechanism IC-LoRA uses for any reference signal
(`ICLoraPipeline.__call__`'s `video_conditioning: list[tuple[path, strength]]`
parameter, in `packages/ltx-pipelines/src/ltx_pipelines/ic_lora.py`): it's
VAE-encoded, patchified into tokens exactly like the noisy video latent, and
**appended to the same self-attention token sequence** the denoiser attends
over (`VideoConditionByReferenceLatent`,
`packages/ltx-core/src/ltx_core/conditioning/types/reference_video_cond.py`).
A LoRA (`--structure-lora`) is what actually teaches the model to *use* those
extra tokens meaningfully β€” the base model has no opinion on what an arbitrary
appended video means.
Important: `reference_video`/`--input-video` is not necessarily raw source
footage β€” it's whatever structure/motion signal your LoRA was trained to
condition on (pose render, edge map, or the raw video itself, if that's what
you trained with). See `data/README.md` β†’ "About `reference_video`".
### Two ways to position-encode the reference image
This is the part that's easy to get subtly wrong, so it's exposed as an
explicit `--pos-mode` flag in `scripts/infer_v2v.py` rather than being baked in
silently:
**`--pos-mode first-frame` (default)** β€” the reference image **reuses** the
denoised video's own frame-0 RoPE position. Implemented via `ICLoraPipeline`'s
native `images: list[(path, frame_idx, strength)]` parameter β†’ this is a
**base-model capability, no LoRA required**. Code path:
`combined_image_conditionings()` in
`packages/ltx-pipelines/src/ltx_pipelines/utils/helpers.py` β†’
`VideoConditionByLatentIndex` / `VideoConditionByKeyframeIndex` in
`packages/ltx-core/src/ltx_core/conditioning/types/`. Because the reference
image is placed at the exact same absolute time position as frame 0, RoPE's
relative-rotation attention naturally treats it as "this is what frame 0 looks
like" rather than as a separate signal.
**`--pos-mode reference`** β€” the reference image gets its **own, disjoint**
RoPE position range instead of sharing frame 0's. Implemented via
`video_conditioning` β†’ `VideoConditionByReferenceLatent`
(`packages/ltx-core/src/ltx_core/conditioning/types/reference_video_cond.py`).
This is the path our custom IC-LoRAs (`configs/ref_image_ic_lora.yaml`,
`configs/v2v_reference_ic_lora.yaml`) are actually trained against, and is
generally the better-performing option **once you have a matching LoRA** β€” the
base model alone doesn't know what to do with a disjoint-position reference
block; it has to be taught.
Why "disjoint" instead of just also using t=0: giving the reference image the
same absolute position as the target's frame 0 means RoPE's relative rotation
between them is **zero** β€” the attention mechanism can't tell the reference
token and the target's own frame-0 token apart by position alone. The fix
(originally a local patch on top of upstream LTX-2, in
`reference_video_cond.py`):
```python
# packages/ltx-core/src/ltx_core/conditioning/types/reference_video_cond.py
REF_POSITION_TICK_SECONDS = 1e-3
current_min_time = latent_state.positions[:, 0, ...].min()
ref_max_time = positions[:, 0, ...].max()
positions[:, 0, ...] = positions[:, 0, ...] - ref_max_time + current_min_time - REF_POSITION_TICK_SECONDS
```
This shifts the whole reference block to end `1ms` **before** whatever's
already the earliest time position in the sequence β€” a strictly ordered,
non-overlapping range. The training side
(`packages/ltx-trainer/src/ltx_trainer/training_strategies/flexible.py`,
`_apply_reference_condition`) applies the **identical** tick-offset logic, so a
LoRA trained with this code sees the same position layout at train and
inference time. If you ever modify one side, modify the other β€” they must stay
in parity.
When two `reference`-type conditions are stacked (as in
`configs/v2v_reference_ic_lora.yaml`, which uses both `reference_video` and
`reference_image`), each additional block is slotted immediately before
whatever range is already occupied, so every block β€” target frames, structure
reference, appearance reference β€” ends up in its own strictly-ordered slice of
time, none of them aliasing.
### Conditioning strength (separate from position mode)
Two independent knobs control how strongly a reference item affects the
output, orthogonal to which position mode you use:
- **`--ref-strength` / `--structure-strength`** (denoise strength, `strength`
in `ConditioningItem`): controls how much noise the conditioning latent
itself gets mixed with during denoising (`GaussianNoiser.torch.lerp`, in
`packages/ltx-core/src/ltx_core/components/noisers.py`). `1.0` = the
reference stays exactly at its clean VAE-encoded value; lower values let the
model deviate from it.
- **`--conditioning-attention-strength`**: scales how strongly conditioning
tokens attend to/from the noisy tokens in self-attention, independent of the
noise blending above.
## Repository layout
```
setup.sh one-click env + weights setup
pyproject.toml, uv.lock workspace definition (uv)
packages/
ltx-core/ LTX-2 model + conditioning + RoPE (patched)
ltx-pipelines/ ICLoraPipeline and friends
ltx-trainer/ LoRA / IC-LoRA training stack (patched)
configs/
v2v_reference_ic_lora.yaml train on (video, reference_video, reference_image, caption)
ref_image_ic_lora.yaml simpler: train on (video, reference_image, caption) only
accelerate/ accelerate configs referenced by the trainer docs
scripts/
train_ic_lora.sh resolves __REPO_ROOT__ in a config, launches training
preprocess_dataset.sh wraps packages/ltx-trainer/scripts/process_dataset.py
infer_v2v.py video + reference image -> video, --pos-mode switch
data/
README.md dataset JSONL schema, resolution buckets, etc.
dataset.jsonl example rows: video + reference_video + reference_image + caption
dataset_image_only.jsonl example rows: video + reference_image + caption
weights/ populated by setup.sh (gitignored β€” large binaries)
```
## Which config to train
- Have paired (source video, structure signal, reference image) data? Use
`configs/v2v_reference_ic_lora.yaml` β€” trains one LoRA that uses both
`--input-video` and `--ref-image` at inference.
- Only have (video, reference image) pairs, no structure video? Use
`configs/ref_image_ic_lora.yaml` β€” simpler, doesn't need `--input-video` at
all (pass an empty/no-op structure conditioning, or adapt
`scripts/infer_v2v.py` to drop `--structure-lora`/`--input-video` for this
case).
See `data/README.md` for the exact JSONL schema, column-to-encoder mapping, and
resolution-bucket rules.
## Known limitations / things we couldn't fully verify
- We could not confirm from the LTX-2 repo alone whether the public
`LTX-2.3-22b-IC-LoRA-Union-Control` LoRA expects raw source video or a
preprocessed control map (depth/pose/edge) for its `video_conditioning`
input β€” its model card (not vendored here) would be the source of truth if
you want to use it instead of training your own structure-control LoRA.
- `reference_video`/`--input-video` preprocessing (pose/edge/depth extraction)
is out of scope for this repo β€” bring your own, or use the basic Canny-edge
`compute_reference()` in `packages/ltx-trainer/scripts/process_videos.py` as
a starting point.