← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
topk_kernels.c
Go to the documentation of this file.
1/**
2 * @file topk_kernels.c
3 * @brief Top-K selection kernels for MoE router dispatch
4 *
5 * CK-ENGINE KERNEL RULES:
6 * =======================
7 * 1. NO malloc/free - memory via bump allocator, pointers passed in
8 * 2. NO OpenMP - parallelization at orchestrator/codegen layer
9 * 3. API must define: inputs, outputs, workspace, and memory layouts
10 * 4. Pure computation - deterministic, no side effects
11 *
12 * After changes: make test && make llamacpp-parity-full
13 *
14 * Provides efficient top-K selection from a score vector.
15 * Used in Mixture-of-Experts models to select which experts process each token.
16 *
17 * Operations:
18 * - topk_f32: Find top-K indices and values from N scores
19 * - topk_softmax_f32: Top-K with softmax normalization of selected scores
20 */
21
22#include <stdint.h>
23#include <stddef.h>
24#include <float.h>
25#include <math.h>
26
27#include "bf16_utils.h"
28
29#if defined(__AVX2__) || defined(__AVX512F__)
30#include <immintrin.h>
31#endif
32
33/* =============================================================================
34 * Top-K Selection (scalar reference)
35 *
36 * Finds the K largest values in an array and returns their indices and values.
37 * Uses a simple min-heap approach: maintain K best, replace minimum when better found.
38 *
39 * For small K (typical MoE: K=2-8), this is efficient. O(N*K) complexity.
40 * ============================================================================= */
41
42/**
43 * @brief Find top-K indices and values from a score vector
44 *
45 * @param scores Input scores [n]
46 * @param n Number of scores (e.g., number of experts)
47 * @param k Number of top scores to select
48 * @param indices Output: indices of top-K scores [k], sorted descending by value
49 * @param values Output: top-K score values [k], sorted descending (can be NULL)
50 */
51void topk_f32(const float *scores,
52 int n,
53 int k,
54 int *indices,
55 float *values)
56{
57 if (!scores || !indices || n <= 0 || k <= 0) {
58 return;
59 }
60
61 /* Clamp k to n */
62 if (k > n) {
63 k = n;
64 }
65
66 /* Initialize with first k elements */
67 float local_values[k];
68 for (int i = 0; i < k; i++) {
69 indices[i] = i;
70 local_values[i] = scores[i];
71 }
72
73 /* Find the minimum in our current top-k */
74 int min_idx = 0;
75 for (int i = 1; i < k; i++) {
76 if (local_values[i] < local_values[min_idx]) {
77 min_idx = i;
78 }
79 }
80
81 /* Scan remaining elements */
82 for (int i = k; i < n; i++) {
83 if (scores[i] > local_values[min_idx]) {
84 /* Replace the minimum */
85 indices[min_idx] = i;
86 local_values[min_idx] = scores[i];
87
88 /* Find new minimum */
89 min_idx = 0;
90 for (int j = 1; j < k; j++) {
91 if (local_values[j] < local_values[min_idx]) {
92 min_idx = j;
93 }
94 }
95 }
96 }
97
98 /* Sort results in descending order (simple insertion sort for small k) */
99 for (int i = 1; i < k; i++) {
100 float val = local_values[i];
101 int idx = indices[i];
102 int j = i - 1;
103 while (j >= 0 && local_values[j] < val) {
104 local_values[j + 1] = local_values[j];
105 indices[j + 1] = indices[j];
106 j--;
107 }
108 local_values[j + 1] = val;
109 indices[j + 1] = idx;
110 }
111
112 /* Copy values if output requested */
113 if (values) {
114 for (int i = 0; i < k; i++) {
115 values[i] = local_values[i];
116 }
117 }
118}
119
120/* =============================================================================
121 * Top-K with Softmax Normalization
122 *
123 * Finds top-K and normalizes the selected scores using softmax.
124 * This is the standard MoE gating: select experts, then compute routing weights.
125 * ============================================================================= */
126
127/**
128 * @brief Find top-K indices with softmax-normalized weights
129 *
130 * @param scores Input scores [n] (router logits)
131 * @param n Number of scores
132 * @param k Number of top scores to select
133 * @param indices Output: indices of top-K scores [k]
134 * @param weights Output: softmax-normalized weights for selected [k], sum to 1.0
135 */
136void topk_softmax_f32(const float *scores,
137 int n,
138 int k,
139 int *indices,
140 float *weights)
141{
142 if (!scores || !indices || !weights || n <= 0 || k <= 0) {
143 return;
144 }
145
146 if (k > n) {
147 k = n;
148 }
149
150 /* First get top-K indices and values */
151 float values[k];
152 topk_f32(scores, n, k, indices, values);
153
154 /* Compute softmax over the selected values */
155 /* Find max for numerical stability */
156 float max_val = values[0];
157 for (int i = 1; i < k; i++) {
158 if (values[i] > max_val) {
159 max_val = values[i];
160 }
161 }
162
163 /* Compute exp and sum */
164 float sum = 0.0f;
165 for (int i = 0; i < k; i++) {
166 weights[i] = expf(values[i] - max_val);
167 sum += weights[i];
168 }
169
170 /* Normalize */
171 float inv_sum = 1.0f / sum;
172 for (int i = 0; i < k; i++) {
173 weights[i] *= inv_sum;
174 }
175}
176
178{
179 if (n_experts <= 0) {
180 return 0;
181 }
182 return ((size_t)n_experts * sizeof(float) + 63u) & ~(size_t)63u;
183}
184
185#if defined(__AVX512F__) && defined(__AVX512DQ__)
186/* Match ggml's AVX-512 exp approximation and instruction grouping exactly. */
187static inline __m512 ck_moe_ggml_expf512(__m512 x)
188{
189 const __m512 r = _mm512_set1_ps(0x1.8p23f);
190 const __m512 z = _mm512_fmadd_ps(x, _mm512_set1_ps(0x1.715476p+0f), r);
191 const __m512 n = _mm512_sub_ps(z, r);
192 const __m512 b = _mm512_fnmadd_ps(
193 n, _mm512_set1_ps(0x1.7f7d1cp-20f),
194 _mm512_fnmadd_ps(n, _mm512_set1_ps(0x1.62e4p-1f), x));
195 const __mmask16 d = _mm512_cmp_ps_mask(
196 _mm512_abs_ps(n), _mm512_set1_ps(192.0f), _CMP_GT_OQ);
197 const __m512 u = _mm512_mul_ps(b, b);
198 const __m512 j = _mm512_fmadd_ps(
199 _mm512_fmadd_ps(
200 _mm512_fmadd_ps(
201 _mm512_set1_ps(0x1.0e4020p-7f), b,
202 _mm512_set1_ps(0x1.573e2ep-5f)),
203 u,
204 _mm512_fmadd_ps(
205 _mm512_set1_ps(0x1.555e66p-3f), b,
206 _mm512_set1_ps(0x1.fffdb6p-2f))),
207 u,
208 _mm512_fmadd_ps(
209 _mm512_set1_ps(0x1.ffffecp-1f), b,
210 _mm512_set1_ps(1.0f)));
211 const __m512 res = _mm512_scalef_ps(j, n);
212 if (_mm512_kortestz(d, d)) {
213 return res;
214 }
215 const __m512 zero = _mm512_setzero_ps();
216 const __m512 alt = _mm512_mask_blend_ps(
217 _mm512_cmp_ps_mask(n, zero, _CMP_LE_OQ),
218 _mm512_set1_ps(INFINITY), zero);
219 return _mm512_mask_blend_ps(d, res, alt);
220}
221#endif
222
223#if defined(__AVX2__) && defined(__FMA__)
224static inline __m256 ck_moe_ggml_expf256(__m256 x)
225{
226 const __m256 r = _mm256_set1_ps(0x1.8p23f);
227 const __m256 z = _mm256_fmadd_ps(x, _mm256_set1_ps(0x1.715476p+0f), r);
228 const __m256 n = _mm256_sub_ps(z, r);
229 const __m256 b = _mm256_fnmadd_ps(
230 n,
231 _mm256_set1_ps(0x1.7f7d1cp-20f),
232 _mm256_fnmadd_ps(n, _mm256_set1_ps(0x1.62e4p-1f), x));
233 const __m256i e = _mm256_slli_epi32(_mm256_castps_si256(z), 23);
234 const __m256 k = _mm256_castsi256_ps(
235 _mm256_add_epi32(e, _mm256_castps_si256(_mm256_set1_ps(1))));
236 const __m256i c = _mm256_castps_si256(_mm256_cmp_ps(
237 _mm256_andnot_ps(_mm256_set1_ps(-0.f), n),
238 _mm256_set1_ps(126),
239 _CMP_GT_OQ));
240 const __m256 u = _mm256_mul_ps(b, b);
241 const __m256 j = _mm256_fmadd_ps(
242 _mm256_fmadd_ps(
243 _mm256_fmadd_ps(
244 _mm256_set1_ps(0x1.0e4020p-7f),
245 b,
246 _mm256_set1_ps(0x1.573e2ep-5f)),
247 u,
248 _mm256_fmadd_ps(
249 _mm256_set1_ps(0x1.555e66p-3f),
250 b,
251 _mm256_set1_ps(0x1.fffdb6p-2f))),
252 u,
253 _mm256_mul_ps(_mm256_set1_ps(0x1.ffffecp-1f), b));
254 if (!_mm256_movemask_ps(_mm256_castsi256_ps(c))) {
255 return _mm256_fmadd_ps(j, k, k);
256 }
257 const __m256i g = _mm256_and_si256(
258 _mm256_castps_si256(_mm256_cmp_ps(
259 n, _mm256_setzero_ps(), _CMP_LE_OQ)),
260 _mm256_set1_epi32(0x82000000u));
261 const __m256 s1 = _mm256_castsi256_ps(
262 _mm256_add_epi32(g, _mm256_set1_epi32(0x7f000000u)));
263 const __m256 s2 = _mm256_castsi256_ps(_mm256_sub_epi32(e, g));
264 const __m256i d = _mm256_castps_si256(_mm256_cmp_ps(
265 _mm256_andnot_ps(_mm256_set1_ps(-0.f), n),
266 _mm256_set1_ps(192),
267 _CMP_GT_OQ));
268 return _mm256_or_ps(
269 _mm256_and_ps(_mm256_castsi256_ps(d), _mm256_mul_ps(s1, s1)),
270 _mm256_andnot_ps(
271 _mm256_castsi256_ps(d),
272 _mm256_or_ps(
273 _mm256_and_ps(
274 _mm256_castsi256_ps(c),
275 _mm256_mul_ps(_mm256_fmadd_ps(s2, j, s2), s1)),
276 _mm256_andnot_ps(
277 _mm256_castsi256_ps(c),
278 _mm256_fmadd_ps(k, j, k)))));
279}
280#endif
281
282static double ck_moe_llama_softmax_row(float *probabilities,
283 const float *logits,
284 int n_experts,
285 float max_value)
286{
287 double sum = 0.0;
288 int expert = 0;
289#if defined(__AVX512F__) && defined(__AVX512DQ__)
290 for (; expert + 15 < n_experts; expert += 16) {
291 const __m512 value = ck_moe_ggml_expf512(_mm512_sub_ps(
292 _mm512_loadu_ps(logits + expert), _mm512_set1_ps(max_value)));
293 _mm512_storeu_ps(probabilities + expert, value);
294 sum += (double)_mm512_reduce_add_ps(value);
295 }
296#elif defined(__AVX2__) && defined(__FMA__)
297 for (; expert + 7 < n_experts; expert += 8) {
298 const __m256 value = ck_moe_ggml_expf256(_mm256_sub_ps(
299 _mm256_loadu_ps(logits + expert), _mm256_set1_ps(max_value)));
300 _mm256_storeu_ps(probabilities + expert, value);
301 __m128 half = _mm_add_ps(
302 _mm256_extractf128_ps(value, 1), _mm256_castps256_ps128(value));
303 half = _mm_add_ps(half, _mm_movehl_ps(half, half));
304 half = _mm_add_ss(half, _mm_movehdup_ps(half));
305 sum += (double)_mm_cvtss_f32(half);
306 }
307#endif
308 for (; expert < n_experts; ++expert) {
309 const float value = expf(logits[expert] - max_value);
310 probabilities[expert] = value;
311 sum += (double)value;
312 }
313 return sum;
314}
315
317 const float *logits,
318 int *indices,
319 float *weights,
320 int rows,
321 int n_experts,
322 int top_k,
323 float routed_scaling_factor,
324 void *workspace,
325 size_t workspace_bytes)
326{
327 const size_t required = moe_softmax_topk_router_workspace_bytes(n_experts);
328 if (!logits || !indices || !weights || !workspace || rows <= 0 ||
329 n_experts <= 0 || top_k <= 0 || top_k > n_experts ||
330 !isfinite(routed_scaling_factor) || required == 0 ||
331 workspace_bytes < required) {
332 return -1;
333 }
334
335 float *probabilities = (float *)workspace;
336 for (int row = 0; row < rows; ++row) {
337 const float *row_logits = logits + (size_t)row * (size_t)n_experts;
338 int *row_indices = indices + (size_t)row * (size_t)top_k;
339 float *row_weights = weights + (size_t)row * (size_t)top_k;
340 float max_value = -INFINITY;
341 for (int expert = 0; expert < n_experts; ++expert) {
342 if (!isfinite(row_logits[expert])) {
343 return -2;
344 }
345 if (row_logits[expert] > max_value) {
346 max_value = row_logits[expert];
347 }
348 }
349
350 const double softmax_sum = ck_moe_llama_softmax_row(
351 probabilities, row_logits, n_experts, max_value);
352 const float inverse_softmax_sum = (float)(1.0 / softmax_sum);
353 for (int expert = 0; expert < n_experts; ++expert) {
354 probabilities[expert] *= inverse_softmax_sum;
355 }
356
357 topk_f32(probabilities, n_experts, top_k, row_indices, NULL);
358 double selected_sum_f64 = 0.0;
359 for (int slot = 0; slot < top_k; ++slot) {
360 row_weights[slot] = probabilities[row_indices[slot]];
361 selected_sum_f64 += (double)row_weights[slot];
362 }
363 float selected_sum = (float)selected_sum_f64;
364 if (selected_sum < 6.103515625e-5f) {
365 selected_sum = 6.103515625e-5f;
366 }
367 for (int slot = 0; slot < top_k; ++slot) {
368 row_weights[slot] =
369 (row_weights[slot] / selected_sum) * routed_scaling_factor;
370 }
371 }
372 return 0;
373}
374
376 const float *logits,
377 int *indices,
378 float *weights,
379 int rows,
380 int n_experts,
381 int top_k,
382 float routed_scaling_factor,
383 void *workspace,
384 size_t workspace_bytes)
385{
387 logits, indices, weights, rows, n_experts, top_k,
388 routed_scaling_factor, workspace, workspace_bytes);
389 if (status != 0) {
390 return status;
391 }
392 for (size_t index = 0; index < (size_t)rows * (size_t)top_k; ++index) {
393 weights[index] = bf16_to_float(float_to_bf16(weights[index]));
394 }
395 return 0;
396}
397
398/**
399 * @brief Backward for hard top-k followed by softmax over selected values.
400 *
401 * Matches PyTorch behavior for:
402 * values, indices = torch.topk(scores, k, dim=-1)
403 * weights = torch.softmax(values, dim=-1)
404 *
405 * The hard selected indices are treated as fixed for this backward pass.
406 * Gradients are scattered only to selected scores; unselected scores are zero.
407 */
408void topk_softmax_backward_f32(const int *indices,
409 const float *weights,
410 const float *d_weights,
411 float *d_scores,
412 int num_tokens,
413 int n_experts_or_keys,
414 int k)
415{
416 if (!indices || !weights || !d_weights || !d_scores ||
417 num_tokens <= 0 || n_experts_or_keys <= 0 || k <= 0) {
418 return;
419 }
420
421 const size_t total = (size_t)num_tokens * (size_t)n_experts_or_keys;
422 for (size_t i = 0; i < total; ++i) {
423 d_scores[i] = 0.0f;
424 }
425
426 for (int t = 0; t < num_tokens; ++t) {
427 const int *row_indices = indices + (size_t)t * (size_t)k;
428 const float *row_weights = weights + (size_t)t * (size_t)k;
429 const float *row_d_weights = d_weights + (size_t)t * (size_t)k;
430 float *row_d_scores = d_scores + (size_t)t * (size_t)n_experts_or_keys;
431
432 float dot = 0.0f;
433 for (int i = 0; i < k; ++i) {
434 const int idx = row_indices[i];
435 if (idx >= 0 && idx < n_experts_or_keys) {
436 dot += row_weights[i] * row_d_weights[i];
437 }
438 }
439
440 for (int i = 0; i < k; ++i) {
441 const int idx = row_indices[i];
442 if (idx >= 0 && idx < n_experts_or_keys) {
443 row_d_scores[idx] += row_weights[i] * (row_d_weights[i] - dot);
444 }
445 }
446 }
447}
448
449/* =============================================================================
450 * Batched Top-K (for multiple tokens)
451 *
452 * Process multiple tokens at once, each with its own routing scores.
453 * ============================================================================= */
454
455/**
456 * @brief Batched top-K selection for multiple tokens
457 *
458 * @param scores Input scores [num_tokens, n_experts]
459 * @param num_tokens Number of tokens
460 * @param n_experts Number of experts
461 * @param k Number of experts to select per token
462 * @param indices Output: selected expert indices [num_tokens, k]
463 * @param weights Output: routing weights [num_tokens, k] (can be NULL for no softmax)
464 */
465void topk_batched_f32(const float *scores,
466 int num_tokens,
467 int n_experts,
468 int k,
469 int *indices,
470 float *weights)
471{
472 if (!scores || !indices || num_tokens <= 0 || n_experts <= 0 || k <= 0) {
473 return;
474 }
475
476 for (int t = 0; t < num_tokens; t++) {
477 const float *token_scores = scores + t * n_experts;
478 int *token_indices = indices + t * k;
479
480 if (weights) {
481 float *token_weights = weights + t * k;
482 topk_softmax_f32(token_scores, n_experts, k, token_indices, token_weights);
483 } else {
484 topk_f32(token_scores, n_experts, k, token_indices, NULL);
485 }
486 }
487}
488
489/* =============================================================================
490 * Argmax (special case of top-1)
491 * ============================================================================= */
492
493/**
494 * @brief Find index of maximum value
495 *
496 * @param scores Input scores [n]
497 * @param n Number of scores
498 * @return Index of maximum value
499 */
500int argmax_f32(const float *scores, int n)
501{
502 if (!scores || n <= 0) {
503 return -1;
504 }
505
506 int max_idx = 0;
507 float max_val = scores[0];
508
509#ifdef __AVX512F__
510 /* AVX-512 vectorized argmax for large arrays */
511 if (n >= 16) {
512 __m512 vmax = _mm512_set1_ps(-FLT_MAX);
513 __m512i vidx = _mm512_setzero_si512();
514 __m512i vcur_max_idx = _mm512_setzero_si512();
515
516 int i = 0;
517 for (; i + 16 <= n; i += 16) {
518 __m512 v = _mm512_loadu_ps(&scores[i]);
519 __m512i cur_idx = _mm512_add_epi32(
520 _mm512_set1_epi32(i),
521 _mm512_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
522 );
523
524 __mmask16 gt_mask = _mm512_cmp_ps_mask(v, vmax, _CMP_GT_OQ);
525 vmax = _mm512_mask_blend_ps(gt_mask, vmax, v);
526 vcur_max_idx = _mm512_mask_blend_epi32(gt_mask, vcur_max_idx, cur_idx);
527 }
528
529 /* Horizontal reduction */
530 float vals[16];
531 int idxs[16];
532 _mm512_storeu_ps(vals, vmax);
533 _mm512_storeu_si512(idxs, vcur_max_idx);
534
535 max_val = vals[0];
536 max_idx = idxs[0];
537 for (int j = 1; j < 16; j++) {
538 if (vals[j] > max_val) {
539 max_val = vals[j];
540 max_idx = idxs[j];
541 }
542 }
543
544 /* Handle remainder */
545 for (; i < n; i++) {
546 if (scores[i] > max_val) {
547 max_val = scores[i];
548 max_idx = i;
549 }
550 }
551
552 return max_idx;
553 }
554#endif
555
556 /* Scalar fallback */
557 for (int i = 1; i < n; i++) {
558 if (scores[i] > max_val) {
559 max_val = scores[i];
560 max_idx = i;
561 }
562 }
563
564 return max_idx;
565}
566
567/* =============================================================================
568 * Speculative decode verifier
569 * ============================================================================= */
570
571/**
572 * @brief Greedy one-token speculative verification.
573 *
574 * The draft model proposes draft_token. The target model is authoritative:
575 * if draft_token equals argmax(target_logits), the candidate is accepted and
576 * emitted. Otherwise the target argmax is emitted and the draft path must be
577 * reset or rewound by the runtime loop.
578 *
579 * @param target_logits Target/backbone logits [vocab_size]
580 * @param vocab_size Number of logits
581 * @param draft_token Candidate token from draft/assistant model
582 * @param accepted Output scalar: 1 if accepted, 0 otherwise
583 * @param verified_token Output scalar: accepted draft token or target argmax
584 */
585void speculative_verify_greedy_f32(const float *target_logits,
586 int vocab_size,
587 int draft_token,
588 int *accepted,
589 int *verified_token)
590{
591 const int target_token = argmax_f32(target_logits, vocab_size);
592 const int ok = (target_token >= 0 && draft_token == target_token) ? 1 : 0;
593
594 if (accepted) {
595 *accepted = ok;
596 }
597 if (verified_token) {
598 *verified_token = ok ? draft_token : target_token;
599 }
600}
601
602/**
603 * @brief Commit one verified speculative token and update decode counters.
604 *
605 * This is the minimal state transition for the first Gemma4 assistant bridge:
606 * greedy, one draft token, target remains authoritative. For this milestone the
607 * draft cache is kept synchronized with the target position after each token.
608 * Multi-token speculative decoding can later replace this with prefix accept
609 * and partial draft-cache rollback.
610 */
612 int verified_token,
613 int *token_buffer,
614 int *token_count,
615 int max_tokens,
616 int *target_position,
617 int *draft_position,
618 int *accepted_count,
619 int *rejected_count)
620{
621 int next_count = token_count ? *token_count : 0;
622 if (token_buffer && token_count && next_count >= 0 && next_count < max_tokens) {
623 token_buffer[next_count] = verified_token;
624 next_count += 1;
625 *token_count = next_count;
626 }
627
628 if (target_position) {
629 *target_position += 1;
630 if (draft_position) {
631 *draft_position = *target_position;
632 }
633 } else if (draft_position) {
634 *draft_position += 1;
635 }
636
637 if (accepted) {
638 if (accepted_count) {
639 *accepted_count += 1;
640 }
641 } else {
642 if (rejected_count) {
643 *rejected_count += 1;
644 }
645 }
646}
647
648
649/* =============================================================================
650 * Group-limited MoE router for Nemotron-H/DeepSeek-style routed experts.
651 *
652 * Contract:
653 * scores [rows, n_experts] router probabilities after sigmoid
654 * correction_bias [n_experts] optional score correction used only for choice
655 * indices [rows, top_k]
656 * weights [rows, top_k]
657 *
658 * Selection matches the HF/Nemotron policy:
659 * choice_scores = scores + correction_bias
660 * group_scores = sum(top2(choice_scores within group))
661 * selected_groups = topk(group_scores, topk_group)
662 * selected_experts = topk(choice_scores masked to selected groups, top_k)
663 * weights = gather(scores, selected_experts)
664 * if norm_topk_prob: weights /= sum(weights) + 1e-20
665 * weights *= routed_scaling_factor
666 * ============================================================================= */
667
668static void ck_topk_insert_desc(int idx, float val, int *indices, float *values, int k)
669{
670 for (int pos = 0; pos < k; ++pos) {
671 if (indices[pos] < 0 || val > values[pos] || (val == values[pos] && idx < indices[pos])) {
672 for (int j = k - 1; j > pos; --j) {
673 indices[j] = indices[j - 1];
674 values[j] = values[j - 1];
675 }
676 indices[pos] = idx;
677 values[pos] = val;
678 return;
679 }
680 }
681}
682
683static void group_limited_topk_router_f32_impl(const float *scores,
684 const float *correction_bias,
685 int *indices,
686 float *weights,
687 int rows,
688 int n_experts,
689 int top_k,
690 int n_group,
691 int topk_group,
692 int norm_topk_prob,
693 float routed_scaling_factor,
694 int apply_sigmoid)
695{
696 if (!scores || !indices || !weights || rows <= 0 || n_experts <= 0 ||
697 top_k <= 0 || n_group <= 0 || topk_group <= 0) {
698 return;
699 }
700 if (top_k > n_experts) top_k = n_experts;
701 if (n_group > n_experts) n_group = n_experts;
702 if (topk_group > n_group) topk_group = n_group;
703 const int experts_per_group = n_experts / n_group;
704 if (experts_per_group <= 0 || experts_per_group * n_group != n_experts) {
705 return;
706 }
707
708 for (int r = 0; r < rows; ++r) {
709 const float *row_probs = scores + (size_t)r * (size_t)n_experts;
710 float row_scores[n_experts];
711 for (int e = 0; e < n_experts; ++e) {
712 row_scores[e] = apply_sigmoid
713 ? (1.0f / (1.0f + expf(-row_probs[e])))
714 : row_probs[e];
715 }
716 int *row_indices = indices + (size_t)r * (size_t)top_k;
717 float *row_weights = weights + (size_t)r * (size_t)top_k;
718
719 int selected_groups[topk_group];
720 float selected_group_scores[topk_group];
721 for (int i = 0; i < topk_group; ++i) {
722 selected_groups[i] = -1;
723 selected_group_scores[i] = -FLT_MAX;
724 }
725
726 for (int g = 0; g < n_group; ++g) {
727 float best0 = -FLT_MAX;
728 float best1 = -FLT_MAX;
729 const int start = g * experts_per_group;
730 for (int j = 0; j < experts_per_group; ++j) {
731 const int e = start + j;
732 const float v = row_scores[e] + (correction_bias ? correction_bias[e] : 0.0f);
733 if (v > best0) {
734 best1 = best0;
735 best0 = v;
736 } else if (v > best1) {
737 best1 = v;
738 }
739 }
740 const float group_score = best0 + ((experts_per_group >= 2) ? best1 : 0.0f);
741 ck_topk_insert_desc(g, group_score, selected_groups, selected_group_scores, topk_group);
742 }
743
744 int out_idx[top_k];
745 float out_choice[top_k];
746 for (int i = 0; i < top_k; ++i) {
747 out_idx[i] = -1;
748 out_choice[i] = -FLT_MAX;
749 }
750
751 for (int sg = 0; sg < topk_group; ++sg) {
752 const int g = selected_groups[sg];
753 if (g < 0) continue;
754 const int start = g * experts_per_group;
755 for (int j = 0; j < experts_per_group; ++j) {
756 const int e = start + j;
757 const float v = row_scores[e] + (correction_bias ? correction_bias[e] : 0.0f);
758 ck_topk_insert_desc(e, v, out_idx, out_choice, top_k);
759 }
760 }
761
762 float denom = 1.0e-20f;
763 for (int i = 0; i < top_k; ++i) {
764 const int e = out_idx[i];
765 const float w = (e >= 0 && e < n_experts) ? row_scores[e] : 0.0f;
766 row_indices[i] = e;
767 row_weights[i] = w;
768 denom += w;
769 }
770 for (int i = 0; i < top_k; ++i) {
771 float w = row_weights[i];
772 if (norm_topk_prob) {
773 w /= denom;
774 }
775 row_weights[i] = w * routed_scaling_factor;
776 }
777 }
778}
779
781 const float *correction_bias,
782 int *indices,
783 float *weights,
784 int rows,
785 int n_experts,
786 int top_k,
787 int n_group,
788 int topk_group,
789 int norm_topk_prob,
790 float routed_scaling_factor)
791{
793 scores, correction_bias, indices, weights, rows, n_experts, top_k,
794 n_group, topk_group, norm_topk_prob, routed_scaling_factor, 0
795 );
796}
797
799 const float *correction_bias,
800 int *indices,
801 float *weights,
802 int rows,
803 int n_experts,
804 int top_k,
805 int n_group,
806 int topk_group,
807 int norm_topk_prob,
808 float routed_scaling_factor)
809{
811 logits, correction_bias, indices, weights, rows, n_experts, top_k,
812 n_group, topk_group, norm_topk_prob, routed_scaling_factor, 1
813 );
814}
static uint16_t float_to_bf16(float f)
Definition bf16_utils.h:90
static float bf16_to_float(uint16_t v)
Definition bf16_utils.h:38
void topk_batched_f32(const float *scores, int num_tokens, int n_experts, int k, int *indices, float *weights)
Batched top-K selection for multiple tokens.
void topk_softmax_backward_f32(const int *indices, const float *weights, const float *d_weights, float *d_scores, int num_tokens, int n_experts_or_keys, int k)
Backward for hard top-k followed by softmax over selected values.
void group_limited_topk_router_sigmoid_f32(const float *logits, const float *correction_bias, int *indices, float *weights, int rows, int n_experts, int top_k, int n_group, int topk_group, int norm_topk_prob, float routed_scaling_factor)
int moe_softmax_topk_router_llama_f32_workspace(const float *logits, int *indices, float *weights, int rows, int n_experts, int top_k, float routed_scaling_factor, void *workspace, size_t workspace_bytes)
static double ck_moe_llama_softmax_row(float *probabilities, const float *logits, int n_experts, float max_value)
int argmax_f32(const float *scores, int n)
Find index of maximum value.
static void ck_topk_insert_desc(int idx, float val, int *indices, float *values, int k)
size_t moe_softmax_topk_router_workspace_bytes(int n_experts)
void nemotron_group_limited_topk_router_f32(const float *scores, const float *correction_bias, int *indices, float *weights, int rows, int n_experts, int top_k, int n_group, int topk_group, int norm_topk_prob, float routed_scaling_factor)
void topk_f32(const float *scores, int n, int k, int *indices, float *values)
Find top-K indices and values from a score vector.
void speculative_verify_greedy_f32(const float *target_logits, int vocab_size, int draft_token, int *accepted, int *verified_token)
Greedy one-token speculative verification.
void topk_softmax_f32(const float *scores, int n, int k, int *indices, float *weights)
Find top-K indices with softmax-normalized weights.
void speculative_commit_one_i32(int accepted, int verified_token, int *token_buffer, int *token_count, int max_tokens, int *target_position, int *draft_position, int *accepted_count, int *rejected_count)
Commit one verified speculative token and update decode counters.
int moe_softmax_topk_router_pytorch_bf16_workspace(const float *logits, int *indices, float *weights, int rows, int n_experts, int top_k, float routed_scaling_factor, void *workspace, size_t workspace_bytes)
static void group_limited_topk_router_f32_impl(const float *scores, const float *correction_bias, int *indices, float *weights, int rows, int n_experts, int top_k, int n_group, int topk_group, int norm_topk_prob, float routed_scaling_factor, int apply_sigmoid)
int vocab_size
Definition true_bpe.h:193
uint32_t start
Definition utf8.c:214