[diffusion] feat: support torch compile for diffusers backend (#19673)
This commit is contained in:
@@ -286,6 +286,8 @@ SGLang diffusion supports a **diffusers backend** that allows you to run any dif
|
|||||||
| `--vae-slicing` | flag | Enable VAE slicing for lower memory usage (decodes slice-by-slice). |
|
| `--vae-slicing` | flag | Enable VAE slicing for lower memory usage (decodes slice-by-slice). |
|
||||||
| `--dit-precision` | `fp16`, `bf16`, `fp32` | Precision for the diffusion transformer. |
|
| `--dit-precision` | `fp16`, `bf16`, `fp32` | Precision for the diffusion transformer. |
|
||||||
| `--vae-precision` | `fp16`, `bf16`, `fp32` | Precision for the VAE. |
|
| `--vae-precision` | `fp16`, `bf16`, `fp32` | Precision for the VAE. |
|
||||||
|
| `--enable-torch-compile` | flag | Enable `torch.compile` for diffusers pipelines. |
|
||||||
|
| `--cache-dit-config` | `{PATH}` | Path to a Cache-DiT YAML/JSON config file for accelerating diffusers pipelines with Cache-DiT. |
|
||||||
|
|
||||||
### Example: Running Ovis-Image-7B
|
### Example: Running Ovis-Image-7B
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from copy import deepcopy
|
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
import zmq
|
import zmq
|
||||||
@@ -233,7 +232,7 @@ class Scheduler:
|
|||||||
height=height,
|
height=height,
|
||||||
prompt="",
|
prompt="",
|
||||||
)
|
)
|
||||||
req.set_as_warmup()
|
req.set_as_warmup(self.server_args.warmup_steps)
|
||||||
self.waiting_queue.append((None, req))
|
self.waiting_queue.append((None, req))
|
||||||
# if server is warmed-up, set this flag to avoid req-based warmup
|
# if server is warmed-up, set this flag to avoid req-based warmup
|
||||||
self.warmed_up = True
|
self.warmed_up = True
|
||||||
@@ -253,8 +252,7 @@ class Scheduler:
|
|||||||
# only the very first req through server's lifetime will be warmed up
|
# only the very first req through server's lifetime will be warmed up
|
||||||
identity, req = recv_reqs[0]
|
identity, req = recv_reqs[0]
|
||||||
if isinstance(req, Req):
|
if isinstance(req, Req):
|
||||||
warmup_req = deepcopy(req)
|
warmup_req = req.copy_as_warmup(self.server_args.warmup_steps)
|
||||||
warmup_req.set_as_warmup()
|
|
||||||
recv_reqs.insert(0, (identity, warmup_req))
|
recv_reqs.insert(0, (identity, warmup_req))
|
||||||
self._warmup_total = 1
|
self._warmup_total = 1
|
||||||
self._warmup_processed = 0
|
self._warmup_processed = 0
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ class DiffusersPipeline(ComposedPipelineBase):
|
|||||||
self.memory_usages: dict[str, float] = {}
|
self.memory_usages: dict[str, float] = {}
|
||||||
self.post_init_called = False
|
self.post_init_called = False
|
||||||
self.executor = executor or SyncExecutor(server_args=server_args)
|
self.executor = executor or SyncExecutor(server_args=server_args)
|
||||||
|
self._cache_dit_enabled = False
|
||||||
|
|
||||||
logger.info("Loading diffusers pipeline from %s", model_path)
|
logger.info("Loading diffusers pipeline from %s", model_path)
|
||||||
self.diffusers_pipe = self._load_diffusers_pipeline(model_path, server_args)
|
self.diffusers_pipe = self._load_diffusers_pipeline(model_path, server_args)
|
||||||
@@ -462,6 +463,8 @@ class DiffusersPipeline(ComposedPipelineBase):
|
|||||||
self._apply_attention_backend(pipe, server_args)
|
self._apply_attention_backend(pipe, server_args)
|
||||||
# Apply cache-dit acceleration if configured
|
# Apply cache-dit acceleration if configured
|
||||||
pipe = self._apply_cache_dit(pipe, server_args)
|
pipe = self._apply_cache_dit(pipe, server_args)
|
||||||
|
# Apply torch.compile if enabled and supported
|
||||||
|
pipe = self._apply_torch_compile(pipe, server_args)
|
||||||
logger.info("Loaded diffusers pipeline: %s", pipe.__class__.__name__)
|
logger.info("Loaded diffusers pipeline: %s", pipe.__class__.__name__)
|
||||||
return pipe
|
return pipe
|
||||||
|
|
||||||
@@ -562,6 +565,58 @@ class DiffusersPipeline(ComposedPipelineBase):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
logger.info("Enabled cache-dit for diffusers pipeline")
|
logger.info("Enabled cache-dit for diffusers pipeline")
|
||||||
|
self._cache_dit_enabled = True
|
||||||
|
return pipe
|
||||||
|
|
||||||
|
def _apply_torch_compile(self, pipe: Any, server_args: ServerArgs) -> Any:
|
||||||
|
"""Apply torch.compile to the pipeline if configured and supported."""
|
||||||
|
if not server_args.enable_torch_compile:
|
||||||
|
return pipe
|
||||||
|
|
||||||
|
# check if the pipeline has 'transformer' or 'unet' components which are
|
||||||
|
# typically the most expensive parts to compile. 'transformer_2' for some
|
||||||
|
# video pipelines, e.g, Wan 2.2 series, also check for that.
|
||||||
|
compilable_components = ["transformer", "transformer_2", "unet"]
|
||||||
|
if not any(hasattr(pipe, comp) for comp in compilable_components):
|
||||||
|
logger.warning(
|
||||||
|
"Pipeline does not have 'transformer' or 'unet' components. "
|
||||||
|
"torch.compile may not provide significant benefits and could increase latency."
|
||||||
|
)
|
||||||
|
return pipe
|
||||||
|
|
||||||
|
if self._cache_dit_enabled:
|
||||||
|
try:
|
||||||
|
import cache_dit
|
||||||
|
|
||||||
|
if hasattr(cache_dit, "set_compile_configs"):
|
||||||
|
cache_dit.set_compile_configs()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to set torch_compile configs for cache-dit: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
for comp in compilable_components:
|
||||||
|
if hasattr(pipe, comp):
|
||||||
|
try:
|
||||||
|
component = getattr(pipe, comp)
|
||||||
|
# TODO(DefTruth): Add support for 'compile_repeated_blocks' for 'transformer'
|
||||||
|
# modules which can significantly reduce compilation time for large models
|
||||||
|
# with repeated blocks.
|
||||||
|
if isinstance(component, torch.nn.Module) and hasattr(
|
||||||
|
component, "compile"
|
||||||
|
):
|
||||||
|
# Prefer in-place compilation if supported. According to PyTorch documentation:
|
||||||
|
# https://docs.pytorch.org/docs/stable/generated/torch.compile.html
|
||||||
|
component.compile()
|
||||||
|
else:
|
||||||
|
compiled_component = torch.compile(component)
|
||||||
|
setattr(pipe, comp, compiled_component)
|
||||||
|
logger.info(
|
||||||
|
f"Applied torch.compile to {comp} component of the pipeline"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to apply torch.compile to {comp}: {e}")
|
||||||
|
|
||||||
return pipe
|
return pipe
|
||||||
|
|
||||||
def _get_dtype(self, server_args: ServerArgs) -> torch.dtype:
|
def _get_dtype(self, server_args: ServerArgs) -> torch.dtype:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
|
from copy import deepcopy
|
||||||
from dataclasses import MISSING, asdict, dataclass, field, fields
|
from dataclasses import MISSING, asdict, dataclass, field, fields
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
@@ -251,12 +252,17 @@ class Req:
|
|||||||
return None
|
return None
|
||||||
return os.path.join(self.output_path, output_file_name)
|
return os.path.join(self.output_path, output_file_name)
|
||||||
|
|
||||||
def set_as_warmup(self):
|
def set_as_warmup(self, warmup_steps: int = 1):
|
||||||
self.is_warmup = True
|
self.is_warmup = True
|
||||||
self.save_output = False
|
self.save_output = False
|
||||||
self.suppress_logs = True
|
self.suppress_logs = True
|
||||||
self.extra["cache_dit_num_inference_steps"] = self.num_inference_steps
|
self.extra["cache_dit_num_inference_steps"] = self.num_inference_steps
|
||||||
self.num_inference_steps = 1
|
self.num_inference_steps = warmup_steps
|
||||||
|
|
||||||
|
def copy_as_warmup(self, warmup_steps: int = 1) -> "Req":
|
||||||
|
req = deepcopy(self)
|
||||||
|
req.set_as_warmup(warmup_steps)
|
||||||
|
return req
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""Initialize dependent fields after dataclass initialization."""
|
"""Initialize dependent fields after dataclass initialization."""
|
||||||
@@ -270,9 +276,6 @@ class Req:
|
|||||||
|
|
||||||
self.metrics = RequestMetrics(request_id=self.request_id)
|
self.metrics = RequestMetrics(request_id=self.request_id)
|
||||||
|
|
||||||
if self.is_warmup:
|
|
||||||
self.set_as_warmup()
|
|
||||||
|
|
||||||
def adjust_size(self, server_args: ServerArgs):
|
def adjust_size(self, server_args: ServerArgs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,7 @@ class ServerArgs:
|
|||||||
# warmup
|
# warmup
|
||||||
warmup: bool = False
|
warmup: bool = False
|
||||||
warmup_resolutions: list[str] = None
|
warmup_resolutions: list[str] = None
|
||||||
|
warmup_steps: int = 1
|
||||||
|
|
||||||
disable_autocast: bool | None = None
|
disable_autocast: bool | None = None
|
||||||
|
|
||||||
@@ -755,6 +756,12 @@ class ServerArgs:
|
|||||||
default=ServerArgs.warmup_resolutions,
|
default=ServerArgs.warmup_resolutions,
|
||||||
help="Specify resolutions for server to warmup. e.g., `--warmup-resolutions 256x256, 720x720`",
|
help="Specify resolutions for server to warmup. e.g., `--warmup-resolutions 256x256, 720x720`",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--warmup-steps",
|
||||||
|
type=int,
|
||||||
|
default=ServerArgs.warmup_steps,
|
||||||
|
help="The number of warmup steps to perform for each resolution.",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dit-cpu-offload",
|
"--dit-cpu-offload",
|
||||||
|
|||||||
Reference in New Issue
Block a user