@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / d93c976a0d
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 15, 09:45 UTC

[Kernel] Have rotary embeddings support tensors (#18046)

Signed-off-by: Lucas Wilkinson <lwilkinson@neuralmagic.com>

Lucas Wilkinson committed May 14, 2025 at 18:43 UTC d93c976a0d78639d0ea9074a9e01607f0d5c5670
4 files changed +59 -31
csrc/pos_encoding_kernels.cu
+28 -12
@@ -44,7 +44,8 @@ inline __device__ void apply_rotary_embedding(
44 // head_size]
45 const scalar_t* cache_ptr, const int head_size, const int num_heads,
46 const int num_kv_heads, const int rot_dim, const int token_idx,
47 - const int64_t query_stride, const int64_t key_stride) {
47 + const int64_t query_stride, const int64_t key_stride,
48 + const int64_t head_stride) {
49 const int embed_dim = rot_dim / 2;
50 const scalar_t* cos_ptr = cache_ptr;
51 const scalar_t* sin_ptr = cache_ptr + embed_dim;
@@ -52,7 +53,8 @@ inline __device__ void apply_rotary_embedding(
53 const int nq = num_heads * embed_dim;
54 for (int i = threadIdx.x; i < nq; i += blockDim.x) {
55 const int head_idx = i / embed_dim;
55 - const int64_t token_head = token_idx * query_stride + head_idx * head_size;
56 + const int64_t token_head =
57 + token_idx * query_stride + head_idx * head_stride;
58 const int rot_offset = i % embed_dim;
59 apply_token_rotary_embedding<scalar_t, IS_NEOX>(
60 query + token_head, cos_ptr, sin_ptr, rot_offset, embed_dim);
@@ -62,7 +64,8 @@ inline __device__ void apply_rotary_embedding(
64 const int nk = num_kv_heads * embed_dim;
65 for (int i = threadIdx.x; i < nk; i += blockDim.x) {
66 const int head_idx = i / embed_dim;
65 - const int64_t token_head = token_idx * key_stride + head_idx * head_size;
67 + const int64_t token_head =
68 + token_idx * key_stride + head_idx * head_stride;
69 const int rot_offset = i % embed_dim;
70 apply_token_rotary_embedding<scalar_t, IS_NEOX>(
71 key + token_head, cos_ptr, sin_ptr, rot_offset, embed_dim);
@@ -84,7 +87,8 @@ __global__ void rotary_embedding_kernel(
87 const scalar_t* __restrict__ cos_sin_cache, // [max_position, 2, rot_dim //
88 // 2]
89 const int rot_dim, const int64_t query_stride, const int64_t key_stride,
87 - const int num_heads, const int num_kv_heads, const int head_size) {
90 + const int64_t head_stride, const int num_heads, const int num_kv_heads,
91 + const int head_size) {
92 // Each thread block is responsible for one token.
93 const int token_idx = blockIdx.x;
94 int64_t pos = positions[token_idx];
@@ -92,7 +96,7 @@ __global__ void rotary_embedding_kernel(
96
97 apply_rotary_embedding<scalar_t, IS_NEOX>(
98 query, key, cache_ptr, head_size, num_heads, num_kv_heads, rot_dim,
95 - token_idx, query_stride, key_stride);
99 + token_idx, query_stride, key_stride, head_stride);
100 }
101
102 template <typename scalar_t, bool IS_NEOX>
@@ -109,9 +113,9 @@ __global__ void batched_rotary_embedding_kernel(
113 const scalar_t* __restrict__ cos_sin_cache, // [max_position, 2, rot_dim //
114 // 2]
115 const int64_t* __restrict__ cos_sin_cache_offsets, // [batch_size, seq_len]
112 - // or [num_tokens]
116 const int rot_dim, const int64_t query_stride, const int64_t key_stride,
114 - const int num_heads, const int num_kv_heads, const int head_size) {
117 + const int64_t head_stride, const int num_heads, const int num_kv_heads,
118 + const int head_size) {
119 // Each thread block is responsible for one token.
120 const int token_idx = blockIdx.x;
121 int64_t pos = positions[token_idx];
@@ -121,7 +125,7 @@ __global__ void batched_rotary_embedding_kernel(
125
126 apply_rotary_embedding<scalar_t, IS_NEOX>(
127 query, key, cache_ptr, head_size, num_heads, num_kv_heads, rot_dim,
124 - token_idx, query_stride, key_stride);
128 + token_idx, query_stride, key_stride, head_stride);
129 }
130
131 } // namespace vllm
@@ -179,6 +183,12 @@ void rotary_embedding(
183 int seq_dim_idx = positions_ndim - 1;
184 int64_t query_stride = query.stride(seq_dim_idx);
185 int64_t key_stride = key.has_value() ? key->stride(seq_dim_idx) : 0;
186 + // Determine head stride: for [*, heads, head_size] use stride of last dim;
187 + // for flat [*, heads*head_size], heads blocks are contiguous of size
188 + // head_size
189 + int query_ndim = query.dim();
190 + int64_t head_stride =
191 + (query_ndim == positions_ndim + 2) ? query.stride(-2) : head_size;
192
193 dim3 grid(num_tokens);
194 dim3 block(std::min<int64_t>(num_heads * rot_dim / 2, 512));
@@ -190,14 +200,14 @@ void rotary_embedding(
200 positions.data_ptr<int64_t>(), query.data_ptr<scalar_t>(),
201 key.has_value() ? key->data_ptr<scalar_t>() : nullptr,
202 cos_sin_cache.data_ptr<scalar_t>(), rot_dim, query_stride, key_stride,
193 - num_heads, num_kv_heads, head_size);
203 + head_stride, num_heads, num_kv_heads, head_size);
204 } else {
205 vllm::rotary_embedding_kernel<scalar_t, false>
206 <<<grid, block, 0, stream>>>(
207 positions.data_ptr<int64_t>(), query.data_ptr<scalar_t>(),
208 key.has_value() ? key->data_ptr<scalar_t>() : nullptr,
209 cos_sin_cache.data_ptr<scalar_t>(), rot_dim, query_stride,
200 - key_stride, num_heads, num_kv_heads, head_size);
210 + key_stride, head_stride, num_heads, num_kv_heads, head_size);
211 }
212 });
213 }
@@ -263,6 +273,12 @@ void batched_rotary_embedding(
273 int seq_dim_idx = positions_ndim - 1;
274 int64_t query_stride = query.stride(seq_dim_idx);
275 int64_t key_stride = key.has_value() ? key->stride(seq_dim_idx) : 0;
276 + // Determine head stride: for [*, heads, head_size] use stride of last dim;
277 + // for flat [*, heads*head_size], heads blocks are contiguous of size
278 + // head_size
279 + int query_ndim = query.dim();
280 + int64_t head_stride =
281 + (query_ndim == positions_ndim + 2) ? query.stride(-2) : head_size;
282
283 dim3 grid(num_tokens);
284 dim3 block(std::min<int64_t>(num_heads * rot_dim / 2, 512));
@@ -276,7 +292,7 @@ void batched_rotary_embedding(
292 key.has_value() ? key->data_ptr<scalar_t>() : nullptr,
293 cos_sin_cache.data_ptr<scalar_t>(),
294 cos_sin_cache_offsets.data_ptr<int64_t>(), rot_dim, query_stride,
279 - key_stride, num_heads, num_kv_heads, head_size);
295 + key_stride, head_stride, num_heads, num_kv_heads, head_size);
296 } else {
297 vllm::batched_rotary_embedding_kernel<scalar_t, false>
298 <<<grid, block, 0, stream>>>(
@@ -284,7 +300,7 @@ void batched_rotary_embedding(
300 key.has_value() ? key->data_ptr<scalar_t>() : nullptr,
301 cos_sin_cache.data_ptr<scalar_t>(),
302 cos_sin_cache_offsets.data_ptr<int64_t>(), rot_dim, query_stride,
287 - key_stride, num_heads, num_kv_heads, head_size);
303 + key_stride, head_stride, num_heads, num_kv_heads, head_size);
304 }
305 });
306 }
tests/kernels/core/test_pos_encoding.py
+13 -1
@@ -29,12 +29,20 @@ def _get_flat_tensor_shape(batch_size: int, seq_len: int, num_heads: int,
29 return (batch_size, seq_len, num_heads * head_size)
30
31
32 +# For testing sliced tensors
33 +def _get_padded_tensor_shape(batch_size: int, seq_len: int, num_heads: int,
34 + head_size: int) -> tuple[int, ...]:
35 + return (batch_size, seq_len, num_heads, head_size + 64)
36 +
37 +
38 def _get_batch_tensor_shape(batch_size: int, seq_len: int, num_heads: int,
39 head_size: int) -> tuple[int, ...]:
40 return (batch_size, seq_len, num_heads, head_size)
41
42
37 -TENSORS_SHAPES_FN = [_get_batch_tensor_shape, _get_flat_tensor_shape]
43 +TENSORS_SHAPES_FN = [
44 + _get_batch_tensor_shape, _get_flat_tensor_shape, _get_padded_tensor_shape
45 +]
46
47
48 @pytest.mark.parametrize("is_neox_style", IS_NEOX_STYLE)
@@ -79,6 +87,10 @@ def test_rotary_embedding(
87 query = torch.randn(query_shape, dtype=dtype)
88 key = torch.randn_like(query) if use_key else None
89
90 + # slice tensor if required, noop otherwise
91 + query = query[..., :head_size]
92 + key = key[..., :head_size] if use_key else None
93 +
94 # NOTE(woosuk): The reference implementation should be executed first
95 # because the custom kernel is in-place.
96 ref_query, ref_key = rope.forward_native(positions, query, key)
tests/kernels/core/test_rotary_embedding.py
+15 -2
@@ -38,9 +38,10 @@ def rotary_embedding_opcheck(rot,
38 @pytest.mark.parametrize("head_size", [32, 108])
39 @pytest.mark.parametrize("seq_len", [11, 1024])
40 @pytest.mark.parametrize("use_key", [True, False])
41 +@pytest.mark.parametrize("head_stride_is_contingous", [True, False])
42 def test_rotary_embedding_opcheck(dist_init, device, max_position,
43 is_neox_style, rotary_dim, head_size,
43 - seq_len, use_key):
44 + seq_len, use_key, head_stride_is_contingous):
45 batch_size = 1
46 base = 10000
47 num_heads = 7
@@ -50,15 +51,27 @@ def test_rotary_embedding_opcheck(dist_init, device, max_position,
51 positions = torch.randint(0,
52 max_position, (batch_size, seq_len),
53 device=device)
54 + head_stride = head_size + (64 if head_stride_is_contingous else 0)
55 +
56 query = torch.randn(batch_size,
57 seq_len,
55 - num_heads * head_size,
58 + num_heads,
59 + head_stride,
60 dtype=torch.float32,
61 device=device)
62 key = torch.randn_like(query) if use_key else None
63 + query = query[..., :head_size]
64 + key = key[..., :head_size] if use_key else None
65
66 rotary_embedding_opcheck(rot, positions, query, key)
67 offsets = torch.zeros(batch_size * seq_len,
68 device=device,
69 dtype=torch.long)
70 rotary_embedding_opcheck(rot, positions, query, key, offsets)
71 +
72 + # if we have a contiguous head stride, test the alternate
73 + # [..., num_heads * head_dim] shape/layout
74 + if head_stride_is_contingous:
75 + rotary_embedding_opcheck(
76 + rot, positions, query.flatten(start_dim=-2),
77 + key.flatten(start_dim=-2) if use_key else None)
vllm/_custom_ops.py
+3 -16
@@ -254,14 +254,8 @@ def rotary_embedding(
254 cos_sin_cache: torch.Tensor,
255 is_neox: bool,
256 ) -> None:
257 - # TODO: Remove this contiguous call when the kernel is updated to support tensor slices
258 - query_contiguous = query.contiguous()
259 - key_contiguous = key.contiguous() if key is not None else None
260 - torch.ops._C.rotary_embedding(positions, query_contiguous, key_contiguous,
261 - head_size, cos_sin_cache, is_neox)
262 - query.copy_(query_contiguous)
263 - if key is not None:
264 - key.copy_(key_contiguous)
257 + torch.ops._C.rotary_embedding(positions, query, key, head_size,
258 + cos_sin_cache, is_neox)
259
260
261 def batched_rotary_embedding(positions: torch.Tensor, query: torch.Tensor,
@@ -269,16 +263,9 @@ def batched_rotary_embedding(positions: torch.Tensor, query: torch.Tensor,
263 cos_sin_cache: torch.Tensor, is_neox: bool,
264 rot_dim: int,
265 cos_sin_cache_offsets: torch.Tensor) -> None:
272 - # TODO: Remove this contiguous call when the kernel is updated to support tensor slices
273 - query_contiguous = query.contiguous()
274 - key_contiguous = key.contiguous() if key is not None else None
275 - torch.ops._C.batched_rotary_embedding(positions, query_contiguous,
276 - key_contiguous, head_size,
266 + torch.ops._C.batched_rotary_embedding(positions, query, key, head_size,
267 cos_sin_cache, is_neox, rot_dim,
268 cos_sin_cache_offsets)
279 - query.copy_(query_contiguous)
280 - if key is not None:
281 - key.copy_(key_contiguous)
269
270
271 # layer norm ops