ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
cuda_kernels.cu
Go to the documentation of this file.
1
17#include "cuda_kernels.h"
18#include "determinism.h"
19#include "logger.h"
20#include "model_macros.h"
21
22#ifdef HAS_CUDA
23
24#include <cublas_v2.h>
25#include <cuda_bf16.h>
26#include <cuda_runtime.h>
27
28#include "decode_attn_tiled.cuh"
29
30// Define CUDA_CHECK macro if not already defined
31#ifndef CUDA_CHECK
32#define CUDA_CHECK(call) gpuErrchk(call)
33#endif
34
35// Host conversion functions for BF16
36inline uint16_t float32_to_bf16_host(float f) {
37 union { float f32; uint32_t u32; } u = {f};
38 uint32_t rounding_bias = 0x00007FFF + ((u.u32 >> 16) & 1);
39 return (u.u32 + rounding_bias) >> 16;
40}
41
43 union { float f32; uint32_t u32; } u;
44 u.u32 = ((uint32_t)bf16) << 16;
45 return u.f32;
46}
47
48#endif
49
50#include <stdint.h>
51#include <stdio.h>
52
53#include <algorithm>
54#include <cmath>
55#include <iostream>
56#include <cfloat>
57#include <cstdlib>
58#include <string>
59
60// Leftover debug probes in the batch wrappers copy a few values of their
61// inputs to pageable host vectors that nothing reads; such a copy blocks the
62// host until the GPU catches up, so each batch op was a full sync (a 1-token
63// forward after a long prefix took ~27 ms). EMBER_KERNEL_DEBUG=1 keeps them.
64static bool kernel_debug_copies() {
65 static const bool on = std::getenv("EMBER_KERNEL_DEBUG") != nullptr;
66 return on;
67}
68
69// Place these at the top of the file before any kernel uses them
70__device__ float atomicMaxFloat(float* address, float val) {
71 int* address_as_int = (int*)address;
73 do {
74 assumed = old;
77 } while (assumed != old);
78 return __int_as_float(old);
79}
80
81__device__ float atomicAddFloat(float* address, float val) {
82 int* address_as_int = (int*)address;
84 do {
85 assumed = old;
88 } while (assumed != old);
89 return __int_as_float(old);
90}
91
103#if __CUDA_ARCH__ >= 800
105
106 memcpy(&bf16_val, &bf16_raw, sizeof(uint16_t));
108#else
109
110 unsigned int bits = ((unsigned int)bf16_raw) << 16;
111 float result;
112 memcpy(&result, &bits, sizeof(float));
113 return result;
114#endif
115}
116
117// Add FP32 to BF16 conversion device function
119#if __CUDA_ARCH__ >= 800
122 memcpy(&bf16_raw, &bf16_val, sizeof(uint16_t));
123 return bf16_raw;
124#else
125 // Manual conversion for older architectures
126 unsigned int bits;
127 memcpy(&bits, &fp32_val, sizeof(float));
128 // Extract upper 16 bits for BF16
129 return (uint16_t)(bits >> 16);
130#endif
131}
132
144 int n) {
145 extern __shared__ float sdata[];
146
147 unsigned int tid = threadIdx.x;
148 unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
149
150 sdata[tid] = (i < n) ? x[i] * x[i] : 0.0f;
152
153 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
154 if (tid < s) {
155 sdata[tid] += sdata[tid + s];
156 }
158 }
159
160 if (tid == 0) {
162 }
163}
164
177__global__ void rmsnorm_apply_kernel(const float* x, const float* weight,
178 float* out, int n, float inv_norm_factor) {
179 int i = blockIdx.x * blockDim.x + threadIdx.x;
180 if (i < n) {
181 out[i] = x[i] * inv_norm_factor * weight[i];
182 }
183}
184
186 extern __shared__ float sdata[];
187 unsigned int tid = threadIdx.x;
188
189 float my_sum = 0.0f;
190 for (unsigned int i = tid; i < num_partial_sums; i += blockDim.x) {
192 }
193 sdata[tid] = my_sum;
195
196 // Standard parallel reduction in shared memory
197 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
198 if (tid < s) {
199 sdata[tid] += sdata[tid + s];
200 }
202 }
203
204 if (tid == 0) {
205 total_sum_sq_out[0] = sdata[0];
206 }
207}
208
224void rmsnorm_vector_cuda(const float* x_dev, const float* weight_dev,
225 float* out_dev, int n, float eps,
227 const int threads_per_block = 256; // General purpose threads per block
230
231 float* partial_sums_dev = nullptr;
233
238
239 float* total_sum_sq_dev = nullptr;
240 gpuErrchk(cudaMalloc(&total_sum_sq_dev, sizeof(float)));
241
242 const int threads_for_final_reduction = (num_blocks_reduce_pass1 <= 256) ? 256 :
243 (num_blocks_reduce_pass1 <= 512) ? 512 : 1024;
244 // Cap at 1024, ensure kernel handles num_partial_sums correctly if it's less than blockDim.x
246
247 // Launch a single block for the final reduction of partial_sums_dev
252 gpuErrchk(cudaFree(partial_sums_dev)); // Free intermediate partial sums
253
254 float h_total_ssq = 0.0f;
257 gpuErrchk(cudaStreamSynchronize(stream)); // Ensure h_total_ssq is ready
259
260 double total_ssq_double = static_cast<double>(h_total_ssq);
262 float inv_norm_factor = 1.0f / SAFE_SQRT(static_cast<float>(total_ssq_double) + eps);
263
268}
269
270void rmsnorm_vector_cuda(const std::vector<float>& x_in_host,
271 const std::vector<float>& weight_host,
272 std::vector<float>& out_host, int n, float eps) {
273 out_host.resize(n);
274 float *x_dev = nullptr, *weight_dev = nullptr, *out_dev = nullptr;
275 gpuErrchk(cudaMalloc(&x_dev, n * sizeof(float)));
276 gpuErrchk(cudaMalloc(&weight_dev, n * sizeof(float)));
277 gpuErrchk(cudaMalloc(&out_dev, n * sizeof(float)));
278 gpuErrchk(cudaMemcpy(x_dev, x_in_host.data(), n * sizeof(float),
280 gpuErrchk(cudaMemcpy(weight_dev, weight_host.data(), n * sizeof(float),
284 gpuErrchk(cudaMemcpy(out_host.data(), out_dev, n * sizeof(float),
289}
290
307 const float* vec_f32_dev, float* out_f32_dev,
308 int rows, int cols, cudaStream_t stream) {
309 // Existing implementation of matvec_f32_f32_cuda
310 // C = A^T * B where A is mat_f32_dev (passed as KxM) and B is vec_f32_dev (KxN)
311 // M_blas = rows, N_blas = 1, K_blas = cols
312 // A is mat_f32_dev, LDA = cols (K_blas)
313 // B is vec_f32_dev, LDB = cols (K_blas)
314 // C is out_f32_dev, LDC = rows (M_blas)
315 const float alpha = 1.0f;
316 const float beta = 0.0f;
318 rows, 1, cols,
319 &alpha,
322 &beta,
325 throw std::runtime_error("cuBLAS Sgemm failed in matvec_f32_f32_cuda: " +
326 std::to_string(status));
327 }
329}
330
354 const uint16_t* mat_bf16_dev,
355 const float* vec_f32_dev, float* out_f32_dev,
356 int rows, int cols,
357 bool use_tensor_cores, // New flag
360 const float alpha = 1.0f;
361 const float beta = 0.0f;
362
363 // For C = A * B (row-major view) where A is mat_bf16_dev (rows x cols) and B is vec_f32_dev (cols x 1)
364 // cuBLAS expects column-major. We use A^T for op(A) to simulate row-major A.
365 // M_blas = rows (rows of C, rows of op(A))
366 // N_blas = 1 (cols of C, cols of op(B))
367 // K_blas = cols (cols of op(A), rows of op(B))
368 //
369 // If op(A) is CUBLAS_OP_T, then A is passed as K_blas x M_blas (cols x rows) column-major. LDA = K_blas = cols.
370 // If op(B) is CUBLAS_OP_N, then B is passed as K_blas x N_blas (cols x 1) column-major. LDB = K_blas = cols.
371 // C is M_blas x N_blas (rows x 1) column-major. LDC = M_blas = rows.
372
373 // Disable Tensor Cores for matrix-vector operations (N=1) as they are not supported
374 // by cublasGemmEx with mixed precision BF16->FP32 (confirmed by standalone test)
375 if (use_tensor_cores) {
376 Logger::info("[CUDA_KERNEL] matvec_bf16_f32_cuda: Tensor Cores requested, but disabling for matrix-vector (N=1) operation. Mixed precision BF16->FP32 with N=1 returns CUBLAS_STATUS_NOT_SUPPORTED in cublasGemmEx.");
377 use_tensor_cores = false;
378 }
379
380 if (use_tensor_cores) {
381 // Set the cuBLAS stream explicitly
384 Logger::warning("[CUDA_KERNEL] cublasSetStream failed with status: " + std::to_string(stream_status) + ". Falling back to FP32 Sgemm.");
385 use_tensor_cores = false;
386 } else {
387 // Logger::debug("[CUDA_KERNEL] matvec_bf16_f32_cuda attempting cublasGemmEx with Tensor Cores.");
388 // Log all parameters for debugging
389 Logger::info("[CUDA_KERNEL] cublasGemmEx parameters: M=" + std::to_string(rows) +
390 ", N=1, K=" + std::to_string(cols) +
391 ", LDA=" + std::to_string(cols) +
392 ", LDB=" + std::to_string(cols) +
393 ", LDC=" + std::to_string(rows) +
394 ", mat_ptr=" + std::to_string(reinterpret_cast<uintptr_t>(mat_bf16_dev)) +
395 ", vec_ptr=" + std::to_string(reinterpret_cast<uintptr_t>(vec_f32_dev)) +
396 ", out_ptr=" + std::to_string(reinterpret_cast<uintptr_t>(out_f32_dev)));
397
398 // A is mat_bf16_dev (CUDA_R_16BF)
399 // B is vec_f32_dev (CUDA_R_32F)
400 // C is out_f32_dev (CUDA_R_32F)
401 // Computation type CUDA_R_32F
403 CUBLAS_OP_T, // Transpose A (mat_bf16_dev)
404 CUBLAS_OP_N, // No transpose B (vec_f32_dev)
405 rows, // M
406 1, // N
407 cols, // K
408 &alpha, // Alpha
409 mat_bf16_dev, // A
410 CUDA_R_16BF, // Atype
411 cols, // LDA (K_blas for A^T)
412 vec_f32_dev, // B
413 CUDA_R_32F, // Btype
414 cols, // LDB (K_blas for B_NoTrans)
415 &beta, // Beta
416 out_f32_dev, // C
417 CUDA_R_32F, // Ctype
418 rows, // LDC (M_blas)
419 CUBLAS_COMPUTE_32F, // Compute type (use FP32 for accumulation for stability with BF16 inputs)
420 // For pure BF16 compute (if B was also BF16), could use CUBLAS_COMPUTE_32F_FAST_16BF
421 // Or CUBLAS_GEMM_DEFAULT_TENSOR_OP for algo to let cuBLAS pick
422 CUBLAS_GEMM_DEFAULT_TENSOR_OP); // Algorithm
423 }
425 Logger::warning("[CUDA_KERNEL] cublasGemmEx with Tensor Cores failed for matvec_bf16_f32_cuda: " + std::to_string(status) + ". Falling back to FP32 Sgemm.");
426 // Fallback to FP32 path
427 use_tensor_cores = false; // Ensure fallback path is taken
428 } else {
429 // Logger::debug("[CUDA_KERNEL] cublasGemmEx with Tensor Cores SUCCEEDED for matvec_bf16_f32_cuda.");
431 return; // Success with Tensor Cores
432 }
433 }
434
435 // Fallback path: Convert BF16 matrix to FP32 and use cublasSgemm
436 if (!use_tensor_cores) {
437 // Logger::debug("[CUDA_KERNEL] matvec_bf16_f32_cuda using fallback: BF16->FP32 conversion then Sgemm.");
438 // Set the cuBLAS stream for fallback path too
441 throw std::runtime_error("cublasSetStream failed in matvec_bf16_f32_cuda fallback: " + std::to_string(stream_status));
442 }
443
444 float* mat_fp32_dev = nullptr;
445 size_t mat_size_elements = static_cast<size_t>(rows) * cols;
447
448 const int threads = 256;
449 const int blocks = (mat_size_elements + threads - 1) / threads;
452 // No explicit sync needed here if stream is managed correctly by caller or if Sgemm uses the same stream.
453
455 rows, 1, cols,
456 &alpha,
457 mat_fp32_dev, cols, // A (mat_fp32_dev), LDA = K_blas = cols
458 vec_f32_dev, cols, // B (vec_f32_dev), LDB = K_blas = cols
459 &beta,
460 out_f32_dev, rows); // C (out_f32_dev), LDC = M_blas = rows
461
464 throw std::runtime_error("cuBLAS Sgemm failed in matvec_bf16_f32_cuda fallback: " + std::to_string(status));
465 }
467 }
468}
469
471 const std::vector<float>& mat_f32_host,
472 const std::vector<float>& vec_f32_host,
473 std::vector<float>& out_f32_host, int rows, int cols) {
474 if (mat_f32_host.size() != (size_t)rows * cols) {
475 throw std::runtime_error(
476 "matvec_f32_f32_cuda (host/host/host): mat size mismatch.");
477 }
478 if (vec_f32_host.size() != (size_t)cols) {
479 throw std::runtime_error(
480 "matvec_f32_f32_cuda (host/host/host): vec size mismatch.");
481 }
482 out_f32_host.resize(rows);
483
484 float* mat_f32_dev = nullptr;
485 float* vec_f32_dev = nullptr;
486 float* out_f32_dev = nullptr;
487
488 gpuErrchk(cudaMalloc(&mat_f32_dev, mat_f32_host.size() * sizeof(float)));
489 gpuErrchk(cudaMalloc(&vec_f32_dev, vec_f32_host.size() * sizeof(float)));
490 gpuErrchk(cudaMalloc(&out_f32_dev, out_f32_host.size() * sizeof(float)));
491
493 mat_f32_host.size() * sizeof(float),
496 vec_f32_host.size() * sizeof(float),
498
500 0);
501
503
505 out_f32_host.size() * sizeof(float),
507
511}
512
513__global__ void silu_kernel(const float* x, float* out, int n) {
514 int i = blockIdx.x * blockDim.x + threadIdx.x;
515 if (i < n) {
516 float x_val = x[i];
517 out[i] = x_val / (1.0f + expf(-x_val));
518 }
519}
520
521void silu_cuda(const std::vector<float>& x_host, std::vector<float>& out_host,
522 int n) {
523 if (x_host.size() != n) {
524 throw std::runtime_error("SiLU CUDA: Input vector size mismatch.");
525 }
526 out_host.resize(n);
527
528 float* x_dev = nullptr;
529 float* out_dev = nullptr;
530 gpuErrchk(cudaMalloc(&x_dev, n * sizeof(float)));
531 gpuErrchk(cudaMalloc(&out_dev, n * sizeof(float)));
532
533 gpuErrchk(cudaMemcpy(x_dev, x_host.data(), n * sizeof(float),
535
536 const int threads_per_block = 256;
540
541 gpuErrchk(cudaMemcpy(out_host.data(), out_dev, n * sizeof(float),
544
547}
548
550 int n) {
551 extern __shared__ float sdata[];
552 unsigned int tid = threadIdx.x;
553 unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
554
555 sdata[tid] = (i < n) ? x[i] : -INFINITY;
557
558 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
559 if (tid < s) {
560 sdata[tid] = fmaxf(sdata[tid], sdata[tid + s]);
561 }
563 }
564
565 if (tid == 0) {
566 partial_max[blockIdx.x] = sdata[0];
567 }
568}
569
571 int n, float max_val) {
572 extern __shared__ float sdata[];
573 unsigned int tid = threadIdx.x;
574 unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
575
576 sdata[tid] = (i < n) ? expf(x[i] - max_val) : 0.0f;
578
579 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
580 if (tid < s) {
581 sdata[tid] += sdata[tid + s];
582 }
584 }
585
586 if (tid == 0) {
588 }
589}
590
591__global__ void softmax_normalize_kernel(const float* x, float* out, int n,
592 float max_val, float inv_sum) {
593 int i = blockIdx.x * blockDim.x + threadIdx.x;
594 if (i < n) {
595 out[i] = expf(x[i] - max_val) * inv_sum;
596 }
597}
598
599void softmax_vector_cuda(const std::vector<float>& x_host,
600 std::vector<float>& out_host, int n) {
601 if (x_host.size() != n) {
602 throw std::runtime_error("Softmax CUDA: Input vector size mismatch.");
603 }
604 if (n == 0) {
605 out_host.clear();
606 return;
607 }
608 out_host.resize(n);
609
610 float* x_dev = nullptr;
611 float* out_dev = nullptr;
612 float* partial_max_dev = nullptr;
613 float* partial_sum_dev = nullptr;
614
615 const int threads_per_block = 256;
617 size_t shared_mem_size = threads_per_block * sizeof(float);
618
619 float* partial_max_host = new float[num_blocks];
620 float* partial_sum_host = new float[num_blocks];
621
622 gpuErrchk(cudaMalloc(&x_dev, n * sizeof(float)));
623 gpuErrchk(cudaMalloc(&out_dev, n * sizeof(float)));
626
627 gpuErrchk(cudaMemcpy(x_dev, x_host.data(), n * sizeof(float),
629
634 num_blocks * sizeof(float), cudaMemcpyDeviceToHost));
635
636 float max_val = -INFINITY;
637 for (int i = 0; i < num_blocks; ++i) {
638 if (partial_max_host[i] > max_val) {
640 }
641 }
642
647 num_blocks * sizeof(float), cudaMemcpyDeviceToHost));
648
649 double exp_sum = 0.0;
650 for (int i = 0; i < num_blocks; ++i) {
652 }
653 float inv_sum = 1.0f / static_cast<float>(exp_sum);
654
658
659 gpuErrchk(cudaMemcpy(out_host.data(), out_dev, n * sizeof(float),
661
662 delete[] partial_max_host;
663 delete[] partial_sum_host;
668}
669
670__global__ void swiglu_kernel(const float* gate, const float* up, float* out,
671 int n) {
672 int i = blockIdx.x * blockDim.x + threadIdx.x;
673 if (i < n) {
674 float gate_val = gate[i];
675 float silu_gate = gate_val / (1.0f + expf(-gate_val));
676 out[i] = silu_gate * up[i];
677 }
678}
679
688
689// New Batched SwiGLU Kernel
691 float* d_out_batch, // Output: [num_tokens, intermediate_size]
692 const float* d_gate_act_batch, // Input: Gate activations [num_tokens, intermediate_size]
693 const float* d_up_act_batch, // Input: Up activations [num_tokens, intermediate_size]
694 int num_tokens,
695 int intermediate_size) {
696
697 int token_idx = blockIdx.x;
698 if (token_idx >= num_tokens) {
699 return;
700 }
701
702 // Base pointers for the current token
703 const float* current_token_gate_ptr = d_gate_act_batch + (size_t)token_idx * intermediate_size;
704 const float* current_token_up_ptr = d_up_act_batch + (size_t)token_idx * intermediate_size;
705 float* current_token_out_ptr = d_out_batch + (size_t)token_idx * intermediate_size;
706
707 // Threads in the block parallelize over intermediate_size
708 for (int i = threadIdx.x; i < intermediate_size; i += blockDim.x) {
710 float silu_gate = gate_val / (1.0f + expf(-gate_val)); // SiLU(x) = x * sigmoid(x)
713 }
714}
715
716// Host wrapper for Batched SwiGLU
718 float* d_out_batch, // Output: [num_tokens, intermediate_size]
719 const float* d_gate_act_batch, // Input: Gate activations [num_tokens, intermediate_size]
720 const float* d_up_act_batch, // Input: Up activations [num_tokens, intermediate_size]
721 int num_tokens,
722 int intermediate_size, // This is typically config_.ffn_hidden_size or similar
724
725
726 if (num_tokens == 0 || intermediate_size == 0) {
727 Logger::info("[SWIGLU_BATCH_CUDA_SKIP] num_tokens or intermediate_size is 0. Nothing to do.");
728 Logger::info("[SWIGLU_BATCH_CUDA_EXIT] Skipped operation.");
729 return; // Nothing to do
730 }
731
732 // Log initial input values (first token, first few elements)
733 int log_count_elements = std::min(3, intermediate_size);
734 if (num_tokens > 0 && intermediate_size > 0) {
735 if (d_gate_act_batch) {
736 std::vector<float> h_gate_sample(log_count_elements);
737 // Gate is [num_tokens, intermediate_size]. Offset for T0 is 0.
739 }
740 if (d_up_act_batch) {
741 std::vector<float> h_up_sample(log_count_elements);
742 // Up is [num_tokens, intermediate_size]. Offset for T0 is 0.
744 }
745 }
746
747 const int threads_per_block = 256;
750
756 intermediate_size
757 );
759
760 // Log output values
761 if (num_tokens > 0 && intermediate_size > 0 && d_out_batch) {
762 std::vector<float> h_out_sample(log_count_elements);
763 // Out is [num_tokens, intermediate_size]. Offset for T0 is 0.
765 }
766}
768 const float* all_freqs_cis_base, int pos, bool use_adjacent_pairing) {
769 int idx = blockIdx.x * blockDim.x + threadIdx.x; // Global thread index for pairs
770 int total_pairs = num_heads * (head_dim / 2); // Total (dim_i, dim_{i+1}) or (dim_i, dim_{i+D/2}) pairs across all heads
771 if (idx >= total_pairs) return;
772
773 int head_idx = idx / (head_dim / 2); // The specific head this thread works on
774 int dim_pair_idx = idx % (head_dim / 2); // The index of the pair within this head (0 to head_dim/2 - 1)
775
778
779 int base_x0, base_x1;
781 // Adjacent pairing: Rotate x[h*HD + 2*j] with x[h*HD + 2*j + 1]
783 base_x1 = head_idx * head_dim + (dim_pair_idx * 2 + 1);
784 } else {
785 // Split-half pairing: Rotate x[h*HD + j] with x[h*HD + j + head_dim/2]
788 }
789
790 float x0 = x[base_x0];
791 float x1 = x[base_x1];
792
795
796 x[base_x0] = x0 * cos_val - x1 * sin_val;
797 x[base_x1] = x0 * sin_val + x1 * cos_val;
798}
799
810
811// New Batched RoPE Kernel
813 float* x_batch_tensor, // Q_batch or K_batch tensor
814 int num_tokens, // Number of tokens in this batch
815 int num_heads_in_tensor, // num_q_heads or num_kv_heads for this tensor
816 int head_dim, // Dimension of each head
817 const float* d_all_freqs_cis_base, // Precomputed cos/sin values table
818 int start_pos_offset, // Starting sequence position for the tokens in this batch
820
821 // Grid: dim3 grid_dim(num_tokens, num_heads_in_tensor);
822 // Block: dim3 block_dim(head_dim / 2);
823 int pair_within_head_idx = threadIdx.x; // Thread index within the block (0 to head_dim/2 - 1)
824 int head_idx = blockIdx.y; // Block index in Y dimension maps to head index
825 int token_idx = blockIdx.x; // Block index in X dimension maps to token index within the batch
826
827 // Boundary checks
828 if (pair_within_head_idx >= (head_dim / 2) ||
831 return;
832 }
833
834 // Determine the actual sequence position for RoPE for the current token
836
837 // Fetch cos and sin values from the precomputed table
838 // d_all_freqs_cis_base is effectively [max_seq_len][head_dim/2][2]
839 // Access: (pos * (head_dim/2) + pair_idx_in_head) * 2 for cos, and +1 for sin
843
844 // Calculate base pointer to the current token's data in x_batch_tensor
845 // x_batch_tensor is [num_tokens][num_heads_in_tensor][head_dim]
847 // Pointer to the start of the current head's data for this token
849
852 // Adjacent pairing: (x_0, x_1), (x_2, x_3), ... for a head
853 // pair_within_head_idx = 0 maps to (x_0, x_1), pair_within_head_idx = 1 maps to (x_2, x_3)
856 } else {
857 // Split-half pairing: (x_0, x_{D/2}), (x_1, x_{D/2+1}), ... for a head
858 // pair_within_head_idx = 0 maps to (x_0, x_{D/2}), pair_within_head_idx = 1 maps to (x_1, x_{D/2+1})
861 }
862
865
866
869}
870
871// Host wrapper for batched RoPE application
873 const float* d_all_freqs_cis_base, // Moved to 3rd position
874 int num_tokens, int num_q_heads, int num_kv_heads, int head_dim,
878
879
880 if (head_dim == 0) {
881 Logger::warning("[ROPE_BATCH_CUDA_SKIP] head_dim is 0. Skipping RoPE application.");
882 return;
883 }
884 if (head_dim % 2 != 0) {
885 Logger::error("rope_batch_cuda: head_dim must be even. Got " + std::to_string(head_dim));
886 Logger::info("[ROPE_BATCH_CUDA_EXIT] Error: head_dim not even.");
887 return;
888 }
889 if (num_tokens == 0) {
890 Logger::info("[ROPE_BATCH_CUDA_EXIT] num_tokens is 0. Nothing to process.");
891 return; // Nothing to process
892 }
893
894 int log_count_elements = std::min(3, head_dim);
895
896 // Log initial Q values
897 if (d_q_batch && num_q_heads > 0 && head_dim > 0) {
898 for (int token_to_log_idx = 0; token_to_log_idx < std::min(num_tokens, 2); ++token_to_log_idx) {
899 std::vector<float> h_q_sample_before(log_count_elements);
901
904 log_count_elements * sizeof(float),
906 }
907 }
908
909 // Log initial K values
910 if (d_k_batch && num_kv_heads > 0 && head_dim > 0) {
911 for (int token_to_log_idx = 0; token_to_log_idx < std::min(num_tokens, 2); ++token_to_log_idx) {
912 std::vector<float> h_k_sample_before(log_count_elements);
914
917 log_count_elements * sizeof(float),
919 }
920 }
921
923
924 // Apply RoPE to Q batch
925 if (d_q_batch && num_q_heads > 0) {
928 d_q_batch,
931 head_dim,
935 );
937 }
938
939 // Apply RoPE to K batch
940 if (d_k_batch && num_kv_heads > 0) {
943 d_k_batch,
946 head_dim,
950 );
952 }
953
954 // Log Q values after RoPE
955 if (d_q_batch && num_q_heads > 0 && head_dim > 0) {
956 for (int token_to_log_idx = 0; token_to_log_idx < std::min(num_tokens, 2); ++token_to_log_idx) {
957 std::vector<float> h_q_sample_after(log_count_elements);
959
962 log_count_elements * sizeof(float),
964 }
965 }
966
967 // Log K values after RoPE
968 if (d_k_batch && num_kv_heads > 0 && head_dim > 0) {
969 for (int token_to_log_idx = 0; token_to_log_idx < std::min(num_tokens, 2); ++token_to_log_idx) {
970 std::vector<float> h_k_sample_after(log_count_elements);
972
975 log_count_elements * sizeof(float),
977 }
978 }
979}
980
982 const float* K_layer_cache_base,
983 const float* V_layer_cache_base, float* out,
984 int current_seq_len, int head_dim, float scale,
986
987
988 int q_head_idx = blockIdx.x;
989 int d_idx = threadIdx.x; // dimension index for this thread
990
991 if (q_head_idx >= num_q_heads) return; // Should not happen with correct launch
992
993 // Determine the corresponding KV head for this Q head (for GQA/MQA)
996
997 // Shared memory for scores (one score per k_pos) and for dot product reduction
998 extern __shared__ float shared_data[];
999 float* scores = shared_data; // First part of shared_data: size current_seq_len
1000 float* dot_product_terms = &shared_data[current_seq_len]; // Second part: size head_dim (blockDim.x)
1001
1002 // Pointer to the current query head's data
1003 const float* q_head_ptr = Q_current + q_head_idx * head_dim;
1004
1005 for (int k_pos = 0; k_pos < current_seq_len; ++k_pos) {
1006 // Pointer to the k_pos-th key vector for the relevant kv_head
1008 (size_t)kv_head_idx * head_dim;
1009 const float* k_vec_ptr = K_layer_cache_base + k_vec_offset;
1010
1012 __syncthreads(); // Ensure all terms are written before reduction
1013
1014 // Reduction in shared memory (e.g., by thread 0 or tree-based)
1015 if (d_idx == 0) {
1016 float current_dot = 0.0f;
1017 for (int r = 0; r < head_dim; ++r) {
1019 }
1020 scores[k_pos] = current_dot * scale;
1021 }
1022 __syncthreads();
1023 }
1024
1025 float thread_max_score = -INFINITY;
1026 for (int i = d_idx; i < current_seq_len; i += blockDim.x) { // blockDim.x is head_dim
1027 if (scores[i] > thread_max_score) {
1028 thread_max_score = scores[i];
1029 }
1030 }
1031 dot_product_terms[d_idx] = thread_max_score; // Repurpose dot_product_terms shared memory
1032 __syncthreads();
1033
1034 float block_max_score = -INFINITY;
1035 if (d_idx == 0) {
1036 for (int r = 0; r < head_dim; ++r) { // head_dim might be > current_seq_len for first few tokens
1037 if (dot_product_terms[r] > block_max_score) { // Check if dot_product_terms[r] was validly written
1039 }
1040 }
1041 }
1042 __syncthreads();
1043 if(d_idx == 0) dot_product_terms[0] = block_max_score; // Store it.
1044 __syncthreads();
1045 block_max_score = dot_product_terms[0]; // All threads read it.
1046
1047
1048 // Calculate exp scores and sum
1049 float thread_exp_sum = 0.0f;
1050 for (int i = d_idx; i < current_seq_len; i += blockDim.x) {
1051 float prob = expf(scores[i] - block_max_score);
1052 scores[i] = prob; // Update scores in-place with exp(val - max)
1054 }
1055 // Reduce thread_exp_sum across the block
1056 dot_product_terms[d_idx] = thread_exp_sum; // Repurpose again
1057 __syncthreads();
1058
1059 float block_exp_sum = 0.0f;
1060 if (d_idx == 0) {
1061 for (int r = 0; r < head_dim; ++r) { // Similar concern as max_score reduction range
1063 }
1064 }
1065 __syncthreads();
1066 // Store and reload block_exp_sum for all threads
1068 __syncthreads();
1070
1071
1072 float inv_sum = 1.0f / (block_exp_sum + 1e-9f); // Add epsilon for stability
1073
1074 // Normalize scores in shared memory
1075 for (int i = d_idx; i < current_seq_len; i += blockDim.x) {
1076 scores[i] *= inv_sum;
1077 }
1078 __syncthreads(); // Ensure all scores are normalized before weighted sum
1079
1080 double weighted_val_d = 0.0; // Use double for accumulation
1081 for (int k_pos = 0; k_pos < current_seq_len; ++k_pos) {
1083 (size_t)kv_head_idx * head_dim;
1084 const float* v_vec_ptr = V_layer_cache_base + v_vec_offset;
1085
1086 weighted_val_d += static_cast<double>(scores[k_pos]) * static_cast<double>(v_vec_ptr[d_idx]);
1087 }
1088
1089 // Write the final result for this thread's dimension d_idx
1090 out[q_head_idx * head_dim + d_idx] = static_cast<float>(weighted_val_d);
1091}
1092
1093void attention_cuda(const float* Q_current_dev, const float* K_layer_cache_base,
1094 const float* V_layer_cache_base, float* out_dev,
1095 int num_heads, int current_seq_len, int head_dim,
1098 // Grid: num_heads blocks, each block handles one attention head
1099 // Block: head_dim threads, each thread handles one output dimension
1102 // scores[current_seq_len] + dot_product_terms[head_dim] (the kernel's extern shared layout)
1103 const size_t shm = (size_t)(current_seq_len + head_dim) * sizeof(float);
1104
1109}
1110
1111// Selective dequantization attention implementation
1113 const float* __restrict__ Q_current_dev,
1116 const float* __restrict__ K_scales_cache_base,
1117 const float* __restrict__ V_scales_cache_base,
1120 float* __restrict__ out_dev,
1121 int num_heads, int current_seq_len, int head_dim,
1122 float scale, int cache_max_seq_len, int cache_num_kv_heads) {
1123
1124 int head_idx = blockIdx.x;
1125 int dim_idx = threadIdx.x;
1126
1127 if (head_idx >= num_heads || dim_idx >= head_dim) return;
1128
1129 // Map query head to corresponding KV head (for GQA/MQA)
1132
1133 // Get current query vector for this head and dimension
1135
1136 float attn_out = 0.0f;
1137
1138 // Selective dequantization and attention computation
1139 for (int t = 0; t < current_seq_len; ++t) {
1140 // Calculate offset for this token and KV head in the quantized cache
1143
1144 // Dequantize only the specific elements we need for this head/dimension
1145 if (dim_idx == 0) {
1146 // Only thread 0 in each block dequantizes the entire head for this token
1149
1150 for (int d = 0; d < head_dim; ++d) {
1151 // Dequantize K values for this token/head
1154
1155 // Dequantize V values for this token/head
1158 }
1159 }
1160
1161 __syncthreads(); // Ensure dequantization is complete before using values
1162
1163 // Compute attention score for this token
1165 float score = q_val * k_val * scale;
1166
1167 // Apply attention score to value
1169 attn_out += score * v_val;
1170
1171 __syncthreads(); // Ensure all threads are done before next iteration
1172 }
1173
1174 // Write output
1176}
1177
1181 const float* K_scales_cache_base,
1182 const float* V_scales_cache_base,
1185 float* out_dev,
1186 int num_heads, int current_seq_len, int head_dim,
1189
1190 if (num_heads == 0 || current_seq_len == 0 || head_dim == 0) {
1191 Logger::warning("[ATTENTION_SELECTIVE_DEQUANT] Invalid parameters: num_heads=" +
1192 std::to_string(num_heads) + ", current_seq_len=" + std::to_string(current_seq_len) +
1193 ", head_dim=" + std::to_string(head_dim));
1194 return;
1195 }
1196
1197 // Grid: num_heads blocks, each block handles one attention head
1198 // Block: head_dim threads, each thread handles one output dimension
1201
1208
1210}
1211
1212__global__ void add_vectors_kernel(const float* a, const float* b,
1213 float* result, int n) {
1214 int i = blockIdx.x * blockDim.x + threadIdx.x;
1215 if (i < n) {
1216 result[i] = a[i] + b[i];
1217 }
1218}
1219
1220void add_vectors_cuda(const float* a_dev, const float* b_dev, float* result_dev,
1221 int n, cudaStream_t stream) {
1222 const int threads_per_block = 256;
1223 const int num_blocks = (n + threads_per_block - 1) / threads_per_block;
1224
1226 a_dev, b_dev, result_dev, n);
1228}
1229
1231 const float* residual, float* result,
1232 int n) {
1233 int i = blockIdx.x * blockDim.x + threadIdx.x;
1234 if (i < n) {
1235 result[i] = matvec_out[i] + residual[i];
1236 }
1237}
1238
1248
1249// New Batched Add Residual Kernel
1251 float* d_output_batch, // Output: [num_tokens, hidden_size]
1252 const float* d_input_a_batch, // Input A: [num_tokens, hidden_size] (e.g., sub-layer output)
1253 const float* d_input_b_batch, // Input B: [num_tokens, hidden_size] (e.g., original input to sub-layer)
1254 int num_tokens,
1255 int hidden_size) {
1256
1257 int token_idx = blockIdx.x;
1258 if (token_idx >= num_tokens) {
1259 return;
1260 }
1261
1262 // Base pointers for the current token
1263 const float* current_token_input_a_ptr = d_input_a_batch + (size_t)token_idx * hidden_size;
1264 const float* current_token_input_b_ptr = d_input_b_batch + (size_t)token_idx * hidden_size;
1265 float* current_token_output_ptr = d_output_batch + (size_t)token_idx * hidden_size;
1266
1267 // Threads in the block parallelize over hidden_size
1268 for (int i = threadIdx.x; i < hidden_size; i += blockDim.x) {
1270 }
1271}
1272
1273// Host wrapper for Batched Add Residual
1275 float* d_output_batch, // Output: [num_tokens, hidden_size]
1276 const float* d_input_a_batch, // Input A: [num_tokens, hidden_size]
1277 const float* d_input_b_batch, // Input B: [num_tokens, hidden_size]
1278 int num_tokens,
1279 int hidden_size,
1281
1282
1283 if (num_tokens == 0 || hidden_size == 0) {
1284 Logger::info("[ADD_RESIDUAL_BATCH_CUDA_SKIP] num_tokens or hidden_size is 0. Nothing to do.");
1285 Logger::info("[ADD_RESIDUAL_BATCH_CUDA_EXIT] Skipped operation.");
1286 return; // Nothing to do
1287 }
1288
1289 // Log initial input values (first token, first few elements)
1290 int log_count_elements = std::min(3, hidden_size);
1291 if (num_tokens > 0 && hidden_size > 0) {
1292 if (d_input_a_batch) {
1293 std::vector<float> h_input_a_sample(log_count_elements);
1294 // Input A is [num_tokens, hidden_size]. Offset for T0 is 0.
1296 }
1297 if (d_input_b_batch) {
1298 std::vector<float> h_input_b_sample(log_count_elements);
1299 // Input B is [num_tokens, hidden_size]. Offset for T0 is 0.
1301 }
1302 }
1303
1304 const int threads_per_block = 256;
1307
1312 num_tokens,
1313 hidden_size
1314 );
1316
1317 // Log output values
1318 if (num_tokens > 0 && hidden_size > 0 && d_output_batch) {
1319 std::vector<float> h_output_sample(log_count_elements);
1320 // Output is [num_tokens, hidden_size]. Offset for T0 is 0.
1322 }
1323}
1325 const float* current_kv_vector, int pos,
1326 int kv_head_idx, int max_seq_len,
1327 int num_kv_heads, int head_dim) {
1328 int d = threadIdx.x;
1329 if (d >= head_dim) return;
1330
1332 (size_t)kv_head_idx * head_dim + d;
1333
1334 size_t source_offset = d;
1335
1338 return;
1339 }
1340
1342}
1343
1345 const float* current_kv_head_vector,
1346
1347 int pos, int kv_head_idx, int max_seq_len,
1350 dim3 gridDim(1);
1351
1353 Logger::error("update_kv_cache_cuda: pos out of bounds (" +
1354 std::to_string(pos) + " >= " + std::to_string(max_seq_len) +
1355 ")");
1356 return;
1357 }
1358
1360 Logger::error("update_kv_cache_cuda: kv_head_idx out of bounds (" +
1361 std::to_string(kv_head_idx) +
1362 " >= " + std::to_string(num_kv_heads) + ")");
1363 return;
1364 }
1366 Logger::error("update_kv_cache_cuda: Input K/V vector pointer is null.");
1367 return;
1368 }
1369
1374}
1375
1377 float* cache_base_ptr, const float* kv_vector_head,
1378
1379 const float* all_freqs_cis_base, int pos, int kv_head_idx, int max_seq_len,
1380 int num_kv_heads, int head_dim) {
1381 int pair_idx = blockIdx.x * blockDim.x + threadIdx.x;
1382 int dim_half = head_dim / 2;
1383
1384 if (pair_idx >= dim_half) return;
1385
1386 int idx0 = pair_idx;
1387 int idx1 = pair_idx + dim_half;
1388 float kv0 = kv_vector_head[idx0];
1389 float kv1 = kv_vector_head[idx1];
1390
1391 size_t freq_base_offset = (size_t)pos * head_dim + (size_t)pair_idx * 2;
1394
1395 float kv0_rotated = kv0 * cos_val - kv1 * sin_val;
1396 float kv1_rotated = kv0 * sin_val + kv1 * cos_val;
1397
1399 (size_t)kv_head_idx * head_dim + idx0;
1401
1405 return;
1406 }
1407
1410}
1411
1413 const float* kv_vector_head,
1414 const float* all_freqs_cis_base, int pos,
1415 int kv_head_idx, int max_seq_len,
1416 int num_kv_heads, int head_dim,
1418 if (head_dim % 2 != 0) {
1419 Logger::error("rope_and_update_kv_cache_cuda: head_dim must be even.");
1420 return;
1421 }
1423 Logger::error("rope_and_update_kv_cache_cuda: pos out of bounds.");
1424 return;
1425 }
1427 Logger::error("rope_and_update_kv_cache_cuda: kv_head_idx out of bounds.");
1428 return;
1429 }
1432 "rope_and_update_kv_cache_cuda: Received null device pointer(s).");
1433 return;
1434 }
1435
1436 int threads_per_block = 128;
1438
1443}
1444
1446 float* __restrict__ output_dev,
1447 int token_id, int hidden_size,
1448 int vocab_size, bool is_bf16) {
1449 int idx = blockIdx.x * blockDim.x + threadIdx.x;
1450
1451 if (idx >= hidden_size) {
1452 return;
1453 }
1454
1455 if (token_id < 0 || token_id >= vocab_size) {
1456 output_dev[idx] = 0.0f;
1457 return;
1458 }
1459 size_t offset = (size_t)token_id * hidden_size + idx;
1460
1461 if (is_bf16) {
1462 const uint16_t* table_bf16 = static_cast<const uint16_t*>(table_dev);
1463
1464 uint16_t val_bf16 = table_bf16[offset];
1465
1467 } else {
1468 const float* table_f32 = static_cast<const float*>(table_dev);
1469
1470 output_dev[idx] = table_f32[offset];
1471 }
1472}
1473
1475 int token_id, int hidden_size, int vocab_size,
1476 bool is_bf16, cudaStream_t stream) {
1477 if (!table_dev || !output_dev) {
1478 Logger::error("lookup_embedding_cuda: Received null device pointer(s).");
1479
1480 return;
1481 }
1482 if (hidden_size <= 0 || vocab_size <= 0) {
1483 Logger::error("lookup_embedding_cuda: Invalid hidden_size or vocab_size.");
1484 return;
1485 }
1486
1487 int threads_per_block = 256;
1488
1489 int blocks_per_grid =
1490 (hidden_size + threads_per_block - 1) / threads_per_block;
1491
1493 table_dev, output_dev, token_id, hidden_size, vocab_size, is_bf16);
1494
1496}
1497
1499 float* __restrict__ fp32_out,
1500 size_t n_elements) {
1501 size_t idx = blockIdx.x * blockDim.x + threadIdx.x;
1502 if (idx < n_elements) {
1504 }
1505}
1506
1507// KVCache Quantization Kernels (FP32 <-> INT8)
1508#ifdef HAS_CUDA
1509
1510// Kernel to find the maximum absolute value in a float array (for per-tensor scaling)
1512 extern __shared__ float sdata[];
1513 unsigned int tid = threadIdx.x;
1514 unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
1515
1516 float local_max = 0.0f;
1517 if (i < n_elements) {
1518 local_max = fabsf(data[i]);
1519 }
1520 sdata[tid] = local_max;
1521 __syncthreads();
1522
1523 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
1524 if (tid < s) {
1525 sdata[tid] = fmaxf(sdata[tid], sdata[tid + s]);
1526 }
1527 __syncthreads();
1528 }
1529
1530 if (tid == 0) {
1531 float old_val = *max_abs_val;
1532 float new_val = sdata[0];
1533 while (new_val > old_val) {
1534 float assumed_old = old_val;
1535 old_val = atomicCAS((unsigned int*)max_abs_val,
1538 // If atomicCAS did not return what we thought was the old value,
1539 // it means another thread updated it. We re-read and retry if our new_val is still greater.
1541 // Re-check condition with the actual current max_abs_val (now in old_val after CAS)
1542 if (new_val <= old_val) break; // Another thread set a higher or equal max, or our new_val is no longer the max
1543 } else {
1544 // Our CAS was successful, or new_val was not greater than the initial read.
1545 break;
1546 }
1547 }
1548 }
1549}
1550
1551// Kernel to quantize FP32 to INT8 using a symmetric per-tensor scale
1553 const float* __restrict__ fp32_in,
1555 float scale,
1556 int n_elements
1557) {
1558 int i = blockIdx.x * blockDim.x + threadIdx.x;
1559 if (i < n_elements) {
1560 float val_fp32 = fp32_in[i];
1561 // Symmetric quantization: scaled_value = round(value / scale)
1562 // Clamp to [-127, 127] for INT8 (as -128 might not be used or handled differently by some dequant schemes)
1563 float scaled_val = roundf(val_fp32 / scale);
1564 int8_out[i] = (int8_t)fmaxf(-127.0f, fminf(127.0f, scaled_val));
1565 }
1566}
1567
1568// Kernel to dequantize INT8 to FP32 using a symmetric per-tensor scale
1571 float scale,
1572 float* __restrict__ fp32_out,
1573 int n_elements
1574) {
1575 int i = blockIdx.x * blockDim.x + threadIdx.x;
1576 if (i < n_elements) {
1577 fp32_out[i] = (float)int8_in[i] * scale;
1578 }
1579}
1580
1582 const float* fp32_in_dev,
1584 float* scale_out_dev, // Device pointer to store the single scale value
1585 int num_elements,
1587) {
1588 if (num_elements == 0) return;
1589
1590 const int threads_per_block = 256;
1591
1592 // --- Step 1: Find the maximum absolute value for scaling ---
1593 float* d_max_abs_val_accumulator = nullptr; // Accumulator on device for max_abs from blocks
1595 gpuErrchk(cudaMemsetAsync(d_max_abs_val_accumulator, 0, sizeof(float), stream)); // Initialize to 0
1596
1597 // Max reduction kernel requires shared memory proportional to block size
1598 int num_blocks_reduce = (num_elements + threads_per_block - 1) / threads_per_block;
1599 size_t shared_mem_size = threads_per_block * sizeof(float);
1600
1602 fp32_in_dev, num_elements, d_max_abs_val_accumulator);
1604
1605 // Copy the final max absolute value back to `scale_out_dev` which is the designated output for the scale.
1606 // The actual scale factor will be calculated from this max_abs_val.
1607 float h_max_abs_val = 0.0f;
1609 gpuErrchk(cudaStreamSynchronize(stream)); // Ensure h_max_abs_val is ready
1610
1612
1613 // Calculate scale: scale = max_abs / 127.0 (so that 127 maps to max_abs)
1614 // Add a small epsilon to prevent division by zero if all values are zero.
1615 float scale = (h_max_abs_val > 1e-8) ? (h_max_abs_val / 127.0f) : 1.0f;
1617
1618 // --- Step 2: Quantize using the calculated scale ---
1619 int num_blocks_quantize = (num_elements + threads_per_block - 1) / threads_per_block;
1621 fp32_in_dev, int8_out_dev, scale, num_elements);
1623}
1624
1626 const int8_t* int8_in_dev,
1627 const float* scale_in_dev, // Device pointer to the single scale value
1628 float* fp32_out_dev,
1629 int num_elements,
1631) {
1632 if (num_elements == 0) return;
1633
1634 // Copy scale from device to host to pass as kernel argument
1635 // (Kernels typically take scalar arguments by value)
1636 float h_scale;
1638 // We need to synchronize here to ensure h_scale is available before launching the kernel
1639 // A more optimized approach might involve a device-side broadcast of the scale if it were part of a larger struct.
1641
1642 const int threads_per_block = 256;
1643 int num_blocks = (num_elements + threads_per_block - 1) / threads_per_block;
1645 int8_in_dev, h_scale, fp32_out_dev, num_elements);
1647}
1648
1649// New Generic GEMM for FP32
1651 bool transa_user, bool transb_user,
1652 int m_user, int n_user, int k_user,
1653 const float* alpha_user,
1654 const float* A_user, int lda_user,
1655 const float* B_user, int ldb_user,
1656 const float* beta_user,
1657 float* C_user, int ldc_user,
1659
1662 Logger::error("cublasSetStream failed in gemm_f32_f32_cuda with error: " + std::to_string(status) + " (" + cublasGetStatusString(status) + ")");
1663 throw std::runtime_error("cublasSetStream failed");
1664 }
1665
1668 const float *A_cublas_ptr, *B_cublas_ptr;
1670
1671
1672 M_cublas = n_user;
1673 N_cublas = m_user;
1674 K_cublas = k_user;
1675
1676 A_cublas_ptr = B_user; // B_user data is the first matrix for cuBLAS call
1678
1679 B_cublas_ptr = A_user; // A_user data is the second matrix for cuBLAS call
1681
1682 // Determine cuBLAS operations for A_cublas_ptr (B_user) and B_cublas_ptr (A_user)
1683 // opA_cublas applies to B_user, opB_cublas applies to A_user.
1684 if (!transa_user && !transb_user) {
1685 // User wants C = A * B
1686 opA_cublas = CUBLAS_OP_N; // for B_user
1687 opB_cublas = CUBLAS_OP_N; // for A_user
1688 } else if (transa_user && !transb_user) {
1689 // User wants C = A^T * B
1690 opA_cublas = CUBLAS_OP_N; // for B_user
1691 opB_cublas = CUBLAS_OP_T; // for A_user (A^T)
1692 } else if (!transa_user && transb_user) {
1693 // User wants C = A * B^T
1694 opA_cublas = CUBLAS_OP_T; // for B_user (B^T)
1695 opB_cublas = CUBLAS_OP_N; // for A_user
1696 } else { // transa_user && transb_user
1697 // User wants C = A^T * B^T
1698 opA_cublas = CUBLAS_OP_T; // for B_user (B^T)
1699 opB_cublas = CUBLAS_OP_T; // for A_user (A^T)
1700 }
1701
1702
1703 // For swapped computation: C^T result is (n_user x m_user) in column-major
1704 int LDC_cublas = ldc_user; // Leading dimension of C (which is n_user for row-major C(m,n))
1705
1708 alpha_user,
1711 beta_user,
1712 C_user, LDC_cublas); // FIXED: Use LDC_cublas instead of ldc_user
1713
1715 Logger::error("cublasSgemm failed in gemm_f32_f32_cuda with error: " + std::to_string(status) + " (" + cublasGetStatusString(status) + ")");
1716 Logger::error("GEMM params (original user view): transa_user=" + std::to_string(transa_user) + " transb_user=" + std::to_string(transb_user) +
1717 " m=" + std::to_string(m_user) + " n=" + std::to_string(n_user) + " k=" + std::to_string(k_user) +
1718 " lda=" + std::to_string(lda_user) + " ldb=" + std::to_string(ldb_user) + " ldc=" + std::to_string(ldc_user));
1719 Logger::error("GEMM params (internal cublas view at failure): transA=" + std::to_string(opA_cublas==CUBLAS_OP_T) + " transB=" + std::to_string(opB_cublas==CUBLAS_OP_T) +
1720 " M_c=" + std::to_string(M_cublas) + " N_c=" + std::to_string(N_cublas) + " K_c=" + std::to_string(K_cublas) +
1721 " LDA_c=" + std::to_string(LDA_cublas) + " LDB_c=" + std::to_string(LDB_cublas) + " LDC_c=" + std::to_string(LDC_cublas));
1722 throw std::runtime_error("cublasSgemm failed");
1723 }
1724}
1725
1726
1728 float* d_output_batch_strided, // Output: [B, H_q, D_h] effectively, though might be [B, H_q*D_h] and reshaped later
1729 const float* d_q_batch_strided, // Q input: [B, H_q, D_h]
1730 const float* d_k_batch_strided, // K input for current batch: [B, H_kv, D_h] (may be nullptr)
1731 const float* d_v_batch_strided, // V input for current batch: [B, H_kv, D_h] (may be nullptr)
1732 float* d_kv_cache_k_base, // K Cache base: [S_max, H_kv, D_h]
1733 float* d_kv_cache_v_base, // V Cache base: [S_max, H_kv, D_h]
1734 int num_tokens_in_batch, // B
1735 int num_q_heads, // H_q
1736 int num_kv_heads, // H_kv
1737 int head_dim, // D_h
1738 int start_pos_in_kv_cache, // S_offset
1739 float scale,
1741 const int* attention_mask_cu) {
1742
1743 // Grid: (num_tokens_in_batch, num_q_heads)
1744 // Block: head_dim threads rounded up to a power of two (the launcher). The softmax's tree
1745 // reductions halve blockDim.x, so a block of head_dim threads lost partials at any head_dim that
1746 // is not a power of two (80, 96: tools/prefill_attention_test.cu), as rmsnorm_batch_cuda did
1747 // before 69d7ca2. The extra threads (dim_thread_idx >= head_dim) read and write nothing; they
1748 // join every barrier and put the reductions' identities (-FLT_MAX, 0) in the scratch.
1749 int token_idx_in_batch = blockIdx.x; // Current token in the batch we are computing attention FOR
1750 int q_head_idx = blockIdx.y; // Current Q head we are computing attention FOR
1751 int dim_thread_idx = threadIdx.x; // Thread index within the head dimension
1752 const bool active = dim_thread_idx < head_dim;
1753
1755 return;
1756 }
1757
1758 // Determine the GQA/MQA mapping: which KV head corresponds to the current Q head
1761
1762 // Pointer to the current Q vector for this token and Q head
1763 const float* q_current_head_ptr = d_q_batch_strided +
1765 (size_t)q_head_idx * head_dim;
1766
1767 // Output pointer for the current token and Q head
1770 (size_t)q_head_idx * head_dim;
1771
1772 // CORRECTED: KV cache already contains current batch, so total context is available in cache
1775
1778 }
1779
1780 extern __shared__ float sdata[];
1781 float* s_scores = sdata; // Size: total_context_len
1782 float* s_dot_product_scratch = &sdata[total_context_len]; // Size: blockDim.x (>= head_dim)
1783
1784 // Calculate QK^T for each K vector in the context with causal masking
1786 // CORRECTED: All K/V comes from cache since cache already contains current batch
1787 const float* k_vec_ptr = d_kv_cache_k_base +
1789 (size_t)kv_head_idx_for_q * head_dim;
1790
1791
1792 // Initialize score for this context position
1793 if (dim_thread_idx == 0) {
1794 // Apply causal masking: token can only attend to positions <= its own position
1796 s_scores[context_pos] = -FLT_MAX; // Mask out future positions
1797 } else {
1798 s_scores[context_pos] = 0.0f;
1799 }
1800 }
1801 __syncthreads(); // Ensure s_scores[context_pos] is initialized before use
1802
1803 // Skip computation if this position is masked
1805 // Parallel dot product: each thread computes one element of Q_d * K_d
1806 // Store into shared memory scratchpad
1807 if (active) {
1811 }
1812 __syncthreads(); // Ensure all partial products are in s_dot_product_scratch
1813
1814 // Thread 0 sums up the partial products from scratchpad
1815 if (dim_thread_idx == 0) {
1816 double dot_prod = 0.0; // USE DOUBLE FOR ACCUMULATION
1817 for (int d = 0; d < head_dim; ++d) {
1819 }
1820 s_scores[context_pos] = (float)(dot_prod * scale); // Scale and cast back to float
1821 }
1822 __syncthreads(); // Ensure s_scores[context_pos] is written by thread 0 before next iteration
1823 }
1824 }
1825
1826 // --- Softmax ---
1827 // Parallel softmax over s_scores[0...total_context_len-1]
1828 // 1. Find max score in s_scores (reduction)
1829 float max_score = -FLT_MAX;
1830
1831 // The scores are split over the head_dim active threads (stride head_dim, which is blockDim.x
1832 // at a power-of-two head_dim); the extra threads hold the identities.
1834 for (int i = dim_thread_idx; active && i < total_context_len; i += head_dim) {
1836 }
1838 __syncthreads();
1839
1840 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
1841 if (dim_thread_idx < s) {
1843 }
1844 __syncthreads();
1845 }
1846 if (dim_thread_idx == 0) {
1848 }
1849 __syncthreads();
1851 __syncthreads(); // every thread has read the maximum before the scratch takes the sums
1852
1853 float thread_local_sum_exp = 0.0f;
1854 for (int i = dim_thread_idx; active && i < total_context_len; i += head_dim) {
1855 float val = expf(s_scores[i] - max_score);
1856 s_scores[i] = val;
1858 }
1860 __syncthreads();
1861
1862 float sum_exp_scores = 0.0f;
1863 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
1864 if (dim_thread_idx < s) {
1866 }
1867 __syncthreads();
1868 }
1869 if (dim_thread_idx == 0) {
1871 }
1872 __syncthreads();
1874
1875 float inv_sum_exp_scores = 1.0f / (sum_exp_scores + 1e-9f);
1876 for (int i = dim_thread_idx; i < total_context_len; i += blockDim.x) {
1878 }
1879 __syncthreads();
1880
1881 // --- Weighted Sum of V ---
1882 if (!active) return; // no barrier follows
1883 double weighted_sum_v_d = 0.0; // USE DOUBLE FOR ACCUMULATION
1885 // CORRECTED: All V comes from cache since cache already contains current batch
1886 const float* v_vec_ptr = d_kv_cache_v_base +
1888 (size_t)kv_head_idx_for_q * head_dim;
1889
1891 }
1892
1894
1895}
1896
1897__global__ void rmsnorm_batch_kernel(float* d_out, const float* d_in, const float* d_weight,
1898 int num_tokens, int hidden_size, float eps) {
1899 int token_idx = blockIdx.x;
1900 if (token_idx >= num_tokens) {
1901 return;
1902 }
1903
1904 const float* current_token_input = d_in + token_idx * hidden_size;
1905 float* current_token_output = d_out + token_idx * hidden_size;
1906
1907 // Calculate sum of squares for the current token's hidden state
1908 float ssq = 0.0f;
1909 // A single thread per token for sum of squares calculation is not efficient.
1910 // This should be a parallel reduction per token.
1911 // For simplicity in this step, doing it serially per token via one thread block.
1912 // A more optimized version would use a grid-stride loop or a 2D grid.
1913 // Or, launch one block per token, and do a parallel reduction within the block.
1914 if (threadIdx.x == 0) { // Let one thread in the block handle this token
1915 for (int i = 0; i < hidden_size; ++i) {
1917 }
1918 ssq /= hidden_size;
1919 float inv_norm_factor = rsqrtf(ssq + eps);
1920
1921 for (int i = 0; i < hidden_size; ++i) {
1923 }
1924 }
1925}
1926
1927// Optimized Batched RMSNorm Kernel - one block per token, parallel reduction within block
1928__global__ void rmsnorm_batch_kernel_optimized(float* d_out, const float* d_in, const float* d_weight,
1929 int num_tokens, int hidden_size, float eps) {
1930 extern __shared__ float sdata[]; // For reduction
1931 int token_idx = blockIdx.x;
1932
1933 if (token_idx >= num_tokens) {
1934 return;
1935 }
1936
1937 const float* current_token_input = d_in + token_idx * hidden_size;
1938 float* current_token_output = d_out + token_idx * hidden_size;
1939
1940 unsigned int tid = threadIdx.x;
1941 unsigned int N_per_token = hidden_size; // elements in one token's hidden state
1942
1943 // Load input into shared memory and calculate sum of squares (partial)
1944 float local_ssq_sum = 0.0f;
1945 for (unsigned int i = tid; i < N_per_token; i += blockDim.x) {
1946 float val = current_token_input[i];
1947 local_ssq_sum += val * val;
1948 }
1950 __syncthreads();
1951
1952 // Parallel reduction in shared memory for ssq
1953 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
1954 if (tid < s) {
1955 sdata[tid] += sdata[tid + s];
1956 }
1957 __syncthreads();
1958 }
1959
1960 // Thread 0 gets final sum of squares for this token, calculates norm, and applies
1961 if (tid == 0) {
1962 float total_ssq = sdata[0];
1963 total_ssq /= hidden_size;
1965 // This final application loop should also be parallelized if hidden_size is large.
1966 // For now, thread 0 does it.
1967 for (unsigned int i = 0; i < N_per_token; ++i) {
1969 }
1970 }
1971}
1972
1973// Final Optimized Batched RMSNorm Kernel - 2D grid, one warp per row (token), parallel reduction within warp
1975 const float* __restrict__ d_in,
1976 const float* __restrict__ d_weight,
1977 int num_tokens, int hidden_size, float eps) {
1978 int token_idx = blockIdx.x; // Each block processes one token
1979
1980 if (token_idx >= num_tokens) {
1981 return;
1982 }
1983
1984 const float* current_token_input = d_in + token_idx * hidden_size;
1985 float* current_token_output = d_out + token_idx * hidden_size;
1986
1987 // MODIFIED: Use double for ssq accumulation
1988 double ssq_double = 0.0;
1989 // Parallel reduction for ssq across threads in the block for the current token
1990 // Each thread handles a subset of the hidden_size elements for this token.
1991 for (int i = threadIdx.x; i < hidden_size; i += blockDim.x) {
1992 float val = current_token_input[i];
1993 // MODIFIED: Accumulate into double
1994 ssq_double += static_cast<double>(val) * static_cast<double>(val);
1995 }
1996
1997 // Reduce ssq_double across threads in the block
1998 extern __shared__ double sdata_double[]; // MODIFIED: Shared memory of type double
2000 __syncthreads();
2001
2002 for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
2003 if (threadIdx.x < s) {
2005 }
2006 __syncthreads();
2007 }
2008
2009 if (threadIdx.x == 0) {
2010 // MODIFIED: ssq_double is now fully reduced in sdata_double[0]
2012 }
2013 __syncthreads();
2014
2015 if (threadIdx.x == 0) {
2016 // MODIFIED: Store mean square (as double) in shared memory
2017 sdata_double[0] = sdata_double[0] / hidden_size;
2018 }
2019 __syncthreads();
2020
2021 // MODIFIED: All threads read the mean_square (as double)
2022 double mean_square_double = sdata_double[0];
2023 // MODIFIED: Cast to float before rsqrtf
2024 float mean_square_float = static_cast<float>(mean_square_double);
2026
2027 // Apply normalization in parallel by all threads in the block for this token
2028 for (int i = threadIdx.x; i < hidden_size; i += blockDim.x) {
2030 }
2031}
2032
2033
2034void rmsnorm_batch_cuda(float* d_out, float* d_in, const float* d_weight,
2035 int num_tokens, int hidden_size, float eps,
2037
2038 if (num_tokens == 0 || hidden_size == 0) {
2039 Logger::info("[RMSNORM_BATCH_CUDA_EXIT] num_tokens or hidden_size is 0. Nothing to do.");
2040 return; // Nothing to do
2041 }
2042
2043 // Log initial input values (first token, first few elements)
2044 if (num_tokens > 0 && hidden_size > 0) {
2045 int log_count = std::min(3, hidden_size);
2046 std::vector<float> h_in_sample(log_count);
2048 std::vector<float> h_weight_sample(log_count);
2050 }
2051
2052
2053 // Choose a reasonable block size. For RMSNorm, threads operate along hidden_size.
2054 // Max threads per block is typically 1024.
2055 // If hidden_size is small (e.g., < 256), could use hidden_size as blockDim.x
2056 // If hidden_size is large, use a fixed size like 256 or 512.
2057 // The kernel's tree reduction halves blockDim.x at each step, so the block is a power of two.
2058 // A block of hidden_size threads loses partial sums at any width up to 256 that is not one: at
2059 // d = 192 the reduction reaches s = 3 and drops the third partial, a third of the sum of
2060 // squares (tools/rmsnorm_batch_test.cu). Power-of-two widths up to 256, and every width above
2061 // 256, get a block of hidden_size or 256 threads as before; the extra threads of a rounded-up
2062 // block contribute 0.
2063 int threads_per_block_x = 1;
2064 while (threads_per_block_x < hidden_size && threads_per_block_x < 256) threads_per_block_x <<= 1;
2065
2066 // We launch one block per token.
2069
2070 // Shared memory for reduction within each block (for one token)
2071 // MODIFIED: Change shared memory size to use sizeof(double)
2072 size_t shared_mem_size = threads_per_block_x * sizeof(double);
2073 if (threads_per_block_x == 0) shared_mem_size = sizeof(double); // Min for sdata_double[0]
2074
2076 d_out, d_in, d_weight, num_tokens, hidden_size, eps
2077 );
2078 gpuErrchk(cudaGetLastError()); // Check for errors immediately after kernel launch
2079
2080 // Log output values (first token, first few elements)
2081 if (num_tokens > 0 && hidden_size > 0) {
2082 int log_count = std::min(3, hidden_size);
2083 std::vector<float> h_out_sample(log_count);
2085 }
2086}
2087
2088// Host wrapper for Batched Attention Prefill
2089// ---------------------------------------------------------------------------
2090// Prefill attention, tiled with online softmax (kernel_variant "flash_v1";
2091// docs/TTFT.md). Selected by EMBER_PREFILL_ATTN=flash; the default stays the
2092// baseline kernel above until this variant passes parity and its TTFT has
2093// been measured under the same protocol.
2094//
2095// What it computes: exactly the baseline's causal attention. Query token t
2096// (absolute position start + t) attends to cache positions 0..start + t, with
2097// K/V read from the cache the batch was just written into.
2098//
2099// Assumptions: head_dim is a multiple of 32 and at most 128 (each lane of a
2100// warp owns head_dim / 32 output dimensions); num_q_heads % num_kv_heads == 0.
2101// The wrapper falls back to the baseline kernel otherwise.
2102//
2103// Shape: one block per (tile of FA_BM = 32 queries, query head), 4 warps,
2104// each warp owning FA_BM / 4 = 8 query rows. K/V move through shared memory
2105// in tiles of FA_BN = 32 positions. K rows are padded by one float, so lane j
2106// reading K[j][d] hits a different bank per lane. Per row and tile, lane j
2107// scores key j; a warp max and sum update the row's running max m and
2108// normaliser l (online softmax), the accumulator is rescaled by
2109// exp(m_old - m_new), and P V is added with p_j broadcast by shuffle. Tiles
2110// that lie entirely beyond a block's last query are never loaded (causal
2111// skip), which is half the work of the baseline's full scan.
2112//
2113// Trade-offs: FP32 throughout (no tensor cores), so it is limited by FP32
2114// FMA throughput rather than memory once tiles are resident. Q is read from
2115// global memory through the read-only cache (each lane reads the same q
2116// element: a broadcast) rather than staged in shared memory, which keeps the
2117// shared footprint under the 48 KB static limit at head_dim 128. Query heads
2118// sharing a KV head re-load the same K/V tiles (GQA handled by indexing, as in
2119// the baseline); sharing tiles across a GQA group is the next variant. Tile
2120// sizes are fixed here; tuning them per GPU is its own experiment.
2121// Accumulation is FP32 with expf; the baseline accumulates its dot products
2122// in FP64, so agreement is to FP32 rounding, not bit-exact.
2123// ---------------------------------------------------------------------------
2124constexpr int FA_BM = 32; // queries per block
2125constexpr int FA_BN = 32; // keys per K/V tile (= warp size: lane j scores key j)
2126constexpr int FA_WARPS = 4;
2127constexpr int FA_ROWS = FA_BM / FA_WARPS;
2128
2129template <int DPL> // output dims per lane = head_dim / 32
2131 float* __restrict__ out, // [B, H_q, D]
2132 const float* __restrict__ q, // [B, H_q, D]
2133 const float* __restrict__ k_cache, // [S_max, H_kv, D]
2134 const float* __restrict__ v_cache, // [S_max, H_kv, D]
2135 int num_tokens, int num_q_heads, int num_kv_heads, int start_pos, float scale) {
2136 constexpr int D = DPL * 32;
2137 __shared__ float sK[FA_BN][D + 1];
2138 __shared__ float sV[FA_BN][D];
2139
2140 const int lane = threadIdx.x & 31;
2141 const int warp = threadIdx.x >> 5;
2142 const int q_head = blockIdx.y;
2143 const int kv_head = q_head / (num_q_heads / num_kv_heads);
2144 const int tile_first = blockIdx.x * FA_BM;
2145 const int tile_last = min(tile_first + FA_BM, num_tokens) - 1; // last query in this block
2146 const int kv_end = start_pos + tile_last + 1; // keys this block can need
2147 const size_t kv_stride = (size_t)num_kv_heads * D;
2148
2149 float m[FA_ROWS], l[FA_ROWS], acc[FA_ROWS][DPL];
2150#pragma unroll
2151 for (int i = 0; i < FA_ROWS; ++i) {
2152 m[i] = -FLT_MAX;
2153 l[i] = 0.0f;
2154#pragma unroll
2155 for (int k = 0; k < DPL; ++k) acc[i][k] = 0.0f;
2156 }
2157
2158 for (int kv0 = 0; kv0 < kv_end; kv0 += FA_BN) {
2159 // Cooperative tile load; positions past kv_end are zero-filled and
2160 // masked below.
2161 for (int idx = threadIdx.x; idx < FA_BN * D; idx += blockDim.x) {
2162 const int j = idx / D, d = idx % D;
2163 const int pos = kv0 + j;
2164 const bool in = pos < kv_end;
2165 const size_t off = (size_t)pos * kv_stride + (size_t)kv_head * D + d;
2166 sK[j][d] = in ? k_cache[off] : 0.0f;
2167 sV[j][d] = in ? v_cache[off] : 0.0f;
2168 }
2169 __syncthreads();
2170
2171#pragma unroll
2172 for (int i = 0; i < FA_ROWS; ++i) {
2173 const int t = tile_first + warp * FA_ROWS + i;
2174 if (t >= num_tokens) continue; // warp-uniform
2175 const int q_pos = start_pos + t;
2176 if (kv0 > q_pos) continue; // whole tile is in this row's future
2177 const int key = kv0 + lane;
2178 const float* q_row = q + ((size_t)t * num_q_heads + q_head) * D;
2179 float s = -FLT_MAX;
2180 if (key <= q_pos) {
2181 float dot = 0.0f;
2182#pragma unroll 8
2183 for (int d = 0; d < D; ++d) dot += __ldg(q_row + d) * sK[lane][d];
2184 s = dot * scale;
2185 }
2186 float tile_max = s;
2187#pragma unroll
2188 for (int o = 16; o > 0; o >>= 1) tile_max = fmaxf(tile_max, __shfl_xor_sync(0xffffffff, tile_max, o));
2189 const float m_new = fmaxf(m[i], tile_max);
2190 const float p = (key <= q_pos) ? expf(s - m_new) : 0.0f;
2191 const float corr = expf(m[i] - m_new); // 0 on the first visited tile
2192 float p_sum = p;
2193#pragma unroll
2194 for (int o = 16; o > 0; o >>= 1) p_sum += __shfl_xor_sync(0xffffffff, p_sum, o);
2195 l[i] = l[i] * corr + p_sum;
2196#pragma unroll
2197 for (int k = 0; k < DPL; ++k) acc[i][k] *= corr;
2198#pragma unroll 4
2199 for (int jj = 0; jj < FA_BN; ++jj) {
2200 const float pj = __shfl_sync(0xffffffff, p, jj);
2201#pragma unroll
2202 for (int k = 0; k < DPL; ++k) acc[i][k] += pj * sV[jj][lane + 32 * k];
2203 }
2204 m[i] = m_new;
2205 }
2206 __syncthreads();
2207 }
2208
2209#pragma unroll
2210 for (int i = 0; i < FA_ROWS; ++i) {
2211 const int t = tile_first + warp * FA_ROWS + i;
2212 if (t >= num_tokens) continue;
2213 float* o_row = out + ((size_t)t * num_q_heads + q_head) * D;
2214 const float inv_l = 1.0f / l[i]; // l >= 1: key 0 is always visible
2215#pragma unroll
2216 for (int k = 0; k < DPL; ++k) o_row[lane + 32 * k] = acc[i][k] * inv_l;
2217 }
2218}
2219
2220// ---------------------------------------------------------------------------
2221// Prefill attention v2 (kernel_variant "flash_v2"; EMBER_PREFILL_ATTN=flash2).
2222//
2223// Same computation as flash_v1 and the baseline: causal attention of the
2224// batch's queries over cache positions 0..start + t. The op map
2225// (docs/TTFT.md) showed v1 spending ~2 shared/L1 loads per FMA and loading
2226// every K/V tile once per query head, though the 8 query heads of a GQA
2227// group read the same K/V. v2 changes the work decomposition, not the
2228// arithmetic:
2229//
2230// * GQA-shared tiles. One block per (KV head, tile of FA2_TQ query
2231// tokens) owns all G = H_q / H_kv query heads of that KV head: 64 rows
2232// (FA2_ROWS = FA2_TQ * G), row r = (token r / G, head r % G). Each K/V
2233// tile is loaded once for all of them.
2234// * Register micro-tiles. Scores S = Q K^T for a 64 x 32 tile are computed
2235// 4 rows x 4 keys per thread, so each pair of shared loads feeds 16 FMAs
2236// instead of 1. P V: each thread owns 2 rows x D/4 dims (dims interleaved
2237// by 4 so shared reads are conflict-free), with 2 P values reused across
2238// D/4 dims.
2239// * Online softmax per row, as in v1: running max m, normaliser l, and the
2240// accumulator rescaled by exp(m_old - m_new). Two threads per row reduce
2241// the tile max and sum with a shuffle. A row entirely masked within a
2242// tile leaves m, l and the accumulator unchanged (guarded, not NaN).
2243//
2244// Assumptions: head_dim is 32 or 64, and G divides 64. Static shared
2245// memory stays under 48 KB at D = 64: Q 64 x 65, K 32 x 65, V 32 x 64,
2246// S 64 x 33 floats. Otherwise the dispatcher uses v1. Still FP32 on CUDA
2247// cores (no tensor cores), so parity with v1 and the baseline is to FP32
2248// rounding. Tile sizes are fixed; tuning them per GPU is its own
2249// experiment.
2250// ---------------------------------------------------------------------------
2251constexpr int FA2_ROWS = 64; // query rows (token x head) per block
2252constexpr int FA2_BN = 32; // keys per K/V tile
2253constexpr int FA2_THREADS = 128;
2254
2255template <int D>
2257 float* __restrict__ out, const float* __restrict__ q, const float* __restrict__ k_cache,
2258 const float* __restrict__ v_cache, int num_tokens, int num_q_heads, int num_kv_heads,
2259 int start_pos, float scale) {
2260 constexpr int DPT = D / 4; // P.V dims per thread
2261 __shared__ float sQ[FA2_ROWS][D + 1];
2262 __shared__ float sK[FA2_BN][D + 1];
2263 __shared__ float sV[FA2_BN][D];
2264 __shared__ float sS[FA2_ROWS][FA2_BN + 1];
2266
2267 const int tid = threadIdx.x;
2268 const int group = num_q_heads / num_kv_heads; // G
2269 const int tq = FA2_ROWS / group; // query tokens per block
2270 const int kv_head = blockIdx.y;
2271 const int tok0 = blockIdx.x * tq;
2272 const int tok_last = min(tok0 + tq, num_tokens) - 1;
2273 const int kv_end = start_pos + tok_last + 1;
2274 const size_t kv_stride = (size_t)num_kv_heads * D;
2275
2276 // Row r -> (token, query head). Out-of-range tokens load zeros and are
2277 // never written.
2278 for (int idx = tid; idx < FA2_ROWS * D; idx += FA2_THREADS) {
2279 const int r = idx / D, d = idx % D;
2280 const int t = tok0 + r / group;
2281 const int h = kv_head * group + r % group;
2282 sQ[r][d] = t < num_tokens ? q[((size_t)t * num_q_heads + h) * D + d] : 0.0f;
2283 }
2284 if (tid < FA2_ROWS) {
2285 sM[tid] = -FLT_MAX;
2286 sL[tid] = 0.0f;
2287 }
2288
2289 // P.V ownership: rows pr0, pr0 + 1; dims pc + 4k.
2290 const int pr0 = (tid / 4) * 2;
2291 const int pc = tid % 4;
2292 float acc[2][DPT];
2293#pragma unroll
2294 for (int i = 0; i < 2; ++i)
2296 for (int k = 0; k < DPT; ++k) acc[i][k] = 0.0f;
2297
2298 // Score micro-tile ownership: rows sr0..sr0+3, keys sc0..sc0+3.
2299 const int sr0 = (tid / 8) * 4;
2300 const int sc0 = (tid % 8) * 4;
2301
2302 for (int kv0 = 0; kv0 < kv_end; kv0 += FA2_BN) {
2303 __syncthreads(); // previous tile's P.V reads of sS/sV are done
2304 for (int idx = tid; idx < FA2_BN * D; idx += FA2_THREADS) {
2305 const int j = idx / D, d = idx % D;
2306 const int pos = kv0 + j;
2307 const bool in = pos < kv_end;
2308 const size_t off = (size_t)pos * kv_stride + (size_t)kv_head * D + d;
2309 sK[j][d] = in ? k_cache[off] : 0.0f;
2310 sV[j][d] = in ? v_cache[off] : 0.0f;
2311 }
2312 __syncthreads();
2313
2314 // S = Q K^T * scale, causal mask.
2315 float s[4][4];
2316#pragma unroll
2317 for (int i = 0; i < 4; ++i)
2319 for (int j = 0; j < 4; ++j) s[i][j] = 0.0f;
2320#pragma unroll 8
2321 for (int d = 0; d < D; ++d) {
2322 float qv[4], kv[4];
2323#pragma unroll
2324 for (int i = 0; i < 4; ++i) qv[i] = sQ[sr0 + i][d];
2325#pragma unroll
2326 for (int j = 0; j < 4; ++j) kv[j] = sK[sc0 + j][d];
2327#pragma unroll
2328 for (int i = 0; i < 4; ++i)
2330 for (int j = 0; j < 4; ++j) s[i][j] += qv[i] * kv[j];
2331 }
2332#pragma unroll
2333 for (int i = 0; i < 4; ++i) {
2334 const int r = sr0 + i;
2335 const int q_pos = start_pos + tok0 + r / group;
2336#pragma unroll
2337 for (int j = 0; j < 4; ++j) {
2338 const int key = kv0 + sc0 + j;
2339 sS[r][sc0 + j] = (key <= q_pos && key < kv_end) ? s[i][j] * scale : -FLT_MAX;
2340 }
2341 }
2342 __syncthreads();
2343
2344 // Online softmax: two threads per row, 16 keys each.
2345 {
2346 const int r = tid / 2, half = tid % 2;
2347 float tmax = -FLT_MAX;
2348#pragma unroll
2349 for (int j = 0; j < FA2_BN / 2; ++j) tmax = fmaxf(tmax, sS[r][half * 16 + j]);
2350 tmax = fmaxf(tmax, __shfl_xor_sync(0xffffffff, tmax, 1));
2351 // Branch-free: lanes of different rows share a warp, so every
2352 // lane runs the same shuffles. A row with no visible key in this
2353 // tile (any == false) gets p = 0 and corr = 1: m, l and its
2354 // accumulator are left as they were.
2355 const bool any = tmax > -FLT_MAX;
2356 const float m_old = sM[r];
2357 const float m_new = any ? fmaxf(m_old, tmax) : m_old;
2358 const float corr = any ? expf(m_old - m_new) : 1.0f; // 0 when m_old is -FLT_MAX
2359 float p_sum = 0.0f;
2360#pragma unroll
2361 for (int j = 0; j < FA2_BN / 2; ++j) {
2362 const float sv = sS[r][half * 16 + j];
2363 const float p = (any && sv > -FLT_MAX) ? expf(sv - m_new) : 0.0f;
2364 sS[r][half * 16 + j] = p;
2365 p_sum += p;
2366 }
2367 p_sum += __shfl_xor_sync(0xffffffff, p_sum, 1);
2368 if (half == 0) {
2369 sM[r] = m_new;
2370 sL[r] = sL[r] * corr + p_sum;
2371 sCorr[r] = corr;
2372 }
2373 }
2374 __syncthreads();
2375
2376 // acc = acc * corr + P V.
2377 const float c0 = sCorr[pr0], c1 = sCorr[pr0 + 1];
2378#pragma unroll
2379 for (int k = 0; k < DPT; ++k) {
2380 acc[0][k] *= c0;
2381 acc[1][k] *= c1;
2382 }
2383#pragma unroll 4
2384 for (int j = 0; j < FA2_BN; ++j) {
2385 const float p0 = sS[pr0][j], p1 = sS[pr0 + 1][j];
2386#pragma unroll
2387 for (int k = 0; k < DPT; ++k) {
2388 const float v = sV[j][pc + 4 * k];
2389 acc[0][k] += p0 * v;
2390 acc[1][k] += p1 * v;
2391 }
2392 }
2393 }
2394
2395 __syncthreads();
2396#pragma unroll
2397 for (int i = 0; i < 2; ++i) {
2398 const int r = pr0 + i;
2399 const int t = tok0 + r / group;
2400 if (t >= num_tokens) continue;
2401 const int h = kv_head * group + r % group;
2402 const float inv_l = 1.0f / sL[r]; // l > 0: key 0 is visible to every row
2403 float* o = out + ((size_t)t * num_q_heads + h) * D;
2404#pragma unroll
2405 for (int k = 0; k < DPT; ++k) o[pc + 4 * k] = acc[i][k] * inv_l;
2406 }
2407}
2408
2409static bool launch_flash2_prefill_attention(float* out, const float* q, const float* k_cache,
2410 const float* v_cache, int num_tokens, int num_q_heads,
2411 int num_kv_heads, int head_dim, int start_pos,
2412 float scale, cudaStream_t stream) {
2413 if (num_kv_heads <= 0 || num_q_heads % num_kv_heads != 0) return false;
2414 const int group = num_q_heads / num_kv_heads;
2415 if (FA2_ROWS % group != 0) return false;
2416 const int tq = FA2_ROWS / group;
2417 dim3 grid((num_tokens + tq - 1) / tq, num_kv_heads);
2419 switch (head_dim) {
2422 default: return false;
2423 }
2424 return true;
2425}
2426
2427// Read once: which prefill attention kernel to launch (docs/TTFT.md).
2428// 0 = baseline, 1 = flash (v1), 2 = flash2 (v2, falls back to v1, then to
2429// the baseline kernel when an assumption does not hold).
2430// Default (unset) is flash2. It matched the baseline kernel to <= 1e-5
2431// relative on the first-token logits at every length up to 4096, and was
2432// 2.8% to 39.8% faster than v1 there, 105x the original TTFT at 4096
2433// (docs/TTFT.md). EMBER_PREFILL_ATTN=baseline restores the original kernel.
2434static int prefill_attention_mode() {
2435 static const int mode = [] {
2436 const char* v = std::getenv("EMBER_PREFILL_ATTN");
2437 if (v == nullptr) return 2;
2438 const std::string s(v);
2439 if (s == "baseline") return 0;
2440 return s == "flash" ? 1 : 2;
2441 }();
2442 return mode;
2443}
2444static bool use_flash_prefill_attention() { return prefill_attention_mode() >= 1; }
2445
2446// Launch the tiled kernel if its assumptions hold; false means "use the
2447// baseline kernel instead".
2448static bool launch_flash_prefill_attention(float* out, const float* q, const float* k_cache,
2449 const float* v_cache, int num_tokens, int num_q_heads,
2450 int num_kv_heads, int head_dim, int start_pos,
2451 float scale, cudaStream_t stream) {
2452 if (head_dim % 32 != 0 || head_dim > 128 || num_q_heads % num_kv_heads != 0) return false;
2454 dim3 block(FA_WARPS * 32);
2455 switch (head_dim / 32) {
2460 default: return false;
2461 }
2462 return true;
2463}
2464
2466 const float* d_q_batch_strided, // Input Q: [B, H_q, D_h]
2467 const float* d_k_batch_strided, // Input K for current batch (NEW)
2468 const float* d_v_batch_strided, // Input V for current batch (NEW)
2469 float* d_kv_cache_k_base, // K Cache: [S_max, H_kv, D_h] (changed to non-const)
2470 float* d_kv_cache_v_base, // V Cache: [S_max, H_kv, D_h] (changed to non-const)
2471 float* d_output_batch_strided, // Output: [B, H_q, D_h]
2472 int num_tokens_in_batch, // B
2473 int start_pos_in_kv_cache, // Start position for this batch in KV cache (NEW)
2474 int cache_max_seq_len, // Max capacity of KV cache (NEW)
2475 int num_q_heads, // H_q
2476 int num_kv_heads, // H_kv
2477 int head_dim, // D_h
2478 float scale,
2480 const int* attention_mask_cu // Optional attention mask
2481 ) {
2482
2483
2484 if (num_tokens_in_batch == 0 || head_dim == 0) {
2485 Logger::info("[ATTN_BATCH_PREFILL_CUDA_SKIP] num_tokens_in_batch or head_dim is 0. Nothing to do.");
2486 Logger::info("[ATTN_BATCH_PREFILL_CUDA_EXIT] Skipped operation.");
2487 return; // Nothing to do
2488 }
2489 if (num_kv_heads == 0 || num_q_heads % num_kv_heads != 0) {
2490 Logger::error("[ATTN_BATCH_PREFILL_CUDA_ERROR] Invalid num_kv_heads (" + std::to_string(num_kv_heads) +
2491 ") or num_q_heads (" + std::to_string(num_q_heads) + ") for GQA.");
2492 Logger::info("[ATTN_BATCH_PREFILL_CUDA_EXIT] Error in head configuration.");
2493 return;
2494 }
2495
2496 // Log initial input values (first token, first head, first few elements)
2497 int log_count_elements = std::min(3, head_dim);
2498 // Debug samples and the device sync below: EMBER_PREFILL_DEBUG=1 only
2499 // (same-stream ordering already orders the next op after this kernel).
2500 static const bool debug = std::getenv("EMBER_PREFILL_DEBUG") != nullptr;
2501 if (debug && num_tokens_in_batch > 0 && head_dim > 0) {
2502 // Log Q for Token 0 and Token 1
2503 if (d_q_batch_strided && num_q_heads > 0) {
2505 std::vector<float> h_q_sample(log_count_elements);
2506 // Q is [B, H_q, D_h].
2507 // Offset for Token `token_to_log_idx`, Head 0 is `token_to_log_idx * num_q_heads * head_dim`.
2508 // We are logging the first `log_count_elements` of the first head (H0) for that token.
2511 }
2512 }
2513
2514 // Log K_batch for Token 0 and Token 1
2515 if (d_k_batch_strided && num_kv_heads > 0) {
2517 std::vector<float> h_k_sample(log_count_elements);
2518 // K_batch is [B, H_kv, D_h].
2521 }
2522 }
2523
2524 // Log V_batch for Token 0 and Token 1
2525 if (d_v_batch_strided && num_kv_heads > 0) {
2527 std::vector<float> h_v_sample(log_count_elements);
2528 // V_batch is [B, H_kv, D_h].
2531 }
2532 }
2533
2534 // Note: K/V cache sampling might be less informative here if start_pos_in_kv_cache is large,
2535 // as it shows past context rather than current batch contributions to cache.
2536 // However, logging it for completeness if cache pointers are valid.
2538 std::vector<float> h_kc_sample(log_count_elements);
2539 // K Cache: [S_max, H_kv, D_h]. Pointer to S_offset, H0.
2542 }
2544 std::vector<float> h_vc_sample(log_count_elements);
2545 // V Cache: [S_max, H_kv, D_h]. Pointer to S_offset, H0.
2548 }
2549 }
2550
2551 dim3 grid_dim(num_tokens_in_batch, num_q_heads); // One block per (token_in_batch, q_head)
2552 // Threads in block work on one head's computation, primarily along head_dim. The kernel's tree
2553 // reductions halve blockDim.x, so the block is head_dim rounded up to a power of two (the extra
2554 // threads contribute the identities; tools/prefill_attention_test.cu). A power-of-two head_dim
2555 // launches as before.
2556 int threads_per_block = 1;
2559
2562 size_t shared_mem_bytes = (total_context_len + threads_per_block) * sizeof(float);
2563
2564 if (shared_mem_bytes > 48 * 1024) {
2565 Logger::warning("[ATTN_BATCH_PREFILL_CUDA_WARN] Requested shared memory (" + std::to_string(shared_mem_bytes) + " bytes) might be large.");
2566 }
2567
2568 // Only the launch differs between kernel variants: the debug copies and
2569 // the device sync around it stay identical, so a TTFT change is the
2570 // kernel's alone.
2571 if (prefill_attention_mode() == 2 &&
2575 scale, stream)) {
2576 // launched v2
2577 } else if (use_flash_prefill_attention() &&
2581 scale, stream)) {
2582 // launched
2583 } else
2594 head_dim,
2596 scale,
2599 );
2601
2602 // Log output values
2603 if (debug && num_tokens_in_batch > 0 && num_q_heads > 0 && head_dim > 0 && d_output_batch_strided) {
2605 std::vector<float> h_out_sample(log_count_elements);
2606 // Output is [B, H_q, D_h].
2609 }
2610 }
2611 if (debug) cudaDeviceSynchronize();
2612
2613 // Log output values
2614 if (debug && num_tokens_in_batch > 0 && num_q_heads > 0 && head_dim > 0 && d_output_batch_strided) {
2616 std::vector<float> h_out_sample(log_count_elements);
2617 // Output is [B, H_q, D_h].
2620 }
2621 }
2622}
2623
2625 float* d_kv_cache_layer_base, // K or V cache for the layer: [max_seq_len, num_kv_heads, head_dim]
2626 const float* d_keys_or_values_batch, // Batch of K or V vectors from current forward pass: [num_tokens_in_batch, num_kv_heads, head_dim]
2627 int start_pos_in_kv_cache, // Starting sequence position in cache for this batch
2629 int num_kv_heads,
2630 int head_dim,
2631 int cache_max_seq_len) {
2632
2633 int token_idx_in_batch = blockIdx.x; // Identifies which token from the batch (0 to num_tokens_in_batch-1)
2634 int kv_head_idx = blockIdx.y; // Identifies which KV head (0 to num_kv_heads-1)
2635 int dim_idx = threadIdx.x; // Identifies which dimension within the head_dim (0 to head_dim-1)
2636
2637 // Boundary checks
2640 dim_idx >= head_dim) {
2641 return;
2642 }
2643
2644 // Calculate the global sequence position in the cache where this token's KV vector will be written
2646
2647 // Boundary check for cache capacity
2649 // This should ideally be prevented by checks in the host code before launching,
2650 // or by model logic that handles sequence length limits.
2651 // For a kernel, simply returning is a common way to handle out-of-bounds access.
2652 return;
2653 }
2654
2655 // Calculate the offset in the source batch tensor (d_keys_or_values_batch)
2656 // It's laid out as [token_idx_in_batch][kv_head_idx][dim_idx]
2658 (size_t)kv_head_idx * head_dim +
2659 dim_idx;
2660
2661 // Calculate the offset in the destination KV cache tensor (d_kv_cache_layer_base)
2662 // It's laid out as [global_seq_pos][kv_head_idx][dim_idx]
2664 (size_t)kv_head_idx * head_dim +
2665 dim_idx;
2666
2667 // Perform the copy
2669}
2670
2672 float* d_kv_cache_layer_base, // Device pointer to the K or V cache for the current layer
2673 const float* d_keys_or_values_batch, // Device pointer to the batch of K or V vectors to be written
2674 int start_pos_in_kv_cache, // The sequence position in the cache where writing for this batch should begin
2675 int num_tokens_in_batch, // Number of tokens in the d_keys_or_values_batch
2676 int num_kv_heads, // Number of K/V heads
2677 int head_dim, // Dimension of each K/V head
2678 int cache_max_seq_len, // Maximum sequence length capacity of the cache
2680
2681 if (num_tokens_in_batch == 0 || head_dim == 0 || num_kv_heads == 0) {
2682 Logger::debug("[UPDATE_KV_CACHE_BATCH_CUDA_SKIP] Nothing to update (num_tokens_in_batch, head_dim, or num_kv_heads is 0).");
2683 Logger::info("[UPDATE_KV_CACHE_BATCH_CUDA_EXIT] Skipped update.");
2684 return;
2685 }
2686
2688 Logger::error("[UPDATE_KV_CACHE_BATCH_CUDA_WARN] Batch (start: " + std::to_string(start_pos_in_kv_cache) + ", count: " + std::to_string(num_tokens_in_batch) +
2689 ") might exceed KV cache capacity (" + std::to_string(cache_max_seq_len) + "). Kernel will clip if not careful, or this is an error.");
2690 }
2691
2692 // Log input K/V values (first token, first head, first few elements)
2693 int log_count_elements = std::min(3, head_dim);
2694 if (num_tokens_in_batch > 0 && num_kv_heads > 0 && head_dim > 0) {
2695 std::vector<float> h_kv_sample_in(log_count_elements);
2696 // d_keys_or_values_batch is [num_tokens_in_batch][num_kv_heads][head_dim]
2697 // Offset for first token, first head = 0 (assuming data is contiguous for the first head of the first token)
2699 }
2700
2701 // Grid: One block per (token_in_batch, kv_head)
2703 // Block: One thread per dimension in the head vector
2705
2712 head_dim,
2714 );
2716
2717 // Log values written to cache (first token written, first head, first few elements)
2719 std::vector<float> h_kv_sample_out(log_count_elements);
2720 // Cache is [cache_max_seq_len][num_kv_heads][head_dim]
2721 // Offset for first token written in this batch, first head:
2722 // (start_pos_in_kv_cache * num_kv_heads * head_dim) + (0 * head_dim)
2723 // Pointer to start_pos_in_kv_cache, for the first head (head 0)
2726
2728 }
2729}
2730
2731#endif // HAS_CUDA
2732
2739 const float* __restrict__ weight,
2740 float* __restrict__ out,
2741 int n, float eps) {
2742 const int tid_global = threadIdx.x + blockIdx.x * blockDim.x;
2743 const int tid_block = threadIdx.x;
2744
2746
2747 if (tid_block == 0) {
2748 s_block_sum_sq = 0.0f;
2749 }
2750 __syncthreads();
2751
2752 float thread_partial_sum_sq = 0.0f;
2753 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2754 float val = x[i];
2756 }
2757
2758 // Reduce within warp
2759 for (int offset = warpSize / 2; offset > 0; offset >>= 1) {
2761 }
2762
2763 // Warp leaders add to shared memory sum
2764 if ((tid_block % warpSize) == 0) {
2766 }
2767 __syncthreads(); // Ensure all warps have added their sums
2768
2770 float rms_scale = rsqrtf(total_sum_sq / n + eps);
2771
2772 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2773 out[i] = x[i] * weight[i] * rms_scale;
2774 }
2775}
2776
2783 float* __restrict__ out,
2784 int n) {
2785 const int tid_global = threadIdx.x + blockIdx.x * blockDim.x;
2786 const int tid_block = threadIdx.x;
2787
2790
2791 // Initialize shared variables by the first thread of the block
2792 if (tid_block == 0) {
2794 s_block_sum_exp = 0.0f;
2795 }
2796 __syncthreads();
2797
2798 // Phase 1: Find max
2799 float thread_max_val = -INFINITY;
2800 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2802 }
2803 for (int offset = warpSize / 2; offset > 0; offset >>= 1) {
2805 }
2806 if ((tid_block % warpSize) == 0) { // Warp leaders
2808 }
2809 __syncthreads();
2811
2812 // Phase 2: Compute sum of exp
2813 float thread_sum_exp = 0.0f;
2814 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2816 }
2817 for (int offset = warpSize / 2; offset > 0; offset >>= 1) {
2818 thread_sum_exp += __shfl_down_sync(0xFFFFFFFF, thread_sum_exp, offset);
2819 }
2820 if ((tid_block % warpSize) == 0) { // Warp leaders
2822 }
2823 __syncthreads();
2825
2826 // Phase 3: Apply softmax
2827 float inv_sum = (global_sum_exp == 0.0f) ? 0.0f : 1.0f / global_sum_exp;
2828 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2829 out[i] = expf(x[i] - global_max) * inv_sum;
2830 }
2831}
2832
2839__global__ void attention_optimized_kernel(const float* __restrict__ Q_current, // Base Q for all heads: [num_q_heads, head_dim] (actually, [num_tokens_batch * num_q_heads * head_dim] from model.cpp)
2840 const float* __restrict__ K_layer_cache_base, // Full K cache: [max_seq_len, num_kv_heads, head_dim]
2841 const float* __restrict__ V_layer_cache_base, // Full V cache: [max_seq_len, num_kv_heads, head_dim]
2842 float* __restrict__ out, // Output for all heads: [num_q_heads, head_dim]
2843 int current_seq_len, int head_dim, float scale,
2845 const int* step_dev) {
2846 // Graph decode: the length comes from device memory (step_dev[1] = pos).
2847 if (step_dev) current_seq_len = step_dev[1] + 1;
2848
2849 const int head_idx = blockIdx.x; // Current Q head index this block is working on
2850 const int tid_in_block = threadIdx.x;
2851 const int block_dim_x = blockDim.x; // Threads per block (e.g., 256)
2852
2853 if (head_idx >= num_q_heads) return;
2854
2855 // Map current Q head to its corresponding KV cache head index (for GQA/MQA)
2857 const int kv_head_idx = head_idx / heads_per_kv;
2858
2859 // Dynamic shared memory. Layout:
2860 // s_scores[current_seq_len]: to store Q.K^T results
2861 // s_reduction_val[1]: for block-wide max and sum_exp
2862 extern __shared__ float s_data[];
2863 float* s_scores = s_data;
2864 float* s_reduction_val = &s_data[current_seq_len]; // Single float for block-wide reduction
2865
2866 // Pointer to the current Q vector for this specific head
2867 // Assuming Q_current is already offset for the specific token if in batch generation.
2868 // Here, Q_current is expected to be just [head_idx * head_dim] for the current token's Q vector.
2869 const float* q_vec = Q_current + head_idx * head_dim;
2870
2871 // --- 1. Compute Q.K^T scores and store in s_scores ---
2873 // Pointer to the k_idx-th K vector in the cache for the relevant kv_head
2875 float score = 0.0f;
2876 for (int d = 0; d < head_dim; ++d) {
2877 score += q_vec[d] * k_vec[d];
2878 }
2879 s_scores[k_idx] = score * scale;
2880 }
2881 __syncthreads(); // Ensure all scores are computed and stored in s_scores
2882
2883 // --- 2. Softmax calculation on s_scores ---
2884 // a. Find max score in s_scores (block-wide reduction)
2886 __syncthreads();
2887
2888 float thread_max_score = -INFINITY;
2891 }
2892 for (int offset = warpSize / 2; offset > 0; offset >>= 1) thread_max_score = fmaxf(thread_max_score, __shfl_down_sync(0xFFFFFFFF, thread_max_score, offset));
2894 __syncthreads();
2895 float max_score = *s_reduction_val;
2896 __syncthreads(); // every thread has read the max before thread 0 reuses the slot
2897
2898 // b. Subtract max, compute exp, and sum exp (block-wide reduction for sum)
2899 if (tid_in_block == 0) *s_reduction_val = 0.0f;
2900 __syncthreads();
2901
2902 float thread_sum_exp = 0.0f;
2904 float val = expf(s_scores[k_idx] - max_score);
2905 s_scores[k_idx] = val; // Store exp(score - max)
2907 }
2908 for (int offset = warpSize / 2; offset > 0; offset >>= 1) thread_sum_exp += __shfl_down_sync(0xFFFFFFFF, thread_sum_exp, offset);
2910 __syncthreads();
2911 float sum_exp = *s_reduction_val;
2912 float inv_sum_exp = (sum_exp == 0.0f || sum_exp < 1e-9f) ? 0.0f : 1.0f / sum_exp; // add small epsilon for stability
2913
2914 // c. Normalize s_scores (they now contain softmax probabilities)
2917 }
2918 __syncthreads();
2919
2920 // --- 3. Multiply by V and accumulate to output ---
2921 // Output for this head: out_for_this_head[d_out]
2923
2924 for (int d_out = tid_in_block; d_out < head_dim; d_out += block_dim_x) {
2925 float weighted_val = 0.0f;
2926 for (int k_idx = 0; k_idx < current_seq_len; ++k_idx) { // All threads iterate full k_idx to sum contributions
2929 }
2930 out_for_this_head[d_out] = weighted_val; // Each thread writes to its assigned output dimension part
2931 }
2932}
2933
2934// ---------------------------------------------------------------------------
2935// Deterministic twins (EMBER_DETERMINISTIC=1, determinism.h). The kernels above
2936// add their warp partials into one shared float with atomicAdd (and
2937// atomicAddFloat), in whatever order the warps arrive, so the same inputs can
2938// round differently from one run to the next. These twins do the same
2939// arithmetic, except that each warp leader stores its partial in its own slot
2940// and thread 0 adds the slots in warp order, starting from 0 as the atomics
2941// do (one of the orders the atomic kernels can produce). The maxima were
2942// already order-free; they take the same slots for symmetry. Only the wrappers
2943// below choose them, and only under the mode: the kernels above are unchanged.
2944// ---------------------------------------------------------------------------
2946 const float* __restrict__ weight,
2947 float* __restrict__ out,
2948 int n, float eps) {
2949 const int tid_global = threadIdx.x + blockIdx.x * blockDim.x;
2950 const int tid_block = threadIdx.x;
2951
2952 __shared__ float s_warp_sum_sq[32];
2954
2955 float thread_partial_sum_sq = 0.0f;
2956 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2957 float val = x[i];
2959 }
2960
2961 // Reduce within warp
2962 for (int offset = warpSize / 2; offset > 0; offset >>= 1) {
2964 }
2965
2966 // Warp leaders store their sums; thread 0 adds them in warp order
2967 if ((tid_block % warpSize) == 0) {
2969 }
2970 __syncthreads();
2971 if (tid_block == 0) {
2972 const int num_warps = (blockDim.x + warpSize - 1) / warpSize;
2973 float total = 0.0f;
2974 for (int w = 0; w < num_warps; ++w) total += s_warp_sum_sq[w];
2976 }
2977 __syncthreads();
2978
2980 float rms_scale = rsqrtf(total_sum_sq / n + eps);
2981
2982 for (int i = tid_global; i < n; i += gridDim.x * blockDim.x) {
2983 out[i] = x[i] * weight[i] * rms_scale;
2984 }
2985}
2986
2988 const float* __restrict__ K_layer_cache_base,
2989 const float* __restrict__ V_layer_cache_base,
2990 float* __restrict__ out,
2991 int current_seq_len, int head_dim, float scale,
2993 const int* step_dev) {
2994 if (step_dev) current_seq_len = step_dev[1] + 1;
2995
2996 const int head_idx = blockIdx.x;
2997 const int tid_in_block = threadIdx.x;
2998 const int block_dim_x = blockDim.x;
2999
3000 if (head_idx >= num_q_heads) return;
3001
3003 const int kv_head_idx = head_idx / heads_per_kv;
3004
3005 extern __shared__ float s_data[];
3006 float* s_scores = s_data; // [current_seq_len]
3007 __shared__ float s_warp_val[32]; // one partial per warp
3008 __shared__ float s_block_val; // the block-wide max, then sum
3009 const int num_warps = (block_dim_x + warpSize - 1) / warpSize;
3010
3011 const float* q_vec = Q_current + head_idx * head_dim;
3012
3013 // --- 1. Q.K^T scores ---
3016 float score = 0.0f;
3017 for (int d = 0; d < head_dim; ++d) {
3018 score += q_vec[d] * k_vec[d];
3019 }
3020 s_scores[k_idx] = score * scale;
3021 }
3022 __syncthreads();
3023
3024 // --- 2a. max score: warp maxima, then in warp order ---
3025 float thread_max_score = -INFINITY;
3028 }
3029 for (int offset = warpSize / 2; offset > 0; offset >>= 1) thread_max_score = fmaxf(thread_max_score, __shfl_down_sync(0xFFFFFFFF, thread_max_score, offset));
3031 __syncthreads();
3032 if (tid_in_block == 0) {
3033 float m = -INFINITY;
3034 for (int w = 0; w < num_warps; ++w) m = fmaxf(s_warp_val[w], m);
3035 s_block_val = m;
3036 }
3037 __syncthreads();
3038 float max_score = s_block_val;
3039 __syncthreads(); // every thread has read the max before the slots are reused
3040
3041 // --- 2b. exp and their sum: warp sums, then added in warp order ---
3042 float thread_sum_exp = 0.0f;
3044 float val = expf(s_scores[k_idx] - max_score);
3045 s_scores[k_idx] = val;
3047 }
3048 for (int offset = warpSize / 2; offset > 0; offset >>= 1) thread_sum_exp += __shfl_down_sync(0xFFFFFFFF, thread_sum_exp, offset);
3050 __syncthreads();
3051 if (tid_in_block == 0) {
3052 float s = 0.0f;
3053 for (int w = 0; w < num_warps; ++w) s = s_warp_val[w] + s;
3054 s_block_val = s;
3055 }
3056 __syncthreads();
3057 float sum_exp = s_block_val;
3058 float inv_sum_exp = (sum_exp == 0.0f || sum_exp < 1e-9f) ? 0.0f : 1.0f / sum_exp;
3059
3060 // --- 2c. normalise ---
3063 }
3064 __syncthreads();
3065
3066 // --- 3. P V ---
3068 for (int d_out = tid_in_block; d_out < head_dim; d_out += block_dim_x) {
3069 float weighted_val = 0.0f;
3070 for (int k_idx = 0; k_idx < current_seq_len; ++k_idx) {
3073 }
3075 }
3076}
3077
3082void rmsnorm_vector_cuda_optimized(const float* x_dev, const float* weight_dev,
3083 float* out_dev, int n, float eps,
3085 if (n <= 0) return;
3086 const int block_size = 256;
3087 // Ensure grid_size is 1 as the kernel is designed for block-wide reduction.
3088 const int grid_size = 1;
3089 if (ember::deterministic()) {
3092 return;
3093 }
3097}
3098
3099void softmax_vector_cuda_optimized(const float* x_dev, float* out_dev, int n,
3101 if (n <= 0) return;
3102 const int block_size = 256;
3103 // Ensure grid_size is 1 as the kernel is designed for block-wide reduction.
3104 const int grid_size = 1;
3105 // Shared memory: 2 floats (max_val, sum_exp)
3107 x_dev, out_dev, n);
3109}
3110
3111// ---------------------------------------------------------------------------
3112// Split-K decode attention ("flash decoding") for one new token.
3113// Phase 1: grid (heads, chunks); each block takes CHUNK consecutive keys of
3114// one head: warps compute q.k with lanes striding head_dim (coalesced K rows)
3115// and a warp reduction, then a chunk-local softmax, and write a partial
3116// (max m, sum l, unnormalised sum_k p_k v_k) to a static scratch array.
3117// Phase 2: one block per head merges the chunks:
3118// out = sum_c e^{m_c - M} acc_c / sum_c e^{m_c - M} l_c.
3119// Chunks past the current length write l = 0 and are skipped, so a CUDA
3120// graph can capture the grid for the cache's full length while the length
3121// itself comes from device memory (step_dev[1] = pos). The scratch is a
3122// static __device__ array (nothing allocated, graph-safe; one decode stream
3123// at a time).
3124// Two split-K kernels share the scratch. The default is the tiled kernel
3125// (decode_attn_tiled.cuh): chunks of 32 keys chosen by the current length,
3126// so short contexts still fill the GPU (experiments/decode_occupancy_2026-10-27).
3127// EMBER_DECODE_ATTN=split selects the kernel below with 256-key chunks for the
3128// cache's capacity (the default before that work); EMBER_DECODE_ATTN=legacy
3129// the one-block-per-head kernel.
3130namespace {
3131constexpr int kDecChunkMax = 2048; // keys per chunk at most (shared memory)
3132constexpr int kDecChunksMax = 64; // chunks per head at most
3133constexpr int kDecHeadsMax = 128;
3134constexpr int kDecHeadDimMax = 256;
3135constexpr int kDecThreads = 128;
3137
3138__global__ void decode_attn_split_kernel(const float* __restrict__ Q, const float* __restrict__ K,
3139 const float* __restrict__ V, int seq_len, int head_dim, float scale,
3140 int num_kv_heads, int num_heads, int chunk, const int* step_dev) {
3141 if (step_dev) seq_len = step_dev[1] + 1;
3142 const int h = blockIdx.x, c = blockIdx.y, nc = gridDim.y;
3143 float* part = g_dec_partial + ((size_t)h * nc + c) * (head_dim + 2); // [m, l, acc[head_dim]]
3144 const int k0 = c * chunk;
3145 const int k1 = min(seq_len, k0 + chunk);
3146 if (k0 >= k1) {
3147 if (threadIdx.x == 0) { part[0] = -INFINITY; part[1] = 0.0f; }
3148 return;
3149 }
3150 const int kvh = h / (num_heads / num_kv_heads);
3151 extern __shared__ float sm[];
3152 float* q = sm; // [head_dim]
3153 float* s = sm + head_dim; // [chunk]
3154 __shared__ float red[kDecThreads / 32];
3155 for (int d = threadIdx.x; d < head_dim; d += blockDim.x) q[d] = Q[h * head_dim + d];
3156 __syncthreads();
3157 const int lane = threadIdx.x & 31, warp = threadIdx.x >> 5, nwarps = blockDim.x >> 5;
3158 for (int k = k0 + warp; k < k1; k += nwarps) {
3159 const float* kv = K + ((size_t)k * num_kv_heads + kvh) * head_dim;
3160 float dot = 0.0f;
3161 for (int d = lane; d < head_dim; d += 32) dot += q[d] * kv[d];
3162 for (int o = 16; o > 0; o >>= 1) dot += __shfl_xor_sync(0xffffffff, dot, o);
3163 if (lane == 0) s[k - k0] = dot * scale;
3164 }
3165 __syncthreads();
3166 const int n = k1 - k0;
3167 float mx = -INFINITY;
3168 for (int i = threadIdx.x; i < n; i += blockDim.x) mx = fmaxf(mx, s[i]);
3169 for (int o = 16; o > 0; o >>= 1) mx = fmaxf(mx, __shfl_xor_sync(0xffffffff, mx, o));
3170 if (lane == 0) red[warp] = mx;
3171 __syncthreads();
3172 mx = red[0];
3173 for (int w = 1; w < nwarps; ++w) mx = fmaxf(mx, red[w]);
3174 __syncthreads();
3175 float sum = 0.0f;
3176 for (int i = threadIdx.x; i < n; i += blockDim.x) {
3177 const float e = expf(s[i] - mx);
3178 s[i] = e;
3179 sum += e;
3180 }
3181 for (int o = 16; o > 0; o >>= 1) sum += __shfl_xor_sync(0xffffffff, sum, o);
3182 if (lane == 0) red[warp] = sum;
3183 __syncthreads();
3184 if (threadIdx.x == 0) {
3185 float l = 0.0f;
3186 for (int w = 0; w < nwarps; ++w) l += red[w];
3187 part[0] = mx;
3188 part[1] = l;
3189 }
3190 for (int d = threadIdx.x; d < head_dim; d += blockDim.x) {
3191 float acc = 0.0f;
3192 for (int i = 0; i < n; ++i) acc += s[i] * V[((size_t)(k0 + i) * num_kv_heads + kvh) * head_dim + d];
3193 part[2 + d] = acc;
3194 }
3195}
3196
3198 const int h = blockIdx.x;
3199 const float* base = g_dec_partial + (size_t)h * nc * (head_dim + 2);
3200 float M = -INFINITY;
3201 for (int c = 0; c < nc; ++c) {
3202 const float* p = base + (size_t)c * (head_dim + 2);
3203 if (p[1] > 0.0f) M = fmaxf(M, p[0]);
3204 }
3205 float L = 0.0f;
3206 for (int c = 0; c < nc; ++c) {
3207 const float* p = base + (size_t)c * (head_dim + 2);
3208 if (p[1] > 0.0f) L += expf(p[0] - M) * p[1];
3209 }
3210 const float inv = L > 0.0f ? 1.0f / L : 0.0f;
3211 for (int d = threadIdx.x; d < head_dim; d += blockDim.x) {
3212 float acc = 0.0f;
3213 for (int c = 0; c < nc; ++c) {
3214 const float* p = base + (size_t)c * (head_dim + 2);
3215 if (p[1] > 0.0f) acc += expf(p[0] - M) * p[2 + d];
3216 }
3217 out[h * head_dim + d] = acc * inv;
3218 }
3219}
3220
3221bool decode_attn_legacy() { return decode_attention_kind() == 2; }
3222
3223// Launches the split kernel (256-key chunks for a cache of max_len positions); false if the
3224// shape does not fit the scratch (the caller then uses the legacy kernel).
3225bool decode_attn_split(const float* Q, const float* K, const float* V, float* out, int num_heads, int seq_len,
3226 int head_dim, float scale, int max_len, int num_kv_heads, const int* step_dev,
3228 if (decode_attn_legacy() || num_heads > kDecHeadsMax || head_dim > kDecHeadDimMax || max_len <= 0) return false;
3229 int nc = (max_len + 255) / 256;
3231 const int chunk = (max_len + nc - 1) / nc;
3232 if (chunk > kDecChunkMax) return false;
3233 const size_t shm = (size_t)(head_dim + chunk) * sizeof(float);
3235 Q, K, V, seq_len, head_dim, scale, num_kv_heads, num_heads, chunk, step_dev);
3238 return true;
3239}
3240
3241// The tiled split-K kernel (decode_attn_tiled.cuh): chunks chosen by the length L, a grid for max_len positions,
3242// the same g_dec_partial scratch (heads * chunks * (head_dim + 2), chunks <= 64).
3243__global__ void decode_attn_tiled_kernel(const float* __restrict__ Q, const float* __restrict__ K,
3244 const float* __restrict__ V, int seq_len, int head_dim, float scale,
3245 int num_kv_heads, int num_heads, int min_chunk, int max_chunks,
3246 const int* step_dev) {
3247 const int L = step_dev ? step_dev[1] + 1 : seq_len;
3248 const int h = blockIdx.x, c = blockIdx.y, nc = gridDim.y;
3251 g_dec_partial + ((size_t)h * nc + c) * (head_dim + 2));
3252}
3253
3254__global__ void decode_attn_tiled_merge_kernel(float* __restrict__ out, int seq_len, int head_dim, int nc,
3255 int min_chunk, int max_chunks, const int* step_dev) {
3256 const int L = step_dev ? step_dev[1] + 1 : seq_len;
3257 const int h = blockIdx.x;
3258 ember_tiled::tiled_merge_block(g_dec_partial + (size_t)h * nc * (head_dim + 2), out + (size_t)h * head_dim, L,
3260}
3261
3262bool aligned16(const void* p) { return (reinterpret_cast<uintptr_t>(p) & 15u) == 0; }
3263
3264// Launches the tiled kernel for a cache of max_len positions; false when it is not selected or the shape is not
3265// its (head_dim % 4, 16-byte rows, the scratch): the caller then tries the split kernel.
3266bool decode_attn_tiled(const float* Q, const float* K, const float* V, float* out, int num_heads, int seq_len,
3267 int head_dim, float scale, int max_len, int num_kv_heads, const int* step_dev,
3269 if (decode_attention_kind() != 0) return false;
3270 int mn = 0, mx = 0;
3273 num_heads % num_kv_heads != 0 || !aligned16(Q) || !aligned16(K) || !aligned16(V))
3274 return false;
3275 const int nc = ember_tiled::tiled_grid_chunks(max_len, mn, mx);
3276 const size_t shm = ember_tiled::tiled_shm_bytes(head_dim, max_len, mn, mx);
3278 Q, K, V, seq_len, head_dim, scale, num_kv_heads, num_heads, mn, mx, step_dev);
3280 mn, mx, step_dev);
3282 return true;
3283}
3284
3285// The decode attention's split-K paths in order: tiled (the default), then split; false: the caller takes the
3286// one-block-per-head kernel.
3287bool decode_attn_device(const float* Q, const float* K, const float* V, float* out, int num_heads, int seq_len,
3288 int head_dim, float scale, int max_len, int num_kv_heads, const int* step_dev,
3290 return decode_attn_tiled(Q, K, V, out, num_heads, seq_len, head_dim, scale, max_len, num_kv_heads, step_dev,
3291 stream) ||
3292 decode_attn_split(Q, K, V, out, num_heads, seq_len, head_dim, scale, max_len, num_kv_heads, step_dev,
3293 stream);
3294}
3295} // namespace
3296
3298 static const int kind = [] {
3299 const char* e = std::getenv("EMBER_DECODE_ATTN");
3300 const std::string s = e ? e : "";
3301 return s == "legacy" ? 2 : s == "split" ? 1 : 0;
3302 }();
3303 return kind;
3304}
3305
3307 static const std::pair<int, int> t = [] {
3309 const char* e = std::getenv("EMBER_DECODE_ATTN_TILE");
3310 int a = 0, b = 0;
3311 if (e && std::sscanf(e, "%d,%d", &a, &b) == 2 && a >= 1 && b >= 1 && b <= ember_tiled::kTiledMaxChunks)
3312 d = {a, b};
3313 return d;
3314 }();
3315 *min_chunk = t.first;
3316 *max_chunks = t.second;
3317}
3318
3343
3344// New BF16 Tensor Core Matrix-Matrix Operations
3345
3346// Kernel for FP32 to BF16 conversion
3349 size_t n_elements) {
3350 size_t idx = blockIdx.x * blockDim.x + threadIdx.x;
3351 if (idx < n_elements) {
3353 }
3354}
3355
3356// Host wrapper for FP32 to BF16 conversion
3367
3368// BF16 x BF16 = BF16 Matrix-Matrix multiplication using Tensor Cores
3370 bool transa_user, bool transb_user,
3371 int m_user, int n_user, int k_user,
3372 const float* alpha_user,
3373 const uint16_t* A_bf16_user, int lda_user,
3374 const uint16_t* B_bf16_user, int ldb_user,
3375 const float* beta_user,
3378
3381 Logger::error("cublasSetStream failed in gemm_bf16_bf16_cuda with error: " + std::to_string(status));
3382 throw std::runtime_error("cublasSetStream failed");
3383 }
3384
3385 // Log parameters for debugging
3386 Logger::info("[GEMM_BF16_BF16] M=" + std::to_string(m_user) + ", N=" + std::to_string(n_user) +
3387 ", K=" + std::to_string(k_user) + ", LDA=" + std::to_string(lda_user) +
3388 ", LDB=" + std::to_string(ldb_user) + ", LDC=" + std::to_string(ldc_user));
3389
3394
3395 // Apply the same column-major transformation as gemm_f32_f32_cuda
3396 M_cublas = n_user;
3397 N_cublas = m_user;
3398 K_cublas = k_user;
3399
3400 A_cublas_ptr = B_bf16_user; // B_user data is the first matrix for cuBLAS call
3402
3403 B_cublas_ptr = A_bf16_user; // A_user data is the second matrix for cuBLAS call
3405
3406 // Determine cuBLAS operations
3407 if (!transa_user && !transb_user) {
3408 opA_cublas = CUBLAS_OP_N; // for B_user
3409 opB_cublas = CUBLAS_OP_N; // for A_user
3410 } else if (transa_user && !transb_user) {
3411 opA_cublas = CUBLAS_OP_N; // for B_user
3412 opB_cublas = CUBLAS_OP_T; // for A_user (A^T)
3413 } else if (!transa_user && transb_user) {
3414 opA_cublas = CUBLAS_OP_T; // for B_user (B^T)
3415 opB_cublas = CUBLAS_OP_N; // for A_user
3416 } else { // transa_user && transb_user
3417 opA_cublas = CUBLAS_OP_T; // for B_user (B^T)
3418 opB_cublas = CUBLAS_OP_T; // for A_user (A^T)
3419 }
3420
3421 int LDC_cublas = ldc_user;
3422
3423 // Use cublasGemmEx with BF16 Tensor Cores
3427 alpha_user,
3430 beta_user,
3432 CUBLAS_COMPUTE_32F, // Use FP32 for accumulation for stability
3433 CUBLAS_GEMM_DEFAULT_TENSOR_OP); // Enable Tensor Cores
3434
3436 Logger::error("cublasGemmEx BF16 failed in gemm_bf16_bf16_cuda with error: " + std::to_string(status));
3437 Logger::error("GEMM params: transa=" + std::to_string(transa_user) + " transb=" + std::to_string(transb_user) +
3438 " m=" + std::to_string(m_user) + " n=" + std::to_string(n_user) + " k=" + std::to_string(k_user));
3439 throw std::runtime_error("cublasGemmEx BF16 failed");
3440 }
3441
3442 Logger::info("[GEMM_BF16_BF16] SUCCESS with Tensor Cores");
3443}
3444
3445// FP32 x BF16 = FP32 Matrix-Matrix multiplication (for inputs that need to stay in FP32)
3447 bool transa_user, bool transb_user,
3448 int m_user, int n_user, int k_user,
3449 const float* alpha_user,
3450 const float* A_f32_user, int lda_user,
3451 const uint16_t* B_bf16_user, int ldb_user,
3452 const float* beta_user,
3453 float* C_f32_user, int ldc_user,
3455
3458 Logger::error("cublasSetStream failed in gemm_f32_to_bf16_f32_cuda with error: " + std::to_string(stream_status));
3459 throw std::runtime_error("cublasSetStream failed");
3460 }
3461
3462 // Convert FP32 input A to BF16
3463 size_t A_size = (size_t)m_user * k_user;
3467
3468 // Allocate BF16 output buffer
3469 size_t C_size = (size_t)m_user * n_user;
3472
3473 // Convert alpha and beta to BF16 for host-side pointers
3476
3477 // DIRECT parameter mapping for cublasGemmEx
3480
3481 // A is A_bf16_temp (converted from A_f32_user)
3482 // B is B_bf16_user (already BF16)
3483 // C is C_bf16_temp (will be converted back to C_f32_user)
3485 opA, opB,
3486 m_user, n_user, k_user,
3487 &alpha_bf16_host, // Pointer to BF16 host alpha
3490 &beta_bf16_host, // Pointer to BF16 host beta
3492 CUBLAS_COMPUTE_16F, // Compute in BF16 (A_16*B_16 -> C_16)
3494 );
3495
3497 Logger::error("cublasGemmEx BF16 (A_bf16*B_bf16->C_bf16) failed in gemm_f32_to_bf16_f32_cuda with error: " + std::to_string(status));
3498 Logger::error("GEMM params: transA=" + std::to_string(opA) + " transB=" + std::to_string(opB) +
3499 " m=" + std::to_string(m_user) + " n=" + std::to_string(n_user) + " k=" + std::to_string(k_user) +
3500 " lda=" + std::to_string(lda_user) + " ldb=" + std::to_string(ldb_user) + " ldc=" + std::to_string(ldc_user));
3503 throw std::runtime_error("BF16 Tensor Core GEMM failed");
3504 }
3505
3506 // Convert BF16 result back to FP32
3508
3509 // Cleanup temporary buffers
3512}
3513
3514// Host wrapper for BF16 to FP32 conversion
3525
3526// ---------------------------------------------------------------------------
3527// Graph decode (19 Oct 2026). One decode step is captured once as a CUDA
3528// graph and replayed per token, so every per-token value lives in device
3529// memory: step_dev[0] = input token id, step_dev[1] = position. These are
3530// the device-position forms of the decode kernels; the arithmetic is the
3531// same as the per-launch path's.
3532// ---------------------------------------------------------------------------
3534 const int* __restrict__ step_dev, int hidden_size, int vocab_size) {
3535 const int i = blockIdx.x * blockDim.x + threadIdx.x;
3536 if (i >= hidden_size) return;
3537 const int tok = step_dev[0];
3538 out[i] = (tok >= 0 && tok < vocab_size) ? table[(size_t)tok * hidden_size + i] : 0.0f;
3539}
3540
3541void decode_embed_cuda(const float* table_dev, float* out_dev, const int* step_dev, int hidden_size,
3542 int vocab_size, cudaStream_t stream) {
3543 decode_embed_kernel<<<(hidden_size + 255) / 256, 256, 0, stream>>>(table_dev, out_dev, step_dev, hidden_size,
3544 vocab_size);
3546}
3547
3549 const int* step_dev, bool use_adjacent_pairing) {
3550 const int idx = blockIdx.x * blockDim.x + threadIdx.x;
3551 const int total_pairs = num_heads * (head_dim / 2);
3552 if (idx >= total_pairs) return;
3553 const int pos = step_dev[1];
3554 const int head_idx = idx / (head_dim / 2);
3555 const int dim_pair_idx = idx % (head_dim / 2);
3556 const size_t freq_base_offset = ((size_t)pos * (head_dim / 2) + dim_pair_idx) * 2;
3557 int base_x0, base_x1;
3560 base_x1 = head_idx * head_dim + (dim_pair_idx * 2 + 1);
3561 } else {
3564 }
3565 const float x0 = x[base_x0];
3566 const float x1 = x[base_x1];
3568 const float sin_val = all_freqs_cis_base[freq_base_offset + 1];
3569 x[base_x0] = x0 * cos_val - x1 * sin_val;
3570 x[base_x1] = x0 * sin_val + x1 * cos_val;
3571}
3572
3581
3582// The KV cache is [pos][kv_head][dim]: position pos's row is `row` floats,
3583// laid out like k/v.
3585 const float* __restrict__ v, const int* step_dev, int row) {
3586 const int i = blockIdx.x * blockDim.x + threadIdx.x;
3587 if (i >= row) return;
3588 const size_t off = (size_t)step_dev[1] * row + i;
3589 k_cache[off] = k[i];
3590 v_cache[off] = v[i];
3591}
3592
3593void kv_write_devpos_cuda(float* k_cache_dev, float* v_cache_dev, const float* k_dev, const float* v_dev,
3594 const int* step_dev, int row, cudaStream_t stream) {
3596 row);
3598}
3599
3609
3630
3631
3632// ---------------------------------------------------------------------------
3633// Mixture-of-experts FFN (see cuda_kernels.h).
3634namespace {
3635// Row-major Y[T][O] = X[T][I] W[O][I]^T with column-major cuBLAS: Y^T = W X^T.
3636void moe_gemm_rm(cublasHandle_t h, const float* X, const float* W, float* Y, int T, int O, int I) {
3637 const float one = 1.0f, zero = 0.0f;
3639 if (s != CUBLAS_STATUS_SUCCESS) throw std::runtime_error("moe_ffn_cuda: cublasSgemm failed");
3640}
3641
3642// One token per block: top-k of the E router logits (ties to the lower
3643// index), then the gates exactly as moe_ffn_f32_cpu computes them.
3645 int E, int k, int renorm) {
3646 if (threadIdx.x != 0) return;
3647 const int t = blockIdx.x;
3648 const float* r = logits + (size_t)t * E;
3649 float* g = gates + (size_t)t * E;
3650 float* s = sel + (size_t)t * E;
3651 for (int e = 0; e < E; ++e) { g[e] = 0.0f; s[e] = 0.0f; }
3652 for (int j = 0; j < k && j < E; ++j) {
3653 int best = -1;
3654 for (int e = 0; e < E; ++e)
3655 if (s[e] == 0.0f && (best < 0 || r[e] > r[best])) best = e;
3656 s[best] = 1.0f;
3657 }
3658 float mx = -INFINITY;
3659 for (int e = 0; e < E; ++e)
3660 if (!renorm || s[e] != 0.0f) mx = fmaxf(mx, r[e]);
3661 double z = 0.0;
3662 for (int e = 0; e < E; ++e)
3663 if (!renorm || s[e] != 0.0f) z += exp((double)(r[e] - mx));
3664 for (int e = 0; e < E; ++e)
3665 if (s[e] != 0.0f) g[e] = (float)(exp((double)(r[e] - mx)) / z);
3666}
3667
3668__global__ void moe_swiglu_kernel(float* __restrict__ g, const float* __restrict__ u, size_t n) {
3669 const size_t i = (size_t)blockIdx.x * blockDim.x + threadIdx.x;
3670 if (i < n) {
3671 const float gv = g[i];
3672 g[i] = gv * (1.0f / (1.0f + expf(-gv))) * u[i];
3673 }
3674}
3675
3676// out[t][i] = sum over the selected experts, ascending, of gate * y[e][t][i].
3677__global__ void moe_combine_kernel(const float* __restrict__ y, const float* __restrict__ gates,
3678 const float* __restrict__ sel, float* __restrict__ out, int T, int d, int E) {
3679 const int t = blockIdx.x;
3680 for (int i = threadIdx.x; i < d; i += blockDim.x) {
3681 float acc = 0.0f;
3682 for (int e = 0; e < E; ++e)
3683 if (sel[(size_t)t * E + e] != 0.0f) acc += gates[(size_t)t * E + e] * y[((size_t)e * T + t) * d + i];
3684 out[(size_t)t * d + i] = acc;
3685 }
3686}
3687} // namespace
3688
3689namespace {
3690bool moe_use_routed(int T, int k) {
3691 static const bool dense = [] {
3692 const char* e = std::getenv("EMBER_MOE_DENSE");
3693 return e && std::string(e) == "1";
3694 }();
3695 return !dense && T * k <= 32;
3696}
3697size_t moe_routed_floats(int T, int d, int d_ff, int E, int k) {
3698 const size_t tk = (size_t)T * k;
3699 // pointer arrays first (9 x tk pointers, 2 floats each), then logits [T][E],
3700 // gates [tk], g/u [tk][d_ff], y [tk][d]
3701 return 18 * tk + (size_t)T * E + tk + 2 * tk * d_ff + tk * d;
3702}
3703
3704// Top-k per token (ties to the lower index), the selected indices in
3705// ascending order with their gates (as moe_ffn_f32_cpu), and the pointer
3706// arrays for the batched expert GEMMs.
3707__global__ void moe_route_sel_kernel(const float* __restrict__ logits, int E, int k, int renorm, const float* x,
3708 int d, int d_ff, const float* gate_exps, const float* up_exps,
3709 const float* down_exps, float* gsel, float* g, float* u, float* y,
3710 const float** Ag, const float** Au, const float** Ad, const float** Bx,
3711 const float** Bg, float** Cg, float** Cu, float** Cy) {
3712 if (threadIdx.x != 0) return;
3713 const int t = blockIdx.x;
3714 const float* r = logits + (size_t)t * E;
3715 int idx[64];
3716 bool taken[256];
3717 for (int e = 0; e < E; ++e) taken[e] = false;
3718 for (int j = 0; j < k; ++j) {
3719 int best = -1;
3720 for (int e = 0; e < E; ++e)
3721 if (!taken[e] && (best < 0 || r[e] > r[best])) best = e;
3722 taken[best] = true;
3723 idx[j] = best;
3724 }
3725 for (int a = 1; a < k; ++a) // ascending expert index
3726 for (int b = a; b > 0 && idx[b - 1] > idx[b]; --b) { const int tmp = idx[b]; idx[b] = idx[b - 1]; idx[b - 1] = tmp; }
3727 float mx = -INFINITY;
3728 for (int e = 0; e < E; ++e)
3729 if (!renorm || taken[e]) mx = fmaxf(mx, r[e]);
3730 double z = 0.0;
3731 for (int e = 0; e < E; ++e)
3732 if (!renorm || taken[e]) z += exp((double)(r[e] - mx));
3733 for (int j = 0; j < k; ++j) {
3734 const int e = idx[j];
3735 const size_t s = (size_t)t * k + j;
3736 gsel[s] = (float)(exp((double)(r[e] - mx)) / z);
3737 Ag[s] = gate_exps + (size_t)e * d_ff * d;
3738 Au[s] = up_exps + (size_t)e * d_ff * d;
3739 Ad[s] = down_exps + (size_t)e * d * d_ff;
3740 Bx[s] = x + (size_t)t * d;
3741 Bg[s] = g + s * d_ff;
3742 Cg[s] = g + s * d_ff;
3743 Cu[s] = u + s * d_ff;
3744 Cy[s] = y + s * d;
3745 }
3746}
3747
3748__global__ void moe_combine_sel_kernel(const float* __restrict__ y, const float* __restrict__ gsel,
3749 float* __restrict__ out, int d, int k) {
3750 const int t = blockIdx.x;
3751 for (int i = threadIdx.x; i < d; i += blockDim.x) {
3752 float acc = 0.0f;
3753 for (int j = 0; j < k; ++j) acc += gsel[(size_t)t * k + j] * y[((size_t)t * k + j) * d + i];
3754 out[(size_t)t * d + i] = acc;
3755 }
3756}
3757} // namespace
3758
3759void gemm_rm_f32_cuda(cublasHandle_t handle, const float* X, const float* W, float* Y, int T, int O, int I,
3762 moe_gemm_rm(handle, X, W, Y, T, O, I);
3763}
3764
3765size_t moe_ffn_scratch_floats(int T, int d, int d_ff, int E, int k) {
3766 const size_t dense = (size_t)T * E * 3 + (size_t)T * d_ff * 2 + (size_t)E * T * d;
3767 return std::max(dense, moe_routed_floats(T, d, d_ff, E, k));
3768}
3769
3770void moe_ffn_cuda(cublasHandle_t handle, const float* x, int T, int d, int d_ff, int E, int k, bool renorm,
3771 const float* router, const float* gate_exps, const float* up_exps, const float* down_exps,
3772 float* scratch, float* out, cudaStream_t stream) {
3773 if (moe_use_routed(T, k) && E <= 256 && k <= 64) {
3774 const int tk = T * k;
3775 const float** Ag = reinterpret_cast<const float**>(scratch);
3776 const float** Au = Ag + tk;
3777 const float** Ad = Au + tk;
3778 const float** Bx = Ad + tk;
3779 const float** Bg = Bx + tk;
3780 float** Cg = reinterpret_cast<float**>(scratch) + 5 * (size_t)tk;
3781 float** Cu = Cg + tk;
3782 float** Cy = Cu + tk;
3783 float* logits = reinterpret_cast<float*>(Cy + tk);
3784 float* gsel = logits + (size_t)T * E;
3785 float* g = gsel + tk;
3786 float* u = g + (size_t)tk * d_ff;
3787 float* y = u + (size_t)tk * d_ff;
3789 moe_gemm_rm(handle, x, router, logits, T, E, d);
3791 down_exps, gsel, g, u, y, Ag, Au, Ad, Bx, Bg, Cg, Cu, Cy);
3792 const float one = 1.0f, zero = 0.0f;
3793 // Each item: row-major [rows][cols] weight times a column vector (see moe_gemm_rm).
3794 if (cublasSgemmBatched(handle, CUBLAS_OP_T, CUBLAS_OP_N, d_ff, 1, d, &one, Ag, d, Bx, d, &zero, Cg, d_ff, tk) !=
3798 throw std::runtime_error("moe_ffn_cuda: cublasSgemmBatched failed");
3799 const size_t n = (size_t)tk * d_ff;
3800 moe_swiglu_kernel<<<(unsigned)((n + 255) / 256), 256, 0, stream>>>(g, u, n);
3803 throw std::runtime_error("moe_ffn_cuda: cublasSgemmBatched failed");
3806 return;
3807 }
3808 float* logits = scratch;
3809 float* gates = logits + (size_t)T * E;
3810 float* sel = gates + (size_t)T * E;
3811 float* g = sel + (size_t)T * E;
3812 float* u = g + (size_t)T * d_ff;
3813 float* y = u + (size_t)T * d_ff;
3815 moe_gemm_rm(handle, x, router, logits, T, E, d);
3817 const size_t n = (size_t)T * d_ff;
3818 for (int e = 0; e < E; ++e) {
3819 moe_gemm_rm(handle, x, gate_exps + (size_t)e * d_ff * d, g, T, d_ff, d);
3820 moe_gemm_rm(handle, x, up_exps + (size_t)e * d_ff * d, u, T, d_ff, d);
3821 moe_swiglu_kernel<<<(unsigned)((n + 255) / 256), 256, 0, stream>>>(g, u, n);
3822 moe_gemm_rm(handle, g, down_exps + (size_t)e * d * d_ff, y + (size_t)e * T * d, T, d, d_ff);
3823 }
3826}
#define SAFE_SQRT(x)
static void debug(const std::string &message)
Definition logger.cpp:131
static void warning(const std::string &message)
Definition logger.cpp:139
static void info(const std::string &message)
Definition logger.cpp:135
static void error(const std::string &message)
Definition logger.cpp:143
void gemm_rm_f32_cuda(cublasHandle_t handle, const float *X, const float *W, float *Y, int T, int O, int I, cudaStream_t stream)
void moe_ffn_cuda(cublasHandle_t handle, const float *x, int T, int d, int d_ff, int E, int k, bool renorm, const float *router, const float *gate_exps, const float *up_exps, const float *down_exps, float *scratch, float *out, cudaStream_t stream)
void decode_embed_cuda(const float *table_dev, float *out_dev, const int *step_dev, int hidden_size, int vocab_size, cudaStream_t stream)
void add_residual_batch_cuda(float *d_output_batch, const float *d_input_a_batch, const float *d_input_b_batch, int num_tokens, int hidden_size, cudaStream_t stream)
__global__ void lookup_embedding_kernel(const void *__restrict__ table_dev, float *__restrict__ output_dev, int token_id, int hidden_size, int vocab_size, bool is_bf16)
void softmax_vector_cuda(const std::vector< float > &x_host, std::vector< float > &out_host, int n)
void matvec_f32_f32_cuda(cublasHandle_t handle, const float *mat_f32_dev, const float *vec_f32_dev, float *out_f32_dev, int rows, int cols, cudaStream_t stream)
Implementation of matrix-vector multiplication with FP32.
__device__ float bf16_to_float32_device(uint16_t bf16_raw)
Converts BF16 to FP32 on the device.
void rope_and_update_kv_cache_cuda(float *cache_base_ptr, const float *kv_vector_head, const float *all_freqs_cis_base, int pos, int kv_head_idx, int max_seq_len, int num_kv_heads, int head_dim, cudaStream_t stream)
__global__ void attention_selective_dequant_kernel(const float *__restrict__ Q_current_dev, const int8_t *__restrict__ K_quantized_cache_base, const int8_t *__restrict__ V_quantized_cache_base, const float *__restrict__ K_scales_cache_base, const float *__restrict__ V_scales_cache_base, float *__restrict__ selective_k_dequant_buffer, float *__restrict__ selective_v_dequant_buffer, float *__restrict__ out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads)
size_t moe_ffn_scratch_floats(int T, int d, int d_ff, int E, int k)
__global__ void decode_embed_kernel(const float *__restrict__ table, float *__restrict__ out, const int *__restrict__ step_dev, int hidden_size, int vocab_size)
void kv_write_devpos_cuda(float *k_cache_dev, float *v_cache_dev, const float *k_dev, const float *v_dev, const int *step_dev, int row, cudaStream_t stream)
static bool kernel_debug_copies()
void rope_cuda(float *x_dev, int num_heads, int head_dim, const float *all_freqs_cis_dev_base, int pos, bool use_adjacent_pairing, cudaStream_t stream)
void attention_cuda_selective_dequant(const float *Q_current_dev, const int8_t *K_quantized_cache_base, const int8_t *V_quantized_cache_base, const float *K_scales_cache_base, const float *V_scales_cache_base, float *selective_k_dequant_buffer, float *selective_v_dequant_buffer, float *out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
void softmax_vector_cuda_optimized(const float *x_dev, float *out_dev, int n, cudaStream_t stream)
__global__ void swiglu_batch_cuda_kernel(float *d_out_batch, const float *d_gate_act_batch, const float *d_up_act_batch, int num_tokens, int intermediate_size)
__global__ void attention_optimized_det_kernel(const float *__restrict__ Q_current, const float *__restrict__ K_layer_cache_base, const float *__restrict__ V_layer_cache_base, float *__restrict__ out, int current_seq_len, int head_dim, float scale, int cache_num_kv_heads, int num_q_heads, const int *step_dev)
__global__ void rope_kernel(float *x, int num_heads, int head_dim, const float *all_freqs_cis_base, int pos, bool use_adjacent_pairing)
void rope_batch_cuda(float *d_q_batch, float *d_k_batch, const float *d_all_freqs_cis_base, int num_tokens, int num_q_heads, int num_kv_heads, int head_dim, int start_pos_in_kv_cache, bool use_adjacent_pairing, cudaStream_t stream)
__global__ void softmax_exp_sum_kernel(const float *x, float *partial_sums, int n, float max_val)
__global__ void softmax_normalize_kernel(const float *x, float *out, int n, float max_val, float inv_sum)
void convert_fp32_to_bf16_cuda(const float *fp32_in_dev, uint16_t *bf16_out_dev, size_t n_elements, cudaStream_t stream)
__global__ void softmax_find_max_kernel(const float *x, float *partial_max, int n)
void add_residual_cuda(const float *matvec_out_dev, const float *residual_dev, float *result_dev, int n, cudaStream_t stream)
void matvec_bf16_f32_cuda(cublasHandle_t handle, const uint16_t *mat_bf16_dev, const float *vec_f32_dev, float *out_f32_dev, int rows, int cols, bool use_tensor_cores, cudaStream_t stream)
Matrix-vector multiplication with BF16 matrix and FP32 vector/output on GPU.
void swiglu_cuda(const float *gate_dev, const float *up_dev, float *out_dev, int n, cudaStream_t stream)
__global__ void rope_devpos_kernel(float *x, int num_heads, int head_dim, const float *all_freqs_cis_base, const int *step_dev, bool use_adjacent_pairing)
__device__ uint16_t float32_to_bf16_device(float fp32_val)
__global__ void add_residual_kernel(const float *matvec_out, const float *residual, float *result, int n)
__global__ void silu_kernel(const float *x, float *out, int n)
__global__ void attention_kernel(const float *Q_current, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out, int current_seq_len, int head_dim, float scale, int cache_num_kv_heads, int num_q_heads)
__global__ void rmsnorm_sum_squares_kernel(const float *x, float *partial_sums, int n)
Kernel for computing sum of squares in RMS normalization.
__global__ void rmsnorm_optimized_kernel(const float *__restrict__ x, const float *__restrict__ weight, float *__restrict__ out, int n, float eps)
Optimized RMS normalization using simple warp primitives and shared memory.
void decode_attention_tiles(int *min_chunk, int *max_chunks)
void rmsnorm_vector_cuda_optimized(const float *x_dev, const float *weight_dev, float *out_dev, int n, float eps, cudaStream_t stream)
Optimized wrappers using the new high-performance kernels.
void silu_cuda(const std::vector< float > &x_host, std::vector< float > &out_host, int n)
__global__ void add_residual_batch_cuda_kernel(float *d_output_batch, const float *d_input_a_batch, const float *d_input_b_batch, int num_tokens, int hidden_size)
__global__ void add_vectors_kernel(const float *a, const float *b, float *result, int n)
void convert_bf16_to_fp32_cuda(const uint16_t *bf16_in_dev, float *fp32_out_dev, size_t n_elements, cudaStream_t stream)
void attention_devpos_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, const int *step_dev, cudaStream_t stream)
__global__ void reduce_partial_sums_kernel(const float *partial_sums, float *total_sum_sq_out, int num_partial_sums)
__global__ void softmax_optimized_kernel(const float *__restrict__ x, float *__restrict__ out, int n)
Optimized softmax using shared memory and warp primitives.
void gemm_bf16_bf16_cuda(cublasHandle_t handle, bool transa_user, bool transb_user, int m_user, int n_user, int k_user, const float *alpha_user, const uint16_t *A_bf16_user, int lda_user, const uint16_t *B_bf16_user, int ldb_user, const float *beta_user, uint16_t *C_bf16_user, int ldc_user, cudaStream_t stream)
void gemm_f32_to_bf16_f32_cuda(cublasHandle_t handle, bool transa_user, bool transb_user, int m_user, int n_user, int k_user, const float *alpha_user, const float *A_f32_user, int lda_user, const uint16_t *B_bf16_user, int ldb_user, const float *beta_user, float *C_f32_user, int ldc_user, cudaStream_t stream)
__global__ void update_kv_cache_kernel(float *cache_base_ptr, const float *current_kv_vector, int pos, int kv_head_idx, int max_seq_len, int num_kv_heads, int head_dim)
void rope_devpos_cuda(float *x_dev, int num_heads, int head_dim, const float *all_freqs_cis_dev_base, const int *step_dev, bool use_adjacent_pairing, cudaStream_t stream)
void swiglu_batch_cuda(float *d_out_batch, const float *d_gate_act_batch, const float *d_up_act_batch, int num_tokens, int intermediate_size, cudaStream_t stream)
int decode_attention_kind()
__device__ float atomicAddFloat(float *address, float val)
__global__ void rope_and_update_kv_cache_kernel(float *cache_base_ptr, const float *kv_vector_head, const float *all_freqs_cis_base, int pos, int kv_head_idx, int max_seq_len, int num_kv_heads, int head_dim)
void lookup_embedding_cuda(const void *table_dev, float *output_dev, int token_id, int hidden_size, int vocab_size, bool is_bf16, cudaStream_t stream)
__global__ void rmsnorm_apply_kernel(const float *x, const float *weight, float *out, int n, float inv_norm_factor)
Kernel for applying RMS normalization.
void attention_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
__global__ void convert_bf16_to_fp32_kernel(const uint16_t *__restrict__ bf16_in, float *__restrict__ fp32_out, size_t n_elements)
__global__ void attention_optimized_kernel(const float *__restrict__ Q_current, const float *__restrict__ K_layer_cache_base, const float *__restrict__ V_layer_cache_base, float *__restrict__ out, int current_seq_len, int head_dim, float scale, int cache_num_kv_heads, int num_q_heads, const int *step_dev)
Corrected and optimized attention kernel.
__global__ void swiglu_kernel(const float *gate, const float *up, float *out, int n)
__global__ void rope_batch_kernel_cuda(float *x_batch_tensor, int num_tokens, int num_heads_in_tensor, int head_dim, const float *d_all_freqs_cis_base, int start_pos_offset, bool use_adjacent_pairing)
__device__ float atomicMaxFloat(float *address, float val)
void update_kv_cache_cuda(float *cache_base_ptr, const float *current_kv_head_vector, int pos, int kv_head_idx, int max_seq_len, int num_kv_heads, int head_dim, cudaStream_t stream)
__global__ void rmsnorm_optimized_det_kernel(const float *__restrict__ x, const float *__restrict__ weight, float *__restrict__ out, int n, float eps)
void attention_decode_at_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
__global__ void convert_fp32_to_bf16_kernel(const float *__restrict__ fp32_in, uint16_t *__restrict__ bf16_out, size_t n_elements)
void rmsnorm_vector_cuda(const float *x_dev, const float *weight_dev, float *out_dev, int n, float eps, cudaStream_t stream)
Implementation of RMS normalization with device pointers.
__global__ void kv_write_devpos_kernel(float *k_cache, float *v_cache, const float *__restrict__ k, const float *__restrict__ v, const int *step_dev, int row)
void attention_cuda_optimized(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
void add_vectors_cuda(const float *a_dev, const float *b_dev, float *result_dev, int n, cudaStream_t stream)
const float * in[expr::Expr::kMaxInputs]
const float * p[kIrDepthSourcesMax]
int n
std::vector< float > v
Kind kind
Tok tok
float c
std::vector< E > a
node out
node scale
size_t j
Logging utilities for the TinyLlama implementation.
__host__ bool tiled_shape_ok(int heads, int head_dim, int max_len, int heads_max, int min_chunk, int max_chunks)
__host__ size_t tiled_shm_bytes(int head_dim, int max_len, int min_chunk, int max_chunks)
__device__ __forceinline__ void tiled_split_block(const float *__restrict__ Qh, const float *__restrict__ K, const float *__restrict__ V, int L, int c, int kvh, int num_kv_heads, int head_dim, float scale, int min_chunk, int max_chunks, float *__restrict__ part)
constexpr int kTiledThreads
constexpr int kTiledMaxChunks
constexpr int kTiledMinChunk
__device__ __forceinline__ void tiled_merge_block(const float *__restrict__ base, float *__restrict__ out_h, int L, int head_dim, int min_chunk, int max_chunks)
__host__ __device__ int tiled_grid_chunks(int max_len, int min_chunk, int max_chunks)
bool deterministic()
Definition determinism.h:22