Known limitations and planned fixes for the LTX-2.3 Image+Audio to Video pipeline, particularly on Apple Silicon (MPS).
Status: Blocked — STG produces corrupted output on Apple Silicon MPS.
With video_stg_scale > 0, the output becomes washed out with no lip sync or meaningful motion. On CUDA, STG=1.0 with stg_blocks=[28] works correctly and improves temporal coherence. On MPS, it destroys the output entirely.
Three confirmed MPS precision issues compound to corrupt STG output:
1. MPS "fast math" (pytorch/pytorch#84936):
Apple's MPSGraph compiles ALL Metal kernels with fast math enabled. Operations like exp(), softmax(), and attention produce ~1e-7 different results per operation vs CUDA. There is NO PyTorch setting to disable this. Apple acknowledged but never fixed it.
2. bfloat16 attention masking errors (pytorch/pytorch#163588): Flex Attention with bfloat16 produces up to 3.4% relative error when masking positions. STG masks self-attention in block 28 for the perturbation pass — directly affected.
3. Error amplification chain:
The cond and ptb passes take different paths through block 28 (real attention vs V-only bypass). Over 30 transformer blocks of cross-attention + feedforward, MPS fast-math errors (~1e-7 per op) accumulate differently in each pass. The subtraction cond - ptb then REVEALS these accumulated errors instead of capturing the intended STG signal. The rescaling (cond.std() / pred.std() in bfloat16) further amplifies the noise.
Key precision loss points (from code trace):
guiders.py:259— STG delta:stg_scale * (cond - ptb)in bfloat16guiders.py:264-265— Rescale:cond.std() / pred.std()in bfloat16attention.py:234-235— Perturbation mask blend:out * mask + v * (1-mask)in bfloat16batch_split.py:75-85— If cond and ptb land in different chunks, MPS non-determinism adds further divergence
Cast the entire guidance formula and rescaling to float32 in MultiModalGuider.calculate() at ltx-core/components/guiders.py:
def calculate(self, cond, uncond_text, uncond_perturbed, uncond_modality):
# Upcast to float32 for precision-sensitive guidance math
dtype = cond.dtype
cond, uncond_text, uncond_perturbed, uncond_modality = (
t.float() if isinstance(t, torch.Tensor) else t
for t in (cond, uncond_text, uncond_perturbed, uncond_modality)
)
pred = (
cond
+ (self.params.cfg_scale - 1) * (cond - uncond_text)
+ self.params.stg_scale * (cond - uncond_perturbed)
+ (self.params.modality_scale - 1) * (cond - uncond_modality)
)
if self.params.rescale_scale != 0:
factor = cond.std() / pred.std()
factor = self.params.rescale_scale * factor + (1 - self.params.rescale_scale)
pred = pred * factor
return pred.to(dtype)Likelihood of success: ~30%. Fixes the subtraction and rescaling precision but not the accumulated MPS fast-math errors inside the transformer.
If Approach A fails, run the STG perturbation as a completely separate transformer forward pass in float32:
- In
_guided_denoise()atltx-pipelines/utils/denoisers.py, split the perturbation pass out of the batch - Run cond+uncond+mod as one batched bfloat16 pass (normal)
- Run ptb alone as a separate float32 forward pass
- Cast cond to float32, compute
cond - ptbin float32, cast back
This eliminates ALL MPS fast-math errors for the ptb pass. Cost: one extra full transformer forward pass in float32 (~30-50% slower per step).
Likelihood of success: ~90%.
- Apply the fix to the LTX-2 submodule
- Set
video_stg_scale: 1.0andvideo_stg_blocks: [28]inconfig.yaml - Generate with the working pirate portrait + short audio (< 6s)
- Verify output is not washed out, has lip sync, and shows improved temporal coherence vs STG=0
Status: Needs investigation.
- Steps > 10 break lip sync: the video has motion but lips don't move to match audio
- max_pixels > 409600 (~512x768) produces garbage output at any step count
- Both work fine on CUDA (upstream defaults are 30 steps at much higher resolution)
Cap at 10 steps and 409600 max_pixels. Use --upscale 2 (ffmpeg lanczos) for higher output resolution.
-
Steps issue: Test intermediate values (12, 15) to find the exact threshold. Check if the
LTX2Scheduler's token-dependent sigma shifting produces a qualitatively different schedule at different step counts on MPS. Log and compare sigma values between 10-step and 20-step runs. -
Resolution issue: Test resolutions incrementally (409600 → 450000 → 500000 → 550000 → 614400) to find the exact threshold. Check if the issue is the Stage 1 half-resolution, the spatial upsampler at larger sizes, or the Stage 2 refinement at full resolution.
-
Precision hypothesis: If both issues share a root cause (bfloat16 precision on MPS), a targeted float32 cast at critical computation points might fix both. The denoising loop's Euler step update (
x = x + dt * dx) is another candidate for precision loss over many iterations.
Status: Enhancement — current ffmpeg lanczos upscaling works but could be better.
The --upscale 2 flag uses ffmpeg's lanczos filter for 2x/4x upscaling. Fast and preserves lip sync perfectly, but the result is a scaled-up version of the low-res output — no detail hallucination.
-
Real-ESRGAN: Frame-by-frame AI upscaling. The
realesrgan-ncnn-vulkanbinary supports macOS. Can also be converted to CoreML for Neural Engine acceleration on Apple Silicon (up to 78x speedup vs CPU PyTorch). -
Video-specific super-resolution: Models like BasicVSR++ or RealBasicVSR that exploit temporal information across frames for more temporally consistent upscaling.
-
Frame interpolation: Models like RIFE or FILM for 2x frame rate (25fps → 50fps), making motion smoother without re-generating.
Add --upscale-model flag to generate.py:
python generate.py --image ... --audio ... --prompt "..." --upscale 2 --upscale-model realesrganFallback to ffmpeg lanczos if the AI model isn't installed.