131 lines
5.7 KiB
Markdown
131 lines
5.7 KiB
Markdown
# Tiled Data Parallelism (TDP)
|
|
|
|
**Source**: [`ltx_core/multigpu/transformer/tiled_data_parallel.py`](../../../ltx-core/src/ltx_core/multigpu/transformer/tiled_data_parallel.py), [`multigpu/tdp_builder.py`](../../src/ltx_pipelines/multigpu/tdp_builder.py)
|
|
|
|
## What it is
|
|
|
|
TDP splits the patchified `(frames, height, width)` latent into **tiles** and gives
|
|
each tile to a GPU. Every rank runs the full transformer on its own tile(s),
|
|
overlapping regions are blended with trapezoidal masks, and a single `all_reduce`
|
|
sums the blended tiles into the final result (masks sum to 1 globally). Tiles are
|
|
assigned **round-robin**, so the tile count may exceed the GPU count (16 tiles on
|
|
4 GPUs = 4 tiles/rank). Audio is processed untiled on every tile forward and averaged
|
|
across tiles.
|
|
|
|
Unlike [sequence parallelism](sequence-parallel.md), TDP is **not** bit-faithful to
|
|
single-GPU: each tile is denoised with only local context and blended, so it is an
|
|
approximation.
|
|
|
|
> ## ⚠️ Do not use the TDP stage's audio output
|
|
>
|
|
> Audio is **not** tiled. It is denoised on **every** tile's forward pass — each with
|
|
> a different, partial video context — and those results are **averaged across all
|
|
> tiles**. That average is not a meaningful audio latent. Take the final audio from
|
|
> the first (SP) stage and keep it frozen through the TDP upscale; treat the TDP
|
|
> stage's audio only as the video-conditioning context it needs internally, never as
|
|
> output.
|
|
|
|
## When to use it
|
|
|
|
TDP is an **upscaler**. It produces video at resolutions the model never saw during
|
|
training by running each tile at a resolution the model handles well and blending the
|
|
results. This is why the shipped two-stage runner uses TDP for **stage 2** (the
|
|
high-resolution upscale).
|
|
|
|
TDP can also be **faster** than running the whole frame on one GPU: self-attention is
|
|
quadratic in the token count, so splitting `N` tokens into `T` tiles drops per-tile
|
|
attention cost from `O(N^2)` to `O((N/T)^2)`.
|
|
|
|
> **Do not run TDP as the first stage.** Starting from pure noise (a high first
|
|
> sigma), each tile denoises independently and produces **unrelated content** — the
|
|
> tiles never converge on a single coherent video. Generate the first stage with
|
|
> [SP](sequence-parallel.md) (faithful, full-frame), then **upscale** that result
|
|
> with TDP.
|
|
>
|
|
> Even as the upscale stage, tiles can **drift** apart, and the drift grows with the
|
|
> **first sigma** of the TDP stage (more noise re-injected means more freedom per
|
|
> tile). For consistency, either condition on the stage-1 result with
|
|
> **negative-index image conditioning** (for i2v), or use a **smaller first sigma**.
|
|
|
|
## Position normalization
|
|
|
|
A tile's tokens must carry positions in the range the model was trained on — not the
|
|
global positions of a tile in the corner of a large frame, which the model was never
|
|
trained to handle. With `normalize_positions=True` (default), each tile's positions
|
|
are shifted so the tile's **generated** tokens start at zero in every dimension:
|
|
|
|
```python
|
|
offset = gen_pos[..., 0].amin(dim=2, keepdim=True)... # min start per (batch, dim)
|
|
positions = positions - offset # shift generated + conditioning tokens
|
|
```
|
|
|
|
Interval widths are preserved (only the origin moves), so RoPE sees a valid,
|
|
in-distribution position grid per tile.
|
|
|
|
## Shared negative (reference) positions
|
|
|
|
Conditioning tokens are appended after the generated tokens. A tile keeps a
|
|
conditioning token when its `[start, end)` interval overlaps the tile in all three
|
|
dimensions — **or** when it has a **negative time coordinate**. Negative-time tokens
|
|
are **reference tokens** (e.g. reference frames / audio references): they are kept by
|
|
**every** tile so all tiles share the same reference context. To avoid
|
|
double-counting a token kept by several tiles, its blend weight is `1 / (number of
|
|
tiles that kept it)`.
|
|
|
|
## API
|
|
|
|
### Tiling config (`ltx_core.tiling`)
|
|
|
|
```python
|
|
from ltx_core.tiling import TileCountConfig, DimensionTilingConfig
|
|
|
|
TileCountConfig(
|
|
frames: DimensionTilingConfig = DimensionTilingConfig(num_tiles=1, overlap=0),
|
|
height: DimensionTilingConfig = DimensionTilingConfig(num_tiles=1, overlap=0),
|
|
width: DimensionTilingConfig = DimensionTilingConfig(num_tiles=1, overlap=0),
|
|
)
|
|
DimensionTilingConfig(num_tiles: int, overlap: int = 0) # counts, not sizes; overlap in latent grid units
|
|
```
|
|
|
|
`TileCountConfig` specifies tile **counts** per dimension (contrast the single-GPU
|
|
VAE `TilingConfig`, which specifies tile **sizes**).
|
|
|
|
### `TiledDataParallelBuilder`
|
|
|
|
```python
|
|
from ltx_pipelines.multigpu.tdp_builder import TiledDataParallelBuilder
|
|
|
|
TiledDataParallelBuilder(
|
|
inner: ModelBuilderProtocol, # the stage's single-GPU transformer builder
|
|
group: dist.ProcessGroup, # self.groups.transformer_group
|
|
tiling: TileCountConfig,
|
|
registry: Registry,
|
|
tracker: TransformerWeightTracker,
|
|
normalize_positions: bool = True,
|
|
)
|
|
```
|
|
|
|
Wraps a `SingleGPUModelBuilder`. Its `build()` requires a `video_tools` kwarg (the
|
|
`VideoLatentTools` for the target shape) so the wrapper can compute tiles — the
|
|
pipeline passes this through automatically.
|
|
|
|
## Usage
|
|
|
|
```python
|
|
# inside runner.setup(), stage 2 -- balanced 2D spatial (height x width) grid over the group:
|
|
from ltx_core.tiling import TileCountConfig, DimensionTilingConfig, balanced_tile_split
|
|
|
|
h_tiles, w_tiles = balanced_tile_split(dist.get_world_size(self.groups.transformer_group))
|
|
tdp_tiling = TileCountConfig(
|
|
height=DimensionTilingConfig(num_tiles=h_tiles, overlap=5),
|
|
width=DimensionTilingConfig(num_tiles=w_tiles, overlap=5),
|
|
)
|
|
pipeline.stage_2._transformer_builder = TiledDataParallelBuilder(
|
|
inner=pipeline.stage_2._transformer_builder,
|
|
group=self.groups.transformer_group,
|
|
tiling=tdp_tiling,
|
|
registry=registry,
|
|
tracker=tracker,
|
|
)
|
|
```
|