22// SPDX-License-Identifier: Apache-2.0
33
44#include < aie_api/aie.hpp>
5+ #include < math.h>
56#include < stdint.h>
67
78#define SM_VEC_LEN 64 // 32
@@ -30,7 +31,7 @@ void softmax_simple_bf16(bfloat16 *restrict input_vector, bfloat16 *restrict out
3031 aie::vector<bfloat16, SM_VEC_LEN > in_elems, exp_val, input_bf16, log2e_vec, max_val_vec;
3132 aie::accum<accfloat, SM_VEC_LEN > out_vals, exp_val_accum, scaled_accum, exp_in_accum;
3233
33- float max_val = 0 ;
34+ float max_val = - INFINITY ;
3435 float accum_exp_val = 0 ;
3536 float running_max = 0 ;
3637 bfloat16 col_sum_inv;
@@ -80,6 +81,17 @@ void softmax_simple_bf16(bfloat16 *restrict input_vector, bfloat16 *restrict out
8081 return ;
8182}
8283
84+ // partial_softmax_alias_bf16 is a flash-attention style single-shot softmax
85+ // used by the projected-fused path. It shares the same three-pass structure as
86+ // softmax_simple_bf16 (find max, exp, normalize) but differs in two ways that
87+ // make code sharing awkward: (1) it folds the query scale into the log2e
88+ // multiply instead of using the fixed log2e constant, and (2) it maintains
89+ // per-row running max/sum in an externally-supplied scale_buffer (flash
90+ // accumulation across key tiles) rather than reducing a whole row locally. The
91+ // softmax_partial_stats_impl / softmax_partial_norm_impl pair below implement a
92+ // different (chunked, two-call) online softmax that keeps its running stats in
93+ // a compact softmax_stats buffer; those are used by the standalone softmax
94+ // operator, not by this projected-fused kernel.
8395void partial_softmax_alias_bf16 (bfloat16 *restrict input_vector,
8496 bfloat16 *restrict output_vector,
8597 bfloat16 *restrict scale_buffer,
@@ -159,6 +171,104 @@ void partial_softmax_alias_bf16(bfloat16 *restrict input_vector,
159171 return ;
160172}
161173
174+ // Online (partial / tiled) softmax helpers.
175+ //
176+ // These kernels implement an online softmax that processes a row in sub-tile
177+ // chunks, keeping running max and sum statistics in a small per-core buffer.
178+ // The max is stored scaled by log2e and the sum accumulates exp2(x*log2e -
179+ // max), matching the exp2-based normalization used below. softmax_stats names
180+ // the two stats slots instead of using hard-coded array indices; it occupies
181+ // the first two bfloat16 elements of the stats buffer.
182+ struct softmax_stats {
183+ bfloat16 max; // running max (scaled by log2e)
184+ bfloat16 sum; // running sum of exp2(x*log2e - max)
185+ };
186+
187+ void softmax_partial_stats_impl (bfloat16 *restrict input, softmax_stats *restrict stats, const int32_t vector_size)
188+ {
189+ event0 ();
190+
191+ const int elem_iters = vector_size / SM_VEC_LEN ;
192+
193+ float running_max = (float )stats->max ;
194+ float running_sum = (float )stats->sum ;
195+
196+ aie::vector<bfloat16, SM_VEC_LEN > input_bf16;
197+ aie::accum<accfloat, SM_VEC_LEN > scaled_accum, exp_in_accum;
198+ aie::accum<accfloat, SM_VEC_LEN > exp_val_accum = aie::zeros<accfloat, SM_VEC_LEN >();
199+
200+ aie::vector<bfloat16, SM_VEC_LEN > log2e_vec = aie::broadcast<bfloat16, SM_VEC_LEN >((bfloat16)log2e);
201+
202+ auto it_in = aie::cbegin_restrict_vector<SM_VEC_LEN >((bfloat16 *)input);
203+
204+ // Single-pass online algorithm (matches aie_kernels/aie2/softmax.cc): for
205+ // each vector chunk, update the running max if needed -- rescaling the
206+ // partial accumulator and running sum by exp2(old_max - new_max) -- then
207+ // accumulate exp2(x*log2e - max).
208+ for (int i = 0 ; i < elem_iters; i++) {
209+ input_bf16 = *it_in++;
210+ scaled_accum = aie::mul (input_bf16, log2e_vec);
211+ float chunk_max = aie::reduce_max (scaled_accum.to_vector <bfloat16>());
212+
213+ if (chunk_max > running_max) {
214+ aie::vector<float , SM_VEC_LEN > diff_vec = aie::broadcast<float , SM_VEC_LEN >(running_max - chunk_max);
215+ aie::vector<bfloat16, SM_VEC_LEN > corr = aie::exp2<bfloat16>(diff_vec);
216+ float scale = (float )corr[0 ];
217+ aie::vector<bfloat16, SM_VEC_LEN > scale_vec = aie::broadcast<bfloat16, SM_VEC_LEN >((bfloat16)scale);
218+ exp_val_accum = aie::mul (exp_val_accum.to_vector <bfloat16>(), scale_vec);
219+ running_sum *= scale;
220+ running_max = chunk_max;
221+ }
222+
223+ aie::vector<bfloat16, SM_VEC_LEN > max_val_vec = aie::broadcast<bfloat16, SM_VEC_LEN >((bfloat16)running_max);
224+ exp_in_accum = aie::sub (scaled_accum, max_val_vec);
225+ aie::vector<bfloat16, SM_VEC_LEN > exp_val = aie::exp2<bfloat16>(exp_in_accum.to_vector <float >());
226+ exp_val_accum = add (exp_val_accum, exp_val);
227+ }
228+
229+ aie::vector<float , SM_VEC_LEN > reduce = exp_val_accum.to_vector <float >();
230+ running_sum += aie::reduce_add (reduce);
231+
232+ stats->max = (bfloat16)running_max;
233+ stats->sum = (bfloat16)running_sum;
234+
235+ event1 ();
236+ }
237+
238+ void softmax_partial_norm_impl (bfloat16 *restrict input,
239+ bfloat16 *restrict output,
240+ softmax_stats *restrict stats,
241+ const int32_t vector_size)
242+ {
243+ event0 ();
244+
245+ const int elem_iters = vector_size / SM_VEC_LEN ;
246+
247+ float max_val = (float )stats->max ;
248+ float sum_val = (float )stats->sum ;
249+ bfloat16 inv_sum = (bfloat16)aie::inv (sum_val);
250+
251+ aie::vector<bfloat16, SM_VEC_LEN > log2e_vec = aie::broadcast<bfloat16, SM_VEC_LEN >((bfloat16)log2e);
252+ aie::vector<bfloat16, SM_VEC_LEN > max_val_vec = aie::broadcast<bfloat16, SM_VEC_LEN >((bfloat16)max_val);
253+
254+ aie::vector<bfloat16, SM_VEC_LEN > input_bf16;
255+ aie::accum<accfloat, SM_VEC_LEN > scaled_accum, exp_in_accum, out_vals;
256+
257+ auto it_in = aie::cbegin_restrict_vector<SM_VEC_LEN >((bfloat16 *)input);
258+ auto it_out = aie::begin_restrict_vector<SM_VEC_LEN >((bfloat16 *)output);
259+
260+ for (int i = 0 ; i < elem_iters; i++) {
261+ input_bf16 = *it_in++;
262+ scaled_accum = aie::mul (input_bf16, log2e_vec);
263+ exp_in_accum = aie::sub (scaled_accum, max_val_vec);
264+ aie::vector<bfloat16, SM_VEC_LEN > exp_val = aie::exp2<bfloat16>(exp_in_accum.to_vector <float >());
265+ out_vals = aie::mul (exp_val, inv_sum);
266+ *it_out++ = out_vals.to_vector <bfloat16>();
267+ }
268+
269+ event1 ();
270+ }
271+
162272extern " C" {
163273
164274void softmax_bf16 (bfloat16 *restrict input, bfloat16 *restrict output, const int32_t input_size)
@@ -177,6 +287,25 @@ void partial_softmax_bf16(bfloat16 *restrict input,
177287 partial_softmax_alias_bf16 (input, output, scale_buffer, input_size, row_idx, num_rows, scale);
178288}
179289
290+ void softmax_partial_init_bf16 (softmax_stats *restrict stats)
291+ {
292+ stats->max = (bfloat16)(-INFINITY );
293+ stats->sum = (bfloat16)(0 .0f );
294+ }
295+
296+ void softmax_partial_stats_bf16 (bfloat16 *restrict input, softmax_stats *restrict stats, const int32_t vector_size)
297+ {
298+ softmax_partial_stats_impl (input, stats, vector_size);
299+ }
300+
301+ void softmax_partial_norm_bf16 (bfloat16 *restrict input,
302+ bfloat16 *restrict output,
303+ softmax_stats *restrict stats,
304+ const int32_t vector_size)
305+ {
306+ softmax_partial_norm_impl (input, output, stats, vector_size);
307+ }
308+
180309void mask_bf16 (bfloat16 *inout, const int32 unmasked_size, const int32 total_size)
181310{
182311 // TODO: Optimize this to use vector code
0 commit comments