main
cuh 433 lines 13.8 KB
Raw
1 #pragma once
2 #include <hip/hip_fp8.h>
3
4 #include <hip/hip_fp16.h>
5 #include <hip/hip_bf16.h>
6 #include <hip/hip_bfloat16.h>
7
8 #include "../../../../attention/attention_dtypes.h"
9 #include <torch/headeronly/core/ScalarType.h>
10
11 namespace vllm {
12 #ifdef USE_ROCM
13
14 namespace fp8 {
15 #ifdef ENABLE_FP8
16
17 // Use hardware cvt instruction for fp8 on rocm
18 template <typename fp8_type>
19 __device__ __forceinline__ fp8_type cvt_c10(float const r) {
20 return {};
21 }
22
23 // __hip_fp8_e4m3 only exists starting in ROCm 6.3. The macro
24 // HIP_FP8_TYPE_OCP comes from the hip_fp8.h header and also makes
25 // its first appearance in ROCm 6.3. Since VLLM_DISPATCH_FP8_TYPES
26 // on ROCm instantiates both OCP and FNUZ kernels, we need to replace
27 // the new HW cvt with something reasonable that doesn't rely on the
28 // ROCm 6.3 feature. This allows compiling on ROCm 6.2 or newer.
29 template <>
30 __device__ __forceinline__ c10::Float8_e4m3fn cvt_c10(float const r) {
31 #if HIP_FP8_TYPE_OCP
32 return c10::Float8_e4m3fn(
33 __hip_cvt_float_to_fp8(r, __hip_fp8_e4m3::__default_saturation,
34 __hip_fp8_e4m3::__default_interpret),
35 c10::Float8_e4m3fn::from_bits());
36 #else
37 // Cast implemented by pytorch. Uses bit manipulation instead of HW cvt.
38 // HW cvt above is faster when it is available (ROCm 6.3 or newer).
39 return static_cast<c10::Float8_e4m3fn>(r);
40 #endif
41 }
42
43 template <>
44 __device__ __forceinline__ c10::Float8_e4m3fnuz cvt_c10(float const r) {
45 return c10::Float8_e4m3fnuz(
46 __hip_cvt_float_to_fp8(r, __hip_fp8_e4m3_fnuz::__default_saturation,
47 __hip_fp8_e4m3_fnuz::__default_interpret),
48 c10::Float8_e4m3fnuz::from_bits());
49 }
50
51 template <typename Tout, typename Tin>
52 __inline__ __device__ Tout vec_conversion(const Tin& x) {
53 return x;
54 }
55
56 template <typename Tout, typename Tin>
57 __inline__ __device__ Tout scaled_vec_conversion(const Tin& x,
58 const float scale) {
59 return x;
60 }
61
62 #if HIP_FP8_TYPE_OCP
63 using fp8_type = __hip_fp8_e4m3;
64 using fp8x2_type = __hip_fp8x2_e4m3;
65 #else
66 using fp8_type = __hip_fp8_e4m3_fnuz;
67 using fp8x2_type = __hip_fp8x2_e4m3_fnuz;
68 #endif
69
70 // fp8x2 -> float2
71 template <>
72 __inline__ __device__ float2
73 vec_conversion<float2, uint16_t>(const uint16_t& a) {
74 fp8x2_type f8x2;
75 f8x2.__x = a;
76 return static_cast<float2>(f8x2);
77 }
78
79 // fp8x4 -> float4
80 template <>
81 __inline__ __device__ Float4_
82 vec_conversion<Float4_, uint32_t>(const uint32_t& a) {
83 Float4_ res;
84 res.x = vec_conversion<float2, uint16_t>((uint16_t)a);
85 res.y = vec_conversion<float2, uint16_t>((uint16_t)(a >> 16U));
86 return res;
87 }
88
89 // fp8x4 -> float4
90 template <>
91 __inline__ __device__ float4
92 vec_conversion<float4, uint32_t>(const uint32_t& a) {
93 Float4_ tmp = vec_conversion<Float4_, uint32_t>(a);
94 float4 res = make_float4(tmp.x.x, tmp.x.y, tmp.y.x, tmp.y.y);
95 return res;
96 }
97
98 /* Scaled and vectorized conversions, for data exchange between high and low
99 precision domains
100
101 Convention of the scale in API, e.g: FP8_data = Quantization(
102 High_Precision_data / scale ) s.t. Quantize(HP / scale) => FP8 Dequant(FP8) *
103 scale => HP
104
105 */
106
107 using __nv_bfloat16 = __hip_bfloat16;
108 using __nv_bfloat162 = __hip_bfloat162;
109
110 // fp8 -> __nv_bfloat16
111 template <>
112 __inline__ __device__ __nv_bfloat16
113 scaled_vec_conversion<__nv_bfloat16, uint8_t>(const uint8_t& a, float scale) {
114 fp8_type f8;
115 f8.__x = a;
116 return __float2bfloat16(static_cast<float>(f8) * scale);
117 }
118
119 // fp8x2 -> __nv_bfloat162
120 template <>
121 __inline__ __device__ __nv_bfloat162
122 scaled_vec_conversion<__nv_bfloat162, uint16_t>(const uint16_t& a,
123 float scale) {
124 __nv_bfloat162 res;
125 res.x = scaled_vec_conversion<__nv_bfloat16, uint8_t>((uint8_t)a, scale);
126 res.y =
127 scaled_vec_conversion<__nv_bfloat16, uint8_t>((uint8_t)(a >> 8U), scale);
128 return res;
129 }
130
131 // fp8x4 -> bf16_4_t
132 template <>
133 __inline__ __device__ bf16_4_t
134 scaled_vec_conversion<bf16_4_t, uint32_t>(const uint32_t& a, float scale) {
135 bf16_4_t res;
136 res.x = scaled_vec_conversion<__nv_bfloat162, uint16_t>((uint16_t)a, scale);
137 res.y = scaled_vec_conversion<__nv_bfloat162, uint16_t>((uint16_t)(a >> 16U),
138 scale);
139 return res;
140 }
141
142 // fp8x8 -> bf16_8_t
143 template <>
144 __inline__ __device__ bf16_8_t
145 scaled_vec_conversion<bf16_8_t, uint2>(const uint2& a, float scale) {
146 bf16_4_t tmp1, tmp2;
147 tmp1 = scaled_vec_conversion<bf16_4_t, uint32_t>(a.x, scale);
148 tmp2 = scaled_vec_conversion<bf16_4_t, uint32_t>(a.y, scale);
149 bf16_8_t res;
150 res.x = tmp1.x;
151 res.y = tmp1.y;
152 res.z = tmp2.x;
153 res.w = tmp2.y;
154 return res;
155 }
156
157 // fp8 -> float
158 template <>
159 __inline__ __device__ float scaled_vec_conversion<float, uint8_t>(
160 const uint8_t& a, float scale) {
161 fp8_type f8;
162 f8.__x = a;
163 return static_cast<float>(f8) * scale;
164 }
165
166 // fp8x2 -> float2
167 template <>
168 __inline__ __device__ float2
169 scaled_vec_conversion<float2, uint16_t>(const uint16_t& a, float scale) {
170 fp8x2_type f8x2;
171 f8x2.__x = a;
172 return static_cast<float2>(f8x2) * scale;
173 }
174
175 // fp8x4 -> float4
176 template <>
177 __inline__ __device__ Float4_
178 scaled_vec_conversion<Float4_, uint32_t>(const uint32_t& a, const float scale) {
179 Float4_ res;
180 res.x = scaled_vec_conversion<float2, uint16_t>((uint16_t)a, scale);
181 res.y = scaled_vec_conversion<float2, uint16_t>((uint16_t)(a >> 16U), scale);
182 return res;
183 }
184
185 // fp8x4 -> float4
186 template <>
187 __inline__ __device__ float4
188 scaled_vec_conversion<float4, uint32_t>(const uint32_t& a, float scale) {
189 Float4_ res = scaled_vec_conversion<Float4_, uint32_t>(a, scale);
190 return {res.x.x, res.x.y, res.y.x, res.y.y};
191 }
192
193 // fp8x8 -> float8
194 template <>
195 __inline__ __device__ Float8_
196 scaled_vec_conversion<Float8_, uint2>(const uint2& a, float scale) {
197 Float4_ tmp1, tmp2;
198 tmp1 = scaled_vec_conversion<Float4_, uint32_t>(a.x, scale);
199 tmp2 = scaled_vec_conversion<Float4_, uint32_t>(a.y, scale);
200 Float8_ res;
201 res.x = tmp1.x;
202 res.y = tmp1.y;
203 res.z = tmp2.x;
204 res.w = tmp2.y;
205 return res;
206 }
207
208 // fp8 -> half
209 template <>
210 __inline__ __device__ uint16_t
211 scaled_vec_conversion<uint16_t, uint8_t>(const uint8_t& a, float scale) {
212 __half_raw res;
213 res.data = scaled_vec_conversion<float, uint8_t>(a, scale);
214 return res.x;
215 }
216
217 // fp8x2 -> half2
218 template <>
219 __inline__ __device__ uint32_t
220 scaled_vec_conversion<uint32_t, uint16_t>(const uint16_t& a, float scale) {
221 union {
222 __half2_raw h2r;
223 uint32_t ui32;
224 } tmp;
225 tmp.h2r = __hip_cvt_fp8x2_to_halfraw2(a, fp8_type::__default_interpret);
226 tmp.h2r.x.data *= scale;
227 tmp.h2r.y.data *= scale;
228 return tmp.ui32;
229 }
230
231 // fp8x4 -> half2x2
232 template <>
233 __inline__ __device__ uint2
234 scaled_vec_conversion<uint2, uint32_t>(const uint32_t& a, float scale) {
235 union {
236 uint2 u32x2;
237 uint32_t u32[2];
238 } tmp;
239 tmp.u32[0] = scaled_vec_conversion<uint32_t, uint16_t>((uint16_t)a, scale);
240 tmp.u32[1] =
241 scaled_vec_conversion<uint32_t, uint16_t>((uint16_t)(a >> 16U), scale);
242 return tmp.u32x2;
243 }
244
245 // fp8x8 -> half2x4
246 template <>
247 __inline__ __device__ uint4 scaled_vec_conversion<uint4, uint2>(const uint2& a,
248 float scale) {
249 union {
250 uint4 u64x2;
251 uint2 u64[2];
252 } tmp;
253 tmp.u64[0] = scaled_vec_conversion<uint2, uint32_t>(a.x, scale);
254 tmp.u64[1] = scaled_vec_conversion<uint2, uint32_t>(a.y, scale);
255 return tmp.u64x2;
256 }
257
258 // half -> fp8
259 template <>
260 __inline__ __device__ uint8_t
261 scaled_vec_conversion<uint8_t, uint16_t>(const uint16_t& a, float scale) {
262 __half_raw tmp;
263 tmp.x = a;
264 tmp.data /= scale;
265 return __hip_cvt_halfraw_to_fp8(tmp, fp8_type::__default_saturation,
266 fp8_type::__default_interpret);
267 }
268
269 // halfx2 -> fp8x2
270 template <>
271 __inline__ __device__ uint16_t
272 scaled_vec_conversion<uint16_t, uint32_t>(const uint32_t& a, float scale) {
273 union {
274 uint32_t ui32;
275 __half2_raw h2r;
276 } tmp;
277 tmp.ui32 = a;
278 tmp.h2r.x.data /= scale;
279 tmp.h2r.y.data /= scale;
280 return __hip_cvt_halfraw2_to_fp8x2(tmp.h2r, fp8_type::__default_saturation,
281 fp8_type::__default_interpret);
282 }
283
284 // half2x2 -> fp8x4
285 template <>
286 __inline__ __device__ uint32_t
287 scaled_vec_conversion<uint32_t, uint2>(const uint2& a, float scale) {
288 union {
289 uint16_t ui16[2];
290 uint32_t ui32;
291 } tmp;
292 tmp.ui16[0] = scaled_vec_conversion<uint16_t, uint32_t>(a.x, scale);
293 tmp.ui16[1] = scaled_vec_conversion<uint16_t, uint32_t>(a.y, scale);
294 return tmp.ui32;
295 }
296
297 // half2x4 -> fp8x8
298 template <>
299 __inline__ __device__ uint2 scaled_vec_conversion<uint2, uint4>(const uint4& a,
300 float scale) {
301 union {
302 uint2 ui2[2];
303 uint4 ui4;
304 } tmp;
305 tmp.ui4 = a;
306 uint2 res;
307 res.x = scaled_vec_conversion<uint32_t, uint2>(tmp.ui2[0], scale);
308 res.y = scaled_vec_conversion<uint32_t, uint2>(tmp.ui2[1], scale);
309 return res;
310 }
311
312 // bf16 -> fp8
313 template <>
314 __inline__ __device__ uint8_t scaled_vec_conversion<uint8_t, __nv_bfloat16>(
315 const __nv_bfloat16& a, float scale) {
316 return __hip_cvt_float_to_fp8(__bfloat162float(a) / scale,
317 fp8_type::__default_saturation,
318 fp8_type::__default_interpret);
319 }
320
321 // bf16x2 -> fp8x2
322 template <>
323 __inline__ __device__ uint16_t scaled_vec_conversion<uint16_t, __nv_bfloat162>(
324 const __nv_bfloat162& a, float scale) {
325 union {
326 uint8_t ui8[2];
327 uint16_t ui16;
328 } tmp;
329 tmp.ui8[0] = scaled_vec_conversion<uint8_t, __nv_bfloat16>(a.x, scale);
330 tmp.ui8[1] = scaled_vec_conversion<uint8_t, __nv_bfloat16>(a.y, scale);
331 return tmp.ui16;
332 }
333
334 // bf16x4 -> fp8x4
335 template <>
336 __inline__ __device__ uint32_t
337 scaled_vec_conversion<uint32_t, bf16_4_t>(const bf16_4_t& a, float scale) {
338 union {
339 uint16_t ui16[2];
340 uint32_t ui32;
341 } tmp;
342 tmp.ui16[0] = scaled_vec_conversion<uint16_t, __nv_bfloat162>(a.x, scale);
343 tmp.ui16[1] = scaled_vec_conversion<uint16_t, __nv_bfloat162>(a.y, scale);
344 return tmp.ui32;
345 }
346
347 // bf16x8 -> fp8x8
348 template <>
349 __inline__ __device__ uint2
350 scaled_vec_conversion<uint2, bf16_8_t>(const bf16_8_t& a, float scale) {
351 uint2 res;
352 res.x = scaled_vec_conversion<uint32_t, bf16_4_t>({a.x, a.y}, scale);
353 res.y = scaled_vec_conversion<uint32_t, bf16_4_t>({a.z, a.w}, scale);
354 return res;
355 }
356
357 // float -> fp8
358 template <>
359 __inline__ __device__ uint8_t
360 scaled_vec_conversion<uint8_t, float>(const float& a, float scale) {
361 return __hip_cvt_float_to_fp8(a / scale, fp8_type::__default_saturation,
362 fp8_type::__default_interpret);
363 }
364
365 // floatx2 -> fp8x2
366 template <>
367 __inline__ __device__ uint16_t
368 scaled_vec_conversion<uint16_t, float2>(const float2& a, float scale) {
369 return __hip_cvt_float2_to_fp8x2(a / scale, fp8_type::__default_saturation,
370 fp8_type::__default_interpret);
371 }
372
373 // floatx4 -> fp8x4
374 template <>
375 __inline__ __device__ uint32_t
376 scaled_vec_conversion<uint32_t, float4>(const float4& a, float scale) {
377 union {
378 uint16_t ui16[2];
379 uint32_t ui32;
380 } tmp;
381 tmp.ui16[0] = scaled_vec_conversion<uint16_t, float2>({a.x, a.y}, scale);
382 tmp.ui16[1] = scaled_vec_conversion<uint16_t, float2>({a.z, a.w}, scale);
383 return tmp.ui32;
384 }
385 #endif // ENABLE_FP8
386
387 template <typename Tout, typename Tin, Fp8KVCacheDataType kv_dt>
388 __inline__ __device__ Tout scaled_convert(const Tin& x, const float scale) {
389 #ifdef ENABLE_FP8
390 if constexpr (kv_dt == Fp8KVCacheDataType::kFp8E4M3) {
391 return scaled_vec_conversion<Tout, Tin>(x, scale);
392 }
393 #endif
394 assert(false);
395 return {}; // Squash missing return statement warning
396 }
397
398 // The following macro is used to dispatch the conversion function based on
399 // the data type of the key and value cache. The FN is a macro that calls a
400 // function with template<typename scalar_t, typename cache_t,
401 // Fp8KVCacheDataType kv_dt>.
402 #define DISPATCH_BY_KV_CACHE_DTYPE(SRC_DTYPE, KV_DTYPE, FN) \
403 vllm::Fp8KVCacheDataType KV_CACHE_DTYPE = \
404 vllm::get_fp8_kv_cache_data_type(KV_DTYPE); \
405 if (KV_CACHE_DTYPE == vllm::Fp8KVCacheDataType::kAuto) { \
406 if (SRC_DTYPE == torch::headeronly::ScalarType::Float) { \
407 FN(float, float, vllm::Fp8KVCacheDataType::kAuto); \
408 } else if (SRC_DTYPE == torch::headeronly::ScalarType::Half) { \
409 FN(uint16_t, uint16_t, vllm::Fp8KVCacheDataType::kAuto); \
410 } else if (SRC_DTYPE == torch::headeronly::ScalarType::BFloat16) { \
411 FN(__nv_bfloat16, __nv_bfloat16, vllm::Fp8KVCacheDataType::kAuto); \
412 } else { \
413 STD_TORCH_CHECK(false, \
414 "Unsupported input type of kv cache: ", SRC_DTYPE); \
415 } \
416 } else if (KV_CACHE_DTYPE == vllm::Fp8KVCacheDataType::kFp8E4M3) { \
417 if (SRC_DTYPE == torch::headeronly::ScalarType::Float) { \
418 FN(float, uint8_t, vllm::Fp8KVCacheDataType::kFp8E4M3); \
419 } else if (SRC_DTYPE == torch::headeronly::ScalarType::Half) { \
420 FN(uint16_t, uint8_t, vllm::Fp8KVCacheDataType::kFp8E4M3); \
421 } else if (SRC_DTYPE == torch::headeronly::ScalarType::BFloat16) { \
422 FN(__nv_bfloat16, uint8_t, vllm::Fp8KVCacheDataType::kFp8E4M3); \
423 } else { \
424 STD_TORCH_CHECK(false, \
425 "Unsupported input type of kv cache: ", SRC_DTYPE); \
426 } \
427 } else { \
428 STD_TORCH_CHECK(false, "Unsupported data type of kv cache: ", KV_DTYPE); \
429 }
430
431 } // namespace fp8
432 #endif // USE_ROCM
433 } // namespace vllm