Skip to content

Commit 7afd104

Browse files
Add tensor parallel and torch compile (#262)
* feat(tp): add tensor parallel and compile support * refactor model weights loading * test(tp): add Qwen Image and Wan I2V cases --------- Co-authored-by: zhuguoxuan.zgx <zhuguoxuan.zgx@alibaba-inc.com>
1 parent f6606fd commit 7afd104

15 files changed

Lines changed: 671 additions & 135 deletions

File tree

diffsynth_engine/args.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,21 @@ def parse_cli_args() -> Dict[str, Any]:
110110
help="Sparge attention topk parameter (default: 0.5)",
111111
)
112112

113+
# Optimization configuration group
114+
optimization_group = parser.add_argument_group("Optimization Configuration")
115+
optimization_group.add_argument(
116+
"--use-torch-compile",
117+
action="store_true",
118+
help="Compile repeated transformer blocks with torch.compile",
119+
)
120+
113121
# Parallelism configuration group
114122
parallel_group = parser.add_argument_group("Parallelism Configuration")
115123
parallel_group.add_argument(
116124
"--parallelism",
117125
type=int,
118126
default=1,
119-
choices=[1, 2, 4, 8],
120-
help="Parallelism degree (default: 1, choices: 1, 2, 4, 8)",
127+
help="Total number of inference workers (default: 1)",
121128
)
122129
parallel_group.add_argument(
123130
"--use-cfg-parallel",
@@ -175,6 +182,9 @@ def parse_cli_args() -> Dict[str, Any]:
175182
args_dict["attn_type"] = attn_type
176183
args_dict["attn_params"] = _parse_attention_params(attn_type, args.sparge_topk)
177184

185+
# Optimization configuration
186+
args_dict["use_torch_compile"] = args.use_torch_compile
187+
178188
# Parallelism configuration
179189
args_dict["parallelism"] = args.parallelism
180190
args_dict["use_cfg_parallel"] = args.use_cfg_parallel

diffsynth_engine/configs/base.py

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class PipelineConfig:
4040
attn_type: AttentionType | str = AttentionType.SDPA
4141
attn_params: Optional[AttentionParams] = None
4242

43+
# optimization
44+
use_torch_compile: bool = False
45+
4346
# parallelism
4447
parallelism: int = 1
4548
use_cfg_parallel: bool = False
@@ -62,37 +65,37 @@ def __post_init__(self):
6265

6366

6467
def init_parallel_config(config: PipelineConfig):
65-
assert config.parallelism in (1, 2, 4, 8), "parallelism must be 1, 2, 4 or 8"
66-
67-
cfg_degree = 2 if config.use_cfg_parallel else 1
68+
if config.parallelism <= 0:
69+
raise ValueError(f"parallelism must be a positive integer, got {config.parallelism}")
70+
71+
cfg_degree = 2 if config.use_cfg_parallel else 1 # TODO: support cfg_degree > 2
72+
73+
if config.tp_degree is not None and config.tp_degree <= 0:
74+
raise ValueError(f"tp_degree must be None or a positive integer, got {config.tp_degree}")
75+
if config.sp_ulysses_degree is not None and config.sp_ulysses_degree <= 0:
76+
raise ValueError(f"sp_ulysses_degree must be None or a positive integer, got {config.sp_ulysses_degree}")
77+
if config.sp_ring_degree is not None and config.sp_ring_degree <= 0:
78+
raise ValueError(f"sp_ring_degree must be None or a positive integer, got {config.sp_ring_degree}")
79+
80+
config.tp_degree = config.tp_degree or 1
81+
config.sp_ring_degree = config.sp_ring_degree or 1
82+
config.sp_ulysses_degree = config.sp_ulysses_degree or (
83+
config.parallelism // (cfg_degree * config.tp_degree * config.sp_ring_degree)
84+
)
6885

69-
if config.tp_degree is not None:
70-
assert config.sp_ulysses_degree is None and config.sp_ring_degree is None, (
71-
"not allowed to enable sequence parallel and tensor parallel together; "
72-
"either set sp_ulysses_degree=None, sp_ring_degree=None or set tp_degree=None during pipeline initialization"
73-
)
74-
assert config.use_fsdp is False, (
75-
"not allowed to enable fully sharded data parallel and tensor parallel together; "
76-
"either set use_fsdp=False or set tp_degree=None during pipeline initialization"
86+
parallel_degree = cfg_degree * config.tp_degree * config.sp_ulysses_degree * config.sp_ring_degree
87+
if parallel_degree != config.parallelism:
88+
raise ValueError(
89+
f"parallelism ({config.parallelism}) must equal cfg_degree({cfg_degree}) * "
90+
f"tp_degree({config.tp_degree}) * sp_ulysses_degree({config.sp_ulysses_degree}) * "
91+
f"sp_ring_degree({config.sp_ring_degree}) = {parallel_degree}"
7792
)
78-
config.sp_ulysses_degree = 1
79-
config.sp_ring_degree = 1
80-
elif config.sp_ulysses_degree is None and config.sp_ring_degree is None:
81-
# use ulysses if not specified
82-
config.sp_ulysses_degree = config.parallelism // cfg_degree
83-
config.sp_ring_degree = 1
84-
config.tp_degree = 1
85-
elif config.sp_ulysses_degree is not None and config.sp_ring_degree is not None:
86-
config.tp_degree = 1
87-
else:
88-
raise ValueError("sp_ulysses_degree and sp_ring_degree must be specified together")
89-
90-
assert config.parallelism == cfg_degree * config.tp_degree * config.sp_ulysses_degree * config.sp_ring_degree, (
91-
f"parallelism ({config.parallelism}) must be equal to cfg_degree ({cfg_degree}) * "
92-
f"tp_degree ({config.tp_degree}) * "
93-
f"sp_ulysses_degree ({config.sp_ulysses_degree}) * "
94-
f"sp_ring_degree ({config.sp_ring_degree})"
95-
)
93+
94+
if config.tp_degree > 1 and config.use_fsdp:
95+
raise ValueError("TP and FSDP cannot be enabled together; set tp_degree=None or use_fsdp=False .")
96+
97+
if config.use_torch_compile and config.use_fsdp:
98+
logger.warning("torch.compile + FSDP may produce graph breaks")
9699

97100
if config.use_vae_parallel:
98101
assert config.parallelism > 1, "use_vae_parallel requires parallelism > 1"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from diffsynth_engine.layers.tensor_parallel.feed_forward import ColumnParallelGELU, TPFeedForward
2+
from diffsynth_engine.layers.tensor_parallel.linear import ColumnParallelLinear, RowParallelLinear
3+
from diffsynth_engine.layers.tensor_parallel.norm import TensorParallelRMSNorm
4+
5+
__all__ = [
6+
"ColumnParallelLinear",
7+
"ColumnParallelGELU",
8+
"RowParallelLinear",
9+
"TensorParallelRMSNorm",
10+
"TPFeedForward",
11+
]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import torch
2+
import torch.nn as nn
3+
import torch.nn.functional as F
4+
5+
from diffsynth_engine.layers.tensor_parallel.linear import ColumnParallelLinear, RowParallelLinear, get_tp_size
6+
7+
8+
class ColumnParallelGELU(nn.Module):
9+
"""Column-parallel linear projection followed by GELU."""
10+
11+
def __init__(self, dim_in: int, dim_out: int, approximate: str = "none", bias: bool = True):
12+
super().__init__()
13+
self.proj = ColumnParallelLinear(dim_in, dim_out, bias=bias, gather_output=False)
14+
self.approximate = approximate
15+
16+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
17+
hidden_states = self.proj(hidden_states)
18+
return F.gelu(hidden_states, approximate=self.approximate)
19+
20+
21+
class TPFeedForward(nn.Module):
22+
def __init__(
23+
self,
24+
dim: int,
25+
dim_out: int | None = None,
26+
mult: float = 4,
27+
inner_dim: int | None = None,
28+
dropout: float = 0.0,
29+
activation_fn: str = "gelu-approximate",
30+
):
31+
super().__init__()
32+
if activation_fn not in ("gelu", "gelu-approximate"):
33+
raise ValueError(f"Unsupported activation_fn={activation_fn!r}; supported: ['gelu', 'gelu-approximate']")
34+
approximate = "tanh" if activation_fn == "gelu-approximate" else "none"
35+
36+
inner_dim = inner_dim if inner_dim is not None else int(dim * mult)
37+
dim_out = dim_out if dim_out is not None else dim
38+
tp_size = get_tp_size()
39+
if inner_dim % tp_size != 0:
40+
raise ValueError(f"inner_dim ({inner_dim}) must be divisible by tp_size ({tp_size})")
41+
42+
self.net = nn.ModuleList(
43+
[
44+
ColumnParallelGELU(
45+
dim,
46+
inner_dim,
47+
approximate=approximate,
48+
bias=True,
49+
),
50+
nn.Dropout(dropout),
51+
RowParallelLinear(inner_dim, dim_out, bias=True, input_is_parallel=True),
52+
]
53+
)
54+
55+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
56+
for module in self.net:
57+
hidden_states = module(hidden_states)
58+
return hidden_states
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import math
2+
3+
import torch
4+
import torch.nn as nn
5+
import torch.nn.functional as F
6+
7+
from diffsynth_engine.distributed.parallel_state import (
8+
get_tensor_model_parallel_rank,
9+
get_tensor_model_parallel_world_size,
10+
get_tp_group,
11+
is_tp_group_initialized,
12+
)
13+
14+
15+
def get_tp_size() -> int:
16+
return get_tensor_model_parallel_world_size() if is_tp_group_initialized() else 1
17+
18+
19+
def get_tp_rank() -> int:
20+
return get_tensor_model_parallel_rank() if is_tp_group_initialized() else 0
21+
22+
23+
@torch.compiler.disable
24+
def tp_all_reduce(output: torch.Tensor) -> torch.Tensor:
25+
return get_tp_group().all_reduce(output)
26+
27+
28+
@torch.compiler.disable
29+
def tp_all_gather(output: torch.Tensor, dim: int) -> torch.Tensor:
30+
return get_tp_group().all_gather(output, dim=dim)
31+
32+
33+
class ColumnParallelLinear(nn.Module):
34+
def __init__(
35+
self,
36+
in_features: int,
37+
out_features: int,
38+
bias: bool = True,
39+
gather_output: bool = False,
40+
dtype: torch.dtype | None = None,
41+
device: torch.device | str | None = None,
42+
):
43+
super().__init__()
44+
tp_size = get_tp_size()
45+
if out_features % tp_size != 0:
46+
raise ValueError(
47+
f"ColumnParallelLinear: out_features ({out_features}) must be divisible by tp_size ({tp_size})"
48+
)
49+
50+
self.in_features = in_features
51+
self.out_features = out_features
52+
self.gather_output = gather_output
53+
self.out_features_per_partition = out_features // tp_size
54+
self.tp_size = tp_size
55+
self.tp_rank = get_tp_rank()
56+
57+
factory_kwargs = {"dtype": dtype, "device": device}
58+
self.weight = nn.Parameter(torch.empty(self.out_features_per_partition, in_features, **factory_kwargs))
59+
if bias:
60+
self.bias = nn.Parameter(torch.empty(self.out_features_per_partition, **factory_kwargs))
61+
else:
62+
self.register_parameter("bias", None)
63+
self.reset_parameters()
64+
65+
def reset_parameters(self) -> None:
66+
nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
67+
if self.bias is not None:
68+
bound = 1 / math.sqrt(self.in_features) if self.in_features > 0 else 0
69+
nn.init.uniform_(self.bias, -bound, bound)
70+
71+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
72+
output = F.linear(hidden_states, self.weight, self.bias)
73+
if self.gather_output and self.tp_size > 1:
74+
output = tp_all_gather(output, dim=-1)
75+
return output
76+
77+
def extra_repr(self) -> str:
78+
return (
79+
f"in_features={self.in_features}, out_features={self.out_features}, "
80+
f"out_per_partition={self.out_features_per_partition}, "
81+
f"bias={self.bias is not None}, gather_output={self.gather_output}"
82+
)
83+
84+
85+
class RowParallelLinear(nn.Module):
86+
def __init__(
87+
self,
88+
in_features: int,
89+
out_features: int,
90+
bias: bool = True,
91+
input_is_parallel: bool = True,
92+
dtype: torch.dtype | None = None,
93+
device: torch.device | str | None = None,
94+
):
95+
super().__init__()
96+
tp_size = get_tp_size()
97+
if in_features % tp_size != 0:
98+
raise ValueError(f"RowParallelLinear: in_features ({in_features}) must be divisible by tp_size ({tp_size})")
99+
100+
self.in_features = in_features
101+
self.out_features = out_features
102+
self.input_is_parallel = input_is_parallel
103+
self.in_features_per_partition = in_features // tp_size
104+
self.tp_size = tp_size
105+
self.tp_rank = get_tp_rank()
106+
107+
factory_kwargs = {"dtype": dtype, "device": device}
108+
self.weight = nn.Parameter(torch.empty(out_features, self.in_features_per_partition, **factory_kwargs))
109+
if bias:
110+
self.bias = nn.Parameter(torch.empty(out_features, **factory_kwargs))
111+
else:
112+
self.register_parameter("bias", None)
113+
self.reset_parameters()
114+
115+
def reset_parameters(self) -> None:
116+
bound = 1 / math.sqrt(self.in_features) if self.in_features > 0 else 0
117+
nn.init.uniform_(self.weight, -bound, bound)
118+
if self.bias is not None:
119+
nn.init.uniform_(self.bias, -bound, bound)
120+
121+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
122+
if self.tp_size == 1:
123+
return F.linear(hidden_states, self.weight, self.bias)
124+
125+
if not self.input_is_parallel:
126+
hidden_states = hidden_states.chunk(self.tp_size, dim=-1)[self.tp_rank].contiguous()
127+
128+
output = F.linear(hidden_states, self.weight, None)
129+
output = tp_all_reduce(output)
130+
if self.bias is not None:
131+
output = output + self.bias
132+
return output
133+
134+
def extra_repr(self) -> str:
135+
return (
136+
f"in_features={self.in_features}, out_features={self.out_features}, "
137+
f"in_per_partition={self.in_features_per_partition}, "
138+
f"bias={self.bias is not None}, input_is_parallel={self.input_is_parallel}"
139+
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import torch
2+
import torch.nn as nn
3+
import torch.nn.functional as F
4+
5+
from diffsynth_engine.layers.tensor_parallel.linear import get_tp_rank, get_tp_size, tp_all_reduce
6+
7+
8+
class TensorParallelRMSNorm(nn.Module):
9+
"""RMSNorm over a hidden dimension sharded across the tensor-parallel group."""
10+
11+
def __init__(
12+
self,
13+
hidden_size: int,
14+
eps: float | None = None,
15+
elementwise_affine: bool = True,
16+
dtype: torch.dtype | None = None,
17+
device: torch.device | str | None = None,
18+
):
19+
super().__init__()
20+
tp_size = get_tp_size()
21+
if hidden_size % tp_size != 0:
22+
raise ValueError(
23+
f"TensorParallelRMSNorm: hidden_size ({hidden_size}) must be divisible by tp_size ({tp_size})"
24+
)
25+
26+
self.hidden_size = hidden_size
27+
self.hidden_size_per_partition = hidden_size // tp_size
28+
self.eps = eps
29+
self.elementwise_affine = elementwise_affine
30+
self.tp_size = tp_size
31+
self.tp_rank = get_tp_rank()
32+
33+
if elementwise_affine:
34+
self.weight = nn.Parameter(torch.ones(self.hidden_size_per_partition, dtype=dtype, device=device))
35+
else:
36+
self.register_parameter("weight", None)
37+
38+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
39+
if self.tp_size == 1:
40+
return F.rms_norm(hidden_states, (self.hidden_size,), self.weight, self.eps)
41+
if hidden_states.shape[-1] != self.hidden_size_per_partition:
42+
raise ValueError(f"Expected last dimension {self.hidden_size_per_partition}, got {hidden_states.shape[-1]}")
43+
44+
variance = hidden_states.float().pow(2).sum(dim=-1, keepdim=True)
45+
variance = tp_all_reduce(variance) / self.hidden_size
46+
eps = self.eps if self.eps is not None else torch.finfo(hidden_states.dtype).eps
47+
output = hidden_states * torch.rsqrt(variance + eps).to(hidden_states.dtype)
48+
if self.weight is not None:
49+
output = output * self.weight
50+
return output
51+
52+
def extra_repr(self) -> str:
53+
return (
54+
f"hidden_size={self.hidden_size}, "
55+
f"hidden_size_per_partition={self.hidden_size_per_partition}, eps={self.eps}, "
56+
f"elementwise_affine={self.elementwise_affine}"
57+
)

0 commit comments

Comments
 (0)