[Diffusion] Move diffusion time embedding to jit kernel (#16879)

This commit is contained in:
Xiaoyu Zhang
2026-01-17 12:21:22 +08:00
committed by GitHub
parent a7b5f75d88
commit 2cdd4370bc
10 changed files with 228 additions and 206 deletions

View File

@@ -0,0 +1,173 @@
#include <sgl_kernel/tensor.h>
#include <sgl_kernel/utils.h>
#include <sgl_kernel/utils.cuh>
#include <dlpack/dlpack.h>
#include <tvm/ffi/container/tensor.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#include <type_traits>
namespace {
template <typename T>
__device__ __forceinline__ float cast_to_float(T v) {
if constexpr (std::is_same_v<T, nv_bfloat16>) {
return __bfloat162float(v);
} else if constexpr (std::is_same_v<T, half>) {
return __half2float(v);
} else {
return static_cast<float>(v);
}
}
template <bool kFlipSinToCos, typename TIn>
__global__ void timestep_embedding_kernel(
const TIn* __restrict__ t_ptr,
float* __restrict__ output_ptr,
int dim,
float neg_log_max_period,
float scale,
int batch_size) {
int row_idx = static_cast<int>(blockIdx.x * blockDim.y + threadIdx.y);
if (row_idx >= batch_size) {
return;
}
float t_val = cast_to_float(t_ptr[row_idx]);
float* output_batch_base_ptr = output_ptr + row_idx * dim;
int half_dim = dim / 2;
int thread_offset = static_cast<int>(threadIdx.x);
while (thread_offset * 4 < half_dim) {
float4* top_half;
float4* bottom_half;
if constexpr (!kFlipSinToCos) {
bottom_half = reinterpret_cast<float4*>(output_batch_base_ptr + thread_offset * 4);
top_half = reinterpret_cast<float4*>(output_batch_base_ptr + half_dim + thread_offset * 4);
} else {
top_half = reinterpret_cast<float4*>(output_batch_base_ptr + thread_offset * 4);
bottom_half = reinterpret_cast<float4*>(output_batch_base_ptr + half_dim + thread_offset * 4);
}
float4 vals;
vals.x = scale * t_val * expf(neg_log_max_period * __int2float_rn(thread_offset * 4 + 0));
vals.y = scale * t_val * expf(neg_log_max_period * __int2float_rn(thread_offset * 4 + 1));
vals.z = scale * t_val * expf(neg_log_max_period * __int2float_rn(thread_offset * 4 + 2));
vals.w = scale * t_val * expf(neg_log_max_period * __int2float_rn(thread_offset * 4 + 3));
float4 sin_vals;
sin_vals.x = cosf(vals.x);
sin_vals.y = cosf(vals.y);
sin_vals.z = cosf(vals.z);
sin_vals.w = cosf(vals.w);
*top_half = sin_vals;
float4 cos_vals;
cos_vals.x = sinf(vals.x);
cos_vals.y = sinf(vals.y);
cos_vals.z = sinf(vals.z);
cos_vals.w = sinf(vals.w);
*bottom_half = cos_vals;
thread_offset += static_cast<int>(blockDim.x);
}
}
template <typename TIn>
inline void launch_timestep_embedding(
const tvm::ffi::TensorView t,
const tvm::ffi::TensorView output,
int dim,
bool flip_sin_to_cos,
float downscale_freq_shift,
float scale,
int max_period) {
using namespace host;
const int batch_size = static_cast<int>(t.shape()[0]);
const int half_dim = dim / 2;
constexpr int kMaxThreadsPerBlock = 1024;
constexpr int kMinThreadsPerBlock = 128;
const int num_threads_per_row = std::min(kMaxThreadsPerBlock, half_dim / 4);
const int num_rows = (kMinThreadsPerBlock + num_threads_per_row - 1) / num_threads_per_row;
dim3 grid((batch_size + num_rows - 1) / num_rows);
dim3 block(num_threads_per_row, num_rows);
const float neg_log_max_period =
std::log(static_cast<float>(max_period)) * (-1.0f) / (static_cast<float>(half_dim) - downscale_freq_shift);
const DLDevice device = output.device();
if (flip_sin_to_cos) {
LaunchKernel(grid, block, device)(
timestep_embedding_kernel<true, TIn>,
static_cast<const TIn*>(t.data_ptr()),
static_cast<float*>(output.data_ptr()),
dim,
neg_log_max_period,
scale,
batch_size);
} else {
LaunchKernel(grid, block, device)(
timestep_embedding_kernel<false, TIn>,
static_cast<const TIn*>(t.data_ptr()),
static_cast<float*>(output.data_ptr()),
dim,
neg_log_max_period,
scale,
batch_size);
}
}
void timestep_embedding(
tvm::ffi::TensorView input,
tvm::ffi::TensorView output,
int dim,
bool flip_sin_to_cos,
float downscale_freq_shift,
float scale,
int max_period) {
using namespace host;
auto B = SymbolicSize{"batch_size"};
auto D = SymbolicSize{"dim"};
auto device = SymbolicDevice{};
TensorMatcher({B}).with_strides({1}).template with_device<kDLCUDA>(device).verify(input);
TensorMatcher({B, D}).with_strides({D, 1}).with_dtype<float>().template with_device<kDLCUDA>(device).verify(output);
RuntimeCheck(D.unwrap() == dim, "Output dim mismatch: ", D.unwrap(), " vs ", dim);
RuntimeCheck(dim % 8 == 0, "dim must align to 8, got ", dim);
const DLDataType in_dtype = input.dtype();
const bool input_dtype_supported = (in_dtype.code == kDLFloat && in_dtype.bits == 16) ||
(in_dtype.code == kDLBfloat && in_dtype.bits == 16) ||
(in_dtype.code == kDLFloat && in_dtype.bits == 32);
RuntimeCheck(input_dtype_supported, "input dtype must be fp16/bf16/fp32, but got ", in_dtype);
auto launch = [&]<typename TIn>() {
launch_timestep_embedding<TIn>(input, output, dim, flip_sin_to_cos, downscale_freq_shift, scale, max_period);
};
if (in_dtype.code == kDLFloat && in_dtype.bits == 32) {
launch.template operator()<float>();
} else if (in_dtype.code == kDLBfloat && in_dtype.bits == 16) {
launch.template operator()<nv_bfloat16>();
} else if (in_dtype.code == kDLFloat && in_dtype.bits == 16) {
launch.template operator()<half>();
}
}
} // namespace

View File

@@ -0,0 +1,114 @@
import numpy as np
import pytest
import tabulate
import torch
from diffusers.models.embeddings import get_timestep_embedding
from sglang.jit_kernel.timestep_embedding import (
timestep_embedding as timestep_embedding_cuda,
)
from sglang.multimodal_gen.runtime.layers.visual_embedding import timestep_embedding
@pytest.mark.parametrize(
"batch_size", [1, 2, 8, 128, 256, 512, 1536, 2048, 4096, 11008, 16384]
)
@pytest.mark.parametrize("dim", [32, 128, 256, 512, 1536, 2048, 4096, 8192])
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32])
def test_timestep_embedding_correctness_with_sgld(batch_size, dim, dtype):
device = "cuda"
t = torch.randint(low=0, high=1000, size=(batch_size,), device=device).to(dtype)
torch_output = timestep_embedding(t, dim)
cuda_output = timestep_embedding_cuda(t, dim, flip_sin_to_cos=True)
torch.testing.assert_close(torch_output, cuda_output, atol=1e-3, rtol=1e-3)
@pytest.mark.parametrize("batch_size", [1, 2, 8, 128, 256, 512, 1536, 2048, 16384])
@pytest.mark.parametrize("dim", [32, 256, 512, 1536, 8192])
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32])
@pytest.mark.parametrize("flip_sin_to_cos", [False, True])
@pytest.mark.parametrize("downscale_freq_shift", [0, 1])
@pytest.mark.parametrize("scale", [1, 0.01])
def test_timestep_embedding_correctness_with_diffusers(
batch_size, dim, flip_sin_to_cos, downscale_freq_shift, scale, dtype
):
device = "cuda"
t = torch.randint(low=0, high=1000, size=(batch_size,), device=device).to(dtype)
torch_output = get_timestep_embedding(
t,
dim,
flip_sin_to_cos=flip_sin_to_cos,
downscale_freq_shift=downscale_freq_shift,
scale=scale,
max_period=10000,
)
cuda_output = timestep_embedding_cuda(
t,
dim,
flip_sin_to_cos=flip_sin_to_cos,
downscale_freq_shift=downscale_freq_shift,
scale=scale,
max_period=10000,
)
torch.testing.assert_close(torch_output, cuda_output, atol=1e-3, rtol=1e-3)
def test_timestep_embedding_perf():
NUM_BATCH = [1, 2, 8, 63, 256, 512, 613, 1024, 1536]
NUM_DIM = [32, 64, 128, 256, 512, 1024, 2048, 4096]
def perf_kernel_fn(kernel_fn: callable, *args, **kwargs):
warmup_times = 4
repeat_times = 20
start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)
for _ in range(warmup_times):
output_fn = kernel_fn(*args, **kwargs)
torch.cuda.synchronize()
start.record()
for _ in range(repeat_times):
output_fn = kernel_fn(*args, **kwargs)
end.record()
end.synchronize()
return start.elapsed_time(end) / repeat_times
device = "cuda"
results = []
cuda_speedups = []
for B in NUM_BATCH:
for dim in NUM_DIM:
t = torch.linspace(0, max(100000, B), steps=B, device=device).to(
torch.float32
)
time_torch = perf_kernel_fn(timestep_embedding, t, dim)
time_cuda = perf_kernel_fn(timestep_embedding_cuda, t, dim)
speedup_cuda = time_torch / time_cuda
results.append(
{
"Batch Size": B,
"Dimension": dim,
"Torch Time (ms)": time_torch,
"CUDA Time (ms)": time_cuda,
"Speedup (CUDA)": speedup_cuda,
}
)
cuda_speedups.append(speedup_cuda)
print("=== Timestep Embedding Benchmark Results ===")
print(
tabulate.tabulate(
results,
headers="keys",
tablefmt="fancy_grid",
floatfmt=(".0f", ".0f", ".6f", ".6f", ".5f"),
)
)
print(f"Average Speedup(cuda): {np.mean(cuda_speedups):.4f}")
if __name__ == "__main__":
pytest.main([__file__])

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
import functools
from typing import TYPE_CHECKING
import torch
from sglang.jit_kernel.utils import load_jit
if TYPE_CHECKING:
from tvm_ffi.module import Module
@functools.cache
def _jit_timestep_embedding_module() -> Module:
return load_jit(
"timestep_embedding",
cuda_files=["diffusion/timestep_embedding.cuh"],
cuda_wrappers=[("timestep_embedding", "timestep_embedding")],
)
def timestep_embedding(
t: torch.Tensor,
dim: int,
flip_sin_to_cos: bool = False,
downscale_freq_shift: float = 0.0,
scale: float = 1,
max_period: int = 10000,
dtype: torch.dtype = torch.float32,
) -> torch.Tensor:
dtype = torch.float32
output = torch.empty((t.shape[0], dim), dtype=dtype, device=t.device)
module = _jit_timestep_embedding_module()
module.timestep_embedding(
t,
output,
dim,
flip_sin_to_cos,
float(downscale_freq_shift),
float(scale),
int(max_period),
)
return output

View File

@@ -19,10 +19,12 @@ from diffusers.models.embeddings import (
)
try:
from sgl_kernel.elementwise import timestep_embedding as timestep_embedding_cuda
from sglang.jit_kernel.timestep_embedding import (
timestep_embedding as timestep_embedding_cuda,
)
except Exception as _e:
# Fallback to diffusers implementation so downstream code can still run
# even if `sgl_kernel` is not installed/available.
# even if `jit_kernel` is not available.
timestep_embedding_cuda = _get_timestep_embedding
from sglang.multimodal_gen.runtime.layers.activation import get_act_fn
@@ -86,14 +88,13 @@ class PatchEmbed(nn.Module):
class Timesteps(_Timesteps):
def forward(self, timesteps: torch.Tensor) -> torch.Tensor:
t_emb = timestep_embedding_cuda(
return timestep_embedding_cuda(
timesteps,
self.num_channels,
flip_sin_to_cos=self.flip_sin_to_cos,
downscale_freq_shift=self.downscale_freq_shift,
scale=self.scale,
)
return t_emb
class CombinedTimestepGuidanceTextProjEmbeddings(