@@ -484,13 +484,7 @@ __device__ __forceinline__ void update_mdo_states(
484484 float2 fp2_scale = make_float2 (o_scale, o_scale);
485485#pragma unroll
486486 for (uint32_t fy = 0 ; fy < num_frags_y; ++fy) {
487- // o_frag[fx][fy][j * 2 + 0] *= o_scale;
488- // o_frag[fx][fy][j * 2 + 1] *= o_scale;
489- // o_frag[fx][fy][j * 2 + 4] *= o_scale;
490- // o_frag[fx][fy][j * 2 + 5] *= o_scale;
491-
492487 float2 * o_frag_ptr = reinterpret_cast <float2 *>(o_frag[fx][fy] + j_id);
493- // printf("fp2_len:%d, %d", sizeof(o_frag_ptr[0]), sizeof(fp2_scale));
494488 o_frag_ptr[0 ] = fast_float2_mul (o_frag_ptr[0 ], fp2_scale);
495489 o_frag_ptr[2 ] = fast_float2_mul (o_frag_ptr[2 ], fp2_scale);
496490 }
@@ -502,14 +496,6 @@ __device__ __forceinline__ void update_mdo_states(
502496 s_frag_ptr[1 ] = __expf (s_frag_ptr[1 ] - tmp_m);
503497 s_frag_ptr[4 ] = __expf (s_frag_ptr[4 ] - tmp_m);
504498 s_frag_ptr[5 ] = __expf (s_frag_ptr[5 ] - tmp_m);
505- // s_frag[fx][fz][j * 2 + 0] =
506- // __expf(s_frag[fx][fz][j * 2 + 0] - m[fx][j]);
507- // s_frag[fx][fz][j * 2 + 1] =
508- // __expf(s_frag[fx][fz][j * 2 + 1] - m[fx][j]);
509- // s_frag[fx][fz][j * 2 + 4] =
510- // __expf(s_frag[fx][fz][j * 2 + 4] - m[fx][j]);
511- // s_frag[fx][fz][j * 2 + 5] =
512- // __expf(s_frag[fx][fz][j * 2 + 5] - m[fx][j]);
513499 }
514500 }
515501 }
@@ -1043,8 +1029,9 @@ __global__ void merge_chunks_kernel(
10431029 const int max_tokens_per_batch = 5 ) {
10441030 const int vid = threadIdx .x , ty = threadIdx .y ;
10451031 const int hid = blockIdx .y ;
1046- __shared__ T smem[bdy * HEAD_DIM ];
1047- __shared__ float md_smem[bdy * 2 ];
1032+ // After intra-warp reduction, only bdy/2 results need smem storage
1033+ __shared__ T smem[(bdy / 2 ) * HEAD_DIM ];
1034+ __shared__ float md_smem[(bdy / 2 ) * 2 ];
10481035#if (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 900))
10491036 cudaGridDependencySynchronize ();
10501037#endif
@@ -1055,6 +1042,7 @@ __global__ void merge_chunks_kernel(
10551042 qid += gridDim .x * bdy) {
10561043 const uint32_t bid = batch_id_per_token[qid];
10571044 if (bid == (uint32_t )-1 ) continue ;
1045+ if (seq_lens_encoder[bid] > 0 ) continue ; // skip prefill batches
10581046 const uint32_t local_seq_id = qid - cu_seqlens_q[bid];
10591047 const int seq_len_q = seq_lens_q[bid];
10601048 if (seq_len_q == 0 ) continue ;
@@ -1075,31 +1063,28 @@ __global__ void merge_chunks_kernel(
10751063 load_vec, &out[(qid * num_heads + hid) * head_dim + vid * vec_size]);
10761064 }
10771065
1078- // Phase 2: Slow path — all ty cooperate on same qid (uses smem + syncthreads)
1066+ // Phase 2: Slow path — merge multi-chunk results
1067+ // Optimization: use warp-shuffle reduction within each warp, then cross-warp
1068+ // via smem. This eliminates the large smem[bdy * HEAD_DIM] buffer and reduces
1069+ // syncthreads from 2 per qid to 1 per qid.
1070+ // Block layout: (blockx=16, bdy=8) => 4 warps, each warp has 2 ty values
1071+ // Warp 0: ty=0,1 Warp 1: ty=2,3 Warp 2: ty=4,5 Warp 3: ty=6,7
1072+ // Lane layout within warp: lanes 0-15 = (ty_low, vid), lanes 16-31 =
1073+ // (ty_high, vid)
1074+ const int lane_id = (ty * blockDim .x + vid) % 32 ;
1075+
10791076 for (int qid = blockIdx .x ; qid < token_num; qid += gridDim .x ) {
10801077 const uint32_t bid = batch_id_per_token[qid];
1081- if (bid == (uint32_t )-1 ) {
1082- __syncthreads ();
1083- continue ;
1084- }
1078+ if (bid == (uint32_t )-1 ) continue ; // uniform skip — no syncthreads needed
1079+ if (seq_lens_encoder[bid] > 0 ) continue ;
10851080 const uint32_t local_seq_id = qid - cu_seqlens_q[bid];
10861081 const int seq_len_q = seq_lens_q[bid];
1087- if (seq_len_q == 0 ) {
1088- __syncthreads ();
1089- continue ;
1090- }
1082+ if (seq_len_q == 0 ) continue ;
10911083 int seq_len_kv = seq_lens_kv[bid];
1092- if (seq_len_kv == 0 ) {
1093- __syncthreads ();
1094- continue ;
1095- }
1084+ if (seq_len_kv == 0 ) continue ;
10961085 seq_len_kv += seq_len_q;
10971086 const int num_chunks_this_seq = div_up (seq_len_kv, *chunk_size_ptr);
1098- if (num_chunks_this_seq == 1 ) {
1099- // Already handled in Phase 1
1100- __syncthreads ();
1101- continue ;
1102- }
1087+ if (num_chunks_this_seq == 1 ) continue ; // handled in Phase 1
11031088
11041089 LoadT load_vec;
11051090 LoadT res_vec;
@@ -1121,6 +1106,9 @@ __global__ void merge_chunks_kernel(
11211106 } else if constexpr (std::is_same<T, __nv_bfloat16>::value) {
11221107 m = -3 .0e+30f ;
11231108 }
1109+
1110+ // Step 1: Each ty iterates over its chunk subset and does local online
1111+ // softmax merge
11241112#pragma unroll 2
11251113 for (int i = ty; i < num_chunks_this_seq; i += bdy) {
11261114 uint32_t offset;
@@ -1149,17 +1137,53 @@ __global__ void merge_chunks_kernel(
11491137 res_vec[j] = res_vec[j] * scale1_T + load_vec[j] * scale2_T;
11501138 }
11511139 }
1152- // store ty res
1153- Store<T, vec_size>(res_vec, &smem[ty * head_dim + vid * vec_size]);
1154- md_smem[2 * ty] = m;
1155- md_smem[2 * ty + 1 ] = d;
1140+
1141+ // Step 2: Intra-warp reduction via warp shuffle
1142+ // Each warp has 2 ty values: ty_low at lanes 0-15, ty_high at lanes 16-31
1143+ // Merge ty_high into ty_low using shuffle
1144+ {
1145+ // Determine the partner ty in the same warp
1146+ // ty_low = ty & ~1, ty_high = ty | 1
1147+ const int partner_lane = lane_id ^ 16 ; // flip bit 4 to swap low/high ty
1148+ const float m_partner = __shfl_sync (0xffffffff , m, partner_lane);
1149+ const float d_partner = __shfl_sync (0xffffffff , d, partner_lane);
1150+ LoadT partner_vec;
1151+ #pragma unroll
1152+ for (int j = 0 ; j < vec_size; j++) {
1153+ partner_vec[j] = __shfl_sync (
1154+ 0xffffffff , reinterpret_cast <unsigned &>(res_vec[j]), partner_lane);
1155+ }
1156+
1157+ // Merge partner into self (only the "low ty" keeps the result)
1158+ float m_new = max (m, m_partner);
1159+ const float scale1 = __expf (m - m_new);
1160+ const float scale2 = __expf (m_partner - m_new);
1161+ float d_new = d * scale1 + d_partner * scale2;
1162+ if ((ty & 1 ) == 0 ) { // low ty keeps merged result
1163+ m = m_new;
1164+ d = d_new;
1165+ const T scale1_T = static_cast <T>(scale1);
1166+ const T scale2_T = static_cast <T>(scale2);
1167+ #pragma unroll
1168+ for (int j = 0 ; j < vec_size; j++) {
1169+ res_vec[j] = res_vec[j] * scale1_T + partner_vec[j] * scale2_T;
1170+ }
1171+ }
1172+ }
1173+
1174+ // Cross-warp: only even ty (0,2,4,6) write to smem
1175+ if ((ty & 1 ) == 0 ) {
1176+ Store<T, vec_size>(res_vec, &smem[(ty / 2 ) * head_dim + vid * vec_size]);
1177+ md_smem[ty] = m;
1178+ md_smem[ty + 1 ] = d;
1179+ }
11561180 __syncthreads ();
1181+
11571182 if (ty == 0 ) {
1158- // merge bdy
11591183 prefill_softmax_state_t <vec_size, T> st;
11601184 st.init ();
11611185#pragma unroll
1162- for (int i = 0 ; i < bdy; i++) {
1186+ for (int i = 0 ; i < bdy / 2 ; i++) {
11631187 Load<T, vec_size>(&smem[i * head_dim + vid * vec_size], &load_vec);
11641188 const float m_tmp = md_smem[2 * i], d_tmp = md_smem[2 * i + 1 ];
11651189 st.merge (load_vec, m_tmp, d_tmp);
0 commit comments