Skip to content

Commit bd43656

Browse files
committed
fix merge
1 parent 3cb6c35 commit bd43656

3 files changed

Lines changed: 93 additions & 62 deletions

File tree

custom_ops/gpu_ops/append_attention/attention_func.cuh

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

custom_ops/gpu_ops/append_attention/config_for_attention.cu

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,11 @@ __global__ void config_decode_attn(const int *__restrict__ seq_lens_this_time,
121121
// already fill all SMs. When splitting is needed, use the LARGEST
122122
// chunk_size that still creates enough blocks to fill SMs, minimizing
123123
// merge count while ensuring SM utilization.
124-
// Target: at least sm_count blocks to cover all SMs (1 block/SM minimum).
125-
const int target_blocks = config_gridx / 4; // sm_count
124+
// Target: at least sm_count*4 blocks to ensure 4+ waves for GPU utilization.
125+
// Too few waves (e.g. 2 waves with target=sm_count*2) leaves SMs idle between
126+
// waves; 4 waves is a balanced tradeoff between utilization and merge
127+
// overhead.
128+
const int target_blocks = config_gridx / 4; // sm_count * 4
126129
const bool use_scheme_e = (num_block_no_chunk >= target_blocks);
127130

128131
if (use_scheme_e) {

fastdeploy/model_executor/layers/attention/flash_attn_backend.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -521,11 +521,14 @@ def forward_mixed(
521521
getattr(layer, "quant_min_bound", 0.0),
522522
self.speculative_method is not None,
523523
)
524-
res_decoder = paddle.empty(
525-
[qkv.shape[0], self.num_heads * self.head_dim],
526-
dtype=qkv.dtype,
527-
)
528-
res_decoder = decode_append_attention(
524+
if use_fa_do_prefill:
525+
res_decoder = res_encoder
526+
else:
527+
res_decoder = paddle.empty(
528+
[qkv.shape[0], self.num_heads * self.head_dim],
529+
dtype=qkv.dtype,
530+
)
531+
decode_append_attention(
529532
qkv_out,
530533
cache_k,
531534
cache_v,
@@ -559,6 +562,7 @@ def forward_mixed(
559562
self.speculate_max_draft_token_num + 1,
560563
self.causal,
561564
)
565+
return res_decoder
562566
else:
563567
res_decoder = append_attention(
564568
qkv,
@@ -615,18 +619,18 @@ def forward_mixed(
615619
self.speculative_method is not None,
616620
)
617621

618-
if use_fa_do_prefill:
619-
merge_prefill_decode_output(
620-
res_encoder,
621-
res_decoder,
622-
forward_meta.seq_lens_encoder,
623-
forward_meta.seq_lens_decoder,
624-
forward_meta.seq_lens_this_time,
625-
forward_meta.cu_seqlens_q,
626-
self.num_heads,
627-
self.head_dim,
628-
self.speculate_max_draft_token_num + 1,
629-
)
630-
return res_encoder
631-
else:
632-
return res_decoder
622+
if use_fa_do_prefill:
623+
merge_prefill_decode_output(
624+
res_encoder,
625+
res_decoder,
626+
forward_meta.seq_lens_encoder,
627+
forward_meta.seq_lens_decoder,
628+
forward_meta.seq_lens_this_time,
629+
forward_meta.cu_seqlens_q,
630+
self.num_heads,
631+
self.head_dim,
632+
self.speculate_max_draft_token_num + 1,
633+
)
634+
return res_encoder
635+
else:
636+
return res_decoder

0 commit comments

Comments
 (0)