-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcuda_ops.cu
More file actions
2395 lines (2254 loc) · 112 KB
/
Copy pathcuda_ops.cu
File metadata and controls
2395 lines (2254 loc) · 112 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// vllm.cpp original (vt runtime, inventory deviation §9.1); no upstream mirror.
// CUDA baseline kernels for rmsnorm / silu_and_mul / embedding / rope_neox.
// Correctness-grade (M0.6): plain grid-stride / one-block-per-row kernels, f32
// accumulation, double-precision RoPE angles matching the CPU reference.
#include <cuda_bf16.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
#include <math_constants.h>
#include <cub/cub.cuh>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <string>
#include <type_traits>
#include "vt/cuda/rmsnorm_decode_fast.h"
#include "vt/ops.h"
namespace vt::cuda {
namespace {
constexpr int kBlock = 256;
void Check(cudaError_t err, const char* what) {
if (err != cudaSuccess) {
throw std::runtime_error(std::string("vt cuda: ") + what + ": " + cudaGetErrorString(err));
}
}
cudaStream_t AsStream(const Queue& q) { return static_cast<cudaStream_t>(q.handle); }
unsigned GridFor(int64_t n) {
const int64_t blocks = (n + kBlock - 1) / kBlock;
return static_cast<unsigned>(blocks < 4096 ? blocks : 4096);
}
// f32 load/store overloads: bf16 converts on the way in/out, math is f32.
__device__ inline float Load(const float* p, int64_t i) { return p[i]; }
__device__ inline float Load(const __nv_bfloat16* p, int64_t i) { return __bfloat162float(p[i]); }
__device__ inline void Store(float* p, int64_t i, float v) { p[i] = v; }
__device__ inline void Store(__nv_bfloat16* p, int64_t i, float v) {
p[i] = __float2bfloat16(v); // round-to-nearest-even, same as host F32ToBF16
}
// Round-trip a f32 value through the residual store dtype so the variance below
// squares the SAME rounded value that gets written back to the residual stream —
// mirrors vLLM fused_add_rms_norm, whose bf16 residual add (`z += residual`) rounds
// to the model dtype before the f32 variance (layernorm_kernels.cu). Identity for a
// f32 residual, so the previous f32-residual path stays byte-for-byte unchanged.
template <typename Tres> __device__ inline float ResRound(float v);
template <> __device__ inline float ResRound<float>(float v) { return v; }
template <> __device__ inline float ResRound<__nv_bfloat16>(float v) {
return __bfloat162float(__float2bfloat16(v));
}
// ---------------------------------------------------------------------------
// rmsnorm: one block per row, shared-memory f32 tree reduction.
// Upstream csrc counterpart: csrc/layernorm_kernels.cu (rms_norm_kernel / fused_add_rms_norm_kernel) — align signatures post-MVP.
template <typename Tin, typename Tout, typename Tres>
__global__ void RmsNormRowKernel(Tout* out, const Tin* x, const Tin* w, Tres* residual,
int64_t h, float eps, bool gemma) {
const int64_t row = blockIdx.x;
const Tin* xrow = x + row * h;
Tout* orow = out + row * h;
Tres* rrow = residual == nullptr ? nullptr : residual + row * h;
__shared__ float partial[kBlock];
float acc = 0.0f;
for (int64_t j = threadIdx.x; j < h; j += kBlock) {
float v = Load(xrow, j);
if (rrow != nullptr) {
v = ResRound<Tres>(v + Load(rrow, j)); // new residual stream: f32 add, round to Tres
Store(rrow, j, v); // updated in place (f32 or bf16)
}
acc += v * v;
}
partial[threadIdx.x] = acc;
__syncthreads();
for (int s = kBlock / 2; s > 0; s /= 2) {
if (static_cast<int>(threadIdx.x) < s) partial[threadIdx.x] += partial[threadIdx.x + s];
__syncthreads();
}
const float inv = 1.0f / sqrtf(partial[0] / static_cast<float>(h) + eps);
for (int64_t j = threadIdx.x; j < h; j += kBlock) {
const float v = rrow != nullptr ? Load(rrow, j) : Load(xrow, j);
float wj = Load(w, j);
if (gemma) wj += 1.0f;
Store(orow, j, v * inv * wj);
}
}
// ---------------------------------------------------------------------------
// Decode-fast rmsnorm variant (VT_RMSNORM_DECODE_FAST). Re-expressed 2026-07-17
// (KERNEL-EW-NORM-ACT numerics rework, CLAIM-EW-NORM-ACT-2) to be BIT-IDENTICAL
// to the shipped RmsNormRowKernel above (the through-stack 235/235 bit-reference
// that matches vLLM's production greedy stream), not merely ≤1-ulp close.
//
// WHY BIT-IDENTICAL (not ≤1-ulp): the 27B greedy token 6 is a RAZOR near-tie
// (198 "\n" prod vs 271 "\n\n" emu; the logit gap is ~zero). The shipped
// RmsNormRowKernel + GDN cubin = 235/235 (198). The prior fast kernel differed
// from shipped by ≤1 ulp (residual-add rounding + a different variance reduction
// ORDER + rsqrtf); accumulated over 64 layers alongside the GDN cubin's own
// ≤1-ulp perturbation, that tipped the tie to 271 (233/235), forcing the default
// back OFF (a875397). Making the fast output the SAME BITS as shipped removes the
// perturbation by construction: fast+cubin ≡ shipped+cubin ≡ 198 always.
//
// The three divergences vs shipped RmsNormRowKernel (cuda_ops.cu:62-93) and the
// fix that makes each bit-exact:
// (1) residual add. shipped:62-79 does v = ResRound<bf16>(f32(x)+f32(res)) — a
// f32 add of the two bf16 operands then a SINGLE round to bf16 (double
// rounding through f32). The prior fast used __hadd2 (single-round bf16
// add), which differs from the shipped double-round on rare carries. Fixed:
// __float2bfloat16(f32(x)+f32(res)) reproduces ResRound exactly.
// (2) variance sum ORDER. shipped:70-85 uses kBlock=256 threads, per-thread
// partial acc = Σ_m sq[tid + 256*m] (increasing m), then the shared-memory
// binary tree `for s=128; s>0; s>>=1`. f32 add is non-associative, so the
// sum's bits depend on this exact 256-partial + tree structure. The prior
// fast used cub::BlockReduce<float,1024> over 1024 threads (a different
// thread count AND a different reduction tree) => a different f32 variance.
// Fixed: this kernel launches with kBlock(=256) threads and reproduces
// shipped's scalar-strided Pass 1 and shared-tree byte-for-byte.
// (3) inv. shipped:86 uses `1.0f / sqrtf(...)` (correctly-rounded sqrt+div);
// the prior fast used rsqrtf (a ≤2-ulp reciprocal-sqrt approximation).
// Fixed: `1.0f / sqrtf(partial[0]/h + eps)` verbatim.
//
// The ONLY thing that legitimately differs from shipped — and the whole source of
// any speedup — is Pass 2 (normalize): out = bf16((f32(res)*inv)*(f32(w)+gemma))
// is ELEMENT-INDEPENDENT, so vectorizing it with 16-byte (4×bf162) loads/stores
// changes memory traffic, NOT the arithmetic, and stays bit-identical. Pass 1
// (the variance) is order-sensitive and is kept byte-for-byte shipped.
//
// Scope: bf16-in / bf16-out / bf16-residual, H%8==0, H>=1024 (the 129
// input/post-attn/final residual RMSNorm launches, H=5120 on the 27B / H=2048 on
// the 35B). Other dtype/residual/small-H keep RmsNormRowKernel. The q/k head norms
// take no residual => not this path. 16-byte load = 4 packed bf162.
struct alignas(16) RmsNormBf16x8 {
__nv_bfloat162 d[4];
};
// Launch geometry. The variance REDUCTION is byte-for-byte the shipped
// RmsNormRowKernel's (kBlock=256 partials + tree), but the memory passes run with
// kFastBlock=1024 threads: at decode the RMSNorm launches only `rows` (= num
// decode tokens, ~16 at c16) blocks, so the GPU is block-starved and thread-level
// parallelism per block — not occupancy — hides the memory latency. That
// thread-count is the ORIGINAL fast kernel's win; this rework keeps it while
// making the arithmetic bit-identical to shipped. kFastMaxH bounds the f32
// square-buffer below (guard rejects larger H); 8192 covers the 27B H=5120 / 35B
// H=2048 decode norms with headroom, at 32 KB static shared (< the 48 KB no-opt-in
// cap, and free here because only ~16 blocks are resident).
constexpr int kFastBlock = 1024;
constexpr int kFastMaxH = 8192;
// One block per row. Pass 1 (vectorized, kFastBlock threads): residual =
// bf16(f32(x)+f32(res)) [== shipped ResRound], stored bf16, with each element's
// f32 square written to the shared buffer ssq. Reduction (kBlock=256 threads):
// p_i = Σ_m ssq[i + 256*m] then the shared-memory binary tree — the EXACT shipped
// summation ORDER (cuda_ops.cu:70-86), reading the same bf16-rounded squares, so
// the f32 variance is bit-identical despite the vectorized loads. inv =
// 1.0f/sqrtf (shipped:86, not rsqrtf). Pass 2 (vectorized): normalize — element-
// independent, so identical bits at higher bandwidth. Every op matches shipped
// bit-for-bit; only the memory access WIDTH and thread COUNT differ.
__global__ void RmsNormRowFastKernel(__nv_bfloat16* __restrict__ out,
const __nv_bfloat16* __restrict__ x,
const __nv_bfloat16* __restrict__ w,
__nv_bfloat16* __restrict__ residual, int h, float eps,
bool gemma) {
const int tid = static_cast<int>(threadIdx.x);
const int64_t base = static_cast<int64_t>(blockIdx.x) * h;
const int vh = h / 8;
const RmsNormBf16x8* xv = reinterpret_cast<const RmsNormBf16x8*>(x + base);
RmsNormBf16x8* rv = reinterpret_cast<RmsNormBf16x8*>(residual + base);
const RmsNormBf16x8* wv = reinterpret_cast<const RmsNormBf16x8*>(w);
RmsNormBf16x8* ov = reinterpret_cast<RmsNormBf16x8*>(out + base);
__shared__ float ssq[kFastMaxH]; // per-element v^2 (v = the bf16-rounded residual)
__shared__ float partial[kBlock]; // 256 partials for shipped's exact tree order
// Pass 1 — vectorized residual add + store + per-element square into ssq. The
// residual add is bf16(f32(x)+f32(res)) (== ResRound<bf16>, shipped:75) and the
// square is of that bf16-rounded value (shipped:78), so ssq[j] is byte-for-byte
// shipped's v*v term for every element.
for (int vi = tid; vi < vh; vi += kFastBlock) {
RmsNormBf16x8 t = xv[vi];
RmsNormBf16x8 r = rv[vi];
RmsNormBf16x8 nr;
#pragma unroll
for (int k = 0; k < 4; k++) {
float2 xf = __bfloat1622float2(t.d[k]);
float2 rf = __bfloat1622float2(r.d[k]);
nr.d[k] = __floats2bfloat162_rn(xf.x + rf.x, xf.y + rf.y); // per-lane == __float2bfloat16
float2 nf = __bfloat1622float2(nr.d[k]); // bf16 value back to f32
ssq[vi * 8 + 2 * k] = nf.x * nf.x;
ssq[vi * 8 + 2 * k + 1] = nf.y * nf.y;
}
rv[vi] = nr; // residual store (bf16), bit-identical to shipped
}
__syncthreads(); // publish ssq (and residual) before the strided reduction reads them
// Reduction — BYTE-FOR-BYTE shipped (cuda_ops.cu:80-86). Threads >=256 idle.
if (tid < kBlock) {
float acc = 0.0f;
for (int j = tid; j < h; j += kBlock) acc += ssq[j]; // p_i = Σ_m ssq[i+256m], increasing m
partial[tid] = acc;
}
__syncthreads();
for (int s = kBlock / 2; s > 0; s >>= 1) {
if (tid < s) partial[tid] += partial[tid + s];
__syncthreads();
}
const float inv = 1.0f / sqrtf(partial[0] / static_cast<float>(h) + eps);
// Pass 2 — vectorized normalize (bit-identical to shipped:87-92; each output
// element is (f32(res)*inv)*(f32(w)+gemma) rounded to bf16, independent of the
// others). Each thread reloads the residual vector it wrote in Pass 1.
for (int vi = tid; vi < vh; vi += kFastBlock) {
RmsNormBf16x8 r = rv[vi];
RmsNormBf16x8 wr = wv[vi];
RmsNormBf16x8 o;
#pragma unroll
for (int k = 0; k < 4; k++) {
float2 rf = __bfloat1622float2(r.d[k]);
float2 wf = __bfloat1622float2(wr.d[k]);
float w0 = wf.x, w1 = wf.y;
if (gemma) {
w0 += 1.0f;
w1 += 1.0f;
}
o.d[k] = __floats2bfloat162_rn(rf.x * inv * w0, rf.y * inv * w1);
}
ov[vi] = o;
}
}
// f32-residual sibling of RmsNormRowFastKernel — BYTE-IDENTICAL to the shipped
// RmsNormRowKernel<float,float,float> (residual!=null, f32 in/out/residual). The Laguna
// NVFP4 decode's post-attn residual RMSNorm runs this shipped kernel as a <<<rows=1,256>>>
// single block (ncu: launch__waves_per_multiprocessor≈0.00, sm__throughput≈0.06% — one SM
// of ~100+, latency-bound). Same fix as the bf16 fast kernel: float4-vectorized memory
// passes over kFastBlock(=1024) threads hide the per-block memory latency (decode launches
// only `rows` blocks), while the variance REDUCTION reproduces the shipped kBlock(=256)
// strided partials + tree over the SAME per-element squares in the SAME Σ_m ssq[i+256m]
// order → bit-identical f32 variance. residual add is f32 x+res (ResRound<float> is
// identity, shipped:75), inv = 1.0f/sqrtf (shipped:86), normalize out=res*inv*(w[+gemma])
// element-independent — every bit matches shipped. Scope guard (h%4==0, 1024<=h<=kFastMaxH,
// 16B-aligned) in TryLaunchRmsNormDecodeFastF32.
__global__ void RmsNormRowFastF32Kernel(float* __restrict__ out, const float* __restrict__ x,
const float* __restrict__ w, float* __restrict__ residual,
int h, float eps, bool gemma) {
const int tid = static_cast<int>(threadIdx.x);
const int64_t base = static_cast<int64_t>(blockIdx.x) * h;
const int vh = h >> 2;
const float4* xv = reinterpret_cast<const float4*>(x + base);
float4* rv = reinterpret_cast<float4*>(residual + base);
const float4* wv = reinterpret_cast<const float4*>(w);
float4* ov = reinterpret_cast<float4*>(out + base);
__shared__ float sv[kFastMaxH]; // residual VALUE v (not v²): the reduction squares with
__shared__ float partial[kBlock]; // shipped's `acc += v*v` expression so nvcc emits the
// SAME fma (f32 v² is NOT exact — pre-squaring the value
// rounds it and diverges by ≤1 ulp; the bf16 sibling can
// pre-square because a bf16² is exactly representable).
// Pass 1 — residual = x + res (f32, == shipped ResRound<float> identity), store residual + v.
for (int vi = tid; vi < vh; vi += kFastBlock) {
float4 t = xv[vi], r = rv[vi], nr;
nr.x = t.x + r.x;
nr.y = t.y + r.y;
nr.z = t.z + r.z;
nr.w = t.w + r.w;
rv[vi] = nr;
const int e = vi << 2;
sv[e] = nr.x;
sv[e + 1] = nr.y;
sv[e + 2] = nr.z;
sv[e + 3] = nr.w;
}
__syncthreads();
// Reduction — BYTE-FOR-BYTE shipped (cuda_ops.cu:80-86): thread t squares+accumulates
// v[t],v[t+256],… with the IDENTICAL `acc += v*v` expression (same nvcc fma). Threads >=256 idle.
if (tid < kBlock) {
float acc = 0.0f;
for (int j = tid; j < h; j += kBlock) acc += sv[j] * sv[j];
partial[tid] = acc;
}
__syncthreads();
for (int s = kBlock / 2; s > 0; s >>= 1) {
if (tid < s) partial[tid] += partial[tid + s];
__syncthreads();
}
const float inv = 1.0f / sqrtf(partial[0] / static_cast<float>(h) + eps);
// Pass 2 — normalize (element-independent, bit-identical to shipped:87-92).
for (int vi = tid; vi < vh; vi += kFastBlock) {
float4 r = rv[vi], wr = wv[vi], o;
float w0 = wr.x, w1 = wr.y, w2 = wr.z, w3 = wr.w;
if (gemma) {
w0 += 1.0f;
w1 += 1.0f;
w2 += 1.0f;
w3 += 1.0f;
}
o.x = r.x * inv * w0;
o.y = r.y * inv * w1;
o.z = r.z * inv * w2;
o.w = r.w * inv * w3;
ov[vi] = o;
}
}
// Runtime predicate + launch for the decode-fast path. Returns true iff it ran.
// Guard: bf16 in/out/weight/residual, 16-byte-aligned pointers (vectorized loads),
// H%8==0, 1024<=H<=kFastMaxH (H>=1024 scopes to the big residual RMSNorm launches;
// H<=kFastMaxH bounds the f32 square-buffer shared array). The launch uses
// kFastBlock threads for the memory passes, while the reduction reproduces the
// shipped RmsNormRowKernel's kBlock=256 partial + tree order, so the output is
// bit-identical. Out-of-scope shapes keep RmsNormRowKernel.
inline bool TryLaunchRmsNormDecodeFast(cudaStream_t s, Tensor& out, const Tensor& x,
const Tensor& w, const RmsNormArgs& args,
Tensor* residual, unsigned rows, int64_t h) {
if (!RmsNormDecodeFastFlagIsOn(std::getenv("VT_RMSNORM_DECODE_FAST"))) return false;
if (out.dtype != DType::kBF16 || x.dtype != DType::kBF16 || w.dtype != DType::kBF16)
return false;
if (residual == nullptr || residual->dtype != DType::kBF16) return false;
if (h % 8 != 0 || h < 1024 || h > kFastMaxH) return false;
auto aligned16 = [](const void* p) {
return (reinterpret_cast<std::uintptr_t>(p) & 0xF) == 0;
};
if (!aligned16(out.data) || !aligned16(x.data) || !aligned16(w.data) ||
!aligned16(residual->data))
return false;
RmsNormRowFastKernel<<<rows, kFastBlock, 0, s>>>(out.Ptr<__nv_bfloat16>(), x.Ptr<__nv_bfloat16>(),
w.Ptr<__nv_bfloat16>(),
residual->Ptr<__nv_bfloat16>(),
static_cast<int>(h), args.eps, args.gemma);
return true;
}
// f32-residual sibling of TryLaunchRmsNormDecodeFast. Same VT_RMSNORM_DECODE_FAST contract
// (default ON, '0' = rollback), same bit-identity guarantee (RmsNormRowFastF32Kernel). Scope:
// f32 in/out/weight AND a f32 residual (the add+RMSNorm decode launch — the Laguna NVFP4
// post-attn residual norm), h%4==0, 1024<=h<=kFastMaxH, 16-byte-aligned. Every other case
// keeps RmsNormRowKernel. float4 vectorization needs h%4==0 (vs the bf16 path's h%8==0).
inline bool TryLaunchRmsNormDecodeFastF32(cudaStream_t s, Tensor& out, const Tensor& x,
const Tensor& w, const RmsNormArgs& args,
Tensor* residual, unsigned rows, int64_t h) {
if (!RmsNormDecodeFastFlagIsOn(std::getenv("VT_RMSNORM_DECODE_FAST"))) return false;
if (out.dtype != DType::kF32 || x.dtype != DType::kF32 || w.dtype != DType::kF32) return false;
if (residual == nullptr || residual->dtype != DType::kF32) return false;
if (h % 4 != 0 || h < 1024 || h > kFastMaxH) return false;
auto aligned16 = [](const void* p) {
return (reinterpret_cast<std::uintptr_t>(p) & 0xF) == 0;
};
if (!aligned16(out.data) || !aligned16(x.data) || !aligned16(w.data) ||
!aligned16(residual->data))
return false;
RmsNormRowFastF32Kernel<<<rows, kFastBlock, 0, s>>>(out.Ptr<float>(), x.Ptr<float>(),
w.Ptr<float>(), residual->Ptr<float>(),
static_cast<int>(h), args.eps, args.gemma);
return true;
}
// Dispatch the residual store dtype (f32 or bf16). A bf16 residual mirrors vLLM's
// bf16 model dtype (model_config.dtype=bfloat16): the residual stream is bf16, only
// the variance/normalize accumulation below stays f32. A f32 residual (or none)
// takes the byte-identical previous path.
template <typename Tin, typename Tout>
void LaunchRmsNormRes(cudaStream_t s, Tensor& out, const Tensor& x, const Tensor& w,
const RmsNormArgs& args, Tensor* residual, unsigned rows, int64_t h) {
if (residual != nullptr && residual->dtype == DType::kBF16) {
RmsNormRowKernel<Tin, Tout, __nv_bfloat16><<<rows, kBlock, 0, s>>>(
out.Ptr<Tout>(), x.Ptr<Tin>(), w.Ptr<Tin>(), residual->Ptr<__nv_bfloat16>(), h,
args.eps, args.gemma);
} else {
float* res = residual == nullptr ? nullptr : residual->Ptr<float>();
RmsNormRowKernel<Tin, Tout, float><<<rows, kBlock, 0, s>>>(
out.Ptr<Tout>(), x.Ptr<Tin>(), w.Ptr<Tin>(), res, h, args.eps, args.gemma);
}
}
template <typename Tin>
void LaunchRmsNorm(cudaStream_t s, Tensor& out, const Tensor& x, const Tensor& w,
const RmsNormArgs& args, Tensor* residual) {
const int64_t t = x.shape[0], h = x.shape[1];
if (t == 0 || h == 0) return;
const unsigned rows = static_cast<unsigned>(t);
// Decode-fast path (VT_RMSNORM_DECODE_FAST, default ON; '0' = rollback): only
// engages for the bf16 add+RMSNorm decode launches; every other case keeps
// RmsNormRowKernel. Output is bit-identical to RmsNormRowKernel by construction.
if constexpr (std::is_same_v<Tin, __nv_bfloat16>) {
if (TryLaunchRmsNormDecodeFast(s, out, x, w, args, residual, rows, h)) {
Check(cudaGetLastError(), "rmsnorm fast launch");
return;
}
}
// f32-residual decode-fast path (Laguna NVFP4 post-attn residual norm). Bit-identical
// to RmsNormRowKernel<float,float,float>; same VT_RMSNORM_DECODE_FAST contract.
if constexpr (std::is_same_v<Tin, float>) {
if (TryLaunchRmsNormDecodeFastF32(s, out, x, w, args, residual, rows, h)) {
Check(cudaGetLastError(), "rmsnorm fast-f32 launch");
return;
}
}
switch (out.dtype) {
case DType::kF32:
LaunchRmsNormRes<Tin, float>(s, out, x, w, args, residual, rows, h);
break;
case DType::kBF16:
LaunchRmsNormRes<Tin, __nv_bfloat16>(s, out, x, w, args, residual, rows, h);
break;
default: VT_CHECK(false, "cuda rmsnorm: unsupported out dtype");
}
Check(cudaGetLastError(), "rmsnorm launch");
}
void RmsNormKernelCuda(Queue& q, Tensor& out, const Tensor& x, const Tensor& w,
const RmsNormArgs& args, Tensor* residual) {
VT_CHECK(w.dtype == x.dtype, "cuda rmsnorm: weight dtype must match x");
switch (x.dtype) {
case DType::kF32: LaunchRmsNorm<float>(AsStream(q), out, x, w, args, residual); break;
case DType::kBF16:
LaunchRmsNorm<__nv_bfloat16>(AsStream(q), out, x, w, args, residual);
break;
default: VT_CHECK(false, "cuda rmsnorm: unsupported input dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// rmsnorm + static fp8 quant, fused: emit the fp8 activation (and optionally the
// bf16 normed activation) directly from the RMSNorm's normalize loop, so the
// standalone QuantFp8Static pass + its bf16 round-trip disappear. Mirror of
// vLLM's Inductor fused_add_rms_norm_static_fp8_quant
// (vllm/compilation/passes/fusion/rms_quant_fusion.py:124). Same reduction as
// RmsNormRowKernel; BIT-IDENTICAL to RmsNorm(bf16)+QuantFp8Static because the
// fp8 is taken from the SAME bf16-rounded value the split path quantizes
// (F32ToFp8Dev(__bfloat162float(bf16(n)) * inv_scale)).
__device__ __forceinline__ uint8_t RmsNormF32ToFp8Dev(float f) {
return static_cast<uint8_t>(__nv_cvt_float_to_fp8(f, __NV_SATFINITE, __NV_E4M3));
}
template <typename Tin, typename Tres>
__global__ void RmsNormQuantFp8RowKernel(uint8_t* out_fp8, __nv_bfloat16* out_bf16, const Tin* x,
const Tin* w, Tres* residual, int64_t h, float eps,
bool gemma, float inv_scale) {
const int64_t row = blockIdx.x;
const Tin* xrow = x + row * h;
uint8_t* orow = out_fp8 + row * h;
__nv_bfloat16* brow = out_bf16 == nullptr ? nullptr : out_bf16 + row * h;
Tres* rrow = residual == nullptr ? nullptr : residual + row * h;
__shared__ float partial[kBlock];
float acc = 0.0f;
for (int64_t j = threadIdx.x; j < h; j += kBlock) {
float v = Load(xrow, j);
if (rrow != nullptr) {
v = ResRound<Tres>(v + Load(rrow, j)); // new residual stream: f32 add, round to Tres
Store(rrow, j, v); // updated in place (f32 or bf16)
}
acc += v * v;
}
partial[threadIdx.x] = acc;
__syncthreads();
for (int s = kBlock / 2; s > 0; s /= 2) {
if (static_cast<int>(threadIdx.x) < s) partial[threadIdx.x] += partial[threadIdx.x + s];
__syncthreads();
}
const float inv = 1.0f / sqrtf(partial[0] / static_cast<float>(h) + eps);
for (int64_t j = threadIdx.x; j < h; j += kBlock) {
const float v = rrow != nullptr ? Load(rrow, j) : Load(xrow, j);
float wj = Load(w, j);
if (gemma) wj += 1.0f;
// bf16-intermediate (matches RmsNorm's bf16 store then QuantFp8Static's bf16 load).
const __nv_bfloat16 nb = __float2bfloat16(v * inv * wj);
if (brow != nullptr) brow[j] = nb;
orow[j] = RmsNormF32ToFp8Dev(__bfloat162float(nb) * inv_scale);
}
}
template <typename Tin>
void LaunchRmsNormQuantFp8(cudaStream_t s, Tensor& out_fp8, Tensor* out_bf16, const Tensor& x,
const Tensor& w, const RmsNormArgs& args, Tensor* residual,
float input_scale) {
const int64_t t = x.shape[0], h = x.shape[1];
if (t == 0 || h == 0) return;
const unsigned rows = static_cast<unsigned>(t);
const float inv_scale = 1.0f / input_scale;
__nv_bfloat16* bf16 = out_bf16 == nullptr ? nullptr : out_bf16->Ptr<__nv_bfloat16>();
if (residual != nullptr && residual->dtype == DType::kBF16) {
RmsNormQuantFp8RowKernel<Tin, __nv_bfloat16><<<rows, kBlock, 0, s>>>(
out_fp8.Ptr<uint8_t>(), bf16, x.Ptr<Tin>(), w.Ptr<Tin>(),
residual->Ptr<__nv_bfloat16>(), h, args.eps, args.gemma, inv_scale);
} else {
float* res = residual == nullptr ? nullptr : residual->Ptr<float>();
RmsNormQuantFp8RowKernel<Tin, float><<<rows, kBlock, 0, s>>>(
out_fp8.Ptr<uint8_t>(), bf16, x.Ptr<Tin>(), w.Ptr<Tin>(), res, h, args.eps, args.gemma,
inv_scale);
}
Check(cudaGetLastError(), "rmsnorm_quant_fp8 launch");
}
void RmsNormQuantFp8KernelCuda(Queue& q, Tensor& out_fp8, Tensor* out_bf16, const Tensor& x,
const Tensor& w, const RmsNormArgs& args, Tensor* residual,
float input_scale) {
VT_CHECK(w.dtype == x.dtype, "cuda rmsnorm_quant_fp8: weight dtype must match x");
switch (x.dtype) {
case DType::kF32:
LaunchRmsNormQuantFp8<float>(AsStream(q), out_fp8, out_bf16, x, w, args, residual,
input_scale);
break;
case DType::kBF16:
LaunchRmsNormQuantFp8<__nv_bfloat16>(AsStream(q), out_fp8, out_bf16, x, w, args, residual,
input_scale);
break;
default: VT_CHECK(false, "cuda rmsnorm_quant_fp8: unsupported input dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// silu_and_mul: grid-stride over the T*D output elements.
// Upstream csrc counterpart: csrc/activation_kernels.cu (act_and_mul_kernel<silu>) — align post-MVP.
template <typename Tin, typename Tout>
__global__ void SiluAndMulKernel(Tout* out, const Tin* x, int64_t n, int64_t d) {
const int64_t step = static_cast<int64_t>(gridDim.x) * blockDim.x;
for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < n;
idx += step) {
const int64_t i = idx / d;
const int64_t j = idx - i * d;
const float gate = Load(x, i * 2 * d + j);
const float up = Load(x, i * 2 * d + d + j);
const float silu = gate / (1.0f + expf(-gate));
Store(out, idx, silu * up);
}
}
template <typename Tin>
void LaunchSiluAndMul(cudaStream_t s, Tensor& out, const Tensor& x) {
const int64_t t = x.shape[0], d = x.shape[1] / 2;
const int64_t n = t * d;
if (n == 0) return;
switch (out.dtype) {
case DType::kF32:
SiluAndMulKernel<Tin, float>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<float>(), x.Ptr<Tin>(), n, d);
break;
case DType::kBF16:
SiluAndMulKernel<Tin, __nv_bfloat16>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<__nv_bfloat16>(), x.Ptr<Tin>(), n, d);
break;
default: VT_CHECK(false, "cuda silu_and_mul: unsupported out dtype");
}
Check(cudaGetLastError(), "silu_and_mul launch");
}
void SiluAndMulKernelCuda(Queue& q, Tensor& out, const Tensor& x) {
switch (x.dtype) {
case DType::kF32: LaunchSiluAndMul<float>(AsStream(q), out, x); break;
case DType::kBF16: LaunchSiluAndMul<__nv_bfloat16>(AsStream(q), out, x); break;
default: VT_CHECK(false, "cuda silu_and_mul: unsupported input dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// gelu_and_mul (Gemma GeGLU): out = gelu_tanh(gate) * up. gelu_tanh is the
// exact `gelu_pytorch_tanh` / F.gelu(approximate="tanh") — computed in f32 then
// stored, mirroring vLLM GeluAndMul(approximate="tanh").
template <typename Tin, typename Tout>
__global__ void GeluAndMulKernel(Tout* out, const Tin* x, int64_t n, int64_t d) {
const int64_t step = static_cast<int64_t>(gridDim.x) * blockDim.x;
for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < n;
idx += step) {
const int64_t i = idx / d;
const int64_t j = idx - i * d;
const float g = Load(x, i * 2 * d + j);
const float up = Load(x, i * 2 * d + d + j);
// 0.5*g*(1 + tanh( sqrt(2/pi) * (g + 0.044715*g^3) )); sqrt(2/pi)=0.7978845608...
const float inner = 0.7978845608028654f * (g + 0.044715f * g * g * g);
const float gelu = 0.5f * g * (1.0f + tanhf(inner));
Store(out, idx, gelu * up);
}
}
template <typename Tin>
void LaunchGeluAndMul(cudaStream_t s, Tensor& out, const Tensor& x) {
const int64_t t = x.shape[0], d = x.shape[1] / 2;
const int64_t n = t * d;
if (n == 0) return;
switch (out.dtype) {
case DType::kF32:
GeluAndMulKernel<Tin, float>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<float>(), x.Ptr<Tin>(), n, d);
break;
case DType::kBF16:
GeluAndMulKernel<Tin, __nv_bfloat16>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<__nv_bfloat16>(), x.Ptr<Tin>(), n, d);
break;
default: VT_CHECK(false, "cuda gelu_and_mul: unsupported out dtype");
}
Check(cudaGetLastError(), "gelu_and_mul launch");
}
void GeluAndMulKernelCuda(Queue& q, Tensor& out, const Tensor& x) {
switch (x.dtype) {
case DType::kF32: LaunchGeluAndMul<float>(AsStream(q), out, x); break;
case DType::kBF16: LaunchGeluAndMul<__nv_bfloat16>(AsStream(q), out, x); break;
default: VT_CHECK(false, "cuda gelu_and_mul: unsupported input dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// mul_scalar: out[i] = x[i] * scalar (f32 compute, out-dtype store). The Gemma
// embedding normalizer `embed * sqrt(hidden_size)`.
template <typename Tin, typename Tout>
__global__ void MulScalarKernel(Tout* out, const Tin* x, int64_t n, float scalar) {
const int64_t step = static_cast<int64_t>(gridDim.x) * blockDim.x;
for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < n;
idx += step)
Store(out, idx, Load(x, idx) * scalar);
}
template <typename Tin>
void LaunchMulScalar(cudaStream_t s, Tensor& out, const Tensor& x, float scalar) {
const int64_t n = x.Numel();
if (n == 0) return;
switch (out.dtype) {
case DType::kF32:
MulScalarKernel<Tin, float>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<float>(), x.Ptr<Tin>(), n, scalar);
break;
case DType::kBF16:
MulScalarKernel<Tin, __nv_bfloat16>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<__nv_bfloat16>(), x.Ptr<Tin>(), n, scalar);
break;
default: VT_CHECK(false, "cuda mul_scalar: unsupported out dtype");
}
Check(cudaGetLastError(), "mul_scalar launch");
}
void MulScalarKernelCuda(Queue& q, Tensor& out, const Tensor& x, double scalar) {
const float s = static_cast<float>(scalar);
switch (x.dtype) {
case DType::kF32: LaunchMulScalar<float>(AsStream(q), out, x, s); break;
case DType::kBF16: LaunchMulScalar<__nv_bfloat16>(AsStream(q), out, x, s); break;
default: VT_CHECK(false, "cuda mul_scalar: unsupported input dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// soft_cap: out[i] = cap * tanh(x[i] / cap) (f32 compute, out-dtype store). The
// Gemma-2 final logit soft-cap (gemma2.py:344-345). Mirrors torch
// logits.div_(cap).tanh_().mul_(cap).
template <typename Tin, typename Tout>
__global__ void SoftCapKernel(Tout* out, const Tin* x, int64_t n, float cap) {
const int64_t step = static_cast<int64_t>(gridDim.x) * blockDim.x;
for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < n;
idx += step)
Store(out, idx, cap * tanhf(static_cast<float>(Load(x, idx)) / cap));
}
template <typename Tin>
void LaunchSoftCap(cudaStream_t s, Tensor& out, const Tensor& x, float cap) {
const int64_t n = x.Numel();
if (n == 0) return;
switch (out.dtype) {
case DType::kF32:
SoftCapKernel<Tin, float>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<float>(), x.Ptr<Tin>(), n, cap);
break;
case DType::kBF16:
SoftCapKernel<Tin, __nv_bfloat16>
<<<GridFor(n), kBlock, 0, s>>>(out.Ptr<__nv_bfloat16>(), x.Ptr<Tin>(), n, cap);
break;
default: VT_CHECK(false, "cuda soft_cap: unsupported out dtype");
}
Check(cudaGetLastError(), "soft_cap launch");
}
void SoftCapKernelCuda(Queue& q, Tensor& out, const Tensor& x, double cap) {
const float c = static_cast<float>(cap);
switch (x.dtype) {
case DType::kF32: LaunchSoftCap<float>(AsStream(q), out, x, c); break;
case DType::kBF16: LaunchSoftCap<__nv_bfloat16>(AsStream(q), out, x, c); break;
default: VT_CHECK(false, "cuda soft_cap: unsupported input dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// embedding: grid-stride gather. Ids live on the device, so bounds are checked
// in-kernel: bad ids are clamped for the gather (no OOB read) and the first bad
// id is recorded in a device-side flag via atomicCAS. The host wrapper
// synchronizes the stream, reads the flag back, and throws — CUDA Embedding is
// synchronizing for now (M0.6 decision, see ops.h; revisit for full async in
// M0.9/M2).
// No direct csrc counterpart (upstream uses torch embedding); keep vt-native.
struct EmbeddingErr {
int status; // 0 = ok, 1 = bad id recorded
int pad; // keep `id` naturally aligned
long long id; // first out-of-range id seen (valid when status != 0)
};
template <typename Tin, typename Tout, typename Tid>
__global__ void EmbeddingKernel(Tout* out, const Tin* table, const Tid* ids, int64_t n,
int64_t h, int64_t v, EmbeddingErr* err) {
const int64_t step = static_cast<int64_t>(gridDim.x) * blockDim.x;
for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < n;
idx += step) {
const int64_t i = idx / h;
const int64_t j = idx - i * h;
int64_t id = static_cast<int64_t>(ids[i]);
if (id < 0 || id >= v) {
if (atomicCAS(&err->status, 0, 1) == 0) err->id = static_cast<long long>(id);
id = id < 0 ? 0 : v - 1; // clamp: keep the gather in-bounds
}
Store(out, idx, Load(table, id * h + j));
}
}
template <typename Tin, typename Tout>
cudaError_t LaunchEmbedding(cudaStream_t s, Tensor& out, const Tensor& table,
const Tensor& ids, EmbeddingErr* err) {
const int64_t t = ids.shape[0], h = table.shape[1], v = table.shape[0];
const int64_t n = t * h;
if (ids.dtype == DType::kI32) {
EmbeddingKernel<Tin, Tout, int32_t><<<GridFor(n), kBlock, 0, s>>>(
out.Ptr<Tout>(), table.Ptr<Tin>(), ids.Ptr<int32_t>(), n, h, v, err);
} else {
EmbeddingKernel<Tin, Tout, int64_t><<<GridFor(n), kBlock, 0, s>>>(
out.Ptr<Tout>(), table.Ptr<Tin>(), ids.Ptr<int64_t>(), n, h, v, err);
}
return cudaGetLastError();
}
template <typename Tin>
cudaError_t LaunchEmbeddingIn(cudaStream_t s, Tensor& out, const Tensor& table,
const Tensor& ids, EmbeddingErr* err) {
if (out.dtype == DType::kF32) return LaunchEmbedding<Tin, float>(s, out, table, ids, err);
return LaunchEmbedding<Tin, __nv_bfloat16>(s, out, table, ids, err);
}
// Out-of-range reporting WITHOUT a per-call barrier.
//
// The original shape was cudaMalloc + kernel + D2H + cudaStreamSynchronize +
// cudaFree on EVERY call. All three of those driver calls SYNCHRONIZE the device,
// and embedding runs once per engine step at the very front of the forward, so
// the sequence is a hard barrier between consecutive steps. It costs almost
// nothing while the engine is serialized anyway (measured 23.7 us/call), and it
// costs the ENTIRE overlap the moment the engine stops being serialized — which
// is exactly what ENG-ASYNC-SCHED is for. See
// .agents/specs/async-discrete-device-combine.md W4e.
//
// The replacement is a small RING of persistent slots. Each slot owns a device
// flag, a PINNED host mirror and an event; a call takes the next slot, resets and
// launches into it, and records the event — no sync, no allocation. The check is
// then DEFERRED: before reusing a slot, its event is consumed. If the event has
// already completed the check is free (cudaEventQuery); only a genuinely
// in-flight slot blocks, which bounds the ring's memory without ever making the
// common path wait.
//
// Semantics change in exactly one way, deliberately: a bad id is reported up to
// kSlots calls later than it was, instead of on the offending call. It is still
// LOUD (the same exception, the same message, on the same queue) and it is still
// memory-safe on the offending call itself, because the kernel clamps the gather
// — an out-of-range id has never produced an out-of-bounds read, only a wrong
// row. The one report the ring cannot deliver is an error in the final embedding
// of a process that then exits without another embedding; that residue is
// covered by the token-exactness gates, which compare the produced ids.
struct EmbeddingErrSlot {
EmbeddingErr* dev = nullptr; // device-side flag the kernel atomically sets
EmbeddingErr* host = nullptr; // pinned mirror the async D2H lands in
cudaEvent_t done = nullptr; // completion of that D2H
bool pending = false; // event recorded and not yet consumed
int64_t vocab = 0; // table rows of the call that armed the slot
};
// One embedding call per engine step per queue, so a handful of slots covers any
// realistic overlap depth; the ring only has to outlive the in-flight window.
constexpr int kEmbeddingErrSlots = 4;
struct EmbeddingErrRing {
EmbeddingErrSlot slots[kEmbeddingErrSlots];
int next = 0;
};
// One ring per process: the forward runs on a single device and a single host
// thread (the same assumption DevicePool and the resident-weight caches make).
EmbeddingErrRing& ErrRing() {
static EmbeddingErrRing ring;
return ring;
}
// Consume a slot's outstanding result, blocking only if `force`. Returns the
// error to report, if any, WITHOUT throwing: the caller decides when to throw so
// a partially-armed slot is never left behind.
bool ConsumeEmbeddingErr(EmbeddingErrSlot& slot, bool force, EmbeddingErr* err_out,
int64_t* vocab_out) {
if (!slot.pending) return false;
if (!force) {
const cudaError_t q = cudaEventQuery(slot.done);
if (q == cudaErrorNotReady) return false; // still in flight: check it later
if (q != cudaSuccess) Check(q, "embedding flag event query");
} else {
Check(cudaEventSynchronize(slot.done), "embedding flag event sync");
}
slot.pending = false;
if (slot.host->status == 0) return false;
*err_out = *slot.host;
*vocab_out = slot.vocab;
return true;
}
void EmbeddingKernelCuda(Queue& q, Tensor& out, const Tensor& table, const Tensor& ids) {
// Validate dtypes before touching the ring so a throw cannot leave a slot armed.
VT_CHECK(table.dtype == DType::kF32 || table.dtype == DType::kBF16,
"cuda embedding: unsupported table dtype (f32/bf16 only)");
VT_CHECK(out.dtype == DType::kF32 || out.dtype == DType::kBF16,
"cuda embedding: unsupported out dtype");
const int64_t n = ids.shape[0] * table.shape[1];
if (n == 0) return;
// v == 0 with nonempty ids can never gather anything valid, and the in-kernel
// clamp (v - 1) would go out of bounds — throw loudly before launching.
VT_CHECK(table.shape[0] > 0, "cuda embedding: empty table (vocab 0) with nonempty ids");
cudaStream_t s = AsStream(q);
EmbeddingErrRing& ring = ErrRing();
EmbeddingErrSlot& slot = ring.slots[ring.next];
ring.next = (ring.next + 1) % kEmbeddingErrSlots;
if (slot.dev == nullptr) {
// First use of this slot: the ONLY allocation this path ever makes. Pinned
// host memory so the D2H is a real async copy rather than a staged one.
Check(cudaMalloc(&slot.dev, sizeof(EmbeddingErr)), "cudaMalloc embedding flag");
Check(cudaHostAlloc(reinterpret_cast<void**>(&slot.host), sizeof(EmbeddingErr),
cudaHostAllocDefault),
"cudaHostAlloc embedding flag mirror");
Check(cudaEventCreateWithFlags(&slot.done, cudaEventDisableTiming),
"cudaEventCreate embedding flag");
slot.host->status = 0;
}
// Reusing this slot means its previous result must be consumed first. Force the
// wait: the slot is about to be overwritten, so skipping the check here would
// DROP a report rather than defer it. With kSlots slots in the ring this only
// blocks when more than kSlots embeddings are genuinely in flight.
EmbeddingErr prev{};
int64_t prev_vocab = 0;
const bool had_prev = ConsumeEmbeddingErr(slot, /*force=*/true, &prev, &prev_vocab);
cudaError_t st = cudaMemsetAsync(slot.dev, 0, sizeof(EmbeddingErr), s);
if (st == cudaSuccess) {
st = table.dtype == DType::kF32
? LaunchEmbeddingIn<float>(s, out, table, ids, slot.dev)
: LaunchEmbeddingIn<__nv_bfloat16>(s, out, table, ids, slot.dev);
}
if (st == cudaSuccess) {
st = cudaMemcpyAsync(slot.host, slot.dev, sizeof(EmbeddingErr), cudaMemcpyDeviceToHost, s);
}
if (st == cudaSuccess) st = cudaEventRecord(slot.done, s);
if (st == cudaSuccess) {
slot.pending = true;
slot.vocab = table.shape[0];
}
// A launch/copy failure is reported before a deferred out-of-range id: it is
// the more immediate fault, and it may be why the older flag never arrived.
Check(st, "embedding");
if (had_prev) {
throw std::runtime_error("vt cuda: embedding: id " + std::to_string(prev.id) +
" out of range [0, " + std::to_string(prev_vocab) + ")");
}
// Opportunistically drain every OTHER slot whose copy has already landed, so a
// bad id surfaces at the next call rather than only when its slot comes round
// again. Free: cudaEventQuery on a completed event does not block.
for (int i = 0; i < kEmbeddingErrSlots; ++i) {
EmbeddingErrSlot& other = ring.slots[i];
if (&other == &slot) continue;
EmbeddingErr err{};
int64_t vocab = 0;
if (ConsumeEmbeddingErr(other, /*force=*/false, &err, &vocab)) {
throw std::runtime_error("vt cuda: embedding: id " + std::to_string(err.id) +
" out of range [0, " + std::to_string(vocab) + ")");
}
}
}
// ---------------------------------------------------------------------------
// rope_neox: grid-stride over (token, head, rotation pair) across q and k.
// Angle math in double (pow/cos/sin) to match the CPU reference numerics.
// Upstream csrc counterpart: csrc/pos_encoding_kernels.cu (rotary_embedding_kernel) — align post-MVP.
// Llama-3 rope frequency rescale (vLLM Llama3RotaryEmbedding._compute_inv_freq,
// rotary_embedding/llama3_rope.py:33-54). `freq` is the base inv_freq
// (base^(-2i/rot)); when scaling_factor <= 0 this is a no-op (plain RoPE). The
// piecewise low/high wavelength interpolation matches vLLM's torch.where ladder.
__device__ __host__ inline double Llama3ScaleFreq(double freq, double scaling_factor,
double low_ff, double high_ff,
double orig_max) {
if (!(scaling_factor > 0.0)) return freq;
constexpr double kTwoPi = 6.283185307179586476925286766559;
const double low_freq_wavelen = orig_max / low_ff;
const double high_freq_wavelen = orig_max / high_ff;
const double wave_len = kTwoPi / freq;
double smooth = 0.0;
if (low_ff != high_ff)
smooth = (orig_max / wave_len - low_ff) / (high_ff - low_ff);
if (wave_len < high_freq_wavelen) return freq; // high-freq: keep
if (wave_len > low_freq_wavelen) return freq / scaling_factor; // low-freq: scale
return (1.0 - smooth) * freq / scaling_factor + smooth * freq; // mid: interpolate
}
template <typename T, typename Tid>
__global__ void RopeNeoxKernel(T* qs, T* ks, const Tid* pos, int64_t hq, int64_t hk,
int64_t d, int64_t half, int rot, double base,
double l3_sf, double l3_lo, double l3_hi, double l3_omax,
int64_t n) {
const int64_t heads = hq + hk;
const int64_t step = static_cast<int64_t>(gridDim.x) * blockDim.x;
for (int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x; idx < n;
idx += step) {
const int64_t pair = idx % half;
const int64_t head = (idx / half) % heads;
const int64_t tok = idx / (half * heads);
T* ptr;
int64_t off;
if (head < hq) {
ptr = qs;
off = (tok * hq + head) * d;
} else {
ptr = ks;
off = (tok * hk + (head - hq)) * d;
}
const int64_t p = static_cast<int64_t>(pos[tok]);
double freq = pow(base, -2.0 * static_cast<double>(pair) / static_cast<double>(rot));
freq = Llama3ScaleFreq(freq, l3_sf, l3_lo, l3_hi, l3_omax);
const double angle = static_cast<double>(p) * freq;
const float c = static_cast<float>(cos(angle));
const float sn = static_cast<float>(sin(angle));
const float x = Load(ptr, off + pair);
const float y = Load(ptr, off + pair + half);
Store(ptr, off + pair, x * c - y * sn);
Store(ptr, off + pair + half, x * sn + y * c);
}
}
template <typename T>
void LaunchRope(cudaStream_t s, Tensor& qs, Tensor& ks, const Tensor& pos,
const RopeArgs& args) {
const int64_t t = qs.shape[0], hq = qs.shape[1], hk = ks.shape[1], d = qs.shape[2];
const int64_t half = args.rotary_dim / 2;
const int64_t n = t * (hq + hk) * half;
if (n == 0) return;
const double base = static_cast<double>(args.base);
const double l3_sf = static_cast<double>(args.llama3_scaling_factor);
const double l3_lo = static_cast<double>(args.llama3_low_freq_factor);
const double l3_hi = static_cast<double>(args.llama3_high_freq_factor);
const double l3_omax = static_cast<double>(args.llama3_orig_max_position);
if (pos.dtype == DType::kI32) {
RopeNeoxKernel<T, int32_t><<<GridFor(n), kBlock, 0, s>>>(
qs.Ptr<T>(), ks.Ptr<T>(), pos.Ptr<int32_t>(), hq, hk, d, half, args.rotary_dim, base,
l3_sf, l3_lo, l3_hi, l3_omax, n);
} else {
RopeNeoxKernel<T, int64_t><<<GridFor(n), kBlock, 0, s>>>(
qs.Ptr<T>(), ks.Ptr<T>(), pos.Ptr<int64_t>(), hq, hk, d, half, args.rotary_dim, base,
l3_sf, l3_lo, l3_hi, l3_omax, n);
}
Check(cudaGetLastError(), "rope_neox launch");
}
void RopeNeoxKernelCuda(Queue& q, Tensor& qs, Tensor& ks, const Tensor& pos,
const RopeArgs& args) {
switch (qs.dtype) {
case DType::kF32: LaunchRope<float>(AsStream(q), qs, ks, pos, args); break;
case DType::kBF16: LaunchRope<__nv_bfloat16>(AsStream(q), qs, ks, pos, args); break;
default: VT_CHECK(false, "cuda rope: unsupported dtype (f32/bf16 only)");
}
}
// ---------------------------------------------------------------------------
// Supplied-cache RoPE. Ported from pinned vLLM base.py:160-252,
// csrc/libtorch_stable/pos_encoding_kernels.cu:8-200, and the 3-axis selection
// in mrope.py:14-187,263-375 @ e24d1b24fe96. This hot kernel performs only
// cache lookup + rotation; YaRN formula construction happens once on the host.
__device__ inline int MropeAxisForPair(int64_t pair, int section_t,
int section_h, int section_w,
bool interleaved) {
if (interleaved) {
if (pair % 3 == 1 && pair <= 3LL * section_h) return 1;
if (pair % 3 == 2 && pair <= 3LL * section_w) return 2;
return 0;
}
if (pair < section_t) return 0;
if (pair < static_cast<int64_t>(section_t) + section_h) return 1;
return 2;
}