main
cuh 547 lines 19.5 KB
Raw
1 #pragma once
2
3 /**
4 * __device__ layernorm utilities.
5 */
6
7 #include "libtorch_stable/quantization/vectorization.cuh"
8 #include "quantization/utils.cuh"
9 #include "quant_conversions.cuh"
10
11 #include "../../cub_helpers.h"
12 #include "../../../cuda_compat.h"
13
14 namespace vllm {
15
16 // has_residual must be true, if residual is not a nullptr
17 template <typename scalar_t, bool has_residual = false>
18 __device__ void compute_rms(float* rms, scalar_t const* __restrict__ input,
19 int32_t const hidden_size,
20 int32_t const input_stride, float const epsilon,
21 scalar_t const* __restrict__ residual = nullptr) {
22 int64_t const input_token_offset =
23 blockIdx.x * static_cast<int64_t>(input_stride);
24 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
25 // sum of squares
26 float ss = 0.0f;
27
28 for (auto i = threadIdx.x; i < hidden_size; i += blockDim.x) {
29 float x = static_cast<float>(input[input_token_offset + i]);
30 if constexpr (has_residual) {
31 x += static_cast<float>(residual[token_offset + i]);
32 }
33
34 ss += x * x;
35 }
36
37 using BlockReduce = cub::BlockReduce<float, 1024>;
38 __shared__ typename BlockReduce::TempStorage reduceStore;
39 ss = BlockReduce(reduceStore).Reduce(ss, CubAddOp{}, blockDim.x);
40
41 __shared__ float s_rms;
42 if (threadIdx.x == 0) {
43 s_rms = rsqrtf(ss / hidden_size + epsilon);
44 }
45 __syncthreads();
46
47 *rms = s_rms;
48 }
49
50 __device__ float warpReduceMax(float val) {
51 static_assert(WARP_SIZE == 32 || WARP_SIZE == 64);
52 #pragma unroll
53 for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {
54 val = fmaxf(val, VLLM_SHFL_DOWN_SYNC(val, offset));
55 }
56 return val;
57 }
58
59 template <typename scalar_t, typename scalar_out_t, bool has_residual = false,
60 bool is_scale_transposed = false>
61 __device__ void compute_dynamic_per_token_scales(
62 float* __restrict__ token_scale, float* __restrict__ all_token_scales,
63 scalar_t const* __restrict__ input, scalar_t const* __restrict__ weight,
64 float const rms, float const* __restrict__ scale_ub,
65 int32_t const hidden_size, int32_t const input_stride,
66 scalar_t const* __restrict__ residual = nullptr,
67 int32_t const group_size = 0, int64_t outer_scale_stride = 1) {
68 float block_absmax_val_maybe = 0.0f;
69 constexpr scalar_out_t qmax{quant_type_max_v<scalar_out_t>};
70 __syncthreads();
71
72 int64_t const input_token_offset =
73 blockIdx.x * static_cast<int64_t>(input_stride);
74 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
75
76 if (group_size > 0) {
77 int64_t num_groups = hidden_size / group_size;
78 __shared__ float s_max_vals[1024];
79 int64_t const threads_per_group = blockDim.x / num_groups;
80 int64_t const thread_in_group = threadIdx.x % threads_per_group;
81 int64_t const group_offset = threadIdx.x / threads_per_group * group_size;
82 int64_t const thread_offset = group_offset + thread_in_group;
83 int64_t const thread_end =
84 min(group_offset + group_size, static_cast<int64_t>(hidden_size));
85 for (auto i = thread_offset; i < thread_end; i += threads_per_group) {
86 float x = static_cast<float>(input[input_token_offset + i]);
87 if constexpr (has_residual) {
88 x += static_cast<float>(residual[token_offset + i]);
89 }
90 x = static_cast<float>(static_cast<scalar_t>(x * rms) * weight[i]);
91 block_absmax_val_maybe = fmaxf(block_absmax_val_maybe, fabsf(x));
92 }
93 s_max_vals[threadIdx.x] = block_absmax_val_maybe;
94 __syncthreads();
95
96 int64_t const warp_size = WARP_SIZE;
97 int64_t const num_warps = blockDim.x / warp_size;
98 int64_t const warp_id = threadIdx.x / warp_size;
99 int64_t const thread_in_warp = threadIdx.x % warp_size;
100 int64_t const groups_per_warp = (num_groups + num_warps - 1) / num_warps;
101 for (auto i = 0; i < groups_per_warp; ++i) {
102 int64_t const group_id = i * num_warps + warp_id;
103 if (group_id < num_groups) {
104 int64_t const warp_start = group_id * threads_per_group;
105 float warp_max = 0.0f;
106 for (auto j = thread_in_warp; j < threads_per_group; j += warp_size) {
107 warp_max = fmaxf(warp_max, s_max_vals[warp_start + j]);
108 }
109 warp_max = warpReduceMax(warp_max);
110 if (thread_in_warp == 0) {
111 s_max_vals[warp_start] = warp_max;
112 }
113 }
114 }
115 __syncthreads();
116
117 if (thread_in_group == 0 && thread_offset < thread_end) {
118 block_absmax_val_maybe = s_max_vals[threadIdx.x];
119 float scale = 0.0f;
120 if (scale_ub) {
121 scale = min(block_absmax_val_maybe, *scale_ub);
122 } else {
123 scale = block_absmax_val_maybe;
124 }
125 // token scale computation
126 scale = max(scale / qmax, min_scaling_factor<scalar_out_t>::val());
127 // Global output store
128 if constexpr (is_scale_transposed) {
129 int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
130 outer_scale_stride * outer_scale_stride;
131 all_token_scales[(threadIdx.x / threads_per_group) * scale_rows +
132 blockIdx.x] = scale;
133 } else {
134 all_token_scales[blockIdx.x * num_groups +
135 threadIdx.x / threads_per_group] = scale;
136 }
137 }
138 __syncthreads();
139 } else {
140 for (auto i = threadIdx.x; i < hidden_size; i += blockDim.x) {
141 float x = static_cast<float>(input[input_token_offset + i]);
142 if constexpr (has_residual) {
143 x += static_cast<float>(residual[token_offset + i]);
144 }
145
146 x = static_cast<float>(static_cast<scalar_t>(x * rms) * weight[i]);
147 block_absmax_val_maybe = fmaxf(block_absmax_val_maybe, fabsf(x));
148 }
149 using BlockReduce = cub::BlockReduce<float, 1024>;
150 __shared__ typename BlockReduce::TempStorage reduceStore;
151 block_absmax_val_maybe =
152 BlockReduce(reduceStore)
153 .Reduce(block_absmax_val_maybe, CubMaxOp{}, blockDim.x);
154
155 __shared__ float s_token_scale;
156 if (threadIdx.x == 0) {
157 float scale = 0.0f;
158 if (scale_ub) {
159 scale = min(block_absmax_val_maybe, *scale_ub);
160 } else {
161 scale = block_absmax_val_maybe;
162 }
163 // token scale computation
164 scale = max(scale / qmax, min_scaling_factor<scalar_out_t>::val());
165 s_token_scale = scale; // Shared memory store
166 all_token_scales[blockIdx.x] = scale; // Global output store
167 }
168 __syncthreads();
169
170 *token_scale = s_token_scale;
171 }
172 }
173
174 template <typename scalar_t, typename scalar_out_t, bool is_scale_inverted,
175 bool has_residual = false, bool is_scale_transposed = false>
176 __device__ void norm_and_quant(
177 scalar_out_t* __restrict__ output, scalar_t const* __restrict__ input,
178 scalar_t const* __restrict__ weight, float const rms, float* const scale,
179 int32_t const hidden_size, int32_t const input_stride,
180 scalar_t* __restrict__ residual = nullptr, int32_t const group_size = 0,
181 int64_t outer_scale_stride = 1) {
182 int64_t const input_token_offset =
183 blockIdx.x * static_cast<int64_t>(input_stride);
184 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
185
186 for (auto i = threadIdx.x; i < hidden_size; i += blockDim.x) {
187 float x = static_cast<float>(input[input_token_offset + i]);
188 if constexpr (has_residual) {
189 x += static_cast<float>(residual[token_offset + i]);
190 residual[token_offset + i] = static_cast<scalar_t>(x);
191 }
192 // Norm
193 x = static_cast<float>(static_cast<scalar_t>(x * rms) * weight[i]);
194 // Quant
195 // If groupwise is_scale_inverted is true, so we invert the scale here.
196 int64_t scale_idx = 0;
197 if (group_size > 0) {
198 if constexpr (is_scale_transposed) {
199 int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
200 outer_scale_stride * outer_scale_stride;
201 scale_idx = (i / group_size) * scale_rows + blockIdx.x;
202 } else {
203 scale_idx = blockIdx.x * (hidden_size / group_size) + i / group_size;
204 }
205 }
206 auto scale_val =
207 (group_size > 0
208 ? (is_scale_inverted ? 1.0f / scale[scale_idx] : scale[scale_idx])
209 : *scale);
210 output[token_offset + i] =
211 ScaledQuant<scalar_out_t, is_scale_inverted>::quant_fn(x, scale_val);
212 }
213 }
214
215 namespace vectorized {
216
217 // Compute 1.0/rms(input)
218 // hidden_size must be a multiple of 4
219 template <typename scalar_t, bool has_residual = false>
220 __device__ void compute_rms(float* rms, scalar_t const* __restrict__ input,
221 int32_t const hidden_size,
222 int32_t const input_stride, float const epsilon,
223 scalar_t const* __restrict__ residual = nullptr) {
224 int64_t const input_token_offset =
225 blockIdx.x * static_cast<int64_t>(input_stride);
226 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
227
228 // Vectorized input/output to better utilize memory bandwidth.
229 vec4_t<scalar_t> const* vec_input =
230 reinterpret_cast<vec4_t<scalar_t> const*>(&input[input_token_offset]);
231 vec4_t<scalar_t> const* vec_residual = nullptr;
232 if constexpr (has_residual) {
233 vec_residual =
234 reinterpret_cast<vec4_t<scalar_t> const*>(&residual[token_offset]);
235 }
236
237 // sum of squares
238 float ss = 0.0f;
239
240 const int VEC_SIZE = 4;
241 int32_t const num_vec_elems = hidden_size >> 2;
242
243 #pragma unroll 4
244 for (auto i = threadIdx.x; i < num_vec_elems; i += blockDim.x) {
245 vec4_t<scalar_t> in = vec_input[i];
246
247 vec4_t<float> x;
248 #pragma unroll
249 for (int j = 0; j < VEC_SIZE; ++j) {
250 x.val[j] = static_cast<float>(in.val[j]);
251 }
252
253 if constexpr (has_residual) {
254 vec4_t<scalar_t> r = vec_residual[i];
255 #pragma unroll
256 for (int j = 0; j < VEC_SIZE; ++j) {
257 x.val[j] += static_cast<float>(r.val[j]);
258 }
259 }
260
261 #pragma unroll
262 for (int j = 0; j < VEC_SIZE; ++j) {
263 ss += x.val[j] * x.val[j];
264 }
265 }
266
267 using BlockReduce = cub::BlockReduce<float, 1024>;
268 __shared__ typename BlockReduce::TempStorage reduceStore;
269 ss = BlockReduce(reduceStore).Reduce(ss, CubAddOp{}, blockDim.x);
270
271 __shared__ float s_rms;
272 if (threadIdx.x == 0) {
273 s_rms = rsqrtf(ss / hidden_size + epsilon);
274 }
275 __syncthreads();
276
277 *rms = s_rms;
278 }
279
280 // Vectorized version of vllm::compute_dynamic_per_token_scales
281 // hidden_size must be a multiple of 4
282 template <typename scalar_t, typename scalar_out_t, bool has_residual = false,
283 bool is_scale_transposed = false, int32_t group_size = 0>
284 __device__ void compute_dynamic_per_token_scales(
285 float* __restrict__ token_scale, float* __restrict__ all_token_scales,
286 scalar_t const* __restrict__ input, scalar_t const* __restrict__ weight,
287 float const rms, float const* __restrict__ scale_ub,
288 int32_t const hidden_size, int32_t const input_stride,
289 scalar_t const* __restrict__ residual = nullptr,
290 int64_t outer_scale_stride = 1) {
291 constexpr scalar_out_t qmax{quant_type_max_v<scalar_out_t>};
292
293 const int VEC_SIZE = 4;
294 float block_absmax_val_maybe = 0.0f;
295
296 // Vectorized input/weight/residual to better utilize memory bandwidth.
297 vec4_t<scalar_t> const* vec_input = nullptr;
298 vec4_t<scalar_t> const* vec_weight = nullptr;
299 vec4_t<scalar_t> const* vec_residual = nullptr;
300
301 int64_t const input_token_offset =
302 blockIdx.x * static_cast<int64_t>(input_stride);
303 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
304
305 if constexpr (group_size > 0) {
306 __shared__ float s_max_vals[1024];
307
308 int64_t const num_groups = hidden_size / group_size;
309 int64_t const threads_per_group = blockDim.x / num_groups;
310 int64_t const thread_in_group = threadIdx.x % threads_per_group;
311 int64_t const group_offset =
312 threadIdx.x / threads_per_group * (group_size >> 2);
313 int64_t const thread_offset = group_offset + thread_in_group;
314 int64_t const thread_end = min(group_offset + (group_size >> 2),
315 static_cast<int64_t>(hidden_size >> 2));
316 vec_input =
317 reinterpret_cast<vec4_t<scalar_t> const*>(&input[input_token_offset]);
318 vec_weight = reinterpret_cast<vec4_t<scalar_t> const*>(weight);
319 if constexpr (has_residual) {
320 vec_residual =
321 reinterpret_cast<vec4_t<scalar_t> const*>(&residual[token_offset]);
322 }
323 int32_t const num_vec_elems = thread_end;
324
325 #pragma unroll 4
326 for (auto i = thread_offset; i < num_vec_elems; i += threads_per_group) {
327 vec4_t<scalar_t> in = vec_input[i];
328 vec4_t<scalar_t> const w = vec_weight[i];
329
330 vec4_t<float> x;
331 #pragma unroll
332 for (int j = 0; j < VEC_SIZE; ++j) {
333 x.val[j] = static_cast<float>(in.val[j]);
334 }
335
336 if constexpr (has_residual) {
337 vec4_t<scalar_t> r = vec_residual[i];
338 #pragma unroll
339 for (int j = 0; j < VEC_SIZE; ++j) {
340 x.val[j] += static_cast<float>(r.val[j]);
341 }
342 }
343
344 #pragma unroll
345 for (int j = 0; j < VEC_SIZE; ++j) {
346 block_absmax_val_maybe =
347 fmaxf(block_absmax_val_maybe,
348 fabs(static_cast<scalar_t>(x.val[j] * rms) * w.val[j]));
349 }
350 }
351
352 s_max_vals[threadIdx.x] = block_absmax_val_maybe;
353 __syncthreads();
354
355 int64_t const warp_size = WARP_SIZE;
356 int64_t const num_warps = blockDim.x / warp_size;
357 int64_t const warp_id = threadIdx.x / warp_size;
358 int64_t const thread_in_warp = threadIdx.x % warp_size;
359 int64_t const groups_per_warp = (num_groups + num_warps - 1) / num_warps;
360 for (auto i = 0; i < groups_per_warp; ++i) {
361 int64_t const group_id = i * num_warps + warp_id;
362 if (group_id < num_groups) {
363 int64_t const warp_start = group_id * threads_per_group;
364 float warp_max = 0.0f;
365 for (auto j = thread_in_warp; j < threads_per_group; j += warp_size) {
366 warp_max = fmaxf(warp_max, s_max_vals[warp_start + j]);
367 }
368 warp_max = warpReduceMax(warp_max);
369 if (thread_in_warp == 0) {
370 s_max_vals[warp_start] = warp_max;
371 }
372 }
373 }
374 __syncthreads();
375
376 if (thread_in_group == 0 && thread_offset < thread_end) {
377 block_absmax_val_maybe = s_max_vals[threadIdx.x];
378 float scale = 0.0f;
379 if (scale_ub) {
380 scale = min(block_absmax_val_maybe, *scale_ub);
381 } else {
382 scale = block_absmax_val_maybe;
383 }
384 // token scale computation
385 scale = max(scale / qmax, min_scaling_factor<scalar_out_t>::val());
386 // Global output store
387 if constexpr (is_scale_transposed) {
388 int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
389 outer_scale_stride * outer_scale_stride;
390 all_token_scales[(threadIdx.x / threads_per_group) * scale_rows +
391 blockIdx.x] = scale;
392 } else {
393 all_token_scales[blockIdx.x * num_groups +
394 threadIdx.x / threads_per_group] = scale;
395 }
396 }
397 __syncthreads();
398
399 } else {
400 vec_input =
401 reinterpret_cast<vec4_t<scalar_t> const*>(&input[input_token_offset]);
402 vec_weight = reinterpret_cast<vec4_t<scalar_t> const*>(weight);
403 if constexpr (has_residual) {
404 vec_residual =
405 reinterpret_cast<vec4_t<scalar_t> const*>(&residual[token_offset]);
406 }
407
408 int32_t const num_vec_elems = (hidden_size >> 2);
409
410 #pragma unroll 4
411 for (auto i = threadIdx.x; i < num_vec_elems; i += blockDim.x) {
412 vec4_t<scalar_t> in = vec_input[i];
413 vec4_t<scalar_t> const w = vec_weight[i];
414
415 vec4_t<float> x;
416 #pragma unroll
417 for (int j = 0; j < VEC_SIZE; ++j) {
418 x.val[j] = static_cast<float>(in.val[j]);
419 }
420
421 if constexpr (has_residual) {
422 vec4_t<scalar_t> r = vec_residual[i];
423 #pragma unroll
424 for (int j = 0; j < VEC_SIZE; ++j) {
425 x.val[j] += static_cast<float>(r.val[j]);
426 }
427 }
428
429 #pragma unroll
430 for (int j = 0; j < VEC_SIZE; ++j) {
431 block_absmax_val_maybe =
432 fmaxf(block_absmax_val_maybe,
433 fabs(static_cast<scalar_t>(x.val[j] * rms) * w.val[j]));
434 }
435 }
436
437 using BlockReduce = cub::BlockReduce<float, 1024>;
438 __shared__ typename BlockReduce::TempStorage reduceStore;
439 block_absmax_val_maybe =
440 BlockReduce(reduceStore)
441 .Reduce(block_absmax_val_maybe, CubMaxOp{}, blockDim.x);
442
443 __shared__ float s_token_scale;
444 if (threadIdx.x == 0) {
445 float scale = 0.0f;
446 if (scale_ub) {
447 scale = min(block_absmax_val_maybe, *scale_ub);
448 } else {
449 scale = block_absmax_val_maybe;
450 }
451 // token scale computation
452 scale = max(scale / qmax, min_scaling_factor<scalar_out_t>::val());
453 s_token_scale = scale; // shared memory store
454 all_token_scales[blockIdx.x] = scale; // global output store
455 }
456 __syncthreads();
457
458 *token_scale = s_token_scale;
459 }
460 }
461
462 // hidden_size must be a multiple of 4
463 template <typename scalar_t, typename scalar_out_t, bool is_scale_inverted,
464 bool has_residual = false, bool is_scale_transposed = false,
465 int32_t group_size = 0>
466 __device__ void norm_and_quant(
467 scalar_out_t* __restrict__ output, scalar_t const* __restrict__ input,
468 scalar_t const* __restrict__ weight, float const rms, float* const scale,
469 int32_t const hidden_size, int32_t const input_stride,
470 scalar_t* __restrict__ residual = nullptr, int64_t outer_scale_stride = 1) {
471 int64_t const input_token_offset =
472 blockIdx.x * static_cast<int64_t>(input_stride);
473 int64_t const token_offset = blockIdx.x * static_cast<int64_t>(hidden_size);
474
475 // Vectorized input/output/weight/residual to better utilize memory bandwidth.
476 vec4_t<scalar_t> const* vec_input =
477 reinterpret_cast<vec4_t<scalar_t> const*>(&input[input_token_offset]);
478 vec4_t<scalar_t> const* vec_weight =
479 reinterpret_cast<vec4_t<scalar_t> const*>(weight);
480 q8x4_t<scalar_out_t>* vec_output =
481 reinterpret_cast<q8x4_t<scalar_out_t>*>(&output[token_offset]);
482 vec4_t<scalar_t>* vec_residual = nullptr;
483 if constexpr (has_residual) {
484 vec_residual = reinterpret_cast<vec4_t<scalar_t>*>(&residual[token_offset]);
485 }
486
487 const int VEC_SIZE = 4;
488 int32_t const num_vec_elems = hidden_size >> 2;
489
490 // TODO(luka/varun) extract into type-agnostic vectorized quant function to
491 // replace scaled_fp8_conversion_vec
492 #pragma unroll 4
493 for (auto i = threadIdx.x; i < num_vec_elems; i += blockDim.x) {
494 vec4_t<scalar_t> const in = vec_input[i];
495 vec4_t<scalar_t> const w = vec_weight[i];
496
497 vec4_t<float> x;
498 #pragma unroll
499 for (int j = 0; j < VEC_SIZE; ++j) {
500 x.val[j] = static_cast<float>(in.val[j]);
501 }
502
503 if constexpr (has_residual) {
504 vec4_t<scalar_t> r = vec_residual[i];
505 #pragma unroll
506 for (int j = 0; j < VEC_SIZE; ++j) {
507 x.val[j] += static_cast<float>(r.val[j]);
508 }
509 // Update residual
510 #pragma unroll
511 for (int j = 0; j < VEC_SIZE; ++j) {
512 r.val[j] = static_cast<scalar_t>(x.val[j]);
513 }
514 vec_residual[i] = r;
515 }
516
517 q8x4_t<scalar_out_t> out;
518
519 float scale_val;
520
521 if constexpr (group_size > 0) {
522 int64_t const num_groups = hidden_size / group_size;
523 int64_t scale_idx = 0;
524 if constexpr (is_scale_transposed) {
525 int64_t const scale_rows = (gridDim.x + outer_scale_stride - 1) /
526 outer_scale_stride * outer_scale_stride;
527 scale_idx = (i * VEC_SIZE / group_size) * scale_rows + blockIdx.x;
528 } else {
529 scale_idx = blockIdx.x * num_groups + i * VEC_SIZE / group_size;
530 }
531 scale_val =
532 is_scale_inverted ? 1.0f / scale[scale_idx] : scale[scale_idx];
533 } else {
534 scale_val = *scale;
535 }
536 #pragma unroll
537 for (int j = 0; j < VEC_SIZE; ++j) {
538 out.val[j] = ScaledQuant<scalar_out_t, is_scale_inverted>::quant_fn(
539 static_cast<scalar_t>(x.val[j] * rms) * w.val[j], scale_val);
540 }
541 vec_output[i] = out;
542 }
543 }
544
545 } // namespace vectorized
546
547 } // namespace vllm