@@ -74,8 +74,10 @@ void _q_at_k_gemm(
7474 accum_t * qk_data) {
7575 ET_CHECK_MSG (q_data.dtype == k_data.dtype , " q and k must have same dtype" );
7676 ET_CHECK_MSG (
77- q_data.dtype == ScalarType::Char || q_data.dtype == ScalarType::Float,
78- " q and k must be either int8 or float" );
77+ q_data.dtype == ScalarType::Char || q_data.dtype == ScalarType::Float ||
78+ q_data.dtype == ScalarType::Half ||
79+ q_data.dtype == ScalarType::BFloat16,
80+ " q and k must be int8, float, half, or bfloat16" );
7981 if (q_data.dtype == ScalarType::Char) {
8082 if constexpr (std::is_same<accum_t , float >::value) {
8183 int a_stride_m_tmp, b_stride_n_tmp;
@@ -103,6 +105,35 @@ void _q_at_k_gemm(
103105 ET_CHECK_MSG (
104106 false , " Accumulation in dtype other than float not supported yet" );
105107 }
108+ } else if (
109+ q_data.dtype == ScalarType::BFloat16 ||
110+ q_data.dtype == ScalarType::Half) {
111+ if constexpr (std::is_same<accum_t , float >::value) {
112+ auto do_gemm = [&](auto rt_tag) {
113+ using rt = decltype (rt_tag);
114+ ::executorch::cpublas::gemm (
115+ ::executorch::cpublas::TransposeType::Transpose,
116+ ::executorch::cpublas::TransposeType::NoTranspose,
117+ k_n,
118+ q_m,
119+ qk_k,
120+ 1 .0f ,
121+ static_cast <const rt*>(k_data.data),
122+ k_stride_n,
123+ static_cast<const rt*>(q_data.data),
124+ q_stride_m,
125+ 0.0f,
126+ qk_data,
127+ k_n);
128+ };
129+ if (q_data.dtype == ScalarType::BFloat16) {
130+ do_gemm (::executorch::aten::BFloat16{});
131+ } else {
132+ do_gemm (::executorch::aten::Half{});
133+ }
134+ } else {
135+ ET_CHECK_MSG (false , " Reduced-precision q@k requires float accumulation" );
136+ }
106137 } else {
107138 ::executorch::cpublas::gemm (
108139 ::executorch::cpublas::TransposeType::Transpose,
@@ -251,7 +282,7 @@ void _qk_at_v_gemm(
251282 const int64_t m,
252283 const int64_t n,
253284 const int64_t k,
254- const accum_t * qk_data,
285+ const void * qk_data,
255286 const int64_t qk_stride_m,
256287 const MaybeQuantizedMatrixData& v_data,
257288 const int64_t v_stride_n,
@@ -261,14 +292,15 @@ void _qk_at_v_gemm(
261292 accum_t * buf_qdq_ptr) {
262293 if (v_data.dtype == ScalarType::Char) {
263294 if constexpr (std::is_same<accum_t , float >::value) {
295+ const float * qk = static_cast <const float *>(qk_data);
264296 if (m > 4 ) {
265297 // For larger batch sizes, dequantize and use BLAS for better
266298 // performance
267299 dequant_and_gemm (
268300 m,
269301 n,
270302 k,
271- const_cast <float *>(qk_data ),
303+ const_cast <float *>(qk ),
272304 qk_stride_m,
273305 v_data,
274306 v_stride_n,
@@ -286,7 +318,7 @@ void _qk_at_v_gemm(
286318 m,
287319 n,
288320 k,
289- qk_data ,
321+ qk ,
290322 qk_stride_m /* lhs_stride_m*/ ,
291323 static_cast <const int8_t *>(v_data.data ),
292324 v_stride_n /* rhs_stride_n*/ ,
@@ -301,6 +333,37 @@ void _qk_at_v_gemm(
301333 ET_CHECK_MSG (
302334 false , " Accumulation in dtype other than float not supported yet" );
303335 }
336+ } else if (
337+ v_data.dtype == ScalarType::BFloat16 ||
338+ v_data.dtype == ScalarType::Half) {
339+ // qk has been cast to the activation dtype (see qk_reduced_data); both
340+ // operands are reduced precision and accumulate into the float output.
341+ if constexpr (std::is_same<accum_t , float >::value) {
342+ auto do_gemm = [&](auto rt_tag) {
343+ using rt = decltype (rt_tag);
344+ ::executorch::cpublas::gemm (
345+ ::executorch::cpublas::TransposeType::NoTranspose,
346+ ::executorch::cpublas::TransposeType::NoTranspose,
347+ n,
348+ m,
349+ k,
350+ 1 .0f ,
351+ static_cast <const rt*>(v_data.data),
352+ v_stride_n,
353+ static_cast<const rt*>(qk_data),
354+ qk_stride_m,
355+ beta,
356+ o_data,
357+ o_stride_m);
358+ };
359+ if (v_data.dtype == ScalarType::BFloat16) {
360+ do_gemm (::executorch::aten::BFloat16{});
361+ } else {
362+ do_gemm (::executorch::aten::Half{});
363+ }
364+ } else {
365+ ET_CHECK_MSG (false , " Reduced-precision qk@v requires float accumulation" );
366+ }
304367 } else {
305368 ::executorch::cpublas::gemm (
306369 ::executorch::cpublas::TransposeType::NoTranspose,
@@ -311,7 +374,7 @@ void _qk_at_v_gemm(
311374 static_cast <accum_t >(1 ),
312375 static_cast<const accum_t*>(v_data.data),
313376 v_stride_n,
314- qk_data,
377+ static_cast< const accum_t*>( qk_data) ,
315378 qk_stride_m,
316379 beta,
317380 o_data,
@@ -572,11 +635,9 @@ void cpu_flash_attention(
572635 constexpr bool is_reduced_type =
573636 ::executorch::runtime::is_reduced_floating_point_v<scalar_t >;
574637
575- ET_CHECK_MSG (
576- !is_reduced_type, " FlashAttention does not support reduced types." );
577- // Figure out mixed precision a little later
578- // using accum_t = at::opmath_type<scalar_t>;
579- using accum_t = scalar_t ;
638+ // Reduced-precision (bf16/fp16) activations accumulate in float: the two
639+ // matmuls run as reduced-in/float-out gemms and the softmax stays in float.
640+ using accum_t = std::conditional_t <is_reduced_type, float , scalar_t >;
580641 using Vec = vec::Vectorized<accum_t >;
581642 accum_t scaling_factor = static_cast <accum_t >(calculate_scale (query, scale));
582643
@@ -774,7 +835,19 @@ void cpu_flash_attention(
774835 } else {
775836 buf = scratch.get ();
776837 }
838+ std::unique_ptr<char []> allocated_buf_reduced;
777839 void * buf_reduced = nullptr ;
840+ if (is_reduced_type) {
841+ int64_t size_reduced_bytes =
842+ qSplitSize * kvSplitSize * num_thread * sizeof (scalar_t );
843+ Result<void *> scratch_reduced = ctx.allocate_temp (size_reduced_bytes, 64 );
844+ if (!scratch_reduced.ok ()) {
845+ allocated_buf_reduced = std::make_unique<char []>(size_reduced_bytes);
846+ buf_reduced = allocated_buf_reduced.get ();
847+ } else {
848+ buf_reduced = scratch_reduced.get ();
849+ }
850+ }
778851 int64_t size_per_thread_qdq_vec = kvSplitSize * headSize;
779852 // Lets align size_per_thread_qdq_vec to 64 bytes, for coalesced cache reads,
780853 // by padding with right number of per thread elements
@@ -1015,18 +1088,16 @@ void cpu_flash_attention(
10151088 if (tmp_max == -std::numeric_limits<accum_t >::infinity ()) {
10161089 // to avoid `nan = exp2f(-inf - (-inf))`
10171090 fill_stub (
1018- conditional_data_ptr (qk_data, qk_reduced_data) +
1019- row * kvBlockSize,
1020- static_cast <scalar_t >(0 ),
1091+ qk_data + row * kvBlockSize,
1092+ static_cast <accum_t >(0 ),
10211093 kvBlockSize);
10221094 } else {
10231095 // qk <- exp(qk - max) and sum per row
10241096 tmp_sum = tmp_max;
10251097 _exp_reduce_sum_fusion_kernel (
10261098 qk_data + row * kvBlockSize,
10271099 kvBlockSize,
1028- conditional_data_ptr (qk_data, qk_reduced_data) +
1029- row * kvBlockSize,
1100+ qk_data + row * kvBlockSize,
10301101 tmp_sum);
10311102 // exp_tmp <- exp(max[row] - max)
10321103 exp_tmp = std::exp (qk_max_data[row] - tmp_max);
@@ -1068,12 +1139,23 @@ void cpu_flash_attention(
10681139 headSize,
10691140 v_quant_params_StrideN,
10701141 value.scalar_type ());
1142+ // For reduced-precision activations the attention weights are cast to
1143+ // the activation dtype so that Softmax(q @ k.T) @ v runs as a
1144+ // reduced-in/float-out gemm matching the value matrix.
1145+ const void * qk_gemm_data = qk_data;
1146+ if constexpr (is_reduced_type) {
1147+ if (!is_quantized_sdpa) {
1148+ vec::convert<accum_t , scalar_t >(
1149+ qk_data, qk_reduced_data, qBlockSize * kvBlockSize);
1150+ qk_gemm_data = qk_reduced_data;
1151+ }
1152+ }
10711153 // Calculate Softmax(q @ k.T) @ v
10721154 _qk_at_v_gemm<accum_t >(
10731155 qBlockSize,
10741156 headSize,
10751157 kvBlockSize,
1076- qk_data ,
1158+ qk_gemm_data ,
10771159 kvBlockSize,
10781160 v_sub_matrix_data,
10791161 vStrideN,
@@ -1086,12 +1168,20 @@ void cpu_flash_attention(
10861168 // reorder MHA output with strides
10871169 for (int64_t row = 0 ; row < qBlockSize; ++row) {
10881170 accum_t sum_reciprocal = 1 / qk_sum_data[row];
1089- vec::map<scalar_t >(
1090- [sum_reciprocal](Vec x) { return x * Vec (sum_reciprocal); },
1091- out_data + i * oStrideB + j * oStrideH + m * oStrideM +
1092- row * oStrideM,
1093- dst_data + row * headSize,
1094- headSize);
1171+ scalar_t * out_row = out_data + i * oStrideB + j * oStrideH +
1172+ m * oStrideM + row * oStrideM;
1173+ const accum_t * dst_row = dst_data + row * headSize;
1174+ if constexpr (is_reduced_type) {
1175+ for (int64_t d = 0 ; d < headSize; ++d) {
1176+ out_row[d] = static_cast <scalar_t >(dst_row[d] * sum_reciprocal);
1177+ }
1178+ } else {
1179+ vec::map<scalar_t >(
1180+ [sum_reciprocal](Vec x) { return x * Vec (sum_reciprocal); },
1181+ out_row,
1182+ dst_row,
1183+ headSize);
1184+ }
10951185 }
10961186 // Move to the next query
10971187 data_index_step (i, batchSize, j, num_head, k, qSlice);
0 commit comments