@@ -72,7 +72,6 @@ __global__ void dequantize_int4_cache_kernel(
7272 cache_V_dq // [B][MAX_T][N_KVH][D_H]
7373) {
7474 auto N_KVH = cache_K.size (2 );
75- auto MAX_T = cache_K.size (1 );
7675 auto D_H = cache_K_dq.size (3 );
7776
7877 auto b = blockIdx .x ;
@@ -2331,6 +2330,112 @@ at::Tensor xpos_qkv_decoding(
23312330 return XQ_O ;
23322331}
23332332
2333+ #if (defined(USE_ROCM))
2334+ /* *
2335+ * Converts the contents of a FP8 KV cache from e4m3fn (NV) to e4m3fnuz (AMD).
2336+ * These formats differ in their support for negative zero, and in their
2337+ * exponent bias. Negative zeros are replaced with positive zero, and the scale
2338+ * qparam is multiplied by 2.0, because we know that the scale will be applied
2339+ * to the k/v value and is equivalent to recomputing the exponent bias.
2340+ *
2341+ * This in an inplace operation.
2342+ *
2343+ * It is assumed that inputs will have been generated with scale_ub = max(fp16)
2344+ * / 2 to avoid overflow. Some debug mode assertions are in place, but there are
2345+ * no runtime guarantees.
2346+ *
2347+ * As written, this kernel is only valid on AMD, because it relies on threads
2348+ * 32-63 to convert the V tensors. NV only has threads 0-31 per warp.
2349+ */
2350+ __global__ void convert_e4m3fn_kv_cache_to_e4m3fnuz_inplace_kernel (
2351+ at::PackedTensorAccessor64<uint8_t , 4 , at::RestrictPtrTraits>
2352+ cache_K, // [B][MAX_T][N_KVH][D_H]
2353+ at::PackedTensorAccessor64<uint8_t , 4 , at::RestrictPtrTraits>
2354+ cache_V, // [B][MAX_T][N_KVH][D_H]
2355+ at::PackedTensorAccessor64<uint32_t , 4 , at::RestrictPtrTraits> qparam_K,
2356+ at::PackedTensorAccessor64<uint32_t , 4 , at::RestrictPtrTraits> qparam_V) {
2357+ auto N_KVH = cache_K.size (2 );
2358+ auto MAX_T = cache_K.size (1 );
2359+ auto D_H = cache_K.size (3 );
2360+ CUDA_KERNEL_ASSERT (D_H == 128 );
2361+
2362+ auto b = blockIdx .x ;
2363+ int h = 0 , t = 0 ;
2364+ uint8_t * head;
2365+ __half* qparam_scale_ptr;
2366+
2367+ for (auto t_h = threadIdx .y + blockIdx .y * blockDim .y ; t_h < MAX_T * N_KVH ;
2368+ t_h += blockDim .y * gridDim .y ) {
2369+ h = t_h % N_KVH ;
2370+ t = t_h / N_KVH ;
2371+
2372+ auto tidx = threadIdx .x ;
2373+ if (threadIdx .x < 32 ) {
2374+ head = &cache_K[b][t][h][0 ];
2375+ qparam_scale_ptr = reinterpret_cast <__half*>(&qparam_K[b][t][h][0 ]);
2376+ } else {
2377+ head = &cache_V[b][t][h][0 ];
2378+ qparam_scale_ptr = reinterpret_cast <__half*>(&qparam_V[b][t][h][0 ]);
2379+ tidx -= 32 ;
2380+ }
2381+ auto D_H_idx = tidx * 4 ; // Reading 4 bytes at once.
2382+
2383+ // Our only goal here is to detect negative zeros that are valid
2384+ // in e4m3fn, but not valid in e4m3fnuz, and overwrite them with zeros.
2385+ // E.g. 0x80708070u ^ 0x80808080u = 0xf000f0u & 0x80708070u = 0x00700070u
2386+ uint32_t packed_fp8x4_vals = *reinterpret_cast <uint32_t *>(&head[D_H_idx]);
2387+ *reinterpret_cast <uint32_t *>(&head[D_H_idx]) =
2388+ (packed_fp8x4_vals ^ 0x80808080 ) & packed_fp8x4_vals;
2389+
2390+ // Multiply qparam scale (member x) by 2 to compensate for the exponent
2391+ // bias difference (1) between e4m3fn and e4m3fnuz. We only need to do this
2392+ // once per row. In debug mode, assert that 2.0*scale as a float would not
2393+ // exceed the max value of __half.
2394+ if (tidx == 0 ) {
2395+ CUDA_KERNEL_ASSERT (
2396+ __half2float (*qparam_scale_ptr) * 2 .0f <=
2397+ __half2float (std::numeric_limits<__half>::max ()));
2398+ *qparam_scale_ptr = __float2half (__half2float (*qparam_scale_ptr) * 2 .0f );
2399+ }
2400+ }
2401+ }
2402+
2403+ void convert_e4m3fn_kv_cache_to_e4m3fnuz_inplace (
2404+ at::Tensor cache_K,
2405+ at::Tensor cache_V,
2406+ at::Tensor qparam_K,
2407+ at::Tensor qparam_V) {
2408+ TORCH_CHECK (cache_K.is_cuda ());
2409+ TORCH_CHECK (cache_V.is_cuda ());
2410+
2411+ auto B = cache_K.size (0 );
2412+
2413+ constexpr int32_t kMaxBlocks = 1 ;
2414+ dim3 blocks (B, std::max<int32_t >(1 , kMaxBlocks / B));
2415+ dim3 threads (kThreadsPerWarp , kWarpsPerBlock );
2416+
2417+ convert_e4m3fn_kv_cache_to_e4m3fnuz_inplace_kernel<<<
2418+ blocks,
2419+ threads,
2420+ 0 ,
2421+ at::cuda::getCurrentCUDAStream ()>>>(
2422+ cache_K.packed_accessor64<uint8_t , 4 , at::RestrictPtrTraits>(),
2423+ cache_V.packed_accessor64<uint8_t, 4, at::RestrictPtrTraits>(),
2424+ qparam_K.packed_accessor64<uint32_t, 4, at::RestrictPtrTraits>(),
2425+ qparam_V.packed_accessor64<uint32_t, 4, at::RestrictPtrTraits>());
2426+ C10_CUDA_KERNEL_LAUNCH_CHECK ();
2427+ }
2428+ #else
2429+ void convert_e4m3fn_kv_cache_to_e4m3fnuz_inplace (
2430+ at::Tensor cache_K,
2431+ at::Tensor cache_V,
2432+ at::Tensor qparam_K,
2433+ at::Tensor qparam_V) {
2434+ throw std::runtime_error (
2435+ " convert_e4m3fn_kv_cache_to_e4m3fnuz_inplace is only supported on AMD" );
2436+ }
2437+ #endif
2438+
23342439#if (defined(USE_ROCM) && ROCM_VERSION >= 60200) || \
23352440 (defined (CUDA_VERSION ) && CUDA_VERSION >= 12000 )
23362441
@@ -2550,7 +2655,6 @@ __global__ void dequantize_fp8_cache_kernel_paged(
25502655 int32_t block_tables_b_stride,
25512656 int32_t page_size) {
25522657 auto N_KVH = cache_K.size (2 );
2553- auto MAX_T = cache_K.size (1 );
25542658 auto D_H = cache_K_dq.size (3 );
25552659 auto D_H_q = cache_K.size (3 );
25562660 CUDA_KERNEL_ASSERT (D_H == 128 );
@@ -2647,20 +2751,20 @@ std::tuple<at::Tensor, at::Tensor> dequantize_fp8_cache(
26472751 auto D_H = (D_HQ - fp8_qparam_offset);
26482752
26492753 // TODO:
2650- // The below allocates Tensors that have the same shape as cache_K and cache_V
2651- // to store their dequantize results. For paged KV cache, this can be a bit
2652- // inefficient because it has the shape of [1 x (MAX_PAGES * PAGE_SIZE) x
2653- // N_KVH x D_H] to accommodate pages globally across batch instances, and
2654- // if we have very large MAX_PAGES then we are essentially allocating a very
2655- // huge Tensor here. The benefit is that the following users of this
2656- // dequantized results can reuse the existing block_tables to access their
2657- // elements. If we want to be more efficient, there are two possible
2658- // approaches: (1) Allocate a shorter Tensor here and store the dequantize
2659- // results in a more compact manner, but that requires creating a new
2660- // block_tables here and making sure the following users all use the
2661- // correct block_tables. (2) From outside, keep a persistent buffer that has a
2662- // matching shape with the original paged KV and feed the same buffer
2663- // into this function at every layer to reuse it and prevent allocation.
2754+ // The below allocates Tensors that have the same shape as cache_K and
2755+ // cache_V to store their dequantize results. For paged KV cache, this can
2756+ // be a bit inefficient because it has the shape of [1 x (MAX_PAGES *
2757+ // PAGE_SIZE) x N_KVH x D_H] to accommodate pages globally across batch
2758+ // instances, and if we have very large MAX_PAGES then we are essentially
2759+ // allocating a very huge Tensor here. The benefit is that the following
2760+ // users of this dequantized results can reuse the existing block_tables to
2761+ // access their elements. If we want to be more efficient, there are two
2762+ // possible approaches: (1) Allocate a shorter Tensor here and store the
2763+ // dequantize results in a more compact manner, but that requires creating a
2764+ // new block_tables here and making sure the following users all use the
2765+ // correct block_tables. (2) From outside, keep a persistent buffer that has
2766+ // a matching shape with the original paged KV and feed the same buffer into
2767+ // this function at every layer to reuse it and prevent allocation.
26642768
26652769 auto cache_K_dq = at::empty (
26662770 {B_KV , MAX_T , N_KVH , D_H }, cache_K.options ().dtype (at::kBFloat16 ));
@@ -2889,8 +2993,8 @@ at::Tensor quantize_qkv_per_head(
28892993 float * const scale_q_ptr = scale_q.data_ptr <float >();
28902994 // Launch the kernel
28912995 // TODO: Launch the kernel with B_T * N_H_L blocks only in case of decode.
2892- // Currently, we are launching the kernel with B_T * HH blocks for decode and
2893- // KV blocks just return when qparam_k is passed as nullptr.
2996+ // Currently, we are launching the kernel with B_T * HH blocks for decode
2997+ // and KV blocks just return when qparam_k is passed as nullptr.
28942998 quantizeQKVPerHead<<<
28952999 grid_size,
28963000 block_size,
0 commit comments