-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdeepseek_v4.cpp
More file actions
2421 lines (2313 loc) · 137 KB
/
Copy pathdeepseek_v4.cpp
File metadata and controls
2421 lines (2313 loc) · 137 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
// DeepSeek-V4-Flash forward — W7 ASSEMBLY. The `VT_CHECK(false, "W3-W8 pending")`
// stub is replaced by a REAL `DeepseekV4Model::Forward` that COMPOSES the four
// landed host-reference primitive stacks (W3 DSA Lightning-Indexer + 512-wide MLA
// output seams, W4 compressor + fp8_ds_mla KV state, W5 Manifold Hyper-Connections
// + Sinkhorn, W6 sqrtsoftplus/hash MoE + clamped SwiGLU) into an end-to-end logits
// producer on the portable CPU path at a SMALL synthetic config.
//
// ─── HONEST SCOPE (mirrors W3-W6) ───────────────────────────────────────────
// The fixed-config 167B V4 does NOT fit ONE GB10 (156.7 GiB, see deepseek_v4.h)
// and its weights are NOT materialized (W2b residual), so W7 is DERIVED +
// BUILD-VERIFIED: it assembles the interleave + is STRUCTURALLY gated at a tiny
// synthetic shape (test_deepseek_v4_forward.cpp) — NOT a real-checkpoint token
// gate (that is W8, multi-Spark). This does NOT claim V4 "runs" a real model; it
// claims the forward ASSEMBLES + is structurally gated at tiny shape. The device
// kernels (MHC Sinkhorn, DSA indexer/compressor, sqrtsoftplus router, clamped
// SwiGLU — the expert GEMM REUSES the existing NVFP4/FP8 grouped-GEMM) + the real
// e2e are named residuals (W7-device + W8).
//
// ─── INTERLEAVE (grounded, file:line on both sides, @ pin 555967922) ─────────
// vllm/models/deepseek_v4/nvidia/model.py:1080-1148 (DeepseekV4Model.forward) +
// :866-957 (DeepseekV4DecoderLayer.forward):
// embed -> for each layer:
// [first layer] MHC-pre BROADCAST expand [T,H] -> [T,hc,H] (mhc_pre_broadcast,
// :880-897) ; [else] MHC fused-post-pre = MhcPost(prev-ffn-out) + MhcPre(attn)
// 512-wide MLA attn: q(wq_a->q_norm->wq_b) + kv(wkv->kv_norm), dual-theta RoPE,
// DSA indexer->topk->compressor->fp8_ds_mla KV, sink softmax, grouped o-LoRA
// MHC fused-post-pre = MhcPost(attn-out) + MhcPre(ffn) (:934-957)
// MoE: sqrtsoftplus/hash router + shared+routed clamped-SwiGLU experts
// final MhcPost(last-ffn-out) -> hc_head collapse (:1136) -> norm -> lm_head.
//
// Where the tiny-config forward diverges from the 167B structure (documented, not
// silent): (i) the compressor pools a fixed W=2 window rather than the real
// (1+overlap)*compress_ratio window (the state-cache gather addressing is a W7
// device concern, deepseek_v4_compressor.h note); (ii) the MLA value is the full
// decoded latent (the W_UK/W_UV absorption geometry is a shared-MLA-extraction W7
// follow-on); (iii) a single rope_theta is used for all layers (the compressed
// layers' dual compress_rope_theta is a device-RoPE seam); (iv) the fp8_ds_mla
// quant_block == nope_head_dim (one block) at tiny width. Each reuses the SAME
// landed primitive math the device kernels will call.
#include "vllm/model_executor/models/deepseek_v4.h"
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "vllm/model_executor/models/deepseek_v4_compressor.h"
#include "vllm/model_executor/models/deepseek_v4_device.h"
#include "vllm/model_executor/models/deepseek_v4_dsa.h"
#include "vllm/model_executor/models/deepseek_v4_mhc.h"
#include "vllm/model_executor/models/deepseek_v4_moe.h"
#include "vt/dtype.h"
#include "vt/quant.h" // BlockToFloat (Phase-2 expert discriminator, #188)
#include "vt/ops.h" // vt::MatmulBT (auto-dispatches kMatmulBTQuant on block weights)
#include "vt/tensor.h" // vt::Tensor::Contiguous
#include "vt/backend.h" // vt::GetBackend / Backend::Synchronize (device GEMM drain)
namespace vllm {
namespace {
void DumpAct(const char* name, const std::vector<float>& v); // fwd (coherence-debug #188)
using deepseek_v4::ClampedSwiGLU;
using deepseek_v4::CompressorPoolNorm;
using deepseek_v4::CompressorSaveScoreApe;
using deepseek_v4::DsaIndexerLogits;
using deepseek_v4::DsaIndexerWeightFold;
using deepseek_v4::DsaTopkSelect;
using deepseek_v4::Fp8DsMlaDecodeToken;
using deepseek_v4::Fp8DsMlaEncodeToken;
using deepseek_v4::Fp8DsMlaLayout;
using deepseek_v4::HcHeadCollapse;
using deepseek_v4::MakeFp8DsMlaLayout;
using deepseek_v4::MhcPost;
using deepseek_v4::MhcPre;
using deepseek_v4::MhcPreResult;
using deepseek_v4::MoeRouteResult;
using deepseek_v4::SoftmaxWithSink;
using deepseek_v4::SqrtSoftplusRouteTopk;
// ── backend policy: HOST refs (the oracle) OR the W7-device CUDA kernels ───────
// The composition below is written ONCE and run either on the portable host
// references (DeepseekV4ForwardHost, the oracle the device kernels are gated
// against) or on the CUDA kernels through the OpProvider seam
// (DeepseekV4Model::ForwardDevice). Only the four NEW V4 op families
// (MHC / DSA indexer+seams / compressor+fp8_ds_mla / sqrtsoftplus-hash MoE +
// clamped SwiGLU) branch; the small linear projections stay host in both modes
// (in the real device path they REUSE the existing GEMM/MLA/MoE-grouped kernels —
// a documented W7 seam, not re-ported here). device==host at the tiny structural
// shape is the ForwardDevice composition gate (test_cuda_deepseek_v4.cpp).
// ── W2C: keep-quant weight SOURCE ─────────────────────────────────────────────
// When `gguf != nullptr` the big MLA/MoE/lm_head GEMMs consume the keep-quant
// `weights.gguf` OwnedTensor blocks DIRECTLY via vt::MatmulBT (which dispatches to
// the landed CPU kMatmulBTQuant CIQ GEMM for a block-quant weight) — NO per-layer
// f32 tower. The small non-GEMM tensors (norms, sinks, MHC/DSA mixing, ape, the
// hash table, embed) still come from the SMALL `hw` host tower, dequant-f32 exactly
// as our other GGUF models keep them (qwen3_5_gguf_weights.cpp). When `gguf ==
// nullptr` every GEMM reads the f32 `hw` tower (the safetensors/NVFP4 + the tiny
// synthetic structural gate), byte-for-byte the pre-W2C behavior. `device` selects
// the CUDA V4-primitive kernels for the four NEW op families (orthogonal to the
// weight source; the GGUF keep-quant path runs device=false, CPU).
struct V4Backend {
bool device = false;
vt::Queue* q = nullptr;
const DeepseekV4GgufWeights* gguf = nullptr;
// Incremental-decode KV cache (Stage 1). Null = stateless full-recompute (the
// default / --gpu path). When set, AttentionBlock appends each token's per-layer
// `deck` latent to cache.deck[layer] and attends over the full cached KV; the
// query's global position is kv_base + local_t (kv_base = cache.len at the call).
DeepseekV4KvCache* kv = nullptr;
int64_t kv_base = 0;
// Re-scoped Stage 2: collapse the routed-expert per-expert keep-quant matvecs
// into ONE grouped kMatmulBTQuantGrouped launch per {gate,up,down} (fewer host
// launches + higher GB10 occupancy). Default ON for the GGUF keep-quant path;
// `VT_V4_GROUPED_MOE=0` rolls back to the per-expert GemmRowSlice loop. The CPU
// grouped provider loops the same kMatmulBTQuant kernel ⇒ byte-identical.
bool grouped_moe = true;
};
// Drain the queue's stream after a keep-quant GEMM. This host-orchestrated GGUF
// forward reads each GEMM's f32 output vector on the host IMMEDIATELY (the next
// Disp*/glue op runs host-side), but the CUDA kMatmulBTQuant kernel is
// stream-async — so on a device queue the stream must be drained before the host
// touches `out`. On GB10 the GEMM operands are unified-memory views (mmap'd
// keep-quant weight blocks + std::vector activations that the coherent GPU reads
// in place — no H2D/D2H, which keeps the ~91 GiB weights single-copy), so this is
// pure ORDERING, not a transfer. On the CPU queue (the default, coherent path)
// MatmulBT is synchronous and this is a no-op. Mirrors qwen3_5.cpp:746
// (Backend::Synchronize after a device matmul the host then consumes).
inline void SyncDeviceGemm(const V4Backend& be) {
if (be.q != nullptr && be.q->device.type != vt::DeviceType::kCPU)
vt::GetBackend(be.q->device).Synchronize(*be.q);
}
// Stage-2 decode-step profiling (env `VT_V4_PROF`): split GEMM-dispatch vs
// stream-drain so the decode bottleneck is MEASURED, not guessed. Host glue =
// step_time - gemm - sync. Accumulators are process-global; the driver resets
// them per step. Inert unless VT_V4_PROF is set.
namespace prof {
double g_gemm_s = 0.0;
double g_sync_s = 0.0;
inline bool On() {
static const bool e = std::getenv("VT_V4_PROF") != nullptr;
return e;
}
} // namespace prof
// Re-scoped Stage 2: grouped routed-expert MoE GEMM default ON; `VT_V4_GROUPED_MOE=0`
// rolls back to the per-expert GemmRowSlice batch. Read once.
inline bool GroupedMoeEnabled() {
static const bool on = [] {
const char* e = std::getenv("VT_V4_GROUPED_MOE");
return e == nullptr || std::string(e) != "0";
}();
return on;
}
// Brick A (device-resident decode campaign): run the MLA attention QK/softmax-sink/AV
// on the device kernel instead of the host Dot loop. Default OFF — the host path
// stays default until the decode graph (Brick D) proves out. `VT_V4_DEVICE_ATTN=1`.
inline bool DeviceAttnEnabled() {
static const bool on = [] {
const char* e = std::getenv("VT_V4_DEVICE_ATTN");
return e != nullptr && std::string(e) == "1";
}();
return on;
}
// Brick B (device-resident decode campaign): run the MoE/MHC glue on device kernels
// (in place on the unified activations) instead of the host reference. Default OFF
// until the decode graph (Brick D) proves out. `VT_V4_DEVICE_GLUE=1`.
inline bool DeviceGlueEnabled() {
static const bool on = [] {
const char* e = std::getenv("VT_V4_DEVICE_GLUE");
return e != nullptr && std::string(e) == "1";
}();
return on;
}
// True when the GGUF keep-quant glue should run on the in-place device kernels:
// VT_V4_DEVICE_GLUE set + a CUDA queue + the V4 device kernels linked/live.
inline bool GlueDev(const V4Backend& be) {
return be.q != nullptr && be.q->device.type != vt::DeviceType::kCPU && DeviceGlueEnabled() &&
deepseek_v4::V4DeviceKernelsAvailable();
}
// One MatmulBT + (optional device) drain, timed when profiling is on. When
// `defer_sync` is true the stream is NOT drained here — the caller issues a batch
// of independent GEMMs and drains ONCE via DrainDevice before the host reads them
// (Stage 2: amortize the ~66 us cudaStreamSynchronize over many GEMMs). Same
// stream ⇒ the GEMMs are serialized regardless, so the result is byte-identical.
inline void TimedMatmul(const V4Backend& be, bool on_dev, bool defer_sync, vt::Queue& gq,
vt::Tensor& o, const vt::Tensor& a, const vt::Tensor& w) {
const bool do_sync = on_dev && !defer_sync;
if (!prof::On()) {
vt::MatmulBT(gq, o, a, w);
if (do_sync) SyncDeviceGemm(be);
return;
}
const auto t0 = std::chrono::steady_clock::now();
vt::MatmulBT(gq, o, a, w);
const auto t1 = std::chrono::steady_clock::now();
prof::g_gemm_s += std::chrono::duration<double>(t1 - t0).count();
if (do_sync) {
SyncDeviceGemm(be);
const auto t2 = std::chrono::steady_clock::now();
prof::g_sync_s += std::chrono::duration<double>(t2 - t1).count();
}
}
// Explicit one-shot stream drain for a batch of deferred GEMMs (Stage 2). No-op on
// the CPU queue. Timed into the sync bucket when profiling.
inline void DrainDevice(const V4Backend& be) {
if (be.q == nullptr || be.q->device.type == vt::DeviceType::kCPU) return;
if (!prof::On()) {
vt::GetBackend(be.q->device).Synchronize(*be.q);
return;
}
const auto t0 = std::chrono::steady_clock::now();
vt::GetBackend(be.q->device).Synchronize(*be.q);
prof::g_sync_s +=
std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
}
deepseek_v4::MhcPreResult DispMhcPre(const V4Backend& be, const std::vector<float>& residual,
const std::vector<float>& fn,
const std::vector<float>& scale,
const std::vector<float>& base, int64_t hc, int64_t hidden,
float rms_eps, float hc_pre_eps, float hc_sinkhorn_eps,
float hc_post_mult, int64_t iters,
const std::vector<float>& norm_weight, float norm_eps) {
if (be.device)
return deepseek_v4::MhcDevice()->pre(*be.q, residual, fn, scale, base, hc, hidden, rms_eps,
hc_pre_eps, hc_sinkhorn_eps, hc_post_mult, iters,
norm_weight, norm_eps);
// Brick B: in-place device MHC-pre via the PARALLEL MhcPreParallelKernel (one
// block over the H width; the #183 <<<1,1>>> stub regressed decode 10×, so this
// is the real parallel kernel). Near-tie vs host (width-reduction reorder).
if (GlueDev(be)) {
deepseek_v4::MhcPreResult out;
out.pre_mix.resize(static_cast<size_t>(hc));
out.post_mix.resize(static_cast<size_t>(hc));
out.comb_mix.resize(static_cast<size_t>(hc * hc));
out.layer_input.resize(static_cast<size_t>(hidden));
std::vector<float> mix(static_cast<size_t>((2 + hc) * hc)); // pre's intermediate mixes
const bool has_norm = !norm_weight.empty();
deepseek_v4::MhcDevice()->pre_ip(
*be.q, out.pre_mix.data(), out.post_mix.data(), out.comb_mix.data(),
out.layer_input.data(), mix.data(), residual.data(), fn.data(), scale.data(), base.data(),
hc, hidden, rms_eps, hc_pre_eps, hc_sinkhorn_eps, hc_post_mult, iters,
has_norm ? norm_weight.data() : nullptr, has_norm, norm_eps);
SyncDeviceGemm(be);
return out;
}
return MhcPre(residual, fn, scale, base, hc, hidden, rms_eps, hc_pre_eps, hc_sinkhorn_eps,
hc_post_mult, iters, norm_weight, norm_eps);
}
std::vector<float> DispMhcPost(const V4Backend& be, const std::vector<float>& x,
const std::vector<float>& residual,
const std::vector<float>& post_mix,
const std::vector<float>& comb, int64_t hc, int64_t hidden) {
if (be.device) return deepseek_v4::MhcDevice()->post(*be.q, x, residual, post_mix, comb, hc, hidden);
if (GlueDev(be)) { // Brick B: in-place device MHC-post
std::vector<float> out(static_cast<size_t>(hc * hidden));
deepseek_v4::MhcDevice()->post_ip(*be.q, out.data(), x.data(), residual.data(),
post_mix.data(), comb.data(), hc, hidden);
SyncDeviceGemm(be);
return out;
}
return MhcPost(x, residual, post_mix, comb, hc, hidden);
}
std::vector<float> DispHcHead(const V4Backend& be, const std::vector<float>& x,
const std::vector<float>& fn, float scale,
const std::vector<float>& base, int64_t hc, int64_t hidden,
float rms_eps, float hc_eps) {
if (be.device) return deepseek_v4::MhcDevice()->head(*be.q, x, fn, scale, base, hc, hidden, rms_eps, hc_eps);
if (GlueDev(be)) { // Brick B: in-place device hc_head collapse
std::vector<float> out(static_cast<size_t>(hidden));
deepseek_v4::MhcDevice()->head_ip(*be.q, out.data(), x.data(), fn.data(), scale, base.data(),
hc, hidden, rms_eps, hc_eps);
SyncDeviceGemm(be);
return out;
}
return HcHeadCollapse(x, fn, scale, base, hc, hidden, rms_eps, hc_eps);
}
std::vector<float> DispSaveScoreApe(const V4Backend& be, const std::vector<float>& score,
const std::vector<float>& ape,
const std::vector<int64_t>& positions, int64_t T,
int64_t width, int64_t cr) {
if (be.device) return deepseek_v4::CompressorDevice()->save_score_ape(*be.q, score, ape, positions, T, width, cr);
return CompressorSaveScoreApe(score, ape, positions, T, width, cr);
}
std::vector<float> DispPoolNorm(const V4Backend& be, const std::vector<float>& kv,
const std::vector<float>& score,
const std::vector<uint8_t>& valid,
const std::vector<float>& rms_w, float eps, int64_t window,
int64_t hd) {
if (be.device) return deepseek_v4::CompressorDevice()->pool_norm(*be.q, kv, score, valid, rms_w, eps, window, hd);
return CompressorPoolNorm(kv, score, valid, rms_w, eps, window, hd);
}
deepseek_v4::Fp8DsMlaToken DispEncode(const V4Backend& be, const std::vector<float>& head,
const Fp8DsMlaLayout& ly) {
if (be.device) return deepseek_v4::CompressorDevice()->encode(*be.q, head, ly);
return Fp8DsMlaEncodeToken(head, ly);
}
std::vector<float> DispDecode(const V4Backend& be, const deepseek_v4::Fp8DsMlaToken& tok,
const Fp8DsMlaLayout& ly) {
if (be.device) return deepseek_v4::CompressorDevice()->decode(*be.q, tok, ly);
return Fp8DsMlaDecodeToken(tok, ly);
}
std::vector<float> DispWeightFold(const V4Backend& be, const std::vector<float>& wp, int64_t T,
int64_t inh, int64_t ihd) {
if (be.device) return deepseek_v4::DsaDevice()->weight_fold(*be.q, wp, T, inh, ihd);
return DsaIndexerWeightFold(wp, T, inh, ihd);
}
std::vector<float> DispLogits(const V4Backend& be, const std::vector<float>& q,
const std::vector<float>& k, const std::vector<float>& folded,
const std::vector<int64_t>& ws, const std::vector<int64_t>& we,
int64_t T, int64_t nk, int64_t inh, int64_t ihd) {
if (be.device) return deepseek_v4::DsaDevice()->logits(*be.q, q, k, folded, ws, we, T, nk, inh, ihd);
return DsaIndexerLogits(q, k, folded, ws, we, T, nk, inh, ihd);
}
std::vector<int64_t> DispTopk(const V4Backend& be, const std::vector<float>& logits,
const std::vector<int64_t>& ws, const std::vector<int64_t>& we,
int64_t T, int64_t nk, int64_t topk) {
if (be.device) return deepseek_v4::DsaDevice()->topk(*be.q, logits, ws, we, T, nk, topk);
return DsaTopkSelect(logits, ws, we, T, nk, topk);
}
std::vector<float> DispSoftmaxSink(const V4Backend& be, const std::vector<float>& scores,
float sink) {
if (be.device) return deepseek_v4::DsaDevice()->softmax_sink(*be.q, scores, sink);
return SoftmaxWithSink(scores, sink);
}
std::vector<float> DispGroupedOLora(const V4Backend& be, const std::vector<float>& o,
const std::vector<float>& wo_a,
const std::vector<float>& wo_b, int64_t T, int64_t nh,
int64_t hd, int64_t ng, int64_t olr, int64_t H) {
if (be.device) return deepseek_v4::DsaDevice()->grouped_olora(*be.q, o, wo_a, wo_b, T, nh, hd, ng, olr, H);
return deepseek_v4::GroupedOutputLora(o, wo_a, wo_b, T, nh, hd, ng, olr, H);
}
deepseek_v4::MoeRouteResult DispRoute(const V4Backend& be, const std::vector<float>& gating,
int64_t T, int64_t E, int64_t topk,
const std::vector<float>& bias, bool renorm, float scale,
const std::vector<int64_t>& in_tokens,
const std::vector<int32_t>& hashtab, int64_t vocab) {
if (be.device)
return deepseek_v4::MoeDevice()->route(*be.q, gating, T, E, topk, bias, renorm, scale,
in_tokens, hashtab, vocab);
if (GlueDev(be)) { // Brick B: in-place device router (softmax + top-k → expert_ids)
const bool has_bias = !bias.empty();
const bool is_hash = !hashtab.empty() && !in_tokens.empty();
deepseek_v4::MoeRouteResult out;
out.topk_ids.assign(static_cast<size_t>(T * topk), 0);
out.topk_weights.assign(static_cast<size_t>(T * topk), 0.0f);
deepseek_v4::MoeDevice()->route_ip(
*be.q, out.topk_ids.data(), out.topk_weights.data(), gating.data(), T, E, topk,
has_bias ? bias.data() : nullptr, has_bias, is_hash ? in_tokens.data() : nullptr, is_hash,
is_hash ? hashtab.data() : nullptr, vocab, renorm, scale);
SyncDeviceGemm(be);
return out;
}
return SqrtSoftplusRouteTopk(gating, T, E, topk, bias, renorm, scale, in_tokens, hashtab, vocab);
}
std::vector<float> DispClampedSwiGLU(const V4Backend& be, const std::vector<float>& gate_up,
int64_t d, float limit, float alpha, float beta) {
if (be.device) return deepseek_v4::MoeDevice()->clamped_swiglu(*be.q, gate_up, d, limit, alpha, beta);
// Brick B: the GGUF keep-quant path (be.device=false) runs the clamped-SwiGLU on
// the device kernel IN PLACE over the unified activation when VT_V4_DEVICE_GLUE +
// a CUDA queue + the V4 device kernels are live; bit-identical (elementwise).
if (be.q != nullptr && be.q->device.type != vt::DeviceType::kCPU && DeviceGlueEnabled() &&
deepseek_v4::V4DeviceKernelsAvailable()) {
std::vector<float> out(static_cast<size_t>(d));
deepseek_v4::MoeDevice()->clamped_swiglu_ip(*be.q, out.data(), gate_up.data(), d, limit,
alpha, beta);
SyncDeviceGemm(be); // Brick B drains; Brick D will capture instead
return out;
}
return ClampedSwiGLU(gate_up, d, limit, alpha, beta);
}
// ── small portable linear-algebra helpers ────────────────────────────────────
float Dot(const float* a, const float* b, int64_t n) {
float acc = 0.0f;
for (int64_t i = 0; i < n; ++i) acc += a[i] * b[i];
return acc;
}
// y[o] = Σ_i W[o*in + i] * x[i] (W is [out, in] row-major).
std::vector<float> MatVec(const std::vector<float>& w, const float* x, int64_t out,
int64_t in) {
VT_CHECK(static_cast<int64_t>(w.size()) == out * in, "MatVec weight size mismatch");
std::vector<float> y(static_cast<size_t>(out));
for (int64_t o = 0; o < out; ++o) y[static_cast<size_t>(o)] = Dot(&w[o * in], x, in);
return y;
}
// ── W2C keep-quant GEMM: Y[T,N] = X[T,K] @ W[N,K]^T ───────────────────────────
// `wq` (the keep-quant / bf16 OwnedTensor block, [N,K] nk=true as on GGUF disk) is
// consumed IN PLACE via vt::MatmulBT — a block-quant dtype routes to the CPU
// kMatmulBTQuant CIQ GEMM (cpu_quant_gemm.cpp, quantizes the activation once then
// integer vec_dot per output, weights never expanded); a bf16 dtype (the expand
// oracle) routes to the elementwise MatmulBT. When `wq` is null/absent (host
// source) it falls back to the per-row f32 MatVec — BIT-IDENTICAL to the pre-W2C
// host composition. Grounded in qwen3_5.cpp:786-838 (host MatmulBT off an
// OwnedTensor.View()) + vt/ops.cpp:134-171 (block-quant dispatch).
std::vector<float> Gemm(const V4Backend& be, const OwnedTensor* wq,
const std::vector<float>& wf32, const std::vector<float>& x,
int64_t T, int64_t N, int64_t K, bool defer_sync = false) {
if (be.gguf != nullptr && wq != nullptr && !wq->Empty()) {
VT_CHECK(be.q != nullptr, "deepseek-v4 keep-quant GEMM needs a queue");
VT_CHECK(wq->rank == 2 && wq->shape[0] == N && wq->shape[1] == K,
"deepseek-v4 keep-quant GEMM: weight shape mismatch: want [N=" +
std::to_string(N) + ",K=" + std::to_string(K) + "] got [" +
std::to_string(wq->shape[0]) + "," + std::to_string(wq->shape[1]) +
"] rank=" + std::to_string(wq->rank));
std::vector<float> out(static_cast<size_t>(T) * N);
// Only BLOCK-QUANT weights (the dominant-FLOP experts / MLA linears / lm_head,
// IQ2_XXS/IQ3_XXS/Q2_K/Q8_0) go to the device kMatmulBTQuant provider. Small
// ELEMENTWISE weights the loader dequantized to bf16 (e.g. the [256,H] router
// gate) stay on the CPU: the CUDA elementwise kMatmulBT has no (f32-act,
// bf16-weight) combo, and these are negligible FLOPs. Both read the SAME
// unified-memory tensors, so mixing CPU/GPU GEMMs across the host-orchestrated
// forward is free (SyncDeviceGemm orders the device ones before host reads).
vt::Queue cpuq{vt::Device{vt::DeviceType::kCPU, 0}, nullptr};
const bool on_dev =
be.q->device.type != vt::DeviceType::kCPU && vt::IsBlockQuant(wq->dtype);
vt::Queue& gq = on_dev ? *be.q : cpuq;
vt::Tensor a = vt::Tensor::Contiguous(const_cast<float*>(x.data()),
vt::DType::kF32, gq.device, {T, K});
vt::Tensor o =
vt::Tensor::Contiguous(out.data(), vt::DType::kF32, gq.device, {T, N});
vt::Tensor w = wq->View();
// The keep-quant blocks are loaded with the CPU device tag; on a device queue
// they are unified-memory views the GPU reads in place — retag to the chosen
// queue's device so MatmulBTQuant's device-consistency check (ops.cpp:198)
// dispatches to the right provider (mirrors GemmRowSlice's wt.device below).
w.device = gq.device;
TimedMatmul(be, on_dev, defer_sync, gq, o, a, w); // MatmulBT + (device) drain
return out;
}
std::vector<float> out(static_cast<size_t>(T) * N);
for (int64_t t = 0; t < T; ++t) {
const std::vector<float> y = MatVec(wf32, &x[t * K], N, K);
for (int64_t n = 0; n < N; ++n) out[t * N + n] = y[static_cast<size_t>(n)];
}
return out;
}
// Keep-quant GEMM against a ROW-SLICE [row_off, row_off+N) of a stacked block
// weight `w` ([E*out, K] nk=true) — the per-expert (moe_*_exps) / per-group (wo_a)
// slice. Rows are whole blocks (RowSizeBytes), so the offset is a byte offset and
// no block is ever cut (mirrors the loader's OwnGgufQuantBlocks row_offset slice,
// qwen3_5_gguf_weights.cpp:57-101, and the kStackedExpertWeight contract). Returns
// [T,N] f32.
std::vector<float> GemmRowSlice(const V4Backend& be, const OwnedTensor& w,
const std::vector<float>& x, int64_t T, int64_t N,
int64_t K, int64_t row_off, bool defer_sync = false) {
VT_CHECK(be.q != nullptr, "deepseek-v4 keep-quant expert GEMM needs a queue");
VT_CHECK(!w.repacked,
"deepseek-v4 keep-quant expert/group slice requires non-repacked blocks "
"(disable VT_CPU_QUANT_REPACK for the stacked-expert weights)");
VT_CHECK(w.rank == 2 && row_off >= 0 && row_off + N <= w.shape[0] && w.shape[1] == K,
"deepseek-v4 keep-quant expert GEMM: slice out of range");
const size_t row_bytes = vt::RowSizeBytes(w.dtype, K);
std::vector<float> out(static_cast<size_t>(T) * N);
// Stacked expert/group slices are always block-quant → the device provider; the
// block-quant guard mirrors Gemm so a stray elementwise weight would fall to CPU
// rather than hit the CUDA kMatmulBT's missing (f32-act, bf16-weight) combo.
vt::Queue cpuq{vt::Device{vt::DeviceType::kCPU, 0}, nullptr};
const bool on_dev =
be.q->device.type != vt::DeviceType::kCPU && vt::IsBlockQuant(w.dtype);
vt::Queue& gq = on_dev ? *be.q : cpuq;
vt::Tensor a = vt::Tensor::Contiguous(const_cast<float*>(x.data()), vt::DType::kF32,
gq.device, {T, K});
vt::Tensor o =
vt::Tensor::Contiguous(out.data(), vt::DType::kF32, gq.device, {T, N});
vt::Tensor wt;
wt.data = const_cast<uint8_t*>(w.bytes.data()) +
static_cast<size_t>(row_off) * row_bytes;
wt.dtype = w.dtype;
wt.device = gq.device;
wt.rank = 2;
wt.shape[0] = N;
wt.shape[1] = K;
wt.stride[0] = K; // inert for a block-quant weight; correct for the bf16 oracle
wt.stride[1] = 1;
TimedMatmul(be, on_dev, defer_sync, gq, o, a, wt); // MatmulBT + (device) drain
return out;
}
// Grouped OUTPUT-LoRA on the keep-quant tower (the GGUF mirror of
// deepseek_v4::GroupedOutputLora): z[t, g*olr+d] = Σ_r wo_a[g,d,r]·o[t,g,r]
// (per-group block-diagonal, so one row-slice quant GEMM per group), then
// out[t] = wo_b @ z. wo_a keep-quant [ng*olr, in_per_group], wo_b keep-quant
// [H, ng*olr]. o_proj.py:58-73.
std::vector<float> GroupedOutputLoraGguf(const V4Backend& be, const OwnedTensor& wo_a,
const OwnedTensor& wo_b,
const std::vector<float>& o, int64_t T,
int64_t nh, int64_t hd, int64_t ng,
int64_t olr, int64_t H) {
VT_CHECK(ng > 0 && nh % ng == 0, "grouped o-LoRA: n_heads % n_groups != 0");
const int64_t ipg = nh * hd / ng; // in_per_group
const int64_t z_dim = ng * olr;
std::vector<float> z(static_cast<size_t>(T) * z_dim);
// Stage 2: the `ng` per-group wo_a GEMMs are independent → separate input/output
// buffers per group, issue them all with a deferred drain, DRAIN ONCE, then
// assemble z. Was ng+1 stream drains → 2. Byte-identical (same GEMMs, same stream).
std::vector<std::vector<float>> og(static_cast<size_t>(ng)), zg(static_cast<size_t>(ng));
for (int64_t g = 0; g < ng; ++g) {
og[static_cast<size_t>(g)].resize(static_cast<size_t>(T) * ipg);
for (int64_t t = 0; t < T; ++t)
for (int64_t r = 0; r < ipg; ++r)
og[static_cast<size_t>(g)][t * ipg + r] = o[t * nh * hd + g * ipg + r];
zg[static_cast<size_t>(g)] = GemmRowSlice(be, wo_a, og[static_cast<size_t>(g)], T, olr, ipg,
/*row_off=*/g * olr, /*defer_sync=*/true); // [T,olr]
}
DrainDevice(be);
for (int64_t g = 0; g < ng; ++g)
for (int64_t t = 0; t < T; ++t)
for (int64_t d = 0; d < olr; ++d)
z[t * z_dim + g * olr + d] = zg[static_cast<size_t>(g)][t * olr + d];
return Gemm(be, &wo_b, /*wf32=*/{}, z, T, H, z_dim); // [T,H] (final; drains normally)
}
// Grouped keep-quant expert GEMM (re-scoped Stage 2): out[P,N] where
// out[p,:] = act[p,:] · weight[expert_ids[p]] (the [e*N,+N) block row-slice of the
// stacked expert weight). ONE vt::MatmulBTQuantGrouped launch replaces P per-expert
// GemmRowSlice matvecs. `act` is [P*K] row-major. Weight retagged to the queue
// device (unified view). Drains before returning (eids is a local buffer the async
// kernel reads, so it must not be deferred past this call). Numerically identical
// to the per-expert path (the grouped kernel is the same integer-dot core, and the
// CPU provider literally loops kMatmulBTQuant per group).
std::vector<float> GemmGroupedExpertsKq(const V4Backend& be, const OwnedTensor& weight,
const std::vector<float>& act,
const std::vector<int32_t>& expert_ids,
int64_t P, int64_t N, int64_t K) {
VT_CHECK(be.q != nullptr, "deepseek-v4 grouped expert GEMM needs a queue");
VT_CHECK(!weight.repacked,
"deepseek-v4 grouped expert GEMM requires non-repacked stacked blocks");
VT_CHECK(vt::IsBlockQuant(weight.dtype),
"deepseek-v4 grouped expert GEMM requires a block-quant stacked weight");
VT_CHECK(static_cast<int64_t>(act.size()) == P * K, "grouped expert GEMM: act size mismatch");
VT_CHECK(static_cast<int64_t>(expert_ids.size()) == P, "grouped expert GEMM: expert_ids size");
const bool on_dev = be.q->device.type != vt::DeviceType::kCPU;
vt::Queue cpuq{vt::Device{vt::DeviceType::kCPU, 0}, nullptr};
vt::Queue& gq = on_dev ? *be.q : cpuq;
std::vector<float> out(static_cast<size_t>(P) * N);
std::vector<int32_t> eids = expert_ids; // stable buffer for the (unified) tensor
vt::Tensor a = vt::Tensor::Contiguous(const_cast<float*>(act.data()), vt::DType::kF32,
gq.device, {P, K});
vt::Tensor o = vt::Tensor::Contiguous(out.data(), vt::DType::kF32, gq.device, {P, N});
vt::Tensor eid =
vt::Tensor::Contiguous(eids.data(), vt::DType::kI32, gq.device, {P});
vt::Tensor w = weight.View();
w.device = gq.device;
vt::MatmulBTQuantGrouped(gq, o, a, w, eid);
if (on_dev) SyncDeviceGemm(be); // drain before eids/out leave scope
return out;
}
// Weighted RMSNorm (the standard DeepSeek/vLLM RMSNorm).
std::vector<float> RmsNorm(const std::vector<float>& x, const std::vector<float>& w,
float eps) {
const int64_t n = static_cast<int64_t>(x.size());
double ss = 0.0;
for (int64_t i = 0; i < n; ++i) ss += static_cast<double>(x[i]) * x[i];
const float r = 1.0f / std::sqrt(static_cast<float>(ss / static_cast<double>(n)) + eps);
std::vector<float> y(static_cast<size_t>(n));
for (int64_t i = 0; i < n; ++i)
y[static_cast<size_t>(i)] = x[static_cast<size_t>(i)] * r * w[static_cast<size_t>(i)];
return y;
}
// Decoupled NeoX-free pairwise RoPE over an `r`-wide (r even) rope subvector
// (common/rope.py deepseek_yarn, mscale disabled — the tiny forward uses a single
// theta; the compressed-layer dual compress_rope_theta is a device-RoPE seam).
// Per-layer YaRN RoPE over the last `r` dims of a head (GPT-J adjacent pairs). The
// REAL DeepSeek-V4 uses a DUAL rope: dense layers (compress_ratio==0) rotate with
// base=rope_theta, freq_scale=1, ext_factor=0 (== RopeInplace); COMPRESSED layers
// (41 of 43) rotate with base=compress_rope_theta (160000), freq_scale=1/factor
// (1/16) YaRN interpolation + the beta_fast/beta_slow correction-dim ramp. The net
// magnitude scale is 1 (ds4 cancels the yarn mscale explicitly). 1:1 port of ds4
// `ds4.c:rope_tail_ext_inplace` (+ `rope_yarn_corr_dim`/`rope_yarn_ramp`). Getting
// this wrong scrambles the rope half of q·k on every compressed layer → the model
// loses positional/context structure (degenerate repetition).
double YarnCorrDim(int64_t n_dims, int64_t n_ctx_orig, double beta, double base) {
return static_cast<double>(n_dims) *
std::log(static_cast<double>(n_ctx_orig) / (beta * 2.0 * M_PI)) /
(2.0 * std::log(base));
}
void RopeInplaceLayer(float* v, int64_t r, int64_t pos, double base, double freq_scale,
double ext_factor, int64_t n_ctx_orig, double beta_fast,
double beta_slow, bool inverse = false) {
const double theta_scale = std::pow(base, -2.0 / static_cast<double>(r));
const double sin_sign = inverse ? -1.0 : 1.0; // inverse rope un-rotates (ds4 sin_sign)
double corr_lo = 0.0, corr_hi = 0.0;
if (ext_factor != 0.0) {
corr_lo = std::max(0.0, std::floor(YarnCorrDim(r, n_ctx_orig, beta_fast, base)));
corr_hi = std::min(static_cast<double>(r - 1),
std::ceil(YarnCorrDim(r, n_ctx_orig, beta_slow, base)));
}
double theta_extrap = static_cast<double>(pos);
for (int64_t i = 0; i < r; i += 2) {
const double theta_interp = freq_scale * theta_extrap;
double theta = theta_interp;
if (ext_factor != 0.0) {
const double y = (static_cast<double>(i / 2) - corr_lo) / std::max(0.001, corr_hi - corr_lo);
const double ramp = (1.0 - std::min(1.0, std::max(0.0, y))) * ext_factor;
theta = theta_interp * (1.0 - ramp) + theta_extrap * ramp;
}
const float c = static_cast<float>(std::cos(theta));
const float s = static_cast<float>(sin_sign * std::sin(theta));
const float x0 = v[i], x1 = v[i + 1];
v[i] = x0 * c - x1 * s;
v[i + 1] = x0 * s + x1 * c;
theta_extrap *= theta_scale;
}
}
std::vector<float> Slice(const std::vector<float>& v, int64_t off, int64_t len) {
return std::vector<float>(v.begin() + off, v.begin() + off + len);
}
// ── 512-wide MLA attention block (W3 + W4 primitives) : [T,H] -> [T,H] ────────
std::vector<float> AttentionBlock(const DeepseekV4LayerHostWeights& L,
const DeepseekV4GgufLayerWeights* Lq,
const DeepseekV4Params& p,
const std::vector<float>& x,
const std::vector<int32_t>& positions, int64_t layer,
V4Miswire miswire, V4ForwardTrace* trace,
const V4Backend& be) {
const int64_t T = static_cast<int64_t>(positions.size());
const int64_t H = p.hidden_size;
const int64_t nh = p.num_attention_heads;
const int64_t hd = p.head_dim;
const int64_t rope = p.qk_rope_head_dim;
const int64_t nope = hd - rope;
const int64_t qlr = p.q_lora_rank;
const float eps = p.rms_norm_eps;
const float scale = 1.0f / std::sqrt(static_cast<float>(hd));
// DSA sparse path (the compressor's separate compressed-KV cache + the indexer's
// learned top-k token selection) is implemented only at the COLLAPSED synthetic
// geometry, where the compressor projects to `head_dim`. At the REAL DeepSeek-V4
// geometry the compressor projects to `comp_width = 2*head_dim` (ds4
// `ds4.c:5016-5021`: `coff=2` for `compress_ratio==4`), so the tiny compressor/
// indexer code does not apply. The real keep-quant run therefore uses DENSE MLA —
// which is EXACT, not an approximation, whenever `seq_len <= index_topk` (=512):
// the indexer cannot select more tokens than exist, so top-k over ≤512 tokens IS
// the full causal set, and no raw row has yet been evicted into the compressed
// cache. Every short single-Spark step satisfies this. The full real-geometry DSA
// sparse path (compressor cache + indexer selection, for contexts > 512) is a
// NAMED residual. The host/device synthetic path (be.gguf==nullptr) keeps
// exercising the compressor/indexer primitives at their gated tiny shape.
const bool dsa_dense = (be.gguf != nullptr);
const bool is_indexer = p.has_indexer(layer) && !dsa_dense;
const bool is_comp = p.has_compressor(layer) && !dsa_dense;
// 1. q [T,nh,hd] and raw kv latent [T,hd] (num_key_value_heads=1 MLA). The MLA
// linears (wq_a, wq_b, wkv) run the keep-quant GEMM (Gemm) — the whole batch
// at once — then the per-token RMSNorm(q_norm/kv_norm) + per-head RoPE.
std::vector<float> qa = Gemm(be, Lq != nullptr ? &Lq->wq_a : nullptr, L.wq_a, x, T, qlr, H);
for (int64_t t = 0; t < T; ++t) {
const std::vector<float> n = RmsNorm(Slice(qa, t * qlr, qlr), L.q_norm_weight, eps);
for (int64_t i = 0; i < qlr; ++i) qa[t * qlr + i] = n[static_cast<size_t>(i)];
}
// Per-layer DUAL RoPE (ds4 layer_rope_freq_base/scale): compressed layers use
// base=compress_rope_theta + 1/factor YaRN interpolation; dense layers plain.
const bool rope_compressed = p.has_compressor(layer);
const double rope_base = rope_compressed ? p.compress_rope_theta : p.rope_theta;
const double rope_fscale =
(rope_compressed && p.rope_scale_factor > 1.0) ? 1.0 / p.rope_scale_factor : 1.0;
const double rope_ext =
(rope_compressed && p.rope_scale_factor > 1.0) ? 1.0 : 0.0;
auto rope_layer = [&](float* v, int64_t pos) {
RopeInplaceLayer(v, rope, pos, rope_base, rope_fscale, rope_ext, p.rope_orig_ctx,
p.rope_beta_fast, p.rope_beta_slow);
};
std::vector<float> q =
Gemm(be, Lq != nullptr ? &Lq->wq_b : nullptr, L.wq_b, qa, T, nh * hd, qlr);
// Per-head query RMS-norm (ds4 head_rms_norm_inplace, AFTER wq_b, BEFORE RoPE) — the
// MLA query normalization our forward previously omitted (#188: q was rel-L2 ~0.96
// vs ds4 at L00 with a bit-exact input; the KV latent already has its attn_kv_a_norm).
DeepseekV4QHeadRmsNormInplace(q, T * nh, hd, eps);
for (int64_t t = 0; t < T; ++t)
for (int64_t h = 0; h < nh; ++h)
rope_layer(&q[t * nh * hd + h * hd + nope], positions[static_cast<size_t>(t)]);
if (std::getenv("VT_DUMP_ACT") != nullptr && (layer <= 5 || (layer >= 28 && layer <= 34))) {
char nm[64]; std::snprintf(nm, sizeof(nm), "ours_q_L%02lld", static_cast<long long>(layer));
DumpAct(nm, Slice(q, 0, nh * hd)); // #188 q operand (post-proj+rope), t=0
}
std::vector<float> kraw = Gemm(be, Lq != nullptr ? &Lq->wkv : nullptr, L.wkv, x, T, hd, H);
for (int64_t t = 0; t < T; ++t) {
std::vector<float> kv = RmsNorm(Slice(kraw, t * hd, hd), L.kv_norm_weight, eps);
rope_layer(&kv[nope], positions[static_cast<size_t>(t)]);
for (int64_t d = 0; d < hd; ++d) kraw[t * hd + d] = kv[static_cast<size_t>(d)];
}
// 2. compressor (compressor layers): softmax-window POOL + save-time APE + RMSNorm
// into the cached latent (deepseek_v4_compressor.h : CompressorSaveScoreApe /
// CompressorPoolNorm). Non-compressor layers cache the raw latent directly.
std::vector<float> latent = kraw;
if (is_comp) {
const int64_t cr = p.compress_ratio(layer);
const int64_t win = 2; // tiny pooling window (device gather addressing = W7 seam)
// compressor pool-score projection (keep-quant comp_wgate) : [T,H] -> [T,hd].
std::vector<float> score =
Gemm(be, Lq != nullptr ? &Lq->comp_wgate : nullptr, L.comp_wgate, x, T, hd, H);
std::vector<int64_t> pos64(positions.begin(), positions.end());
score = DispSaveScoreApe(be, score, L.comp_ape, pos64, T, hd, cr);
for (int64_t t = 0; t < T; ++t) {
std::vector<float> kvwin(static_cast<size_t>(win) * hd, 0.0f);
std::vector<float> scwin(static_cast<size_t>(win) * hd, 0.0f);
std::vector<uint8_t> valid(static_cast<size_t>(win), 0);
for (int64_t i = 0; i < win; ++i) {
const int64_t row = t - (win - 1) + i;
if (row < 0) continue;
valid[static_cast<size_t>(i)] = 1;
for (int64_t d = 0; d < hd; ++d) {
kvwin[i * hd + d] = kraw[row * hd + d];
scwin[i * hd + d] = score[row * hd + d];
}
}
std::vector<float> comp =
DispPoolNorm(be, kvwin, scwin, valid, L.comp_norm_weight, eps, win, hd);
for (int64_t d = 0; d < hd; ++d) latent[t * hd + d] = comp[static_cast<size_t>(d)];
}
if (trace != nullptr) trace->layer_compressor_ran[static_cast<size_t>(layer)] = 1;
}
// 3. KV state. The synthetic path round-trips through the fp8_ds_mla layout to
// EXERCISE the paged-cache encoding (W4). The REAL keep-quant run uses the
// latent DIRECTLY: MakeFp8DsMlaLayout quantizes the whole `nope` span (448 at
// the real geometry) as ONE fp8 block — a single scale over 448 values, whose
// dynamic-range loss corrupts the latent and degrades generation. Skipping it
// is strictly MORE faithful (ds4 keeps per-sub-block KV-cache scales; matching
// that block layout is a named residual). Position-precision for attention is
// unaffected (dense recompute; no paged cache in the stateless run).
std::vector<float> deck;
if (dsa_dense) {
deck = latent;
} else {
const Fp8DsMlaLayout ly = MakeFp8DsMlaLayout(nope, rope, /*quant_block=*/nope);
deck.resize(static_cast<size_t>(T) * hd);
for (int64_t t = 0; t < T; ++t) {
const auto tok = DispEncode(be, Slice(latent, t * hd, hd), ly);
const std::vector<float> dec = DispDecode(be, tok, ly);
for (int64_t d = 0; d < hd; ++d) deck[t * hd + d] = dec[static_cast<size_t>(d)];
}
}
if (std::getenv("VT_DUMP_ACT") != nullptr && (layer <= 5 || (layer >= 28 && layer <= 34))) {
char nm[64]; std::snprintf(nm, sizeof(nm), "ours_kv_L%02lld", static_cast<long long>(layer));
DumpAct(nm, Slice(deck, 0, hd)); // #188 kv latent operand (deck), t=0
}
// 3b. KV CACHE (Stage 1, incremental decode). When a cache is bound, APPEND this
// call's T new `deck` latents to the layer's cache and attend over the FULL
// cached KV (global positions 0..kv_base+T-1). `deck[t]` depends only on
// token t and its position, so the cached value equals the recomputed one →
// incremental decode is token-identical to full-recompute. Null cache = the
// stateless path (kv_keys == the local `deck`, base 0).
const std::vector<float>* kv_keys = &deck;
int64_t kv_base = 0;
int64_t n_keys = T;
if (be.kv != nullptr) {
VT_CHECK(!is_indexer && !is_comp,
"kv-cache incremental decode requires dense MLA (no indexer/compressor)");
VT_CHECK(be.kv->head_dim == hd, "kv cache head_dim mismatch");
std::vector<float>& lc = be.kv->deck[static_cast<size_t>(layer)];
kv_base = be.kv_base;
VT_CHECK(static_cast<int64_t>(lc.size()) == kv_base * hd,
"kv cache length mismatch (layer cache out of sync with kv_base)");
lc.insert(lc.end(), deck.begin(), deck.end()); // append the T new latents
kv_keys = &lc;
n_keys = kv_base + T;
}
(void)n_keys;
// 4. selection: DSA Lightning-Indexer top-k on indexer layers, else dense causal
// over GLOBAL positions [0..kv_base+t] (kv_base==0 in the stateless path).
std::vector<std::vector<int64_t>> sel(static_cast<size_t>(T));
if (is_indexer) {
const int64_t inh = p.index_n_heads, ihd = p.index_head_dim, itopk = p.index_topk;
// indexer q/k projections keep-quant (idx_wq_b / indexer_compressor_kv); the
// weights_proj (idx_wproj) is a small V role and stays f32 (host).
const std::vector<float> iq =
Gemm(be, Lq != nullptr ? &Lq->idx_wq_b : nullptr, L.idx_wq, x, T, inh * ihd, H);
const std::vector<float> ik =
Gemm(be, Lq != nullptr ? &Lq->idx_comp_wkv : nullptr, L.idx_wk, x, T, ihd, H);
const std::vector<float> wproj = Gemm(be, nullptr, L.idx_wproj, x, T, inh, H);
const std::vector<float> folded = DispWeightFold(be, wproj, T, inh, ihd);
std::vector<int64_t> ws(static_cast<size_t>(T)), we(static_cast<size_t>(T));
for (int64_t t = 0; t < T; ++t) {
ws[static_cast<size_t>(t)] = 0;
we[static_cast<size_t>(t)] = t + 1; // causal candidate window
}
const std::vector<float> logits =
DispLogits(be, iq, ik, folded, ws, we, T, T, inh, ihd);
const std::vector<int64_t> topk = DispTopk(be, logits, ws, we, T, T, itopk);
for (int64_t t = 0; t < T; ++t)
for (int64_t j = 0; j < itopk; ++j) {
const int64_t s = topk[t * itopk + j];
if (s >= 0) sel[static_cast<size_t>(t)].push_back(s);
}
if (trace != nullptr) {
trace->layer_is_indexer[static_cast<size_t>(layer)] = 1;
trace->layer_indexer_selected[static_cast<size_t>(layer)] =
T > 0 ? static_cast<int>(sel[static_cast<size_t>(T - 1)].size()) : 0;
}
} else {
for (int64_t t = 0; t < T; ++t) {
const int64_t g = kv_base + t; // this query's GLOBAL position
for (int64_t s = 0; s <= g; ++s) sel[static_cast<size_t>(t)].push_back(s);
}
}
// 5. attention with per-head sink softmax; key = value = the cached latent
// (kv_keys, GLOBAL-indexed) — the decoded MLA latent (W3 seams). Brick A:
// when VT_V4_DEVICE_ATTN + a CUDA queue + the V4 device kernels are live, this
// runs on the device kernel over the unified KV cache (dense-causal only, no
// indexer); else the host Dot loop. The device kernel preserves the host
// accumulation order (bit-identical target); the caller drains after it.
std::vector<float> o(static_cast<size_t>(T) * nh * hd, 0.0f);
const float kNegInf = -std::numeric_limits<float>::infinity();
const bool dev_attn = be.q != nullptr && be.q->device.type != vt::DeviceType::kCPU &&
!is_indexer && DeviceAttnEnabled() &&
deepseek_v4::V4DeviceKernelsAvailable();
if (dev_attn) {
// kv_keys holds the cached deck [n_keys_total, hd]; sel is dense-causal, so the
// device kernel derives it from kv_base+t (no per-key index list needed).
deepseek_v4::DsaDevice()->decode_attn(
*be.q, o.data(), q.data(), kv_keys->data(), L.attn_sink.data(), nh, hd, kv_base, T,
scale, /*no_sink=*/miswire == V4Miswire::kNoAttnSink);
SyncDeviceGemm(be); // Brick A drains; Brick D will capture instead
} else {
for (int64_t t = 0; t < T; ++t) {
const std::vector<int64_t>& S = sel[static_cast<size_t>(t)];
for (int64_t h = 0; h < nh; ++h) {
std::vector<float> sc(S.size());
const float* qh = &q[(t * nh + h) * hd];
for (size_t si = 0; si < S.size(); ++si)
sc[si] = Dot(qh, &(*kv_keys)[S[si] * hd], hd) * scale;
const float sink = (miswire == V4Miswire::kNoAttnSink)
? kNegInf
: L.attn_sink[static_cast<size_t>(h)];
const std::vector<float> probs = DispSoftmaxSink(be, sc, sink);
float* oh = &o[(t * nh + h) * hd];
for (size_t si = 0; si < S.size(); ++si) {
const float w = probs[si];
const float* v = &(*kv_keys)[S[si] * hd];
for (int64_t d = 0; d < hd; ++d) oh[d] += w * v[d];
}
}
}
}
// 5b. INVERSE RoPE on the attention output heads (ds4 `rope_tail_layer_inplace(
// heads, ..., inverse=true)`, AFTER attention, BEFORE the o-proj). The value =
// the rope-rotated latent, so the position rotation must be undone on the output
// before the o-LoRA. IDENTITY at pos 0 (so it is invisible on the pos-0 single-
// token gate) but load-bearing for multi-token generation (pos>0). #188.
for (int64_t t = 0; t < T; ++t)
for (int64_t h = 0; h < nh; ++h)
RopeInplaceLayer(&o[(t * nh + h) * hd + nope], rope, positions[static_cast<size_t>(t)],
rope_base, rope_fscale, rope_ext, p.rope_orig_ctx, p.rope_beta_fast,
p.rope_beta_slow, /*inverse=*/true);
// 6. grouped OUTPUT-LoRA (W3) : [T,nh,hd] -> [T,H]. Keep-quant wo_a/wo_b on the
// GGUF source; the host/device-synthetic path keeps the f32 primitive.
if (be.gguf != nullptr && Lq != nullptr) {
return GroupedOutputLoraGguf(be, Lq->wo_a, Lq->wo_b, o, T, nh, hd, p.o_groups,
p.o_lora_rank, H);
}
return DispGroupedOLora(be, o, L.wo_a, L.wo_b, T, nh, hd, p.o_groups, p.o_lora_rank, H);
}
// ── DeepSeek-V4 MoE block (W6 primitives) : [T,H] -> [T,H] ────────────────────
std::vector<float> MoeBlock(const DeepseekV4LayerHostWeights& L,
const DeepseekV4GgufLayerWeights* Lq,
const DeepseekV4Params& p, const std::vector<float>& x,
const std::vector<int32_t>& token_ids, int64_t layer,
V4Miswire miswire, V4ForwardTrace* trace, const V4Backend& be) {
const int64_t T = static_cast<int64_t>(token_ids.size());
const int64_t H = p.hidden_size;
const int64_t ne = p.n_routed_experts;
const int64_t topk = p.num_experts_per_tok;
const int64_t mi = p.moe_intermediate_size;
const float lim = static_cast<float>(p.swiglu_limit);
const bool cfg_hash = p.is_hash_layer(layer);
const bool hash_route = cfg_hash && miswire != V4Miswire::kAllLayersGated;
const bool kq = be.gguf != nullptr && Lq != nullptr;
// router gating logits [T, ne] (keep-quant moe_gate).
const std::vector<float> gating =
Gemm(be, kq ? &Lq->moe_gate : nullptr, L.gate_weight, x, T, ne, H);
std::vector<int64_t> in_tokens;
std::vector<int32_t> hashtab;
std::vector<float> bias;
if (hash_route) {
in_tokens.assign(token_ids.begin(), token_ids.end());
hashtab = L.tid2eid;
} else {
bias = L.gate_bias; // may be empty (then plain top-k on the unbiased scores)
}
if (std::getenv("VT_DUMP_ACT") != nullptr && layer == 34) { // #188 router logits/bias
DumpAct("ours_gating_L34", std::vector<float>(gating.begin(), gating.begin() + ne));
DumpAct("ours_gatebias_L34", bias.empty() ? std::vector<float>(ne, 0.0f) : bias);
std::fprintf(stderr, " [router L34] logit[33]=%.4f logit[233]=%.4f bias[33]=%.4f bias[233]=%.4f\n",
gating[33], gating[233], bias.empty() ? 0.f : bias[33], bias.empty() ? 0.f : bias[233]);
}
const MoeRouteResult route =
DispRoute(be, gating, T, ne, topk, bias, p.norm_topk_prob,
static_cast<float>(p.routed_scaling_factor), in_tokens, hashtab, p.vocab_size);
if (trace != nullptr) {
trace->layer_is_hash[static_cast<size_t>(layer)] = cfg_hash ? 1 : 0;
trace->layer_hash_routed[static_cast<size_t>(layer)] = hash_route ? 1 : 0;
}
// one clamped-SwiGLU expert on the f32 host tower: w1/w3 [mi,H], w2 [H,mi].
const auto expert_f32 = [&](const float* w1, const float* w3, const float* w2,
const float* xin) -> std::vector<float> {
std::vector<float> gate_up(static_cast<size_t>(2) * mi);
for (int64_t r = 0; r < mi; ++r) {
gate_up[static_cast<size_t>(r)] = Dot(&w1[r * H], xin, H);
gate_up[static_cast<size_t>(mi + r)] = Dot(&w3[r * H], xin, H);
}
const std::vector<float> act = DispClampedSwiGLU(be, gate_up, mi, lim, 1.0f, 0.0f);
std::vector<float> out(static_cast<size_t>(H));
for (int64_t hh = 0; hh < H; ++hh)
out[static_cast<size_t>(hh)] = Dot(&w2[hh * mi], act.data(), mi);
return out;
};
// clamped-SwiGLU host activation from separate gate `g` and up `u` [mi] vectors.
const auto swiglu = [&](const std::vector<float>& g,
const std::vector<float>& u) -> std::vector<float> {
std::vector<float> gate_up(static_cast<size_t>(2) * mi);
for (int64_t r = 0; r < mi; ++r) {
gate_up[static_cast<size_t>(r)] = g[static_cast<size_t>(r)];
gate_up[static_cast<size_t>(mi + r)] = u[static_cast<size_t>(r)];
}
return DispClampedSwiGLU(be, gate_up, mi, lim, 1.0f, 0.0f);
};
std::vector<float> out(static_cast<size_t>(T) * H, 0.0f);
for (int64_t t = 0; t < T; ++t) {
const std::vector<float> x1(x.begin() + t * H, x.begin() + (t + 1) * H);
const bool dbg = std::getenv("VT_DUMP_ACT") != nullptr && t == 0;
if (kq) {
// ── Stage 2: BATCHED expert GEMMs. The shared + `topk` routed experts are
// mutually independent and gate/up feed only their own down, so issue all
// gate+up (deferred, no per-GEMM sync), DRAIN ONCE, host clamped-SwiGLU,
// issue all down (deferred), DRAIN ONCE, then combine. Was ~21 stream
// drains/layer (one per GEMM) → 2. BYTE-IDENTICAL to the per-expert path:
// same GEMMs on the same stream (serialized), only the host drain is
// amortized. Index 0 = shared expert (weight 1); 1..topk = routed.
const int64_t A = 1 + topk;
std::vector<std::vector<float>> g(static_cast<size_t>(A)), u(static_cast<size_t>(A)),
act(static_cast<size_t>(A)), eo(static_cast<size_t>(A));
// The grouped kernel requires a BLOCK-QUANT stacked weight (the keep-quant
// load); the near-tie dequant oracle (kExpandAll) has bf16 expert weights, so
// fall back to the per-expert path there.
const bool grouped = be.grouped_moe && vt::IsBlockQuant(Lq->moe_gate_exps.dtype) &&
vt::IsBlockQuant(Lq->moe_up_exps.dtype) &&
vt::IsBlockQuant(Lq->moe_down_exps.dtype);
// The topk routed expert ids (i32), shared by phases 1 and 3.
std::vector<int32_t> eids(static_cast<size_t>(topk));
for (int64_t j = 0; j < topk; ++j)
eids[static_cast<size_t>(j)] = static_cast<int32_t>(route.topk_ids[t * topk + j]);
// phase 1: gate + up. Shared expert stays a per-expert Gemm; the topk routed
// experts collapse into ONE grouped kMatmulBTQuantGrouped launch each when
// grouped_moe (else the Stage-2 per-expert GemmRowSlice batch).
g[0] = Gemm(be, &Lq->shared_gate, {}, x1, 1, mi, H, /*defer_sync=*/true);
u[0] = Gemm(be, &Lq->shared_up, {}, x1, 1, mi, H, /*defer_sync=*/true);
if (grouped) {
std::vector<float> xrep(static_cast<size_t>(topk) * H); // topk copies of x1
for (int64_t j = 0; j < topk; ++j)
std::copy(x1.begin(), x1.end(), xrep.begin() + j * H);
const std::vector<float> gr =
GemmGroupedExpertsKq(be, Lq->moe_gate_exps, xrep, eids, topk, mi, H);
const std::vector<float> ur =
GemmGroupedExpertsKq(be, Lq->moe_up_exps, xrep, eids, topk, mi, H);
for (int64_t j = 0; j < topk; ++j) {
g[1 + j].assign(gr.begin() + j * mi, gr.begin() + (j + 1) * mi);
u[1 + j].assign(ur.begin() + j * mi, ur.begin() + (j + 1) * mi);
}
} else {
for (int64_t j = 0; j < topk; ++j) {
const int64_t e = route.topk_ids[t * topk + j];
g[1 + j] = GemmRowSlice(be, Lq->moe_gate_exps, x1, 1, mi, H, e * mi, /*defer_sync=*/true);
u[1 + j] = GemmRowSlice(be, Lq->moe_up_exps, x1, 1, mi, H, e * mi, /*defer_sync=*/true);
}
}
DrainDevice(be);