[Perf] RMSNorm uncontiguous support, 1.2~3.1x kernel performance improvement (#49750)
Signed-off-by: yewentao256 <zhyanwentao@126.com> Signed-off-by: Wentao Ye <44945378+yewentao256@users.noreply.github.com>
Wentao Ye committed
Jul 29, 2026 at 16:45 UTC
82642d7d6c332fefdec06b260a246bd1a4ebf5ff
9 files changed
+31
-29
csrc/libtorch_stable/layernorm_kernels.cu
+15
-11
@@ -110,7 +110,8 @@ fused_add_rms_norm_kernel(
110
const int64_t input_stride,
111
scalar_t* __restrict__ residual, // [..., hidden_size]
112
const scalar_t* __restrict__ weight, // [hidden_size], null if !HasWeight
113
- const float epsilon, const int num_tokens, const int hidden_size) {
113
+ const float epsilon, const int num_tokens, const int hidden_size,
114
+ const int64_t residual_stride) {
115
// Sanity checks on our vector struct and type-punned pointer arithmetic
116
static_assert(std::is_pod_v<_f16Vec<scalar_t, width>>);
117
static_assert(sizeof(_f16Vec<scalar_t, width>) == sizeof(scalar_t) * width);
@@ -130,7 +131,7 @@ fused_add_rms_norm_kernel(
131
reinterpret_cast<const _f16Vec<scalar_t, width>*>(weight);
132
133
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
133
- int id = blockIdx.x * vec_hidden_size + idx;
134
+ int64_t id = blockIdx.x * residual_stride / width + idx;
135
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
136
_f16Vec<scalar_t, width> temp = input_v[strided_id];
137
temp += residual_v[id];
@@ -148,7 +149,7 @@ fused_add_rms_norm_kernel(
149
__syncthreads();
150
151
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
151
- int id = blockIdx.x * vec_hidden_size + idx;
152
+ int64_t id = blockIdx.x * residual_stride / width + idx;
153
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
154
_f16Vec<scalar_t, width> res = residual_v[id];
155
_f16Vec<scalar_t, width> out;
@@ -182,16 +183,17 @@ fused_add_rms_norm_kernel(
183
const int64_t input_stride,
184
scalar_t* __restrict__ residual, // [..., hidden_size]
185
const scalar_t* __restrict__ weight, // [hidden_size], null if !HasWeight
185
- const float epsilon, const int num_tokens, const int hidden_size) {
186
+ const float epsilon, const int num_tokens, const int hidden_size,
187
+ const int64_t residual_stride) {
188
__shared__ float s_variance;
189
float variance = 0.0f;
190
191
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
192
scalar_t z = input[blockIdx.x * input_stride + idx];
191
- z += residual[blockIdx.x * hidden_size + idx];
193
+ z += residual[blockIdx.x * residual_stride + idx];
194
float x = (float)z;
195
variance += x * x;
194
- residual[blockIdx.x * hidden_size + idx] = z;
196
+ residual[blockIdx.x * residual_stride + idx] = z;
197
}
198
199
using BlockReduce = cub::BlockReduce<float, 1024>;
@@ -204,7 +206,7 @@ fused_add_rms_norm_kernel(
206
__syncthreads();
207
208
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
207
- float x = (float)residual[blockIdx.x * hidden_size + idx];
209
+ float x = (float)residual[blockIdx.x * residual_stride + idx];
210
if constexpr (HasWeight) {
211
float w = (float)weight[idx];
212
input[blockIdx.x * input_stride + idx] = (scalar_t)(x * s_variance * w);
@@ -299,13 +301,13 @@ void rms_norm(torch::stable::Tensor& out, // [..., hidden_size]
301
input.mutable_data_ptr<scalar_t>(), input_stride, \
302
residual.mutable_data_ptr<scalar_t>(), \
303
weight->const_data_ptr<scalar_t>(), epsilon, num_tokens, \
302
- hidden_size); \
304
+ hidden_size, residual_stride); \
305
} else { \
306
vllm::fused_add_rms_norm_kernel<scalar_t, width, false> \
307
<<<grid, block, 0, stream>>>( \
308
input.mutable_data_ptr<scalar_t>(), input_stride, \
309
residual.mutable_data_ptr<scalar_t>(), nullptr, epsilon, \
308
- num_tokens, hidden_size); \
310
+ num_tokens, hidden_size, residual_stride); \
311
} \
312
});
313
@@ -314,13 +316,14 @@ void fused_add_rms_norm(torch::stable::Tensor& input, // [..., hidden_size]
316
std::optional<torch::stable::Tensor> weight,
317
double epsilon) {
318
STD_TORCH_CHECK(input.scalar_type() == residual.scalar_type());
317
- STD_TORCH_CHECK(residual.is_contiguous());
319
+ STD_TORCH_CHECK(residual.stride(-1) == 1);
320
if (weight.has_value()) {
321
STD_TORCH_CHECK(weight->scalar_type() == input.scalar_type());
322
STD_TORCH_CHECK(weight->is_contiguous());
323
}
324
int hidden_size = input.size(-1);
325
int64_t input_stride = input.stride(-2);
326
+ int64_t residual_stride = residual.stride(-2);
327
int num_tokens = input.numel() / hidden_size;
328
329
dim3 grid(num_tokens);
@@ -343,7 +346,8 @@ void fused_add_rms_norm(torch::stable::Tensor& input, // [..., hidden_size]
346
auto inp_ptr = reinterpret_cast<std::uintptr_t>(input.data_ptr());
347
auto res_ptr = reinterpret_cast<std::uintptr_t>(residual.data_ptr());
348
bool offsets_are_multiple_of_vector_width =
346
- hidden_size % vector_width == 0 && input_stride % vector_width == 0;
349
+ hidden_size % vector_width == 0 && input_stride % vector_width == 0 &&
350
+ residual_stride % vector_width == 0;
351
const bool has_weight = weight.has_value();
352
if (has_weight) {
353
auto wt_ptr = reinterpret_cast<std::uintptr_t>(weight->data_ptr());
tests/kernels/core/test_layernorm.py
+3
-1
@@ -60,7 +60,9 @@ def test_rms_norm(
60
x = x[..., :hidden_size]
61
assert x.is_contiguous() != strided_input
62
x *= scale
63
- residual = torch.randn_like(x) * scale if add_residual else None
63
+ residual = x.new_empty_strided(x.size(), x.stride()) if add_residual else None
64
+ if residual is not None:
65
+ residual.normal_(std=scale)
66
67
# NOTE(woosuk): The reference implementation should be executed first
68
# because the custom kernel is in-place.
vllm/model_executor/models/apertus.py
+2
-2
@@ -211,8 +211,8 @@ class ApertusAttention(nn.Module):
211
) -> torch.Tensor:
212
qkv, _ = self.qkv_proj(hidden_states)
213
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
214
- q = self.q_norm(q.contiguous().view(-1, self.head_dim)).view_as(q)
215
- k = self.k_norm(k.contiguous().view(-1, self.head_dim)).view_as(k)
214
+ q = self.q_norm(q.view(-1, self.num_heads, self.head_dim)).view_as(q)
215
+ k = self.k_norm(k.view(-1, self.num_kv_heads, self.head_dim)).view_as(k)
216
q, k = self.rotary_emb(positions, q, k)
217
attn_output = self.attn(q, k, v)
218
output, _ = self.o_proj(attn_output)
vllm/model_executor/models/deepseek_v2.py
-4
@@ -1481,8 +1481,6 @@ class DeepseekV2Model(nn.Module):
1481
hidden_states, residual = combined_states.split(
1482
[self.hidden_size, self.hidden_size], dim=-1
1483
)
1484
- # fused_add_rms_norm requires a contiguous residual
1485
- residual = residual.contiguous()
1484
if idx in self.aux_hidden_state_layers:
1485
aux_hidden_state = hidden_states + residual
1486
if aux_hidden_state.shape[0] != positions.shape[0]:
@@ -1507,8 +1505,6 @@ class DeepseekV2Model(nn.Module):
1505
hidden_states, residual = combined_states.split(
1506
[self.hidden_size, self.hidden_size], dim=-1
1507
)
1510
- # fused_add_rms_norm requires a contiguous residual
1511
- residual = residual.contiguous()
1508
1509
if self.end_layer in self.aux_hidden_state_layers:
1510
aux_hidden_states.append(hidden_states + residual)
vllm/model_executor/models/hunyuan_v1.py
+4
-4
@@ -238,10 +238,10 @@ class HunYuanAttention(nn.Module):
238
ori_k = k
239
if self.use_qk_norm:
240
q = self.query_layernorm(
241
- q.view(-1, self.num_heads, self.head_dim).contiguous()
241
+ q.view(-1, self.num_heads, self.head_dim),
242
)
243
k = self.key_layernorm(
244
- k.view(-1, self.num_kv_heads, self.head_dim).contiguous()
244
+ k.view(-1, self.num_kv_heads, self.head_dim),
245
)
246
247
attn_output = self.attn(q, k, v)
@@ -346,10 +346,10 @@ class HunYuanCrossAttention(nn.Module):
346
q, _ = self.rotary_emb(positions, q, k_tmp)
347
if self.use_qk_norm:
348
q = self.query_layernorm(
349
- q.view(-1, self.num_heads, self.head_dim).contiguous()
349
+ q.view(-1, self.num_heads, self.head_dim),
350
)
351
k = self.key_layernorm(
352
- k.view(-1, self.num_kv_heads, self.head_dim).contiguous()
352
+ k.view(-1, self.num_kv_heads, self.head_dim),
353
)
354
355
attn_output = self.attn(q, k, v)
vllm/model_executor/models/lfm2.py
+2
-2
@@ -169,8 +169,8 @@ class Lfm2Attention(nn.Module):
169
n_tokens, _ = hidden_states.shape
170
qkv, _ = self.qkv_proj(hidden_states)
171
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
172
- q = q.view(n_tokens, self.num_heads, self.head_dim).contiguous()
173
- k = k.view(n_tokens, self.num_kv_heads, self.head_dim).contiguous()
172
+ q = q.view(n_tokens, self.num_heads, self.head_dim)
173
+ k = k.view(n_tokens, self.num_kv_heads, self.head_dim)
174
q = self.q_layernorm(q)
175
k = self.k_layernorm(k)
176
q, k = self.rotary_emb(positions, q, k)
vllm/model_executor/models/lfm2_moe.py
+2
-2
@@ -255,8 +255,8 @@ class Lfm2MoeAttention(nn.Module):
255
n_tokens, _ = hidden_states.shape
256
qkv, _ = self.qkv_proj(hidden_states)
257
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
258
- q = q.view(n_tokens, self.num_heads, self.head_dim).contiguous()
259
- k = k.view(n_tokens, self.num_kv_heads, self.head_dim).contiguous()
258
+ q = q.view(n_tokens, self.num_heads, self.head_dim)
259
+ k = k.view(n_tokens, self.num_kv_heads, self.head_dim)
260
q = self.q_layernorm(q)
261
k = self.k_layernorm(k)
262
q, k = self.rotary_emb(positions, q, k)
vllm/model_executor/models/minicpm3.py
+1
-1
@@ -146,7 +146,7 @@ class MiniCPM3Attention(nn.Module):
146
latent_cache, _ = self.kv_a_proj_with_mqa(hidden_states)
147
kv_a, _ = latent_cache.split([self.kv_lora_rank, self.qk_rope_head_dim], dim=-1)
148
latent_cache = latent_cache.unsqueeze(1)
149
- kv_a = self.kv_a_layernorm(kv_a.contiguous())
149
+ kv_a = self.kv_a_layernorm(kv_a)
150
kv, _ = self.kv_b_proj(kv_a)
151
kv = kv.view(-1, self.num_local_heads, self.qk_nope_head_dim + self.v_head_dim)
152
k_nope, v = kv.split([self.qk_nope_head_dim, self.v_head_dim], dim=-1)
vllm/model_executor/models/step3p5.py
+2
-2
@@ -271,11 +271,11 @@ class Step3p5Attention(nn.Module):
271
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
272
# Add qk-norm inline similar to Qwen3 MOE attention
273
q_by_head = q.view(*q.shape[:-1], q.shape[-1] // self.head_dim, self.head_dim)
274
- q_by_head = self.q_norm(q_by_head.contiguous())
274
+ q_by_head = self.q_norm(q_by_head)
275
q = q_by_head.view(q.shape)
276
277
k_by_head = k.view(*k.shape[:-1], k.shape[-1] // self.head_dim, self.head_dim)
278
- k_by_head = self.k_norm(k_by_head.contiguous())
278
+ k_by_head = self.k_norm(k_by_head)
279
k = k_by_head.view(k.shape)
280
if self.use_rope:
281
q, k = self.rotary_emb(positions, q, k)