Card rewrite: audio-only framing, examples, honest eval status
Browse files- .gitattributes +8 -0
- README.md +51 -42
- assets/example_1960s_era.mp4 +3 -0
- assets/example_1960s_era.png +3 -0
- assets/example_dance_tempo.mp4 +3 -0
- assets/example_dance_tempo.png +3 -0
- assets/example_hiphop_identity.mp4 +3 -0
- assets/example_hiphop_identity.png +3 -0
- assets/example_pitch_whisper.mp4 +3 -0
- assets/example_pitch_whisper.png +3 -0
- data_recipe.md +12 -15
- ltx2_audio_reference.yaml +4 -4
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/example_1960s_era.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/example_1960s_era.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/example_dance_tempo.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
assets/example_dance_tempo.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
assets/example_hiphop_identity.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
assets/example_hiphop_identity.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
assets/example_pitch_whisper.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
assets/example_pitch_whisper.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -21,68 +21,73 @@ tags:
|
|
| 21 |
- proof-of-concept
|
| 22 |
---
|
| 23 |
|
| 24 |
-
# LTX-2.3 22B "Helium": an audio-only IC-LoRA (experimental,
|
| 25 |
|
| 26 |
-
|
| 27 |
|
| 28 |
-
|
| 29 |
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
|
| 42 |
-
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
|
| 46 |
-
|
| 47 |
-
- **Type:** audio-only IC-LoRA, trained with the `audio_reference` strategy.
|
| 48 |
-
- **Control:** one in-context audio reference (a voiced tone), meant to steer the pitch of the generated speech.
|
| 49 |
-
- **Adapted modules:** the audio stack only, `audio_attn1`/`audio_attn2` (q/k/v/out) and `audio_ff`, across all 48 blocks, nothing on the video or cross-modal side. The bet is that pitch transfer is audio→audio; it's a bet, not a proven thing.
|
| 50 |
-
- **Rank / alpha:** 32 / 32, no dropout.
|
| 51 |
-
- **Reference conditioning:** appended clean at negative RoPE positions, strength 1.0, as out-of-timeline context (the ID-LoRA convention), not something the model generates.
|
| 52 |
-
- **Training:** 2000 steps, batch 1, lr 2e-4 (adamw8bit), bf16, int8-quanto plus block-swap so it fits a single 24 GB 4090, gradient checkpointing.
|
| 53 |
-
- **Format:** plain LoRA safetensors in the diffusers/PEFT convention (`lora_A`/`lora_B` under `diffusion_model.`, no alpha keys, no metadata), bf16. Not a comfy-specific format; ComfyUI's LTX IC-LoRA loader reads it directly (the same one the official Lightricks IC-LoRAs use), no conversion.
|
| 54 |
-
- **Reference Downscale Factor:** not applicable to audio. The loader's "couldn't find reference_downscale_factor" warning is harmless and defaults to 1.0.
|
| 55 |
-
- **Status:** trains end to end and fits a 24 GB 4090 (but training on distilled); pitch-tracking efficacy is still under evaluation.
|
| 56 |
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
##
|
| 66 |
|
| 67 |
-
|
| 68 |
|
| 69 |
-
The
|
| 70 |
|
| 71 |
-
|
| 72 |
|
| 73 |
-
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
I don't know yet, I still need to get inference code working that I can trust. What I can say for certain is that it *trains*: it fits a 4090, there are no NaNs, the checkpoint looks healthy, and the reference measurably moves the training loss, so the model is at least paying attention to *something* in it. Whether that turns into clean pitch-tracking at generation time is still being evaluated; the inference path was still being debugged when I put this up. So treat any "it tracks pitch!" claim as unproven until there's an actual with-IC-LoRA-vs-without pitch slope to back it up.
|
| 80 |
-
|
| 81 |
-
The bigger caveat: this base model was trained on audio and video jointly. By holding the video back (audio-only reference, audio-only adaptation), I'm deliberately isolating the audio path, and it's genuinely possible that's the wrong cut. Maybe pitch just isn't steerable from audio alone in a model where the two are this entangled, and you'd need the video in the loop, and that explains why nobody is doing it and LTX didn't build a training path for it. A null result wouldn't prove audio-only is impossible; it'd mean isolate-and-test came back "no", and the next thing to try is coupling the video back in. Which is fine; finding that out was the whole point. If the audio-only adapter turns out too weak, the obvious next lever is adding the cross-modal bridge modules (`audio_to_video_attn`, `video_to_audio_attn`) that the identity-transfer ID-LoRA uses.
|
| 82 |
-
|
| 83 |
-
## credits and lineage
|
| 84 |
-
|
| 85 |
-
The trick of putting the audio reference at negative RoPE positions comes from Lightricks' LipDub and the ID-LoRA work (cited below); the pitch-gate framing and the tone-as-tracer idea are mine. And to be upfront: my read on *how* the conditioning behaves is mostly from poking at it during inference in ComfyUI, not a clean ablation, so take the mechanism explanations as informed guesses rather than gospel.
|
| 86 |
|
| 87 |
```bibtex
|
| 88 |
@misc{dahan2026idlora,
|
|
@@ -95,6 +100,10 @@ The trick of putting the audio reference at negative RoPE positions comes from L
|
|
| 95 |
}
|
| 96 |
```
|
| 97 |
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
Base model is by Lightricks
|
|
|
|
| 21 |
- proof-of-concept
|
| 22 |
---
|
| 23 |
|
| 24 |
+
# LTX-2.3 22B "Helium": an audio-only IC-LoRA (experimental, proof of concept)
|
| 25 |
|
| 26 |
+
Last updated: 2026-05-31
|
| 27 |
|
| 28 |
+
An IC-LoRA for LTX-2.3-22B (distilled) where the in-context reference is **only audio**: no image, no video, no init frame. The model still generates audio and video jointly, but the only thing you hand it as a reference is an audio file. I trained it on a narrow, measurable task (a voiced tone is meant to steer the pitch of the generated speech, the "helium" idea), and in practice the audio reference visibly steers the whole output: a voice or a music track shifts the speaker's identity, the setting, even the era. That broader transfer is the interesting part. The pitch task was just a clean probe for whether an audio-only reference could do anything at all.
|
| 29 |
|
| 30 |
+
> **Status:** evaluated a few different ways, and it clearly produces interesting effects, the audio reference steers speaker identity, scene, and era. It has *not* been through a rigorous, controlled test, and the specific pitch-tracking it was trained for is not cleanly confirmed. Treat the examples below as illustrative single takes, not benchmarks.
|
| 31 |
|
| 32 |
+
## Model Files
|
| 33 |
|
| 34 |
+
- `lora_weights_step_02000.safetensors`: the final checkpoint (2000 steps).
|
| 35 |
+
- `lora_weights_step_01500.safetensors`: an earlier checkpoint, about 8% apart by weight norm, included to compare. Both are clean (960 finite bf16 tensors each).
|
| 36 |
+
- [`ltx2_audio_reference.yaml`](ltx2_audio_reference.yaml): the training config.
|
| 37 |
+
- [`data_recipe.md`](data_recipe.md): how the dataset was built.
|
| 38 |
+
- Training fork: [fblissjr/LTX-2 @ audio-guidance-iclora-vtv](https://github.com/fblissjr/LTX-2/tree/audio-guidance-iclora-vtv) (the `audio_reference` strategy).
|
| 39 |
|
| 40 |
+
## Model Details
|
| 41 |
|
| 42 |
+
- **Base Model:** LTX-2.3-22B distilled (`ltx-2.3-22b-distilled-1.1`), a *joint* audio-video model (which matters for the caveats).
|
| 43 |
+
- **Training Type:** IC-LoRA, `audio_reference` strategy.
|
| 44 |
+
- **Control Type:** **Audio.** One in-context audio reference, no image and no video. The model generates both modalities; only the reference is audio-only.
|
| 45 |
+
- **Reference Downscale Factor:** not applicable to audio (defaults to 1; the loader's "couldn't find reference_downscale_factor" warning is harmless).
|
| 46 |
+
- **Adapted modules:** the audio stack only, `audio_attn1`/`audio_attn2` (q/k/v/out) and `audio_ff`, across all 48 blocks. Nothing on the video or cross-modal side.
|
| 47 |
+
- **Rank / alpha:** 32 / 32, no dropout. **Reference:** appended clean at negative RoPE positions (out-of-timeline context, the ID-LoRA convention).
|
| 48 |
+
- **Training:** 2000 steps, batch 1, lr 2e-4 (adamw8bit), bf16, int8-quanto plus block-swap to fit a single 24 GB 4090, gradient checkpointing. Trained at **256x256 with 2-second audio references**, a deliberate limitation set by consumer-grade hardware.
|
| 49 |
+
- **Format:** plain LoRA safetensors, diffusers/PEFT convention (`lora_A`/`lora_B` under `diffusion_model.`, no alpha keys), bf16. ComfyUI's LTX IC-LoRA loader reads it directly, no conversion.
|
| 50 |
|
| 51 |
+
## Using in ComfyUI
|
| 52 |
|
| 53 |
+
1. Copy the LoRA into `models/loras`.
|
| 54 |
+
2. Install the custom nodes from **[ComfyUI-AudioLoopHelper](https://github.com/fblissjr/ComfyUI-AudioLoopHelper)**. The audio IC-LoRA loader and the **Add Audio IC-LoRA Guide** node are not in stock ComfyUI-LTXVideo, so you need this repo.
|
| 55 |
+
3. Use the example workflow [`audio-ic-lora_single-pass.json`](https://github.com/fblissjr/ComfyUI-AudioLoopHelper/blob/main/example_workflows/audio-ic-lora_single-pass.json): load the LoRA with the audio IC-LoRA loader, feed your audio through the Add Audio IC-LoRA Guide node with a neutral caption, and generate audio and video together.
|
| 56 |
|
| 57 |
+
How it works and the eval notes are in [`docs/audio_iclora`](https://github.com/fblissjr/ComfyUI-AudioLoopHelper/tree/main/docs/audio_iclora).
|
| 58 |
|
| 59 |
+
### Usage recommendations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
- **Strength: start at ~0.5.** Higher increases instability, and toward 1.0 the video garbles. The usable ceiling depends on the reference and the generation, so if it breaks up, back off. 0.3 to 0.5 is the working band.
|
| 62 |
+
- **Caption:** keep it neutral and attribute-free, so the audio reference (not the caption) drives the attribute.
|
| 63 |
+
- **Reference:** the reference is audio only. Any audio works; the effect lands as a global attribute (voice, genre, era, scene).
|
| 64 |
|
| 65 |
+
### Examples
|
| 66 |
|
| 67 |
+
Single takes: same model, a neutral caption, and the only reference fed in is an audio file. Click a thumbnail for the video.
|
| 68 |
|
| 69 |
+
| result | audio reference | what happens (prompt shown) |
|
| 70 |
+
|---|---|---|
|
| 71 |
+
| <a href="assets/example_pitch_whisper.mp4"><img src="assets/example_pitch_whisper.png" width="200"></a> | a voiced tone | the trained "helium" task: the generated speech drops to a whisper. `A person says, "I made an audio IC Lora"` |
|
| 72 |
+
| <a href="assets/example_hiphop_identity.mp4"><img src="assets/example_hiphop_identity.png" width="200"></a> | hip-hop audio | the speaker and setting shift to match. `A person says, "Hello, how are you doing today?"` |
|
| 73 |
+
| <a href="assets/example_dance_tempo.mp4"><img src="assets/example_dance_tempo.png" width="200"></a> | a high-tempo dance track | the delivery speeds up. `A person says, "I made an audio IC Lora"` |
|
| 74 |
+
| <a href="assets/example_1960s_era.mp4"><img src="assets/example_1960s_era.png" width="200"></a> | a 1960s broadcast | the video takes on a vintage 1960s look. `A person says calmly, "Bandoco is good"` |
|
| 75 |
|
| 76 |
+
## Dataset
|
| 77 |
|
| 78 |
+
For the audio reference to be in control (not the caption), the reference and target must differ in content and share only the attribute. So the reference is a bare voiced tone, the target is real talking-head speech shifted to that pitch, and the caption is a constant pitch-free string. Built from real footage ([CelebVHQ](https://huggingface.co/datasets/noakraicer/ID-LoRA-CelebVHQ) + [TalkVid](https://huggingface.co/datasets/noakraicer/ID-LoRA-TalkVid)): measure each clip's natural pitch, shift its speech a few semitones (capped at ±7) while keeping timing so lips still line up, render at 256x256, and synthesize a 2-second tone at that pitch with timbre varied independently so the model cannot read timbre instead. Caption always `a person speaking`. Kept a pair only if the shifted pitch re-measured within 30 Hz of target: 292 clean pairs, 76 to 353 Hz.
|
| 79 |
|
| 80 |
+
The hole: the target pitch is a bounded shift off each clip's *own* pitch, so the two correlate about 0.75, and the speaker's face also hints at their natural pitch. So the tone is not the *only* route to the answer. That does not wreck a positive result (sweep the tone with everything else fixed and watch the pitch follow), but it makes a *null* ambiguous. The fix is a decorrelated rerun (random target pitch, formant-preserving shift); that is the next dataset.
|
| 81 |
|
| 82 |
+
## Caveats
|
| 83 |
|
| 84 |
+
- The pitch-tracking eval is **not a clean win**. A controlled reference-swap (fixed prompt and seed, sweep the tone) came back noisy: the base arm leaks the reference tone into the measured audio and the LoRA arm often comes out unvoiced, so the with-vs-without pitch slope is not trustworthy yet. The transfer effect in the examples is obvious; the narrow pitch number is not.
|
| 85 |
+
- The base model is trained on audio and video **jointly**. By holding video back (audio-only reference, audio-only adaptation) I am deliberately isolating the audio path, and it is genuinely possible that is the wrong cut and pitch needs the video in the loop. A null would not prove audio-only is impossible; the next lever is the cross-modal bridge modules (`audio_to_video_attn`, `video_to_audio_attn`) the identity-transfer ID-LoRA uses.
|
| 86 |
+
- Where this could go if pursued: voice / accent / emotion transfer, audio-driven style and scene. This was just a POC to see if it works. It seems to?
|
| 87 |
|
| 88 |
+
## Citation
|
| 89 |
|
| 90 |
+
The trick of putting the audio reference at negative RoPE positions comes from Lightricks' LipDub and the ID-LoRA work below. My read on *how* the conditioning behaves is from poking at it in ComfyUI, not a clean ablation, so take the mechanism explanations as informed guesses.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
```bibtex
|
| 93 |
@misc{dahan2026idlora,
|
|
|
|
| 100 |
}
|
| 101 |
```
|
| 102 |
|
| 103 |
+
## Acknowledgments
|
| 104 |
+
|
| 105 |
+
Thanks to WepeNerd ([HF](https://huggingface.co/WepeNerd)) for the whole idea of an audio-only IC-LoRA and for thinking through how an IC-LoRA learns (including "helium"); to the LTX-2 community trainer, Musubi Tuner, and Kijai's work for the infra; and to Throttlekitty for pulling me into LTX-2 in the first place.
|
| 106 |
+
|
| 107 |
+
## License
|
| 108 |
|
| 109 |
+
Base model is by Lightricks under the **LTX-2 community license**. See https://github.com/Lightricks/LTX-2/blob/main/LICENSE for the full terms.
|
assets/example_1960s_era.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:164ceb9cf7b57e29e03d808661b3f33c239837acc60d8f196fa9c90e40f317ea
|
| 3 |
+
size 923676
|
assets/example_1960s_era.png
ADDED
|
Git LFS Details
|
assets/example_dance_tempo.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c6f0d0d0112a8994d86c116dea0313b5b0c13678fe19cb60aad730673e41449c
|
| 3 |
+
size 1543748
|
assets/example_dance_tempo.png
ADDED
|
Git LFS Details
|
assets/example_hiphop_identity.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0d1fc113e3083cdae153efe911ab07d6c028bef0a607933b0890dbd7ae244e41
|
| 3 |
+
size 1263677
|
assets/example_hiphop_identity.png
ADDED
|
Git LFS Details
|
assets/example_pitch_whisper.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a3ddededb0aad7dc9d9aa09395d5d64e2ee0df769f0d3f774910199f9377d7f1
|
| 3 |
+
size 1132721
|
assets/example_pitch_whisper.png
ADDED
|
Git LFS Details
|
data_recipe.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
This is the companion to the model card; the actual recipe used here and how to rebuild it. The *why* (why pitch, why a bare tone, the seesaw, the correlation hole) is all in the README; this is just the how. It's not perfect, but hopefully provides a way for others to make better ones.
|
| 4 |
|
| 5 |
-
##
|
| 6 |
|
| 7 |
Real talking-head footage, so the target side has no synthetic-to-real gap: the [CelebVHQ](https://huggingface.co/datasets/noakraicer/ID-LoRA-CelebVHQ) and [TalkVid](https://huggingface.co/datasets/noakraicer/ID-LoRA-TalkVid) preprocessed sets. Both are 512×512, 25 fps, ~5-second (121-frame) clips; I re-render the targets at 256×256 due to consumer grade GPU limitations (and video wasn't the target goal).
|
| 8 |
|
| 9 |
-
##
|
| 10 |
|
| 11 |
-
For a clip, I measure its natural speaking pitch, then pick a target pitch that's a bounded shift off it (at most
|
| 12 |
|
| 13 |
Then a quality gate: keep a pair only if the shifted speech re-measures within 30 Hz of its target pitch. That drops the clips where the pitch-shift fell apart and leaves **292 clean pairs out of 300**, spanning 76 to 353 Hz. Two correlations worth knowing: reference timbre vs. pitch comes out near zero (~0.03), which is what I wanted; target pitch vs. the clip's *own* natural pitch is ~0.75, because the target is a bounded shift off it, which is the known weak spot (the README explains why it matters). The fix for the next run is to pick the target pitch at random, independent of the clip, with a formant-preserving shift.
|
| 14 |
|
| 15 |
-
##
|
| 16 |
|
| 17 |
This is the layout of the *dataset itself*, what the trainer reads, not of this HF download. The dataset isn't shipped here (it's derived from the CelebVHQ/TalkVid sets above, and it's large); this release contains only the IC-LoRA checkpoints, the training config, and this recipe (and a link to the github trainer fork). The structure below is what you'd produce by rebuilding it.
|
| 18 |
|
|
@@ -32,7 +32,7 @@ pitch_ref_gate_v1/
|
|
| 32 |
|
| 33 |
Each row in `manifest_train.jsonl` carries the video and reference paths, the caption, the natural / target / actual (re-measured) pitches, the shift in semitones, a timbre id, and a train/heldout split label. One heads-up if you're reproducing: the trainer finds samples by globbing the precomputed `.pt` files and doesn't read that split label, so it trains on everything; the label is informational unless you wire it up yourself.
|
| 34 |
|
| 35 |
-
##
|
| 36 |
|
| 37 |
The scripts and the `audio_reference` strategy live in the trainer fork, [fblissjr/LTX-2 @ audio-guidance-iclora-vtv](https://github.com/fblissjr/LTX-2/tree/audio-guidance-iclora-vtv), under `packages/ltx-trainer/`. Clone it and set up the env (it uses `uv`), then run these from `packages/ltx-trainer/` (paths below are relative to it):
|
| 38 |
|
|
@@ -56,14 +56,11 @@ uv run python scripts/train.py ltx2_audio_reference.yaml
|
|
| 56 |
|
| 57 |
The reference encode runs the audio VAE in float32 to match the target-audio encode; the LoRA itself is bf16. Target modules, rank, and the rest of the setup are in `ltx2_audio_reference.yaml` and the README.
|
| 58 |
|
| 59 |
-
##
|
| 60 |
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
What I would not chase, and I'd said the opposite earlier, is a pitch that varies over time, a contour the speech has to follow. It's tempting because a contour can't be guessed from a face, but it's probably the wrong tool for this port. A contour only means something if the model lines up "reference moment τ" with "output moment τ", and the negative-RoPE reference sits in a disjoint region with no alignment to the target's timeline, so there's nothing to learn that mapping from. If you want time-varying control (prosody, intonation, rhythm), the right mechanism is an *aligned* driving channel at positive positions, the way LipDub's audio drives lips frame by frame. That's a different experiment, not this one.
|
| 68 |
-
|
| 69 |
-
And if the data is fine but the audio-only adapter just turns out too weak, the lever is the model side: add the cross-modal bridges (`audio_to_video_attn`, `video_to_audio_attn`, see the README), not more data.
|
|
|
|
| 1 |
+
# How the "Helium" Dataset Was Built
|
| 2 |
|
| 3 |
This is the companion to the model card; the actual recipe used here and how to rebuild it. The *why* (why pitch, why a bare tone, the seesaw, the correlation hole) is all in the README; this is just the how. It's not perfect, but hopefully provides a way for others to make better ones.
|
| 4 |
|
| 5 |
+
## Where It Comes From
|
| 6 |
|
| 7 |
Real talking-head footage, so the target side has no synthetic-to-real gap: the [CelebVHQ](https://huggingface.co/datasets/noakraicer/ID-LoRA-CelebVHQ) and [TalkVid](https://huggingface.co/datasets/noakraicer/ID-LoRA-TalkVid) preprocessed sets. Both are 512×512, 25 fps, ~5-second (121-frame) clips; I re-render the targets at 256×256 due to consumer grade GPU limitations (and video wasn't the target goal).
|
| 8 |
|
| 9 |
+
## How Each Pair Is Made
|
| 10 |
|
| 11 |
+
For a clip, I measure its natural speaking pitch, then pick a target pitch that's a bounded shift off it (at most ±7 semitones, ~3.6 on average), and pitch shift the speech to that target while keeping the timing so the lips still line up. The video stays untouched. The reference paired with it is a synthesized voiced tone at the same target pitch: 2 seconds, 16 kHz, mono, with the timbre varied across four flavors that are decorrelated from pitch so the model can't read timbre as a pitch shortcut. Every pair gets the same caption, "a person speaking", and no init frame, so the tone is the only non-caption input.
|
| 12 |
|
| 13 |
Then a quality gate: keep a pair only if the shifted speech re-measures within 30 Hz of its target pitch. That drops the clips where the pitch-shift fell apart and leaves **292 clean pairs out of 300**, spanning 76 to 353 Hz. Two correlations worth knowing: reference timbre vs. pitch comes out near zero (~0.03), which is what I wanted; target pitch vs. the clip's *own* natural pitch is ~0.75, because the target is a bounded shift off it, which is the known weak spot (the README explains why it matters). The fix for the next run is to pick the target pitch at random, independent of the clip, with a formant-preserving shift.
|
| 14 |
|
| 15 |
+
## What the Dataset Looks Like on Disk
|
| 16 |
|
| 17 |
This is the layout of the *dataset itself*, what the trainer reads, not of this HF download. The dataset isn't shipped here (it's derived from the CelebVHQ/TalkVid sets above, and it's large); this release contains only the IC-LoRA checkpoints, the training config, and this recipe (and a link to the github trainer fork). The structure below is what you'd produce by rebuilding it.
|
| 18 |
|
|
|
|
| 32 |
|
| 33 |
Each row in `manifest_train.jsonl` carries the video and reference paths, the caption, the natural / target / actual (re-measured) pitches, the shift in semitones, a timbre id, and a train/heldout split label. One heads-up if you're reproducing: the trainer finds samples by globbing the precomputed `.pt` files and doesn't read that split label, so it trains on everything; the label is informational unless you wire it up yourself.
|
| 34 |
|
| 35 |
+
## Rebuilding It
|
| 36 |
|
| 37 |
The scripts and the `audio_reference` strategy live in the trainer fork, [fblissjr/LTX-2 @ audio-guidance-iclora-vtv](https://github.com/fblissjr/LTX-2/tree/audio-guidance-iclora-vtv), under `packages/ltx-trainer/`. Clone it and set up the env (it uses `uv`), then run these from `packages/ltx-trainer/` (paths below are relative to it):
|
| 38 |
|
|
|
|
| 56 |
|
| 57 |
The reference encode runs the audio VAE in float32 to match the target-audio encode; the LoRA itself is bf16. Target modules, rank, and the rest of the setup are in `ltx2_audio_reference.yaml` and the README.
|
| 58 |
|
| 59 |
+
## What I'd Change Next Time
|
| 60 |
|
| 61 |
+
The organizing idea first: a clean reference at negative RoPE positions suits *time-invariant* attributes (an average pitch, a timbre, a voice). That is my read, not something I tested, but it shapes the list.
|
| 62 |
|
| 63 |
+
- **Fix the 0.75 by construction, not luck.** Reuse each clip at several target pitches, each with its own reference tone, caption held constant. The same face then appears across many pitches, so identity can't predict the answer and the tone is the only thing that co-varies, and it multiplies a too-small dataset for free. Cheap partial version: pick the target pitch at random, independent of the clip, with a formant-preserving shift.
|
| 64 |
+
- **Voice transfer is the real goal:** a reference of someone speaking, and the generated voice adopts it. Voice identity is time-invariant, so it fits. Harder than pitch, for the same reason the 0.75 hole exists: the generated face carries a voice prior the audio reference has to override. The clean test is two variants on the same voice data, audio-only reference versus image+audio. Pitch is the leading indicator: if pure-audio pitch doesn't move, pure-audio voice almost certainly won't.
|
| 65 |
+
- **Don't chase a time-varying contour.** A negative-RoPE reference sits in a disjoint region with no alignment to the target timeline, so there is nothing to learn a moment-to-moment mapping from. Time-varying control (prosody, rhythm) wants an *aligned* driving channel at positive positions, the way LipDub drives lips frame by frame. Different experiment.
|
| 66 |
+
- **If the data is fine but the adapter is just too weak,** the lever is the model side: add the cross-modal bridges (`audio_to_video_attn`, `video_to_audio_attn`), not more data.
|
|
|
|
|
|
|
|
|
ltx2_audio_reference.yaml
CHANGED
|
@@ -6,9 +6,9 @@
|
|
| 6 |
# attribute of the generated audio+video. The reference is the CONTROLLER
|
| 7 |
# (transfer paradigm): content varies, the attribute is read from the reference,
|
| 8 |
# the caption is neutral. First proof = a voiced TONE reference carrying pitch ->
|
| 9 |
-
# generated speech F0 tracks the tone
|
| 10 |
#
|
| 11 |
-
# WHY THIS SHAPE (the seesaw
|
| 12 |
# "matched content" and "audio controls the attribute" are mutually exclusive.
|
| 13 |
# If reference and target share content, the per-example attribute must ride the
|
| 14 |
# caption -> TEXT controls it. For the audio to be load-bearing, reference and
|
|
@@ -47,7 +47,7 @@ lora:
|
|
| 47 |
alpha: 32
|
| 48 |
dropout: 0.0
|
| 49 |
# AUDIO-STACK ONLY. The reference->target mixing happens in the audio
|
| 50 |
-
# SELF-attention (audio_attn1) over the [target | reference] audio sequence
|
| 51 |
# that is the load-bearing module. audio_attn2 (text cross-attn) + audio_ff
|
| 52 |
# round out the audio stack. No cross-modal bridges: pitch transfer is
|
| 53 |
# audio->audio; we do not need audio<->video coupling for this gate.
|
|
@@ -88,7 +88,7 @@ acceleration:
|
|
| 88 |
mixed_precision_mode: "bf16"
|
| 89 |
quantization: "int8-quanto"
|
| 90 |
load_text_encoder_in_8bit: true
|
| 91 |
-
# The run+fit smoke measured an 8.70 GB training peak at 36/48 swap
|
| 92 |
# headroom (startup ~17.8 GB is the tight point). 24/48 keeps resident well
|
| 93 |
# under that while cutting the per-step PCIe cost vs 36 (the 14 s/step at 36
|
| 94 |
# was swap-bound). Lower toward ~16 for more speed if VRAM allows.
|
|
|
|
| 6 |
# attribute of the generated audio+video. The reference is the CONTROLLER
|
| 7 |
# (transfer paradigm): content varies, the attribute is read from the reference,
|
| 8 |
# the caption is neutral. First proof = a voiced TONE reference carrying pitch ->
|
| 9 |
+
# generated speech F0 tracks the tone.
|
| 10 |
#
|
| 11 |
+
# WHY THIS SHAPE (the seesaw - read before changing the pairing/targets):
|
| 12 |
# "matched content" and "audio controls the attribute" are mutually exclusive.
|
| 13 |
# If reference and target share content, the per-example attribute must ride the
|
| 14 |
# caption -> TEXT controls it. For the audio to be load-bearing, reference and
|
|
|
|
| 47 |
alpha: 32
|
| 48 |
dropout: 0.0
|
| 49 |
# AUDIO-STACK ONLY. The reference->target mixing happens in the audio
|
| 50 |
+
# SELF-attention (audio_attn1) over the [target | reference] audio sequence -
|
| 51 |
# that is the load-bearing module. audio_attn2 (text cross-attn) + audio_ff
|
| 52 |
# round out the audio stack. No cross-modal bridges: pitch transfer is
|
| 53 |
# audio->audio; we do not need audio<->video coupling for this gate.
|
|
|
|
| 88 |
mixed_precision_mode: "bf16"
|
| 89 |
quantization: "int8-quanto"
|
| 90 |
load_text_encoder_in_8bit: true
|
| 91 |
+
# The run+fit smoke measured an 8.70 GB training peak at 36/48 swap - huge
|
| 92 |
# headroom (startup ~17.8 GB is the tight point). 24/48 keeps resident well
|
| 93 |
# under that while cutting the per-step PCIe cost vs 36 (the 14 s/step at 36
|
| 94 |
# was swap-bound). Lower toward ~16 for more speed if VRAM allows.
|