Buckets:
| # ParaAttention | |
| Large image and video generation models, such as [FLUX.1-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev) and [HunyuanVideo](https://huggingface.co/tencent/HunyuanVideo), can be an inference challenge for real-time applications and deployment because of their size. | |
| [ParaAttention](https://github.com/chengzeyi/ParaAttention)는 **context parallelism**과 **first block cache**를 구현한 라이브러리로, 다른 기법(torch.compile, fp8 dynamic quantization)과 결합하여 추론을 가속화할 수 있습니다. | |
| 이 가이드에서는 NVIDIA L20 GPU에서 FLUX.1-dev와 HunyuanVideo에 ParaAttention을 적용하는 방법을 보여줍니다. 베이스라인 벤치마크에는 최적화가 적용되지 않았으며, HunyuanVideo의 경우 메모리 부족 오류를 방지하기 위해 예외적으로 적용했습니다. | |
| Our baseline benchmark에 따르면, FLUX.1-dev은 28단계로 1024x1024 해상도의 이미지를 26.36초 만에 생성할 수 있으며, HunyuanVideo는 30단계로 720p 해상도의 129프레임을 3675.71초 만에 생성할 수 있습니다. | |
| > [!TIP] | |
| > 컨텍스트 병렬성을 사용하여 추론 속도를 더욱 높이고자 한다면, 특히 GPU 수가 많은 경우 NVLink를 지원하는 NVIDIA A100 또는 H100 GPU(사용 가능한 경우)를 사용하는 것을 권장합니다. | |
| ## 첫 번째 블록 캐시 | |
| 모델의 transformers 블록 출력을 캐싱하여 다음 추론 단계에서 재사용하면 계산 비용을 줄이고 추론 속도를 높일 수 있습니다. | |
| 그러나 품질이 보장된 이미지나 비디오를 생성하기 위해 캐시를 언제 재사용할지 결정하는 것은 어렵습니다. ParaAttention은 **첫 번째 transformer 블록 출력의 잔차 차이**를 직접 사용하여 모델 출력 간의 차이를 근사합니다. 차이가 충분히 작으면 이전 추론 단계의 잔차 차이가 재사용됩니다. 다시 말해, 디노이징 단계가 건너뛸 수 있습니다. | |
| 이를 통해 FLUX.1-dev 및 HunyuanVideo 추론에서 매우 좋은 품질을 유지하면서 2배의 속도 향상을 달성합니다. | |
| AdaCache의 작동 방식, First Block Cache는 그 변형입니다 | |
| FLUX.1-dev에 first block cache를 적용하려면 아래처럼 `apply_cache_on_pipe`를 호출하세요. 0.08은 FLUX 모델의 기본 residual difference 값입니다. | |
| ```python | |
| import time | |
| import torch | |
| from diffusers import FluxPipeline | |
| pipe = FluxPipeline.from_pretrained( | |
| "black-forest-labs/FLUX.1-dev", | |
| dtype=torch.bfloat16, | |
| ).to("cuda") # or "mps", "xpu", "cpu" | |
| from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe | |
| apply_cache_on_pipe(pipe, residual_diff_threshold=0.08) | |
| # Enable memory savings | |
| # pipe.enable_model_cpu_offload() | |
| # pipe.enable_sequential_cpu_offload() | |
| begin = time.time() | |
| image = pipe( | |
| "A cat holding a sign that says hello world", | |
| num_inference_steps=28, | |
| ).images[0] | |
| end = time.time() | |
| print(f"Time: {end - begin:.2f}s") | |
| print("Saving image to flux.png") | |
| image.save("flux.png") | |
| ``` | |
| | 최적화 | 원본 | FBCache rdt=0.06 | FBCache rdt=0.08 | FBCache rdt=0.10 | FBCache rdt=0.12 | | |
| | - | - | - | - | - | - | | |
| | 미리 보기 |  |  |  |  |  | | |
| | 실제 시간 (초) | 26.36 | 21.83 | 17.01 | 16.00 | 13.78 | | |
| First Block Cache는 베이스라인 대비 추론 속도를 17.01초로 줄여 1.55배 더 빠르게 만들었으며, 거의 품질 손실이 없도록 유지했습니다. | |
| HunyuanVideo에 First Block Cache를 적용하려면, 아래에 표시된 대로 `apply_cache_on_pipe`를 사용하세요. 0.06은 HunyuanVideo 모델의 기본 잔차 차이 값입니다. | |
| ```python | |
| import time | |
| import torch | |
| from diffusers import HunyuanVideoPipeline, HunyuanVideoTransformer3DModel | |
| from diffusers.utils import export_to_video | |
| model_id = "tencent/HunyuanVideo" | |
| transformer = HunyuanVideoTransformer3DModel.from_pretrained( | |
| model_id, | |
| subfolder="transformer", | |
| dtype=torch.bfloat16, | |
| revision="refs/pr/18", | |
| ) | |
| pipe = HunyuanVideoPipeline.from_pretrained( | |
| model_id, | |
| transformer=transformer, | |
| dtype=torch.float16, | |
| revision="refs/pr/18", | |
| ).to("cuda") # or "mps", "xpu", "cpu" | |
| from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe | |
| apply_cache_on_pipe(pipe, residual_diff_threshold=0.6) | |
| pipe.vae.enable_tiling() | |
| begin = time.time() | |
| output = pipe( | |
| prompt="A cat walks on the grass, realistic", | |
| height=720, | |
| width=1280, | |
| num_frames=129, | |
| num_inference_steps=30, | |
| ).frames[0] | |
| end = time.time() | |
| print(f"Time: {end - begin:.2f}s") | |
| print("Saving video to hunyuan_video.mp4") | |
| export_to_video(output, "hunyuan_video.mp4", fps=15) | |
| ``` | |
| 브라우저가 비디오 태그를 지원하지 않습니다. | |
| FBCache가 없는 HunyuanVideo | |
| 브라우저가 비디오 태그를 지원하지 않습니다. | |
| FBCache를 사용한 HunyuanVideo | |
| First Block Cache는 베이스라인 대비 추론 속도를 2271.06초로 줄여 1.62배 더 빠르게 만들었으며, 거의 품질 손실이 없는 상태를 유지했습니다. | |
| ## fp8 양자화 | |
| 동적 양자화를 적용한 fp8은 추론 속도를 더 빠르게 하고 메모리 사용량을 줄입니다. 8비트 [NVIDIA Tensor Cores](https://www.nvidia.com/en-us/data-center/tensor-cores/)를 사용하려면 활성화 값과 가중치 모두를 양자화해야 합니다. | |
| `float8_weight_only`와 `float8_dynamic_activation_float8_weight`를 사용하여 텍스트 인코더와 트랜스포머 모델을 양자화합니다. | |
| 기본 양자화 방법은 텐서별(per tensor) 양자화이지만, GPU가 행별(row-wise) 양자화를 지원한다면 더 높은 정확도를 위해 이 방법도 시도해 볼 수 있습니다. | |
| 아래 명령어로 [torchao](https://github.com/pytorch/ao/tree/main)를 설치합니다. | |
| ```bash | |
| pip3 install -U torch torchao | |
| ``` | |
| `mode="max-autotune-no-cudagraphs"` 또는 `mode="max-autotune"`를 사용한 [torch.compile](https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html)은 성능을 위해 최적의 커널을 선택합니다. 모델이 처음 호출되는 경우 컴파일 시간이 오래 걸릴 수 있지만, 모델이 컴파일된 후에는 그 가치가 있습니다. | |
| 이 예시에서는 transformer 모델만 양자화하지만, 메모리 사용량을 더 줄이기 위해 텍스트 인코더도 양자화할 수 있습니다. | |
| > [!TIP] | |
| > Dynamic quantization은 모델 출력의 분포를 크게 변경할 수 있으므로, 효과를 보려면 `residual_diff_threshold`를 더 큰 값으로 변경해야 합니다. | |
| ```python | |
| import time | |
| import torch | |
| from diffusers import FluxPipeline | |
| pipe = FluxPipeline.from_pretrained( | |
| "black-forest-labs/FLUX.1-dev", | |
| dtype=torch.bfloat16, | |
| ).to("cuda") # or "mps", "xpu", "cpu" | |
| from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe | |
| apply_cache_on_pipe( | |
| pipe, | |
| residual_diff_threshold=0.12, # Use a larger value to make the cache take effect | |
| ) | |
| from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight, float8_weight_only | |
| quantize_(pipe.text_encoder, float8_weight_only()) | |
| quantize_(pipe.transformer, float8_dynamic_activation_float8_weight()) | |
| pipe.transformer = torch.compile( | |
| pipe.transformer, mode="max-autotune-no-cudagraphs", | |
| ) | |
| # Enable memory savings | |
| # pipe.enable_model_cpu_offload() | |
| # pipe.enable_sequential_cpu_offload() | |
| for i in range(2): | |
| begin = time.time() | |
| image = pipe( | |
| "A cat holding a sign that says hello world", | |
| num_inference_steps=28, | |
| ).images[0] | |
| end = time.time() | |
| if i == 0: | |
| print(f"Warm up time: {end - begin:.2f}s") | |
| else: | |
| print(f"Time: {end - begin:.2f}s") | |
| print("Saving image to flux.png") | |
| image.save("flux.png") | |
| ``` | |
| fp8 동적 양자화 및 torch.compile을 적용하면 베이스라인 대비 추론 속도가 7.56초로 줄어 3.48배 빨라집니다. | |
| ```python | |
| import time | |
| import torch | |
| from diffusers import HunyuanVideoPipeline, HunyuanVideoTransformer3DModel | |
| from diffusers.utils import export_to_video | |
| model_id = "tencent/HunyuanVideo" | |
| transformer = HunyuanVideoTransformer3DModel.from_pretrained( | |
| model_id, | |
| subfolder="transformer", | |
| dtype=torch.bfloat16, | |
| revision="refs/pr/18", | |
| ) | |
| pipe = HunyuanVideoPipeline.from_pretrained( | |
| model_id, | |
| transformer=transformer, | |
| dtype=torch.float16, | |
| revision="refs/pr/18", | |
| ).to("cuda") # or "mps", "xpu", "cpu" | |
| from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe | |
| apply_cache_on_pipe(pipe) | |
| from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight, float8_weight_only | |
| quantize_(pipe.text_encoder, float8_weight_only()) | |
| quantize_(pipe.transformer, float8_dynamic_activation_float8_weight()) | |
| pipe.transformer = torch.compile( | |
| pipe.transformer, mode="max-autotune-no-cudagraphs", | |
| ) | |
| # Enable memory savings | |
| pipe.vae.enable_tiling() | |
| # pipe.enable_model_cpu_offload() | |
| # pipe.enable_sequential_cpu_offload() | |
| for i in range(2): | |
| begin = time.time() | |
| output = pipe( | |
| prompt="A cat walks on the grass, realistic", | |
| height=720, | |
| width=1280, | |
| num_frames=129, | |
| num_inference_steps=1 if i == 0 else 30, | |
| ).frames[0] | |
| end = time.time() | |
| if i == 0: | |
| print(f"Warm up time: {end - begin:.2f}s") | |
| else: | |
| print(f"Time: {end - begin:.2f}s") | |
| print("Saving video to hunyuan_video.mp4") | |
| export_to_video(output, "hunyuan_video.mp4", fps=15) | |
| ``` | |
| NVIDIA L20 GPU는 메모리가 48GB에 불과하여, 컴파일 후 `enable_model_cpu_offload`를 호출하지 않으면 OOM(Out-Of-Memory) 오류가 발생할 수 있습니다. 이는 HunyuanVideo가 고해상도와 많은 프레임 수로 실행될 때 매우 큰 활성화 텐서를 생성하기 때문입니다. 80GB 미만의 메모리를 가진 GPU에서는 OOM 오류를 피하기 위해 해상도와 프레임 수를 줄여 보실 수 있습니다. | |
| 대형 비디오 생성 모델은 일반적으로 완전 연결 계층보다 어텐션 계산에 병목 현상이 발생합니다. 이러한 모델은 양자화 및 torch.compile에서 상당한 이점을 얻지 못합니다. | |
| ## 컨텍스트 병렬 | |
| Context Parallelism은 추론을 병렬화하며 여러 GPU로 확장됩니다. ParaAttention의 구성式设计은 Context Parallelism을 First Block Cache 및 동적 양자화와 결합할 수 있도록 합니다. | |
| > [!TIP] | |
| > 여러 GPU로 추론을 확장하는 방법에 대한 자세한 지침과 예제는 [ParaAttention](https://github.com/chengzeyi/ParaAttention/tree/main) 저장소를 참조하세요. | |
| 추론 프로세스가 영속적이고 서비스 가능해야 하는 경우, [torch.multiprocessing](https://pytorch.org/docs/stable/multiprocessing.html)를 사용하여 자체 추론 프로세서를 작성하는 것이 좋습니다. 이를 통해 프로세스 시작 오버헤드와 모델 로딩 및 재컴파일 비용을 제거할 수 있습니다. | |
| 아래 코드 샘플은 First Block Cache, fp8 동적 양자화, torch.compile, Context Parallelism을 결합하여 가장 빠른 추론 속도를 제공합니다. | |
| ```python | |
| import time | |
| import torch | |
| import torch.distributed as dist | |
| from diffusers import FluxPipeline | |
| dist.init_process_group() | |
| torch.cuda.set_device(dist.get_rank()) | |
| pipe = FluxPipeline.from_pretrained( | |
| "black-forest-labs/FLUX.1-dev", | |
| dtype=torch.bfloat16, | |
| ).to("cuda") # or "mps", "xpu", "cpu" | |
| from para_attn.context_parallel import init_context_parallel_mesh | |
| from para_attn.context_parallel.diffusers_adapters import parallelize_pipe | |
| from para_attn.parallel_vae.diffusers_adapters import parallelize_vae | |
| mesh = init_context_parallel_mesh( | |
| pipe.device.type, | |
| max_ring_dim_size=2, | |
| ) | |
| parallelize_pipe( | |
| pipe, | |
| mesh=mesh, | |
| ) | |
| parallelize_vae(pipe.vae, mesh=mesh._flatten()) | |
| from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe | |
| apply_cache_on_pipe( | |
| pipe, | |
| residual_diff_threshold=0.12, # Use a larger value to make the cache take effect | |
| ) | |
| from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight, float8_weight_only | |
| quantize_(pipe.text_encoder, float8_weight_only()) | |
| quantize_(pipe.transformer, float8_dynamic_activation_float8_weight()) | |
| torch._inductor.config.reorder_for_compute_comm_overlap = True | |
| pipe.transformer = torch.compile( | |
| pipe.transformer, mode="max-autotune-no-cudagraphs", | |
| ) | |
| # Enable memory savings | |
| # pipe.enable_model_cpu_offload(gpu_id=dist.get_rank()) | |
| # pipe.enable_sequential_cpu_offload(gpu_id=dist.get_rank()) | |
| for i in range(2): | |
| begin = time.time() | |
| image = pipe( | |
| "A cat holding a sign that says hello world", | |
| num_inference_steps=28, | |
| output_type="pil" if dist.get_rank() == 0 else "pt", | |
| ).images[0] | |
| end = time.time() | |
| if dist.get_rank() == 0: | |
| if i == 0: | |
| print(f"Warm up time: {end - begin:.2f}s") | |
| else: | |
| print(f"Time: {end - begin:.2f}s") | |
| if dist.get_rank() == 0: | |
| print("Saving image to flux.png") | |
| image.save("flux.png") | |
| dist.destroy_process_group() | |
| ``` | |
| `run_flux.py`에 저장하고 [torchrun](https://pytorch.org/docs/stable/elastic/run.html)으로 실행하세요. | |
| ```bash | |
| # Use --nproc_per_node to specify the number of GPUs | |
| torchrun --nproc_per_node=2 run_flux.py | |
| ``` | |
| 2개의 NVIDIA L20 GPU를 사용할 때, 기준값 대비 추론 속도가 8.20초로 감소하여 3.21배 빨라집니다. 4개의 L20을 사용할 경우, 추론 속도는 3.90초로, 6.75배 빨라집니다. | |
| 아래 코드 샘플은 First Block Cache와 Context Parallelism을 결합하여 가장 빠른 추론 속도를 제공합니다. | |
| ```python | |
| import time | |
| import torch | |
| import torch.distributed as dist | |
| from diffusers import HunyuanVideoPipeline, HunyuanVideoTransformer3DModel | |
| from diffusers.utils import export_to_video | |
| dist.init_process_group() | |
| torch.cuda.set_device(dist.get_rank()) | |
| model_id = "tencent/HunyuanVideo" | |
| transformer = HunyuanVideoTransformer3DModel.from_pretrained( | |
| model_id, | |
| subfolder="transformer", | |
| dtype=torch.bfloat16, | |
| revision="refs/pr/18", | |
| ) | |
| pipe = HunyuanVideoPipeline.from_pretrained( | |
| model_id, | |
| transformer=transformer, | |
| dtype=torch.float16, | |
| revision="refs/pr/18", | |
| ).to("cuda") # or "mps", "xpu", "cpu" | |
| from para_attn.context_parallel import init_context_parallel_mesh | |
| from para_attn.context_parallel.diffusers_adapters import parallelize_pipe | |
| from para_attn.parallel_vae.diffusers_adapters import parallelize_vae | |
| mesh = init_context_parallel_mesh( | |
| pipe.device.type, | |
| ) | |
| parallelize_pipe( | |
| pipe, | |
| mesh=mesh, | |
| ) | |
| parallelize_vae(pipe.vae, mesh=mesh._flatten()) | |
| from para_attn.first_block_cache.diffusers_adapters import apply_cache_on_pipe | |
| apply_cache_on_pipe(pipe) | |
| # from torchao.quantization import quantize_, float8_dynamic_activation_float8_weight, float8_weight_only | |
| # | |
| # torch._inductor.config.reorder_for_compute_comm_overlap = True | |
| # | |
| # quantize_(pipe.text_encoder, float8_weight_only()) | |
| # quantize_(pipe.transformer, float8_dynamic_activation_float8_weight()) | |
| # pipe.transformer = torch.compile( | |
| # pipe.transformer, mode="max-autotune-no-cudagraphs", | |
| # ) | |
| # Enable memory savings | |
| pipe.vae.enable_tiling() | |
| # pipe.enable_model_cpu_offload(gpu_id=dist.get_rank()) | |
| # pipe.enable_sequential_cpu_offload(gpu_id=dist.get_rank()) | |
| for i in range(2): | |
| begin = time.time() | |
| output = pipe( | |
| prompt="A cat walks on the grass, realistic", | |
| height=720, | |
| width=1280, | |
| num_frames=129, | |
| num_inference_steps=1 if i == 0 else 30, | |
| output_type="pil" if dist.get_rank() == 0 else "pt", | |
| ).frames[0] | |
| end = time.time() | |
| if dist.get_rank() == 0: | |
| if i == 0: | |
| print(f"Warm up time: {end - begin:.2f}s") | |
| else: | |
| print(f"Time: {end - begin:.2f}s") | |
| if dist.get_rank() == 0: | |
| print("Saving video to hunyuan_video.mp4") | |
| export_to_video(output, "hunyuan_video.mp4", fps=15) | |
| dist.destroy_process_group() | |
| ``` | |
| `run_hunyuan_video.py`에 저장하고 [torchrun](https://pytorch.org/docs/stable/elastic/run.html)으로 실행하세요. | |
| ```bash | |
| # Use --nproc_per_node to specify the number of GPUs | |
| torchrun --nproc_per_node=8 run_hunyuan_video.py | |
| ``` | |
| 8개의 NVIDIA L20 GPU를 사용할 때, 추론 속도는 베이스라인 대비 649.23초로 감소하여 5.66배 빨라집니다. | |
| ## 벤치마크 | |
| | GPU 유형 | GPU 수 | 최적화 | 실제 시간 (초) | 속도 향상 | | |
| | - | - | - | - | - | | |
| | NVIDIA L20 | 1 | 베이스라인 | 26.36 | 1.00x | | |
| | NVIDIA L20 | 1 | FBCache (rdt=0.08) | 17.01 | 1.55x | | |
| | NVIDIA L20 | 1 | FP8 DQ | 13.40 | 1.96x | | |
| | NVIDIA L20 | 1 | FBCache (rdt=0.12) + FP8 DQ | 7.56 | 3.48x | | |
| | NVIDIA L20 | 2 | FBCache (rdt=0.12) + FP8 DQ + CP | 4.92 | 5.35x | | |
| | NVIDIA L20 | 4 | FBCache (rdt=0.12) + FP8 DQ + CP | 3.90 | 6.75x | | |
| | GPU 유형 | GPU 수 | 최적화 | 실제 시간 (초) | 속도 향상 | | |
| | - | - | - | - | - | | |
| | NVIDIA L20 | 1 | 베이스라인 | 3675.71 | 1.00x | | |
| | NVIDIA L20 | 1 | FBCache | 2271.06 | 1.62x | | |
| | NVIDIA L20 | 2 | FBCache + CP | 1132.90 | 3.24x | | |
| | NVIDIA L20 | 4 | FBCache + CP | 718.15 | 5.12x | | |
| | NVIDIA L20 | 8 | FBCache + CP | 649.23 | 5.66x | | |
Xet Storage Details
- Size:
- 17.9 kB
- Xet hash:
- 12cf5aee73945e739401ca8dd03a87892a76711f61245566d682c181861309c5
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.