129 lines
4.8 KiB
Markdown
129 lines
4.8 KiB
Markdown
# Parallelism Acceleration Guide
|
|
|
|
This guide includes how to use parallelism methods in vLLM-Omni to speed up diffusion model inference as well as reduce the memory requirement on each device.
|
|
|
|
## Overview
|
|
|
|
The following parallelism methods are currently supported in vLLM-Omni:
|
|
|
|
1. DeepSpeed Ulysses Sequence Parallel (Ulysses-SP) ([paper](https://arxiv.org/pdf/2309.14509)): Ulysses-SP splits the input along the sequence dimension and uses all-to-all communication to allow each device to compute only a subset of attention heads.
|
|
|
|
|
|
The following table shows which models are currently supported by parallelism method:
|
|
|
|
|
|
| Model | Model Identifier | Ulysses-SP |
|
|
|-------|-----------------|-----------|
|
|
| **Qwen-Image** | `Qwen/Qwen-Image` | ✅ |
|
|
| **Z-Image** | `Tongyi-MAI/Z-Image-Turbo` | ❌ |
|
|
| **Qwen-Image-Edit** | `Qwen/Qwen-Image-Edit` | ✅ |
|
|
|
|
### Sequence Parallelism
|
|
|
|
#### Ulysses-SP
|
|
|
|
##### Quick Start
|
|
|
|
An example of using Ulysses-SP is shown below:
|
|
```python
|
|
from vllm_omni import Omni
|
|
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
ulysses_degree = 2
|
|
|
|
omni = Omni(
|
|
model="Qwen/Qwen-Image",
|
|
parallel_config=DiffusionParallelConfig(ulysses_degree=2)
|
|
)
|
|
|
|
outputs = omni.generate(prompt="A cat sitting on a windowsill", num_inference_steps=50, width=2048, height=2048)
|
|
```
|
|
|
|
See `examples/offline_inference/text_to_image/text_to_image.py` for a complete working example.
|
|
|
|
##### Benchmarks
|
|
!!! note "Benchmark Disclaimer"
|
|
These benchmarks are provided for **general reference only**. The configurations shown use default or common parameter settings and have not been exhaustively optimized for maximum performance. Actual performance may vary based on:
|
|
|
|
- Specific model and use case
|
|
- Hardware configuration
|
|
- Careful parameter tuning
|
|
- Different inference settings (e.g., number of steps, image resolution)
|
|
|
|
|
|
To measure the parallelism methods, we run benchmarks with **Qwen/Qwen-Image** model generating images (**2048x2048** as long sequence input) with 50 inference steps. The hardware devices are NVIDIA H800 GPUs. `sdpa` is the attention backends.
|
|
|
|
| Configuration | Ulysses degree |Generation Time | Speedup |
|
|
|---------------|----------------|---------|---------|
|
|
| **Baseline (diffusers)** | - | 112.5s | 1.0x |
|
|
| Ulysses-SP | 2 | 65.2s | 1.73x |
|
|
| Ulysses-SP | 4 | 39.6s | 2.84x |
|
|
| Ulysses-SP | 8 | 30.8s | 3.65x |
|
|
|
|
##### How to parallelize a new model
|
|
|
|
If a diffusion model has been deployed in vLLM-Omni and supports single-card inference, you can refer to the following instruction on how to parallelize this model with Ulysses-SP.
|
|
|
|
First, please edit the `TransformerModel`'s `forward` function in the `xxx_model_transformer.py` to make the inputs (image hidden states, positional embeddings, etc.) as chunks separated at the sequence dimension. Taking `qwen_image_transformer.py` as an example:
|
|
|
|
```diff
|
|
class QwenImageTransformer2DModel(nn.Module):
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
encoder_hidden_states: torch.Tensor = None,
|
|
...
|
|
):
|
|
+ if self.parallel_config.sequence_parallel_size > 1:
|
|
+ hidden_states = torch.chunk(hidden_states, get_sequence_parallel_world_size(), dim=-2)[
|
|
+ get_sequence_parallel_rank()
|
|
+ ]
|
|
|
|
hidden_states = self.img_in(hidden_states)
|
|
|
|
...
|
|
image_rotary_emb = self.pos_embed(img_shapes, txt_seq_lens, device=hidden_states.device)
|
|
|
|
+ def get_rotary_emb_chunk(freqs):
|
|
+ freqs = torch.chunk(freqs, get_sequence_parallel_world_size(), dim=0)[get_sequence_parallel_rank()]
|
|
+ return freqs
|
|
|
|
+ if self.parallel_config.sequence_parallel_size > 1:
|
|
+ img_freqs, txt_freqs = image_rotary_emb
|
|
+ img_freqs = get_rotary_emb_chunk(img_freqs)
|
|
+ image_rotary_emb = (img_freqs, txt_freqs)
|
|
```
|
|
|
|
Next, at the end of the `forward` function, please call `get_sp_group().all_gather` to gather the chunked outputs across devices, and concatenate them at the sequence dimension.
|
|
|
|
|
|
```diff
|
|
class QwenImageTransformer2DModel(nn.Module):
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
encoder_hidden_states: torch.Tensor = None,
|
|
...
|
|
):
|
|
# Use only the image part (hidden_states) from the dual-stream blocks
|
|
hidden_states = self.norm_out(hidden_states, temb)
|
|
output = self.proj_out(hidden_states)
|
|
|
|
+ if self.parallel_config.sequence_parallel_size > 1:
|
|
+ output = get_sp_group().all_gather(output, dim=-2)
|
|
return Transformer2DModelOutput(sample=output)
|
|
```
|
|
|
|
Finally, you can set the parallel configuration and pass it to `Omni` and start parallel inference with:
|
|
```diff
|
|
from vllm_omni import Omni
|
|
+from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
ulysses_degree = 2
|
|
|
|
omni = Omni(
|
|
model="Qwen/Qwen-Image",
|
|
+ parallel_config=DiffusionParallelConfig(ulysses_degree=2)
|
|
)
|
|
|
|
outputs = omni.generate(prompt="A cat sitting on a windowsill", num_inference_steps=50)
|
|
```
|