[diffusion] perf: absorb Qwen-Image output projection biases (#37116)
This commit is contained in:
@@ -40,6 +40,7 @@ struct NormScaleShiftParams {
|
|||||||
void* y;
|
void* y;
|
||||||
void* res_out;
|
void* res_out;
|
||||||
const void* x;
|
const void* x;
|
||||||
|
const void* input_bias;
|
||||||
const void* residual;
|
const void* residual;
|
||||||
const void* gate;
|
const void* gate;
|
||||||
const void* scale;
|
const void* scale;
|
||||||
@@ -65,7 +66,7 @@ SGL_DEVICE float cta_reduce_sum(float v, int warp, int lane, float* scratch) {
|
|||||||
return scratch[kWarps];
|
return scratch[kWarps];
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool kHasResidual>
|
template <bool kHasResidual, bool kHasInputBias = false>
|
||||||
__global__ void norm_scale_shift_kernel(const NormScaleShiftParams __grid_constant__ params) {
|
__global__ void norm_scale_shift_kernel(const NormScaleShiftParams __grid_constant__ params) {
|
||||||
using namespace device;
|
using namespace device;
|
||||||
using Vec = AlignedVector<bf16_t, kVecElems>;
|
using Vec = AlignedVector<bf16_t, kVecElems>;
|
||||||
@@ -89,6 +90,16 @@ __global__ void norm_scale_shift_kernel(const NormScaleShiftParams __grid_consta
|
|||||||
v[i] = static_cast<float>(xv[i]);
|
v[i] = static_cast<float>(xv[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constexpr (kHasInputBias) {
|
||||||
|
Vec bv;
|
||||||
|
bv.load(static_cast<const bf16_t*>(params.input_bias) + elem_offset);
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < kVecElems; ++i) {
|
||||||
|
// Match the standalone BF16 output-projection bias addition.
|
||||||
|
v[i] = static_cast<float>(static_cast<bf16_t>(v[i] + static_cast<float>(bv[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (kHasResidual) {
|
if constexpr (kHasResidual) {
|
||||||
Vec gv;
|
Vec gv;
|
||||||
Vec rv;
|
Vec rv;
|
||||||
@@ -135,6 +146,31 @@ __global__ void norm_scale_shift_kernel(const NormScaleShiftParams __grid_consta
|
|||||||
yv.store(static_cast<bf16_t*>(params.y) + row_offset + elem_offset);
|
yv.store(static_cast<bf16_t*>(params.y) + row_offset + elem_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__global__ void bias_mul_add_kernel(const NormScaleShiftParams __grid_constant__ params) {
|
||||||
|
using namespace device;
|
||||||
|
using Vec = AlignedVector<bf16_t, kVecElems>;
|
||||||
|
|
||||||
|
const int row_offset = blockIdx.x * kHidden;
|
||||||
|
const int elem_offset = threadIdx.x * kVecElems;
|
||||||
|
|
||||||
|
Vec xv;
|
||||||
|
Vec bv;
|
||||||
|
Vec gv;
|
||||||
|
Vec rv;
|
||||||
|
Vec yv;
|
||||||
|
xv.load(static_cast<const bf16_t*>(params.x) + row_offset + elem_offset);
|
||||||
|
bv.load(static_cast<const bf16_t*>(params.input_bias) + elem_offset);
|
||||||
|
gv.load(static_cast<const bf16_t*>(params.gate) + elem_offset);
|
||||||
|
rv.load(static_cast<const bf16_t*>(params.residual) + row_offset + elem_offset);
|
||||||
|
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < kVecElems; ++i) {
|
||||||
|
const bf16_t biased = static_cast<bf16_t>(static_cast<float>(xv[i]) + static_cast<float>(bv[i]));
|
||||||
|
yv[i] = __hfma(biased, gv[i], rv[i]);
|
||||||
|
}
|
||||||
|
yv.store(static_cast<bf16_t*>(params.y) + row_offset + elem_offset);
|
||||||
|
}
|
||||||
|
|
||||||
inline uint32_t verify_nss_geometry(host::SymbolicSize& num_rows) {
|
inline uint32_t verify_nss_geometry(host::SymbolicSize& num_rows) {
|
||||||
using namespace host;
|
using namespace host;
|
||||||
RuntimeCheck(num_rows.unwrap() > 0, "num_rows must be positive");
|
RuntimeCheck(num_rows.unwrap() > 0, "num_rows must be positive");
|
||||||
@@ -162,6 +198,7 @@ struct NormScaleShiftKernel {
|
|||||||
.y = y.data_ptr(),
|
.y = y.data_ptr(),
|
||||||
.res_out = nullptr,
|
.res_out = nullptr,
|
||||||
.x = x.data_ptr(),
|
.x = x.data_ptr(),
|
||||||
|
.input_bias = nullptr,
|
||||||
.residual = nullptr,
|
.residual = nullptr,
|
||||||
.gate = nullptr,
|
.gate = nullptr,
|
||||||
.scale = scale.data_ptr(),
|
.scale = scale.data_ptr(),
|
||||||
@@ -201,6 +238,7 @@ struct ScaleResidualNormScaleShiftKernel {
|
|||||||
.y = y.data_ptr(),
|
.y = y.data_ptr(),
|
||||||
.res_out = res_out.data_ptr(),
|
.res_out = res_out.data_ptr(),
|
||||||
.x = x.data_ptr(),
|
.x = x.data_ptr(),
|
||||||
|
.input_bias = nullptr,
|
||||||
.residual = residual.data_ptr(),
|
.residual = residual.data_ptr(),
|
||||||
.gate = gate.data_ptr(),
|
.gate = gate.data_ptr(),
|
||||||
.scale = scale.data_ptr(),
|
.scale = scale.data_ptr(),
|
||||||
@@ -211,6 +249,84 @@ struct ScaleResidualNormScaleShiftKernel {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BiasScaleResidualNormScaleShiftKernel {
|
||||||
|
static void
|
||||||
|
run(tvm::ffi::TensorView y,
|
||||||
|
tvm::ffi::TensorView res_out,
|
||||||
|
tvm::ffi::TensorView residual,
|
||||||
|
tvm::ffi::TensorView x,
|
||||||
|
tvm::ffi::TensorView input_bias,
|
||||||
|
tvm::ffi::TensorView gate,
|
||||||
|
tvm::ffi::TensorView scale,
|
||||||
|
tvm::ffi::TensorView shift,
|
||||||
|
double eps) {
|
||||||
|
using namespace host;
|
||||||
|
auto N = SymbolicSize{"num_rows"};
|
||||||
|
auto device = SymbolicDevice{};
|
||||||
|
device.set_options<kDLCUDA>();
|
||||||
|
|
||||||
|
TensorMatcher({N, kHidden})
|
||||||
|
.with_dtype<bf16_t>()
|
||||||
|
.with_device(device)
|
||||||
|
.verify(x)
|
||||||
|
.verify(residual)
|
||||||
|
.verify(y)
|
||||||
|
.verify(res_out);
|
||||||
|
TensorMatcher({kHidden})
|
||||||
|
.with_dtype<bf16_t>()
|
||||||
|
.with_device(device)
|
||||||
|
.verify(input_bias)
|
||||||
|
.verify(gate)
|
||||||
|
.verify(scale)
|
||||||
|
.verify(shift);
|
||||||
|
|
||||||
|
const uint32_t grid = verify_nss_geometry(N);
|
||||||
|
const auto params = NormScaleShiftParams{
|
||||||
|
.y = y.data_ptr(),
|
||||||
|
.res_out = res_out.data_ptr(),
|
||||||
|
.x = x.data_ptr(),
|
||||||
|
.input_bias = input_bias.data_ptr(),
|
||||||
|
.residual = residual.data_ptr(),
|
||||||
|
.gate = gate.data_ptr(),
|
||||||
|
.scale = scale.data_ptr(),
|
||||||
|
.shift = shift.data_ptr(),
|
||||||
|
.eps = static_cast<float>(eps),
|
||||||
|
};
|
||||||
|
LaunchKernel(grid, kThreads, device.unwrap())(norm_scale_shift_kernel<true, true>, params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BiasMulAddKernel {
|
||||||
|
static void
|
||||||
|
run(tvm::ffi::TensorView y,
|
||||||
|
tvm::ffi::TensorView x,
|
||||||
|
tvm::ffi::TensorView input_bias,
|
||||||
|
tvm::ffi::TensorView gate,
|
||||||
|
tvm::ffi::TensorView residual) {
|
||||||
|
using namespace host;
|
||||||
|
auto N = SymbolicSize{"num_rows"};
|
||||||
|
auto device = SymbolicDevice{};
|
||||||
|
device.set_options<kDLCUDA>();
|
||||||
|
|
||||||
|
TensorMatcher({N, kHidden}).with_dtype<bf16_t>().with_device(device).verify(x).verify(residual).verify(y);
|
||||||
|
TensorMatcher({kHidden}).with_dtype<bf16_t>().with_device(device).verify(input_bias).verify(gate);
|
||||||
|
|
||||||
|
const uint32_t grid = verify_nss_geometry(N);
|
||||||
|
const auto params = NormScaleShiftParams{
|
||||||
|
.y = y.data_ptr(),
|
||||||
|
.res_out = nullptr,
|
||||||
|
.x = x.data_ptr(),
|
||||||
|
.input_bias = input_bias.data_ptr(),
|
||||||
|
.residual = residual.data_ptr(),
|
||||||
|
.gate = gate.data_ptr(),
|
||||||
|
.scale = nullptr,
|
||||||
|
.shift = nullptr,
|
||||||
|
.eps = 0.0f,
|
||||||
|
};
|
||||||
|
LaunchKernel(grid, kThreads, device.unwrap())(bias_mul_add_kernel, params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace norm_scale_shift
|
} // namespace norm_scale_shift
|
||||||
|
|
||||||
} // namespace sglang
|
} // namespace sglang
|
||||||
|
|||||||
@@ -373,6 +373,8 @@ _EXPORTS: dict[str, str] = {
|
|||||||
"rmsnorm_tanh_residual": "norm.native_bf16_rmsnorm_triton",
|
"rmsnorm_tanh_residual": "norm.native_bf16_rmsnorm_triton",
|
||||||
"norm_infer": "norm.norm_triton",
|
"norm_infer": "norm.norm_triton",
|
||||||
"rms_norm_fn": "norm.norm_triton",
|
"rms_norm_fn": "norm.norm_triton",
|
||||||
|
"try_fused_bias_mul_add": "norm.norm_scale_shift_jit",
|
||||||
|
"try_fused_bias_scale_residual_norm_scale_shift": "norm.norm_scale_shift_jit",
|
||||||
"triton_one_pass_rms_norm": "norm.rmsnorm_onepass_triton",
|
"triton_one_pass_rms_norm": "norm.rmsnorm_onepass_triton",
|
||||||
"can_use_fused_rmsnorm_scale_shift": "norm.rmsnorm_scale_shift_bitexact",
|
"can_use_fused_rmsnorm_scale_shift": "norm.rmsnorm_scale_shift_bitexact",
|
||||||
"can_use_fused_scale_residual_rmsnorm_scale_shift": "norm.rmsnorm_scale_shift_bitexact",
|
"can_use_fused_scale_residual_rmsnorm_scale_shift": "norm.rmsnorm_scale_shift_bitexact",
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ def _blackwell_or_newer(device: torch.device) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _sm103(device: torch.device) -> bool:
|
||||||
|
return torch.cuda.is_available() and torch.cuda.get_device_capability(device) == (
|
||||||
|
10,
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _nss_activation(t, like=None) -> bool:
|
def _nss_activation(t, like=None) -> bool:
|
||||||
return (
|
return (
|
||||||
isinstance(t, torch.Tensor)
|
isinstance(t, torch.Tensor)
|
||||||
@@ -72,6 +79,14 @@ def norm_scale_shift_module() -> Module:
|
|||||||
"srnss_bf16_row",
|
"srnss_bf16_row",
|
||||||
"norm_scale_shift::ScaleResidualNormScaleShiftKernel::run",
|
"norm_scale_shift::ScaleResidualNormScaleShiftKernel::run",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"bias_srnss_bf16_row",
|
||||||
|
"norm_scale_shift::BiasScaleResidualNormScaleShiftKernel::run",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bias_mul_add_bf16_row",
|
||||||
|
"norm_scale_shift::BiasMulAddKernel::run",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,3 +143,58 @@ def try_fused_scale_residual_norm_scale_shift(
|
|||||||
float(eps),
|
float(eps),
|
||||||
)
|
)
|
||||||
return y, residual_out
|
return y, residual_out
|
||||||
|
|
||||||
|
|
||||||
|
def try_fused_bias_scale_residual_norm_scale_shift(
|
||||||
|
residual, x, input_bias, gate, weight, bias, scale, shift, norm_type, eps
|
||||||
|
):
|
||||||
|
if torch.compiler.is_compiling():
|
||||||
|
return None
|
||||||
|
if norm_type != "layer" or weight is not None or bias is not None:
|
||||||
|
return None
|
||||||
|
if not (_nss_activation(x) and _nss_activation(residual, x) and _sm103(x.device)):
|
||||||
|
return None
|
||||||
|
|
||||||
|
input_bias = _row_bf16(input_bias, x.device)
|
||||||
|
gate = _row_bf16(gate, x.device)
|
||||||
|
scale = _row_bf16(scale, x.device)
|
||||||
|
shift = _row_bf16(shift, x.device)
|
||||||
|
if any(tensor is None for tensor in (input_bias, gate, scale, shift)):
|
||||||
|
return None
|
||||||
|
|
||||||
|
y = torch.empty_like(x)
|
||||||
|
residual_out = torch.empty_like(x)
|
||||||
|
_module().bias_srnss_bf16_row(
|
||||||
|
y.view(-1, _HIDDEN),
|
||||||
|
residual_out.view(-1, _HIDDEN),
|
||||||
|
residual.view(-1, _HIDDEN),
|
||||||
|
x.view(-1, _HIDDEN),
|
||||||
|
input_bias,
|
||||||
|
gate,
|
||||||
|
scale,
|
||||||
|
shift,
|
||||||
|
float(eps),
|
||||||
|
)
|
||||||
|
return y, residual_out
|
||||||
|
|
||||||
|
|
||||||
|
def try_fused_bias_mul_add(x, input_bias, gate, residual):
|
||||||
|
if torch.compiler.is_compiling():
|
||||||
|
return None
|
||||||
|
if not (_nss_activation(x) and _nss_activation(residual, x) and _sm103(x.device)):
|
||||||
|
return None
|
||||||
|
|
||||||
|
input_bias = _row_bf16(input_bias, x.device)
|
||||||
|
gate = _row_bf16(gate, x.device)
|
||||||
|
if input_bias is None or gate is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
y = torch.empty_like(x)
|
||||||
|
_module().bias_mul_add_bf16_row(
|
||||||
|
y.view(-1, _HIDDEN),
|
||||||
|
x.view(-1, _HIDDEN),
|
||||||
|
input_bias,
|
||||||
|
gate,
|
||||||
|
residual.view(-1, _HIDDEN),
|
||||||
|
)
|
||||||
|
return y
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ from sglang.kernels.ops.diffusion import (
|
|||||||
fused_gelu_active,
|
fused_gelu_active,
|
||||||
fused_linear_gelu_tanh,
|
fused_linear_gelu_tanh,
|
||||||
mark_fused_gelu_site,
|
mark_fused_gelu_site,
|
||||||
|
try_fused_bias_mul_add,
|
||||||
|
try_fused_bias_scale_residual_norm_scale_shift,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.configs.models.dits.qwenimage import QwenImageDitConfig
|
from sglang.multimodal_gen.configs.models.dits.qwenimage import QwenImageDitConfig
|
||||||
from sglang.multimodal_gen.configs.models.fsdp import is_transformer_block
|
from sglang.multimodal_gen.configs.models.fsdp import is_transformer_block
|
||||||
@@ -76,7 +78,10 @@ from sglang.multimodal_gen.runtime.managers.memory_managers.layerwise_offload im
|
|||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
|
||||||
from sglang.multimodal_gen.runtime.models.dits.common import get_qkv_projections
|
from sglang.multimodal_gen.runtime.models.dits.common import get_qkv_projections
|
||||||
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
|
from sglang.multimodal_gen.runtime.platforms import (
|
||||||
|
AttentionBackendEnum,
|
||||||
|
current_platform,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||||
from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph import (
|
from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph import (
|
||||||
is_in_breakable_cuda_graph,
|
is_in_breakable_cuda_graph,
|
||||||
@@ -110,6 +115,27 @@ def _local_seq_len(seq_len: int, sp_world_size: int) -> int:
|
|||||||
_get_qkv_projections = get_qkv_projections
|
_get_qkv_projections = get_qkv_projections
|
||||||
|
|
||||||
|
|
||||||
|
def _can_defer_modelopt_output_bias(
|
||||||
|
quant_config: Optional[QuantizationConfig], capability: Any
|
||||||
|
) -> bool:
|
||||||
|
# Absorbing the bias moves a BF16 rounding point. The resulting image
|
||||||
|
# quality has only been validated on SM103, so other GPUs keep the GEMM
|
||||||
|
# bias epilogue used before this optimization.
|
||||||
|
return (
|
||||||
|
quant_config is not None
|
||||||
|
and hasattr(quant_config, "get_name")
|
||||||
|
and quant_config.get_name() in {"modelopt_fp8", "modelopt_fp4"}
|
||||||
|
and capability is not None
|
||||||
|
and (capability.major, capability.minor) == (10, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _defer_modelopt_output_bias(quant_config: Optional[QuantizationConfig]) -> bool:
|
||||||
|
return _can_defer_modelopt_output_bias(
|
||||||
|
quant_config, current_platform.get_device_capability()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _safe_tensor_version(tensor: torch.Tensor) -> Optional[int]:
|
def _safe_tensor_version(tensor: torch.Tensor) -> Optional[int]:
|
||||||
"""Read a tensor version counter without rejecting inference tensors."""
|
"""Read a tensor version counter without rejecting inference tensors."""
|
||||||
return None if tensor.is_inference() else tensor._version
|
return None if tensor.is_inference() else tensor._version
|
||||||
@@ -579,6 +605,7 @@ class QwenImageCrossAttention(nn.Module):
|
|||||||
self.parallel_attention = parallel_attention
|
self.parallel_attention = parallel_attention
|
||||||
self.added_kv_proj_dim = added_kv_proj_dim
|
self.added_kv_proj_dim = added_kv_proj_dim
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
|
self.defer_output_bias = _defer_modelopt_output_bias(quant_config)
|
||||||
|
|
||||||
self.use_fused_qkv = isinstance(quant_config, NunchakuConfig)
|
self.use_fused_qkv = isinstance(quant_config, NunchakuConfig)
|
||||||
|
|
||||||
@@ -672,6 +699,7 @@ class QwenImageCrossAttention(nn.Module):
|
|||||||
self.dim,
|
self.dim,
|
||||||
bias=out_bias,
|
bias=out_bias,
|
||||||
input_is_parallel=True,
|
input_is_parallel=True,
|
||||||
|
skip_bias_add=self.defer_output_bias,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=f"{prefix}.to_add_out",
|
prefix=f"{prefix}.to_add_out",
|
||||||
)
|
)
|
||||||
@@ -686,6 +714,7 @@ class QwenImageCrossAttention(nn.Module):
|
|||||||
self.dim,
|
self.dim,
|
||||||
bias=out_bias,
|
bias=out_bias,
|
||||||
input_is_parallel=True,
|
input_is_parallel=True,
|
||||||
|
skip_bias_add=self.defer_output_bias,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=f"{prefix}.to_out.0",
|
prefix=f"{prefix}.to_out.0",
|
||||||
)
|
)
|
||||||
@@ -857,13 +886,13 @@ class QwenImageCrossAttention(nn.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Apply output projections
|
# Apply output projections
|
||||||
img_attn_output, _ = self.to_out[0](img_attn_output)
|
img_attn_output, img_attn_bias = self.to_out[0](img_attn_output)
|
||||||
if len(self.to_out) > 1:
|
if len(self.to_out) > 1:
|
||||||
(img_attn_output,) = self.to_out[1](img_attn_output) # dropout
|
(img_attn_output,) = self.to_out[1](img_attn_output) # dropout
|
||||||
|
|
||||||
txt_attn_output, _ = self.to_add_out(txt_attn_output)
|
txt_attn_output, txt_attn_bias = self.to_add_out(txt_attn_output)
|
||||||
|
|
||||||
return img_attn_output, txt_attn_output
|
return img_attn_output, txt_attn_output, img_attn_bias, txt_attn_bias
|
||||||
|
|
||||||
|
|
||||||
class QwenImageGELU(nn.Module):
|
class QwenImageGELU(nn.Module):
|
||||||
@@ -918,6 +947,7 @@ class QwenImageFeedForward(nn.Module):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
inner_dim = dim * mult
|
inner_dim = dim * mult
|
||||||
|
self.defer_output_bias = _defer_modelopt_output_bias(quant_config)
|
||||||
if replicated:
|
if replicated:
|
||||||
# Keep the whole FFN resident on every rank: no per-block
|
# Keep the whole FFN resident on every rank: no per-block
|
||||||
# all-reduce. Only worth it when the branch's token count is small
|
# all-reduce. Only worth it when the branch's token count is small
|
||||||
@@ -926,6 +956,7 @@ class QwenImageFeedForward(nn.Module):
|
|||||||
inner_dim,
|
inner_dim,
|
||||||
dim_out,
|
dim_out,
|
||||||
bias=True,
|
bias=True,
|
||||||
|
skip_bias_add=self.defer_output_bias,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=f"{prefix}.net.2",
|
prefix=f"{prefix}.net.2",
|
||||||
)
|
)
|
||||||
@@ -935,6 +966,7 @@ class QwenImageFeedForward(nn.Module):
|
|||||||
dim_out,
|
dim_out,
|
||||||
bias=True,
|
bias=True,
|
||||||
input_is_parallel=True,
|
input_is_parallel=True,
|
||||||
|
skip_bias_add=self.defer_output_bias,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=f"{prefix}.net.2",
|
prefix=f"{prefix}.net.2",
|
||||||
)
|
)
|
||||||
@@ -952,11 +984,16 @@ class QwenImageFeedForward(nn.Module):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
def forward_with_bias(
|
||||||
|
self, hidden_states: torch.Tensor
|
||||||
|
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
||||||
hidden_states = self.net[0](hidden_states)
|
hidden_states = self.net[0](hidden_states)
|
||||||
hidden_states = self.net[1](hidden_states)
|
hidden_states = self.net[1](hidden_states)
|
||||||
hidden_states, _ = self.net[2](hidden_states)
|
return self.net[2](hidden_states)
|
||||||
return hidden_states
|
|
||||||
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
||||||
|
hidden_states, bias = self.forward_with_bias(hidden_states)
|
||||||
|
return hidden_states if bias is None else hidden_states + bias
|
||||||
|
|
||||||
|
|
||||||
class QwenImageTransformerBlock(nn.Module):
|
class QwenImageTransformerBlock(nn.Module):
|
||||||
@@ -1120,6 +1157,25 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
return self.fuse_mul_add(a, b, c, k)
|
return self.fuse_mul_add(a, b, c, k)
|
||||||
|
|
||||||
|
def _bias_mul_add(
|
||||||
|
self,
|
||||||
|
a: torch.Tensor,
|
||||||
|
bias: Optional[torch.Tensor],
|
||||||
|
b: torch.Tensor,
|
||||||
|
c: torch.Tensor,
|
||||||
|
*,
|
||||||
|
use_bcg_helpers: bool,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
if bias is not None and not use_bcg_helpers:
|
||||||
|
fused = try_fused_bias_mul_add(a, bias, b, c)
|
||||||
|
if fused is not None:
|
||||||
|
return fused
|
||||||
|
if bias is not None:
|
||||||
|
a = a + bias
|
||||||
|
if use_bcg_helpers:
|
||||||
|
return self._mul_add(a, b, c)
|
||||||
|
return self.fuse_mul_add(a, b, c)
|
||||||
|
|
||||||
def _get_modulation_params(
|
def _get_modulation_params(
|
||||||
self,
|
self,
|
||||||
temb_img_silu: torch.Tensor,
|
temb_img_silu: torch.Tensor,
|
||||||
@@ -1152,6 +1208,7 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
index: Optional[torch.Tensor] = None,
|
index: Optional[torch.Tensor] = None,
|
||||||
gate_x: Optional[torch.Tensor] = None,
|
gate_x: Optional[torch.Tensor] = None,
|
||||||
residual_x: Optional[torch.Tensor] = None,
|
residual_x: Optional[torch.Tensor] = None,
|
||||||
|
x_bias: Optional[torch.Tensor] = None,
|
||||||
use_bcg_helpers: bool = False,
|
use_bcg_helpers: bool = False,
|
||||||
) -> Union[
|
) -> Union[
|
||||||
Tuple[torch.Tensor, torch.Tensor],
|
Tuple[torch.Tensor, torch.Tensor],
|
||||||
@@ -1164,6 +1221,8 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
|
|
||||||
shift, scale, gate = mod_params.chunk(3, dim=-1)
|
shift, scale, gate = mod_params.chunk(3, dim=-1)
|
||||||
if index is not None:
|
if index is not None:
|
||||||
|
if x_bias is not None:
|
||||||
|
x = x + x_bias
|
||||||
actual_batch = x.shape[0]
|
actual_batch = x.shape[0]
|
||||||
shift0, shift1 = (
|
shift0, shift1 = (
|
||||||
shift[:actual_batch],
|
shift[:actual_batch],
|
||||||
@@ -1214,6 +1273,24 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
scale_result = scale.unsqueeze(1)
|
scale_result = scale.unsqueeze(1)
|
||||||
gate_result = gate.unsqueeze(1)
|
gate_result = gate.unsqueeze(1)
|
||||||
if is_scale_residual:
|
if is_scale_residual:
|
||||||
|
if x_bias is not None and not use_bcg_helpers:
|
||||||
|
fused = try_fused_bias_scale_residual_norm_scale_shift(
|
||||||
|
residual_x,
|
||||||
|
x,
|
||||||
|
x_bias,
|
||||||
|
gate_x,
|
||||||
|
getattr(norm_module.norm, "weight", None),
|
||||||
|
getattr(norm_module.norm, "bias", None),
|
||||||
|
scale_result,
|
||||||
|
shift_result,
|
||||||
|
norm_module.norm_type,
|
||||||
|
norm_module.eps,
|
||||||
|
)
|
||||||
|
if fused is not None:
|
||||||
|
modulated, residual_out = fused
|
||||||
|
return modulated, residual_out, gate_result
|
||||||
|
if x_bias is not None:
|
||||||
|
x = x + x_bias
|
||||||
if use_bcg_helpers:
|
if use_bcg_helpers:
|
||||||
modulated, residual_out = self._scale_residual_norm_scale_shift(
|
modulated, residual_out = self._scale_residual_norm_scale_shift(
|
||||||
norm_module,
|
norm_module,
|
||||||
@@ -1324,7 +1401,12 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# QwenAttnProcessor2_0 returns (img_output, txt_output) when encoder_hidden_states is provided
|
# QwenAttnProcessor2_0 returns (img_output, txt_output) when encoder_hidden_states is provided
|
||||||
img_attn_output, txt_attn_output = attn_output
|
(
|
||||||
|
img_attn_output,
|
||||||
|
txt_attn_output,
|
||||||
|
img_attn_bias,
|
||||||
|
txt_attn_bias,
|
||||||
|
) = attn_output
|
||||||
# Process image stream - norm2 + MLP
|
# Process image stream - norm2 + MLP
|
||||||
img_modulated2, hidden_states, img_gate2 = self._modulate(
|
img_modulated2, hidden_states, img_gate2 = self._modulate(
|
||||||
img_attn_output,
|
img_attn_output,
|
||||||
@@ -1333,20 +1415,32 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
modulate_index,
|
modulate_index,
|
||||||
gate_x=img_gate1,
|
gate_x=img_gate1,
|
||||||
residual_x=hidden_states,
|
residual_x=hidden_states,
|
||||||
|
x_bias=img_attn_bias,
|
||||||
use_bcg_helpers=use_bcg_helpers,
|
use_bcg_helpers=use_bcg_helpers,
|
||||||
)
|
)
|
||||||
|
if isinstance(self.img_mlp, QwenImageFeedForward):
|
||||||
|
img_mlp_output, img_mlp_bias = self.img_mlp.forward_with_bias(
|
||||||
|
img_modulated2
|
||||||
|
)
|
||||||
|
else:
|
||||||
img_mlp_output = self.img_mlp(img_modulated2)
|
img_mlp_output = self.img_mlp(img_modulated2)
|
||||||
|
img_mlp_bias = None
|
||||||
|
|
||||||
if img_mlp_output.dim() == 2:
|
if img_mlp_output.dim() == 2:
|
||||||
img_mlp_output = img_mlp_output.unsqueeze(0)
|
img_mlp_output = img_mlp_output.unsqueeze(0)
|
||||||
if use_bcg_helpers:
|
hidden_states = self._bias_mul_add(
|
||||||
hidden_states = self._mul_add(img_mlp_output, img_gate2, hidden_states)
|
img_mlp_output,
|
||||||
else:
|
img_mlp_bias,
|
||||||
hidden_states = self.fuse_mul_add(img_mlp_output, img_gate2, hidden_states)
|
img_gate2,
|
||||||
|
hidden_states,
|
||||||
|
use_bcg_helpers=use_bcg_helpers,
|
||||||
|
)
|
||||||
|
|
||||||
# Process text stream - norm2 + MLP
|
# Process text stream - norm2 + MLP
|
||||||
txt_shift2, txt_scale2, txt_gate2_raw = txt_mod2.chunk(3, dim=-1)
|
txt_shift2, txt_scale2, txt_gate2_raw = txt_mod2.chunk(3, dim=-1)
|
||||||
if use_bcg_helpers:
|
if use_bcg_helpers:
|
||||||
|
if txt_attn_bias is not None:
|
||||||
|
txt_attn_output = txt_attn_output + txt_attn_bias
|
||||||
(
|
(
|
||||||
txt_modulated2,
|
txt_modulated2,
|
||||||
encoder_hidden_states,
|
encoder_hidden_states,
|
||||||
@@ -1358,6 +1452,15 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
shift=txt_shift2,
|
shift=txt_shift2,
|
||||||
scale=txt_scale2,
|
scale=txt_scale2,
|
||||||
)
|
)
|
||||||
|
elif txt_attn_bias is not None:
|
||||||
|
txt_modulated2, encoder_hidden_states, _ = self._modulate(
|
||||||
|
txt_attn_output,
|
||||||
|
txt_mod2,
|
||||||
|
self.txt_norm2,
|
||||||
|
gate_x=txt_gate1,
|
||||||
|
residual_x=encoder_hidden_states,
|
||||||
|
x_bias=txt_attn_bias,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
txt_modulated2, encoder_hidden_states = self.txt_norm2(
|
txt_modulated2, encoder_hidden_states = self.txt_norm2(
|
||||||
residual=encoder_hidden_states,
|
residual=encoder_hidden_states,
|
||||||
@@ -1367,17 +1470,22 @@ class QwenImageTransformerBlock(nn.Module):
|
|||||||
scale=txt_scale2,
|
scale=txt_scale2,
|
||||||
)
|
)
|
||||||
txt_gate2 = txt_gate2_raw.unsqueeze(1)
|
txt_gate2 = txt_gate2_raw.unsqueeze(1)
|
||||||
|
if isinstance(self.txt_mlp, QwenImageFeedForward):
|
||||||
|
txt_mlp_output, txt_mlp_bias = self.txt_mlp.forward_with_bias(
|
||||||
|
txt_modulated2
|
||||||
|
)
|
||||||
|
else:
|
||||||
txt_mlp_output = self.txt_mlp(txt_modulated2)
|
txt_mlp_output = self.txt_mlp(txt_modulated2)
|
||||||
|
txt_mlp_bias = None
|
||||||
|
|
||||||
if txt_mlp_output.dim() == 2:
|
if txt_mlp_output.dim() == 2:
|
||||||
txt_mlp_output = txt_mlp_output.unsqueeze(0)
|
txt_mlp_output = txt_mlp_output.unsqueeze(0)
|
||||||
if use_bcg_helpers:
|
encoder_hidden_states = self._bias_mul_add(
|
||||||
encoder_hidden_states = self._mul_add(
|
txt_mlp_output,
|
||||||
txt_mlp_output, txt_gate2, encoder_hidden_states
|
txt_mlp_bias,
|
||||||
)
|
txt_gate2,
|
||||||
else:
|
encoder_hidden_states,
|
||||||
encoder_hidden_states = self.fuse_mul_add(
|
use_bcg_helpers=use_bcg_helpers,
|
||||||
txt_mlp_output, txt_gate2, encoder_hidden_states
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clip to prevent overflow for fp16
|
# Clip to prevent overflow for fp16
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
import sglang.multimodal_gen.runtime.models.dits.qwen_image as qwen_image
|
||||||
|
from sglang.kernels.ops.diffusion import (
|
||||||
|
try_fused_bias_mul_add,
|
||||||
|
try_fused_bias_scale_residual_norm_scale_shift,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.layers.elementwise import MulAdd
|
||||||
|
from sglang.multimodal_gen.runtime.layers.layernorm import (
|
||||||
|
ScaleResidualLayerNormScaleShift,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.platforms.interface import DeviceCapability
|
||||||
|
from sglang.test.ci.ci_register import register_cuda_ci
|
||||||
|
|
||||||
|
register_cuda_ci(est_time=15, stage="base-b-kernel-unit", runner_config="4-gpu-b200")
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
|
||||||
|
|
||||||
|
requires_sm103 = pytest.mark.skipif(
|
||||||
|
not torch.cuda.is_available() or torch.cuda.get_device_capability() != (10, 3),
|
||||||
|
reason="Qwen-Image output-bias fusion is validated on SM103",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _seed_cuda():
|
||||||
|
torch.cuda.manual_seed(0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("quant_name", ["modelopt_fp8", "modelopt_fp4"])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"capability,expected",
|
||||||
|
[
|
||||||
|
(DeviceCapability(10, 0), False),
|
||||||
|
(DeviceCapability(10, 3), True),
|
||||||
|
(DeviceCapability(12, 0), False),
|
||||||
|
(None, False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_qwen_output_bias_absorption_is_sm103_only(quant_name, capability, expected):
|
||||||
|
class _QuantConfig:
|
||||||
|
def get_name(self):
|
||||||
|
return quant_name
|
||||||
|
|
||||||
|
assert (
|
||||||
|
qwen_image._can_defer_modelopt_output_bias(_QuantConfig(), capability)
|
||||||
|
is expected
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@requires_sm103
|
||||||
|
def test_qwen_output_bias_absorption_is_bit_exact():
|
||||||
|
hidden = 3072
|
||||||
|
shape = (1, 64, hidden)
|
||||||
|
x = torch.randn(shape, device="cuda", dtype=torch.bfloat16)
|
||||||
|
residual = torch.randn_like(x)
|
||||||
|
bias = torch.randn(hidden, device="cuda", dtype=torch.bfloat16)
|
||||||
|
gate = torch.randn(1, 1, hidden, device="cuda", dtype=torch.bfloat16)
|
||||||
|
scale = torch.randn_like(gate)
|
||||||
|
shift = torch.randn_like(gate)
|
||||||
|
norm = ScaleResidualLayerNormScaleShift(
|
||||||
|
hidden, eps=1e-6, elementwise_affine=False
|
||||||
|
).cuda()
|
||||||
|
|
||||||
|
expected_norm, expected_residual = norm(
|
||||||
|
residual=residual,
|
||||||
|
x=x + bias,
|
||||||
|
gate=gate,
|
||||||
|
scale=scale,
|
||||||
|
shift=shift,
|
||||||
|
)
|
||||||
|
actual_norm, actual_residual = try_fused_bias_scale_residual_norm_scale_shift(
|
||||||
|
residual,
|
||||||
|
x,
|
||||||
|
bias,
|
||||||
|
gate,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
scale,
|
||||||
|
shift,
|
||||||
|
"layer",
|
||||||
|
1e-6,
|
||||||
|
)
|
||||||
|
expected_final = MulAdd().cuda()(x + bias, gate, residual)
|
||||||
|
actual_final = try_fused_bias_mul_add(x, bias, gate, residual)
|
||||||
|
|
||||||
|
assert torch.equal(actual_norm, expected_norm)
|
||||||
|
assert torch.equal(actual_residual, expected_residual)
|
||||||
|
assert torch.equal(actual_final, expected_final)
|
||||||
|
|
||||||
|
# A small residual must still break a BF16 product-rounding tie. Casting
|
||||||
|
# through FP32 before the final BF16 store loses this information at large
|
||||||
|
# magnitudes, so keep this case as a guard for the native BF16 FMA.
|
||||||
|
x.fill_(-24576.0)
|
||||||
|
bias.fill_(-0.01055908203125)
|
||||||
|
gate.fill_(206.0)
|
||||||
|
residual.fill_(-0.2080078125)
|
||||||
|
expected_tie = MulAdd().cuda()(x + bias, gate, residual)
|
||||||
|
actual_tie = try_fused_bias_mul_add(x, bias, gate, residual)
|
||||||
|
assert torch.equal(actual_tie, expected_tie)
|
||||||
|
|
||||||
|
|
||||||
|
def test_qwen_output_bias_absorption_rejects_unsupported_inputs():
|
||||||
|
hidden = 3072
|
||||||
|
x = torch.randn(2, 17, hidden, device="cuda", dtype=torch.bfloat16)
|
||||||
|
residual = torch.randn_like(x)
|
||||||
|
row = torch.randn(hidden, device="cuda", dtype=torch.bfloat16)
|
||||||
|
assert try_fused_bias_mul_add(x, row, row, residual) is None
|
||||||
|
|
||||||
|
x = x[:1].contiguous()
|
||||||
|
residual = residual[:1].contiguous()
|
||||||
|
if torch.cuda.get_device_capability() != (10, 3):
|
||||||
|
assert try_fused_bias_mul_add(x, row, row, residual) is None
|
||||||
|
with patch("torch.compiler.is_compiling", return_value=True):
|
||||||
|
assert try_fused_bias_mul_add(x, row, row, residual) is None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(pytest.main([__file__, "-v"]))
|
||||||
Reference in New Issue
Block a user