94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
635 lines
22 KiB
Python
635 lines
22 KiB
Python
"""Krea-2 (K2) single-stream MMDiT.
|
|
|
|
Text and image tokens are concatenated into a single joint-attention stream. The
|
|
model uses GQA attention with a sigmoid output gate, 6-way shared adaLN
|
|
modulation, a text-fusion transformer that fuses the selected text-encoder
|
|
hidden-state layers into one, and interleaved 3-axis RoPE. Module and parameter
|
|
names follow the released K2 checkpoint, so weights load without remapping.
|
|
"""
|
|
|
|
import math
|
|
import os
|
|
from typing import Any, Optional
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
from einops import rearrange
|
|
from torch import Tensor
|
|
|
|
from sglang.multimodal_gen.configs.models.dits.krea2 import Krea2DitConfig
|
|
from sglang.multimodal_gen.runtime.distributed import (
|
|
get_sp_world_size,
|
|
get_tp_world_size,
|
|
)
|
|
from sglang.multimodal_gen.runtime.layers.attention import USPAttention
|
|
from sglang.multimodal_gen.runtime.layers.attention.layer import build_varlen_mask_meta
|
|
from sglang.multimodal_gen.runtime.layers.linear import (
|
|
ColumnParallelLinear,
|
|
RowParallelLinear,
|
|
)
|
|
from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload import (
|
|
LayerwiseOffloadableModuleMixin,
|
|
)
|
|
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Functional helpers
|
|
# --------------------------------------------------------------------------- #
|
|
def rope(pos: Tensor, dim: int, theta: float = 1e4, ntk: float = 1.0) -> Tensor:
|
|
scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim
|
|
omega = 1.0 / ((theta * ntk) ** scale)
|
|
out = torch.einsum("...n,d->...nd", pos, omega)
|
|
out = torch.stack(
|
|
[torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1
|
|
)
|
|
out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2)
|
|
return out.float()
|
|
|
|
|
|
def ropeapply(xq: Tensor, xk: Tensor, freqs: Tensor) -> tuple[Tensor, Tensor]:
|
|
xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2)
|
|
xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
|
|
freqs = freqs[:, None, :, :, :]
|
|
xq_ = freqs[..., 0] * xq_[..., 0] + freqs[..., 1] * xq_[..., 1]
|
|
xk_ = freqs[..., 0] * xk_[..., 0] + freqs[..., 1] * xk_[..., 1]
|
|
return xq_.reshape(*xq.shape).to(xq.dtype), xk_.reshape(*xk.shape).to(xk.dtype)
|
|
|
|
|
|
def _fused_qknorm_rope_enabled() -> bool:
|
|
return os.getenv("SGLANG_ENABLE_FUSED_QKNORM_ROPE", "1").lower() not in (
|
|
"0",
|
|
"false",
|
|
"off",
|
|
"no",
|
|
)
|
|
|
|
|
|
def _can_use_fused_qknorm_rope(head_dim: int, dtype: torch.dtype) -> bool:
|
|
from sglang.jit_kernel.diffusion.qknorm_rope import (
|
|
can_use_fused_inplace_qknorm_rope,
|
|
)
|
|
|
|
return can_use_fused_inplace_qknorm_rope(head_dim, head_dim, False, dtype)
|
|
|
|
|
|
def _qknorm_rope_cos_sin_cache(freqs: Tensor) -> Tensor:
|
|
"""``[num_tokens, head_dim]`` cos|sin cache for the fused QKNorm+RoPE kernel.
|
|
|
|
K2's ``rope`` packs each token's rotation as ``[[cos, -sin], [sin, cos]]`` in a
|
|
``[B, N, head_dim//2, 2, 2]`` tensor; the kernel wants the per-token cosines then
|
|
sines concatenated. Positions come from the image grid (batch-invariant), so the
|
|
first batch row is representative.
|
|
"""
|
|
return torch.cat([freqs[0, :, :, 0, 0], freqs[0, :, :, 1, 0]], dim=-1).float()
|
|
|
|
|
|
def temb(
|
|
t: Tensor,
|
|
dim: int,
|
|
period: float = 1e4,
|
|
tfactor: float = 1e3,
|
|
device: torch.device = None,
|
|
dtype: torch.dtype = None,
|
|
) -> Tensor:
|
|
half = dim // 2
|
|
freqs = torch.exp(
|
|
-math.log(period)
|
|
* torch.arange(half, dtype=torch.float32, device=device)
|
|
/ half
|
|
)
|
|
args = (t.float() * tfactor)[:, None, None] * freqs
|
|
sin, cos = torch.sin(args), torch.cos(args)
|
|
return torch.cat((cos, sin), dim=-1).to(dtype=dtype)
|
|
|
|
|
|
def norm_scale_shift(
|
|
x: Tensor, weight: Tensor, scale: Tensor, shift: Tensor, eps: float
|
|
) -> Tensor:
|
|
"""Fused RMSNorm + modulation: ``rms_norm(x) * weight * (1 + scale) + shift``.
|
|
|
|
``weight`` is the effective RMSNorm weight (K2 stores ``scale``, so callers
|
|
pass ``scale + 1``), kept off the checkpoint so the identity load is unaffected.
|
|
"""
|
|
if x.is_cuda and x.shape[-1] % 256 == 0:
|
|
from sglang.jit_kernel.diffusion.cutedsl.scale_residual_norm_scale_shift import (
|
|
fused_norm_scale_shift,
|
|
)
|
|
|
|
return fused_norm_scale_shift(
|
|
x.contiguous(),
|
|
weight.contiguous(),
|
|
None,
|
|
scale.contiguous(),
|
|
shift.contiguous(),
|
|
"rms",
|
|
eps,
|
|
)
|
|
normed = F.rms_norm(x.float(), (x.shape[-1],), weight=weight.float(), eps=eps)
|
|
return (normed.to(x.dtype) * (1 + scale) + shift).to(x.dtype)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Submodules
|
|
# --------------------------------------------------------------------------- #
|
|
class TimeEmbed(nn.Module):
|
|
"""Timestep embedding MLP: linear_1 -> gelu(tanh) -> linear_2."""
|
|
|
|
def __init__(self, in_dim: int, dim: int):
|
|
super().__init__()
|
|
self.linear_1 = nn.Linear(in_dim, dim)
|
|
self.linear_2 = nn.Linear(dim, dim)
|
|
|
|
def forward(self, x: Tensor) -> Tensor:
|
|
return self.linear_2(F.gelu(self.linear_1(x), approximate="tanh"))
|
|
|
|
|
|
class TxtIn(nn.Module):
|
|
"""Text-context projection: rms-norm -> linear_1 -> gelu(tanh) -> linear_2."""
|
|
|
|
def __init__(self, txt_dim: int, dim: int):
|
|
super().__init__()
|
|
self.norm = RMSNorm(txt_dim)
|
|
self.linear_1 = nn.Linear(txt_dim, dim)
|
|
self.linear_2 = nn.Linear(dim, dim)
|
|
|
|
def forward(self, x: Tensor) -> Tensor:
|
|
return self.linear_2(F.gelu(self.linear_1(self.norm(x)), approximate="tanh"))
|
|
|
|
|
|
class PositionalEncoding(nn.Module):
|
|
def __init__(self, dim, axdims: list[int], theta: float = 1e2, ntk: float = 1.0):
|
|
super().__init__()
|
|
self.axdims = axdims
|
|
self.theta = theta
|
|
self.ntk = ntk
|
|
|
|
def forward(self, pos: Tensor) -> Tensor:
|
|
return torch.cat(
|
|
[
|
|
rope(pos[..., i], d, self.theta, self.ntk)
|
|
for i, d in enumerate(self.axdims)
|
|
],
|
|
dim=-3,
|
|
)
|
|
|
|
|
|
class RMSNorm(nn.Module):
|
|
"""RMSNorm with effective scale ``weight + 1`` (``weight`` initialized to 0),
|
|
computed in fp32. The parameter is named ``weight`` to match the released
|
|
checkpoint; the ``+ 1`` is applied in the forward."""
|
|
|
|
def __init__(self, features: int, eps: float = 1e-05, device: torch.device = None):
|
|
super().__init__()
|
|
self.features = features
|
|
self.eps = eps
|
|
self.weight = nn.Parameter(
|
|
torch.zeros(features, device=device, dtype=torch.float32)
|
|
)
|
|
|
|
def forward(self, x: Tensor) -> Tensor:
|
|
t, dtype = x.float(), x.dtype
|
|
t = F.rms_norm(
|
|
t, (self.features,), eps=self.eps, weight=(self.weight.float() + 1.0)
|
|
)
|
|
return t.to(dtype)
|
|
|
|
|
|
class SwiGLU(nn.Module):
|
|
def __init__(
|
|
self, features: int, multiplier: int, bias: bool = False, multiple: int = 128
|
|
):
|
|
super().__init__()
|
|
mlpdim = int(2 * features / 3) * multiplier
|
|
mlpdim = multiple * ((mlpdim + multiple - 1) // multiple)
|
|
# Tensor-parallel: gate/up shard the hidden dim by column, down all-reduces.
|
|
self.gate = ColumnParallelLinear(
|
|
features, mlpdim, bias=bias, gather_output=False
|
|
)
|
|
self.up = ColumnParallelLinear(features, mlpdim, bias=bias, gather_output=False)
|
|
self.down = RowParallelLinear(
|
|
mlpdim, features, bias=bias, input_is_parallel=True
|
|
)
|
|
|
|
def forward(self, x: Tensor) -> Tensor:
|
|
gate, _ = self.gate(x)
|
|
up, _ = self.up(x)
|
|
out, _ = self.down(F.silu(gate) * up)
|
|
return out
|
|
|
|
|
|
class Attention(nn.Module):
|
|
def __init__(self, dim: int, heads: int, kvheads: int = None, bias: bool = False):
|
|
super().__init__()
|
|
self.heads = heads
|
|
self.kvheads = kvheads if kvheads is not None else heads
|
|
self.headdim = dim // self.heads
|
|
|
|
# Tensor-parallel: q/k/v/gate shard heads by column, to_out all-reduces.
|
|
# Parameter names match the released checkpoint (to_q/to_k/to_v/to_gate,
|
|
# norm_q/norm_k, to_out.0) so the checkpoint loads with an identity mapping.
|
|
tp = get_tp_world_size()
|
|
assert (
|
|
self.heads % tp == 0 and self.kvheads % tp == 0
|
|
), f"heads={self.heads}, kvheads={self.kvheads} must be divisible by tp={tp}"
|
|
self.local_heads = self.heads // tp
|
|
self.local_kvheads = self.kvheads // tp
|
|
|
|
self.to_q = ColumnParallelLinear(
|
|
dim, self.headdim * self.heads, bias=bias, gather_output=False
|
|
)
|
|
self.to_k = ColumnParallelLinear(
|
|
dim, self.headdim * self.kvheads, bias=bias, gather_output=False
|
|
)
|
|
self.to_v = ColumnParallelLinear(
|
|
dim, self.headdim * self.kvheads, bias=bias, gather_output=False
|
|
)
|
|
self.to_gate = ColumnParallelLinear(dim, dim, bias=bias, gather_output=False)
|
|
self.norm_q = RMSNorm(self.headdim)
|
|
self.norm_k = RMSNorm(self.headdim)
|
|
# to_out is a ModuleList ([linear]) so the param is to_out.0.weight, matching
|
|
# the diffusers Attention layout in the released checkpoint.
|
|
self.to_out = nn.ModuleList(
|
|
[RowParallelLinear(dim, dim, bias=bias, input_is_parallel=True)]
|
|
)
|
|
# Native GQA flash via the platform backend; parameterless.
|
|
self.attn = USPAttention(
|
|
num_heads=self.local_heads,
|
|
head_size=self.headdim,
|
|
num_kv_heads=self.local_kvheads,
|
|
dropout_rate=0,
|
|
softmax_scale=None,
|
|
causal=False,
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
qkv: Tensor,
|
|
freqs: Tensor | None = None,
|
|
key_mask: Tensor | None = None,
|
|
mask_meta: dict | None = None,
|
|
num_replicated_prefix: int = 0,
|
|
skip_sequence_parallel: bool = False,
|
|
) -> Tensor:
|
|
q, _ = self.to_q(qkv)
|
|
k, _ = self.to_k(qkv)
|
|
v, _ = self.to_v(qkv)
|
|
gate, _ = self.to_gate(qkv)
|
|
|
|
hd = self.headdim
|
|
# Fast path: fuse RMSNorm(q), RMSNorm(k) and RoPE into one in-place kernel on
|
|
# the [B, S, H, D] layout USPAttention consumes (also skips the [B, H, L, D]
|
|
# transpose round-trip the eager path needs). Eager fallback below preserves
|
|
# parity off CUDA / for unsupported dtypes.
|
|
if (
|
|
freqs is not None
|
|
and q.is_cuda
|
|
and q.dtype in (torch.float16, torch.bfloat16)
|
|
and _fused_qknorm_rope_enabled()
|
|
and _can_use_fused_qknorm_rope(hd, q.dtype)
|
|
):
|
|
from sglang.jit_kernel.diffusion.qknorm_rope import (
|
|
fused_inplace_qknorm_rope,
|
|
)
|
|
|
|
b, s = qkv.shape[0], qkv.shape[1]
|
|
q = q.view(b, s, self.local_heads, hd)
|
|
k = k.view(b, s, self.local_kvheads, hd)
|
|
v = v.view(b, s, self.local_kvheads, hd)
|
|
positions = torch.arange(s, device=q.device, dtype=torch.long)
|
|
if b > 1:
|
|
positions = positions.repeat(b)
|
|
fused_inplace_qknorm_rope(
|
|
q.reshape(-1, self.local_heads, hd),
|
|
k.reshape(-1, self.local_kvheads, hd),
|
|
(self.norm_q.weight.float() + 1.0).to(q.dtype),
|
|
(self.norm_k.weight.float() + 1.0).to(k.dtype),
|
|
_qknorm_rope_cos_sin_cache(freqs),
|
|
positions,
|
|
is_neox=False,
|
|
eps=self.norm_q.eps,
|
|
head_dim=hd,
|
|
rope_dim=hd,
|
|
)
|
|
out = self.attn(
|
|
q,
|
|
k,
|
|
v,
|
|
attn_mask=key_mask,
|
|
attn_mask_meta=mask_meta,
|
|
num_replicated_prefix=num_replicated_prefix,
|
|
skip_sequence_parallel_override=skip_sequence_parallel,
|
|
).flatten(2)
|
|
else:
|
|
q, k, v = (
|
|
rearrange(q, "B L (H D) -> B H L D", H=self.local_heads),
|
|
rearrange(k, "B L (H D) -> B H L D", H=self.local_kvheads),
|
|
rearrange(v, "B L (H D) -> B H L D", H=self.local_kvheads),
|
|
)
|
|
q, k = self.norm_q(q), self.norm_k(k)
|
|
if freqs is not None:
|
|
q, k = ropeapply(q, k, freqs)
|
|
# USPAttention expects [B, S, H, D]; a [B, S] key mask + varlen metadata
|
|
# routes a ragged batch through the FA varlen fast path, else maskless.
|
|
out = self.attn(
|
|
q.transpose(1, 2).contiguous(),
|
|
k.transpose(1, 2).contiguous(),
|
|
v.transpose(1, 2).contiguous(),
|
|
attn_mask=key_mask,
|
|
attn_mask_meta=mask_meta,
|
|
num_replicated_prefix=num_replicated_prefix,
|
|
skip_sequence_parallel_override=skip_sequence_parallel,
|
|
).flatten(2)
|
|
out, _ = self.to_out[0](out * F.sigmoid(gate))
|
|
return out
|
|
|
|
|
|
class LastLayer(nn.Module):
|
|
def __init__(self, features: int, patch: int, channels: int):
|
|
super().__init__()
|
|
self.norm = RMSNorm(features)
|
|
self.linear = nn.Linear(features, patch * patch * channels, bias=True)
|
|
self.scale_shift_table = nn.Parameter(torch.zeros(2, features))
|
|
|
|
def forward(self, x: Tensor, tvec: Tensor) -> Tensor:
|
|
mod = tvec + rearrange(self.scale_shift_table, "two d -> 1 two d")
|
|
scale, shift = mod.chunk(2, dim=1)
|
|
x = norm_scale_shift(x, self.norm.weight + 1, scale, shift, self.norm.eps)
|
|
x = self.linear(x)
|
|
return x
|
|
|
|
|
|
class TextFusionBlock(nn.Module):
|
|
def __init__(
|
|
self,
|
|
features: int,
|
|
heads: int,
|
|
multiplier: int,
|
|
bias: bool = False,
|
|
kvheads: int = None,
|
|
):
|
|
super().__init__()
|
|
self.norm1 = RMSNorm(features)
|
|
self.norm2 = RMSNorm(features)
|
|
self.attn = Attention(dim=features, heads=heads, bias=bias, kvheads=kvheads)
|
|
self.ff = SwiGLU(features, multiplier, bias)
|
|
|
|
def forward(
|
|
self,
|
|
x: Tensor,
|
|
key_mask: Tensor | None = None,
|
|
mask_meta: dict | None = None,
|
|
) -> Tensor:
|
|
# Text-fusion runs on the full replicated text, so skip the SP all-to-all.
|
|
x = x + self.attn(
|
|
self.norm1(x),
|
|
key_mask=key_mask,
|
|
mask_meta=mask_meta,
|
|
skip_sequence_parallel=True,
|
|
)
|
|
x = x + self.ff(self.norm2(x))
|
|
return x
|
|
|
|
|
|
class TextFusionTransformer(nn.Module):
|
|
"""Fuses `num_txt_layers` selected encoder hidden-state layers into one.
|
|
|
|
Depth is fixed at 2 layerwise + 2 refiner blocks; `num_txt_layers` is the
|
|
projector input width (the layer axis), NOT the transformer depth.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
num_txt_layers: int,
|
|
txt_dim: int,
|
|
heads: int,
|
|
multiplier: int,
|
|
bias: bool = False,
|
|
kvheads: int = None,
|
|
):
|
|
super().__init__()
|
|
self.layerwise_blocks = nn.ModuleList(
|
|
[
|
|
TextFusionBlock(txt_dim, heads, multiplier, bias, kvheads)
|
|
for _ in range(2)
|
|
]
|
|
)
|
|
self.projector = nn.Linear(num_txt_layers, 1, bias=False)
|
|
self.refiner_blocks = nn.ModuleList(
|
|
[
|
|
TextFusionBlock(txt_dim, heads, multiplier, bias, kvheads)
|
|
for _ in range(2)
|
|
]
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
x: Tensor,
|
|
key_mask: Tensor | None = None,
|
|
mask_meta: dict | None = None,
|
|
) -> Tensor:
|
|
b, l, n, d = x.shape
|
|
x = x.reshape(b * l, n, d)
|
|
for block in self.layerwise_blocks:
|
|
x = block(x.contiguous())
|
|
x = rearrange(x, "(b l) n d -> b l d n", b=b, l=l)
|
|
x = self.projector(x)
|
|
x = x.squeeze(-1)
|
|
for block in self.refiner_blocks:
|
|
x = block(x, key_mask=key_mask, mask_meta=mask_meta)
|
|
return x
|
|
|
|
|
|
class SingleStreamBlock(nn.Module):
|
|
def __init__(
|
|
self,
|
|
features: int,
|
|
heads: int,
|
|
multiplier: int,
|
|
bias: bool = False,
|
|
kvheads: int = None,
|
|
):
|
|
super().__init__()
|
|
# (6, features) modulation table added to the timestep projection (AdaLN-single),
|
|
# stored directly on the block to match the released checkpoint.
|
|
self.scale_shift_table = nn.Parameter(torch.zeros(6, features))
|
|
self.norm1 = RMSNorm(features)
|
|
self.norm2 = RMSNorm(features)
|
|
self.attn = Attention(dim=features, heads=heads, bias=bias, kvheads=kvheads)
|
|
self.ff = SwiGLU(features, multiplier, bias)
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: Tensor,
|
|
vec: Tensor,
|
|
freqs: Tensor,
|
|
key_mask: Tensor | None = None,
|
|
mask_meta: dict | None = None,
|
|
num_replicated_prefix: int = 0,
|
|
) -> Tensor:
|
|
mod = vec + self.scale_shift_table.reshape(-1)
|
|
prescale, preshift, pregate, postscale, postshift, postgate = mod.chunk(
|
|
6, dim=-1
|
|
)
|
|
hidden_states = hidden_states + pregate * self.attn(
|
|
norm_scale_shift(
|
|
hidden_states,
|
|
self.norm1.weight + 1,
|
|
prescale,
|
|
preshift,
|
|
self.norm1.eps,
|
|
),
|
|
freqs,
|
|
key_mask,
|
|
mask_meta,
|
|
num_replicated_prefix=num_replicated_prefix,
|
|
)
|
|
hidden_states = hidden_states + postgate * self.ff(
|
|
norm_scale_shift(
|
|
hidden_states,
|
|
self.norm2.weight + 1,
|
|
postscale,
|
|
postshift,
|
|
self.norm2.eps,
|
|
)
|
|
)
|
|
return hidden_states
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Top-level model
|
|
# --------------------------------------------------------------------------- #
|
|
class Krea2Transformer2DModel(CachableDiT, LayerwiseOffloadableModuleMixin):
|
|
"""K2 single-stream MMDiT for the SGLang diffusion runtime.
|
|
|
|
Attribute names follow the released K2 checkpoint, so weights load with an
|
|
identity ``param_names_mapping``.
|
|
"""
|
|
|
|
_fsdp_shard_conditions = []
|
|
_compile_conditions = []
|
|
param_names_mapping = Krea2DitConfig().arch_config.param_names_mapping
|
|
reverse_param_names_mapping = {}
|
|
|
|
def __init__(
|
|
self,
|
|
config: Krea2DitConfig,
|
|
hf_config: dict[str, Any],
|
|
quant_config: Optional[Any] = None,
|
|
) -> None:
|
|
super().__init__(config=config, hf_config=hf_config)
|
|
ac = config.arch_config
|
|
self.arch_config = ac
|
|
|
|
self.hidden_size = ac.features
|
|
self.num_attention_heads = ac.heads
|
|
self.num_channels_latents = ac.channels
|
|
self.patch = ac.patch
|
|
self.channels = ac.channels
|
|
self.tdim = ac.tdim
|
|
|
|
head_dim = ac.features // ac.heads
|
|
axes = list(ac.axes_dims)
|
|
assert sum(axes) == head_dim, f"sum(axes)={sum(axes)}, head_dim={head_dim}"
|
|
assert all(a % 2 == 0 for a in axes), f"axes={axes}"
|
|
|
|
self.posemb = PositionalEncoding(ac.features, axes, theta=ac.theta, ntk=1.0)
|
|
self.img_in = nn.Linear(ac.channels * ac.patch**2, ac.features, bias=True)
|
|
self.transformer_blocks = nn.ModuleList(
|
|
[
|
|
SingleStreamBlock(
|
|
ac.features, ac.heads, ac.multiplier, ac.bias, ac.kvheads
|
|
)
|
|
for _ in range(ac.layers)
|
|
]
|
|
)
|
|
self.time_embed = TimeEmbed(ac.tdim, ac.features)
|
|
self.text_fusion = TextFusionTransformer(
|
|
ac.txtlayers,
|
|
ac.txtdim,
|
|
ac.txtheads,
|
|
ac.multiplier,
|
|
ac.bias,
|
|
ac.txtkvheads,
|
|
)
|
|
self.txt_in = TxtIn(ac.txtdim, ac.features)
|
|
self.final_layer = LastLayer(ac.features, ac.patch, ac.channels)
|
|
# GELU(tanh) is applied in the forward; the linear matches time_mod_proj.weight.
|
|
self.time_mod_proj = nn.Linear(ac.features, ac.features * 6)
|
|
self.seq_multiple_of = ac.seq_multiple_of
|
|
# The 28 single-stream blocks (the ~24GB bulk) are streamed layer-by-layer
|
|
# under --dit-layerwise-offload, keeping only a small working set resident.
|
|
self.layer_names = ["transformer_blocks"]
|
|
|
|
def _forward_impl(
|
|
self,
|
|
img: Tensor,
|
|
context: Tensor,
|
|
t: Tensor,
|
|
pos: Tensor,
|
|
mask: Tensor | None = None,
|
|
) -> Tensor:
|
|
img = self.img_in(img)
|
|
t = self.time_embed(temb(t, self.tdim, device=img.device, dtype=img.dtype))
|
|
tvec = self.time_mod_proj(F.gelu(t, approximate="tanh"))
|
|
|
|
# A single or same-prompt batch has no padding, so attention runs maskless
|
|
# (native-GQA flash). A ragged batch builds varlen metadata from the
|
|
# key mask and takes the FA varlen path instead.
|
|
txt_key = txt_meta = joint_key = joint_meta = None
|
|
if mask is not None and not bool(mask.all()):
|
|
txt_key = mask[:, : context.shape[1]]
|
|
txt_meta = build_varlen_mask_meta(txt_key)
|
|
joint_key = mask
|
|
joint_meta = build_varlen_mask_meta(mask)
|
|
|
|
context = self.text_fusion(context, key_mask=txt_key, mask_meta=txt_meta)
|
|
context = self.txt_in(context)
|
|
|
|
txtlen, imglen = context.shape[1], img.shape[1]
|
|
combined = torch.cat((context, img), dim=1)
|
|
freqs = self.posemb(pos)
|
|
|
|
# Under SP the image tokens are sharded across ranks while the text prefix
|
|
# stays replicated; keep the leading txtlen tokens out of the all-to-all.
|
|
num_replicated_prefix = txtlen if get_sp_world_size() > 1 else 0
|
|
for block in self.transformer_blocks:
|
|
combined = block(
|
|
combined,
|
|
tvec,
|
|
freqs,
|
|
joint_key,
|
|
joint_meta,
|
|
num_replicated_prefix=num_replicated_prefix,
|
|
)
|
|
|
|
final = self.final_layer(combined, t)
|
|
output = final[:, txtlen : txtlen + imglen, :]
|
|
return output
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: Tensor,
|
|
encoder_hidden_states: Tensor,
|
|
timestep: Tensor,
|
|
encoder_hidden_states_image=None,
|
|
guidance=None,
|
|
pos: Tensor = None,
|
|
mask: Tensor = None,
|
|
**kwargs,
|
|
) -> Tensor:
|
|
return self._forward_impl(
|
|
img=hidden_states,
|
|
context=encoder_hidden_states,
|
|
t=timestep,
|
|
pos=pos,
|
|
mask=mask,
|
|
)
|
|
|
|
|
|
EntryClass = [Krea2Transformer2DModel]
|