Add LTX-2.X IC LoRA and HDR Pipelines#13572
Conversation
…nector embeddings
… suggested params for the LTX-2.3 model
…eline to LTX2HDRPipeline
…preserved positional arg ordering
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
|
|
||
| # Adapted from ltx_pipelines.utils.media_io.save_exr_tensor | ||
| # https://github.com/Lightricks/LTX-2/blob/41d924371612b692c0fd1e4d9d94c3dfb3c02cb3/packages/ltx-pipelines/src/ltx_pipelines/utils/media_io.py#L609 | ||
| def save_exr_tensor( |
There was a problem hiding this comment.
I think with this PR, encode_hdr_tensor_to_mp4 is enough, no?
can we remove all the other functions here?
| num_frames: int = 121, | ||
| frame_rate: float = 24.0, | ||
| num_inference_steps: int = 40, | ||
| num_inference_steps: int = 30, |
There was a problem hiding this comment.
did they change the default since the release? 🤯
There was a problem hiding this comment.
I ended up changing the LTX2ConditionPipeline.__call__ defaults to the LTX-2.3 defaults (since the implementation now follows the newest behavior rather than the original LTX-2.0 behavior); num_inference_steps=30 is the default for LTX-2.3, while num_inference_steps=40 is the default for LTX-2.0.
There was a problem hiding this comment.
Let's treat the update on image conditioning strategy as a bug fix and not change anything else
There was a problem hiding this comment.
Changed the params back to the previous default values.
yiyixuxu
left a comment
There was a problem hiding this comment.
left a bit more feedbacks,
can we add tests too?
| container.close() | ||
|
|
||
|
|
||
| def simple_tone_map(x: np.ndarray) -> np.ndarray: |
There was a problem hiding this comment.
let's just move this line of code inside encode_hdr_tensor_to_mp4
|
|
||
| # Adapted from ltx_pipelines.utils.medio_io._linear_to_srgb | ||
| # https://github.com/Lightricks/LTX-2/blob/41d924371612b692c0fd1e4d9d94c3dfb3c02cb3/packages/ltx-pipelines/src/ltx_pipelines/utils/media_io.py#L644 | ||
| def linear_to_srgb(x: np.ndarray) -> np.ndarray: |
There was a problem hiding this comment.
same here, let's move this small function
| decode_timestep: float | list[float] = 0.0, | ||
| decode_noise_scale: float | list[float] | None = None, | ||
| use_cross_timestep: bool = False, | ||
| use_cross_timestep: bool = True, |
| prompt_attention_mask = torch.cat([negative_prompt_attention_mask, prompt_attention_mask], dim=0) | ||
|
|
||
| tokenizer_padding_side = "left" | ||
| if getattr(self, "tokenizer", None) is not None: |
There was a problem hiding this comment.
can we move this to __init__?
| ) | ||
| audio_num_frames = round(duration_s * audio_latents_per_second) | ||
|
|
||
| num_mel_bins = self.audio_vae.config.mel_bins if getattr(self, "audio_vae", None) is not None else 64 |
There was a problem hiding this comment.
can you move this to init
| num_mel_bins = self.audio_vae.config.mel_bins if getattr(self, "audio_vae", None) is not None else 64 | ||
| latent_mel_bins = num_mel_bins // self.audio_vae_mel_compression_ratio | ||
| num_channels_latents_audio = ( | ||
| self.audio_vae.config.latent_channels if getattr(self, "audio_vae", None) is not None else 8 |
| self.scheduler.config.get("base_shift", 0.95), | ||
| self.scheduler.config.get("max_shift", 2.05), | ||
| ) | ||
| audio_scheduler = copy.deepcopy(self.scheduler) |
There was a problem hiding this comment.
maybe adding another scheduler component? audio_sehcduler?
| xm.mark_step() | ||
|
|
||
| # Silence unused-variable lints for `audio_latent_model_input` / `latent_mel_bins`. | ||
| del audio_latent_model_input, latent_mel_bins |
There was a problem hiding this comment.
| del audio_latent_model_input, latent_mel_bins |
| video = video.permute(0, 2, 3, 4, 1).contiguous() | ||
| if output_type == "np": | ||
| video = video.cpu().numpy() | ||
| elif output_type != "pt": |
There was a problem hiding this comment.
move this inot check_inputs?
| # VAE decode returns a video tensor in the VAE's native range ([-1, 1]). | ||
| decoded = self.vae.decode(latents, timestep, return_dict=False)[0] | ||
| # HDR postprocess: LogC3 decompress → linear HDR [0, ∞). Always float32 for HDR fidelity. | ||
| video = self.hdr_video_processor.postprocess_hdr_video(decoded) |
There was a problem hiding this comment.
we already have a custom image processor, should we just make it take output_type and return expected output?
…mp4 for HDR export
What does this PR do?
This PR adds two new LTX-2.X pipelines:
LTX2InContextPipeline, which supports in-context (IC) conditioning (used for example by some IC LoRAs) andLTX2HDRPipeline, which supports the newly released high dynamic range (HDR) pipeline (and HDR IC-LoRA) introduced in the LTX LumiVid paper.This PR also updates
LTX2ConditionPipelineto follow the LTX-2 repo's current image conditioning strategy, which overwrites the noisy latents only for first-frame (I2V) conditions and treats non-first-frame as keyframe conditions which are appended to the noisy latents. (Previously, the pipeline overwrote the noisy latents at all latent indices.)Here is an example of using
LTX2InContextPipelinewith theLightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-InIC LoRA, which biases the generated video toward panning in on the subject:LTX-2.3 IC LoRA Example Script
ltx2_ic_lora_output.mp4
And here is an example of using
LTX2HDRPipelinewith theLightricks/LTX-2.3-22b-IC-LoRA-HDRHDR IC-LoRA, using the video above as the reference:LTX-2.3 HDR IC LoRA Example Script
ltx2_hdr_lora_output.mp4
This uses the default tone-mapper that simply clips HDR values to
[0, 1], which is also used by the original LTX-2 code.Who can review?
Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.
@yiyixuxu
@sayakpaul
@linoytsaban