ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
verify_kernels.h
Go to the documentation of this file.
1// Kernels of the speculative verification pass (speculative_verify.cpp, EMBER_SPEC=k on CUDA).
2//
3// A verification pass runs n = 1 + draft rows (positions pos .. pos + n - 1) through every layer in one CUDA graph,
4// built like the graph decode (model.cpp forward_device_graph). Every kernel here computes each row with the decode
5// step's arithmetic: the same statements, the same reduction order, the same attention chunking. Only the linear
6// layers have a choice (VerifyLinear): kRows is the decode step's own cuBLAS call per row, and kFixed is
7// fixed_linear, the decode step's linear kernel when the decode runs on it (EMBER_DECODE_LINEAR=fixed, opt-in;
8// experiments/spec_r1_2026-10-28). Apart from fixed_linear these kernels serve only
9// the pass. pos lives in device memory (pos_dev[0]) and the n
10// token ids after it (pos_dev[1 .. n]), so one captured graph per n serves every position.
11#ifndef EMBER_VERIFY_KERNELS_H
12#define EMBER_VERIFY_KERNELS_H
13
14#ifdef HAS_CUDA
15#include <cublas_v2.h>
16#include <cuda_runtime.h>
17
18namespace ember_verify {
19
20constexpr int kMaxRows = 16; // rows per pass on this path (EMBER_SPEC <= 15)
21
22// The linear layers Y[n][O] = X[n][I] W[O][I]^T, FP32.
23enum class VerifyLinear {
24 kSmallM = 0, // one warp per output, all n rows at once: the weights are read once (not the decode's order)
25 kGemm = 1, // cublasSgemm with n columns (the batched prefill's call; not the decode's order)
26 kGemvBatched = 2, // cublasSgemvStridedBatched, the weight shared (stride 0)
27 kRows = 3, // the decode step's cublasSgemm(n = 1), once per row: the decode's arithmetic exactly
28 kFixed = 4, // fixed_linear: kSmallM's kernel with a scalar twin for any shape; each row's arithmetic is the
29 // same for every n, so with the decode step on fixed_linear (n = 1) a row is the decode's bits
30};
31
32// Y[n][O] = X[n][I] W[O][I]^T, one warp per output and every row at once, each row's dot product a fixed fma chain
33// (lane l takes the inputs 4 (l + 32 j) .. 4 (l + 32 j) + 3, j = 0, 1, ..., in order) and a fixed butterfly over
34// the warp. A row's result depends only on its own X row and W, never on n, the other rows, the alignment or the
35// stream: the float4 kernel (I % 4 == 0, X and W 16-byte aligned) and its scalar twin (any other shape) compute the
36// same chain. 1 <= n <= kMaxRows.
37void fixed_linear(const float* X, const float* W, float* Y, int n, int O, int I, cudaStream_t st);
38
39// Per row of the last pass: the largest logit, the first and last index holding it, the largest logit below it
40// (-inf if none), and whether the row holds a NaN. Enough to reproduce api.cpp's greedy sampler without the row.
41struct RowPick {
42 float max;
43 float second;
44 int first;
45 int last;
46 int nan;
47};
48
49// out[r][i] = table[ids[r]][i] (decode_embed_kernel's rule per row); ids = pos_dev + 1.
50void embed_rows(const float* table, float* out, const int* pos_dev, int n, int hidden, int vocab, cudaStream_t st);
51// rmsnorm_vector_cuda_optimized per row (its deterministic kernel under EMBER_DETERMINISTIC).
52void rmsnorm_rows(const float* x, const float* w, float* out, int n, int hidden, float eps, cudaStream_t st);
53// rope_devpos_cuda per row at position pos + r; x is [n][heads * head_dim].
54void rope_rows(float* x, int n, int heads, int head_dim, const float* freqs, const int* pos_dev, bool adjacent,
56// kv_write_devpos_cuda per row at position pos + r; k, v are [n][row].
57void kv_write_rows(float* k_cache, float* v_cache, const float* k, const float* v, const int* pos_dev, int n, int row,
59// The graph decode's split-K attention per row (row r attends to pos + r + 1 keys), the kernel and chunking
60// attention_devpos_cuda uses (the tiled kernel's block bodies, decode_attn_tiled.cuh, by default; EMBER_DECODE_ATTN=split
61// the 256-key kernel for max_len positions). scratch: attention_rows_scratch_floats(...). False when the decode itself
62// would not use a split-K kernel for this shape (EMBER_DECODE_ATTN=legacy, or past its limits): the caller then takes
63// another path.
64size_t attention_rows_scratch_floats(int n, int heads, int head_dim, int max_len);
65// q, out: rows row0 .. row0 + n - 1 of the pass (row0 = 0 for all rows at once; the per-row form launches it with
66// n = 1 per row, for measurement).
67bool attention_rows(const float* q, const float* k_cache, const float* v_cache, float* out, int n, int row0,
68 int heads, int head_dim, float scale, int max_len, int kv_heads, const int* pos_dev,
69 float* scratch, cudaStream_t st);
70// Y[n][O] = X[n][I] W[O][I]^T. kSmallM needs n <= kMaxRows, I % 4 == 0 and 16-byte aligned X, W (else it runs as
71// kGemm). The handle's stream is set to st.
72void linear_rows(cublasHandle_t h, VerifyLinear mode, const float* X, const float* W, float* Y, int n, int O, int I,
74// picks[r] for logits [n][vocab].
75void select_rows(const float* logits, int n, int vocab, RowPick* picks, cudaStream_t st);
76
77} // namespace ember_verify
78
79#endif // HAS_CUDA
80#endif // EMBER_VERIFY_KERNELS_H
int n
std::vector< float > v
node out
node scale
size_t j