Skip to content

Commit 13fd1ea

Browse files
mudlerclaude
andcommitted
27B: paged dense forward + runner route (M0.8 paged dense path)
Adds the batched/paged 27B dense text forward and wires the runner to it, so the dense (Qwen3.6-27B W4A4) arch runs through the same PAGED engine as the 35B MoE model. CPU-only; the W4A4 GPU GEMM + oracle golden stay GPU-gated. - Qwen3_5DenseModel::Forward (qwen3_5.cpp): batched/paged dense forward with the same signature/structure as Qwen3_5Model::Forward (paged KV cache for full-attn, batched GDN recurrent state for GDN, f32 residual thread). Reuses the 35B GdnBlockPaged/FullAttnBlockPaged + paged machinery VERBATIM via a new RunDenseLayerPaged (copy of RunLayerPaged with DenseMlpBlock in place of MoeBlock). Single-seq ForwardDense kept as the paged==dense reference anchor. - GPUModelRunner: Qwen3_5DenseWeights constructor overload + a {moe,dense} weights pointer pair (was a single MoE reference); execute_model routes to the dense forward when dense_weights_ is set. The MoE fp4 decode-graph fast path stays inert on the dense arch. initialize_kv_cache unchanged (config-driven). - test_qwen27_paged_forward.cpp: 27B analogue of the 35B paged anchor test (paged==dense full-prefill, multi-block, decode-via-KV-cache, GDN-state zeroing), all within tolerance. Full CPU ctest 86/86 green; clean -Werror. - Notes §5 step 3b checked off; deviations recorded in porting-inventory §9. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UJyFKcK62CcR3imhgbiBnW
1 parent cd91fec commit 13fd1ea

8 files changed

Lines changed: 666 additions & 23 deletions

File tree

.agents/porting-inventory.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ implementation) is ported at T0.
131131

132132
| Family | Marquee members | Needs | Tier |
133133
|---|---|---|---|
134-
| **Qwen3.5/3.6 hybrid (incl. MoE)** | `Qwen3_5ForConditionalGeneration` (27B dense-hybrid, **VL multimodal** wrapper — vision_config present), `Qwen3_5MoeForConditionalGeneration` / `qwen35moe` (35B-A3B) | GDN layers ×3 : 1 gated full-attn (qk-norm, partial RoPE 64d, output gate), MoE 256e top-8 + shared expert (35B) / **dense SwiGLU MLP** (27B), GemmaRMSNorm-style `(1+w)` — ✅ `25326fc` (35B forward correctness-grade, **safetensors**; 16/16 greedy on GB10 = M0 exit; GGUF k-quant load M0.10). **27B (co-equal gate):** CPU-first scaffolding started — arch/quant surveyed (`.agents/qwen27b-w4a4-notes.md`: dense hybrid, W4A4, **and a VL wrapper — text path first, ViT deferred**), CPU W4A4 emulation reference + skipping greedy-parity gate landed; dense forward + W4A4 GPU GEMM pending (GPU-gated). serving M1–M3 | **T0 (the gate)** |
134+
| **Qwen3.5/3.6 hybrid (incl. MoE)** | `Qwen3_5ForConditionalGeneration` (27B dense-hybrid, **VL multimodal** wrapper — vision_config present), `Qwen3_5MoeForConditionalGeneration` / `qwen35moe` (35B-A3B) | GDN layers ×3 : 1 gated full-attn (qk-norm, partial RoPE 64d, output gate), MoE 256e top-8 + shared expert (35B) / **dense SwiGLU MLP** (27B), GemmaRMSNorm-style `(1+w)` — ✅ `25326fc` (35B forward correctness-grade, **safetensors**; 16/16 greedy on GB10 = M0 exit; GGUF k-quant load M0.10). **27B (co-equal gate):** CPU-first scaffolding started — arch/quant surveyed (`.agents/qwen27b-w4a4-notes.md`: dense hybrid, W4A4, **and a VL wrapper — text path first, ViT deferred**), CPU W4A4 emulation reference + skipping greedy-parity gate landed; dense loader + single-seq `ForwardDense` + batched PAGED `Qwen3_5DenseModel::Forward` (paged==dense CPU-anchored) + `GPUModelRunner` dense route all landed CPU-green; W4A4 GPU GEMM + oracle golden pending (GPU-gated). serving M1–M3 | **T0 (the gate)** |
135135
| Dense decoders | Llama 3.x, Qwen2/3, Mistral, Gemma 2/3, Phi | GQA + RoPE + SwiGLU + RMSNorm (subset of T0 layer set) | T1 |
136136
| MoE decoders | Mixtral, Qwen3-MoE (30B-A3B), GLM-4-MoE, OLMoE | FusedMoE 🚧 `65788b3` (correctness-grade eager; grouped-GEMM perf M2.2) | T1 |
137137
| Qwen3-Next | `Qwen3NextForCausalLM` | same stack, interleaved-GQA weight layout | T1 |
@@ -288,7 +288,18 @@ Examples: `examples/cli` ✅ (C-API client), `examples/server` ✅ (OpenAI serve
288288
the later GPU step (qwen27b-w4a4-notes.md §5 steps 6-7), not a permanent
289289
deviation. Per-Linear bf16-vs-W4A4 routing is `IsQwen27QuantizedLinear`
290290
(encodes the checkpoint `ignore` list, §3.6). Text path only; the ViT/merger
291-
and MTP head are deferred stubs.
291+
and MTP head are deferred stubs. The batched PAGED 27B forward is
292+
`Qwen3_5DenseModel::Forward` — same signature/structure as the 35B
293+
`Qwen3_5Model::Forward`, reusing the file-local `GdnBlockPaged`/
294+
`FullAttnBlockPaged`/paged machinery VERBATIM via `RunDenseLayerPaged` (the
295+
only delta vs `RunLayerPaged` is `DenseMlpBlock` in place of `MoeBlock`).
296+
Runner deviation: `GPUModelRunner` now carries EITHER arch via a
297+
`{moe,dense}_weights_` pointer pair (was a single `Qwen3_5MoeWeights&`
298+
reference) + a `Qwen3_5DenseWeights` constructor overload; `execute_model`
299+
dispatches to the dense forward when `dense_weights_` is set. `initialize_kv_cache`
300+
is unchanged (config-driven; same hybrid backbone). Full LoadedEngine dense
301+
dispatch (arch-select in `model_loader.cpp`) is a small follow-up, gated behind
302+
the W4A4 GPU GEMM (qwen27b-w4a4-notes.md §5 step 6).
292303
8. **Extension platforms** (T2): Apple Metal and Vulkan backends — upstream has
293304
no equivalent under `vllm/platforms/`; we add them through the mirrored
294305
Platform/AttentionBackend/vt-op seams so they behave as vLLM platforms

.agents/qwen27b-w4a4-notes.md

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,29 @@ pinned upstream checkout `/home/mudler/_git/vllm` @ `e24d1b24`. Cites are
1010
Checkpoint (read-only on dgx):
1111
`~/.cache/huggingface/hub/models--unsloth--Qwen3.6-27B-NVFP4`.
1212

13-
STATUS (2026-07-04): CPU-first correctness path LANDED (§5 steps 1-4). Delivered:
13+
STATUS (2026-07-04): CPU-first correctness path LANDED (§5 steps 1-4b). Delivered:
1414
the CPU W4A4 dequant + activation-quant reference (`nvfp4_emulation.h`,
1515
unit-tested); the dense loader (`LoadQwen3_5Dense``Qwen3_5DenseWeights`,
1616
`qwen3_5_dense.h`/`qwen3_5_dense_weights.cpp`) that routes each Linear bf16 vs
1717
W4A4-materialized-to-bf16 by name (`IsQwen27QuantizedLinear` +
1818
`MaterializeCtNvfp4Bf16Transposed`); the dense TEXT forward
1919
(`Qwen3_5DenseModel::ForwardDense` in `qwen3_5.cpp`, reusing the 35B GDN +
2020
gated-attn + norm helpers with the MoE block swapped for the dense SwiGLU MLP);
21-
and CPU unit tests (`test_qwen27_dense_forward.cpp`: routing + materialization +
22-
finite/deterministic forward + MLP-wired, 4 cases / 280 assertions, CPU-green).
23-
The full CPU ctest suite (83 targets) stays green. What is NOT done and is GPU-gated: the
24-
pip-vLLM oracle greedy golden capture (step 5), the W4A4 matmul kernel wiring
25-
(step 6), and flipping `kW4A4ForwardReady` to close the gate (step 7). §5 is the
26-
ordered plan, GPU steps marked.
21+
the batched PAGED dense forward (`Qwen3_5DenseModel::Forward`, same signature/
22+
structure as the 35B `Qwen3_5Model::Forward` — paged KV cache for full-attn,
23+
batched GDN state for GDN, via a new `RunDenseLayerPaged` reusing the 35B
24+
`GdnBlockPaged`/`FullAttnBlockPaged` VERBATIM) + the runner route (a dense-arch
25+
`GPUModelRunner` constructor overload holding `Qwen3_5DenseWeights` and routing
26+
`execute_model` to the dense forward; the MoE reference member became a
27+
`{moe,dense}_weights_` pointer pair); and CPU unit tests
28+
(`test_qwen27_dense_forward.cpp`: routing + materialization + finite/deterministic
29+
forward + MLP-wired, 4 cases / 280 assertions; `test_qwen27_paged_forward.cpp`:
30+
the paged==dense anchor + multi-block + decode-via-cache + GDN-state-zeroing, 4
31+
cases / 8 assertions, CPU-green). The full CPU ctest suite (86 targets) stays
32+
green. What is NOT done and is GPU-gated: the pip-vLLM oracle greedy golden
33+
capture (step 5), the W4A4 matmul kernel wiring (step 6), and flipping
34+
`kW4A4ForwardReady` to close the gate (step 7). §5 is the ordered plan, GPU steps
35+
marked.
2736

2837
---
2938

@@ -284,17 +293,43 @@ Legend: **[CPU]** doable on the dev box now; **[GPU]** needs the free GB10.
284293
(finite + deterministic logits; MLP-perturbation moves the output). The ViT +
285294
image/video merger + MTP head are DEFERRED (text-first, §0.1) — no stub code,
286295
the loader simply does not request `model.visual.*`/`mtp.*`.
287-
⚠ REMAINING before step 5/7 flips the gate: (a) a paged `Qwen3_5DenseModel`
288-
forward + a runner route (the 35B rides `Qwen3_5Model::Forward` + the runner;
289-
the dense arch needs the analogous paged path OR the runner extended to the
290-
dense weights) — the dense ForwardDense here is the single-sequence reference,
291-
as the 35B's ForwardDense was before its paged refactor; (b) mrope_section
292-
handling for genuine multimodal positions (inert for text).
296+
`ForwardDense` is retained as the single-sequence parity reference (the
297+
paged==dense anchor), exactly as the 35B's `ForwardDense`.
298+
⚠ REMAINING before step 7 flips the gate (all GPU-gated now): (a) the W4A4
299+
matmul kernel (step 6); (b) mrope_section handling for genuine multimodal
300+
positions (inert for text).
301+
302+
3b. **[CPU] ✅ DONE — Paged dense path + runner wiring.**
303+
`Qwen3_5DenseModel::Forward` (in `qwen3_5.cpp`) is the batched/paged 27B text
304+
forward with the SAME signature/structure as `Qwen3_5Model::Forward`: paged KV
305+
cache (`PagedKvCache`) for the full-attn layers + batched GDN recurrent state
306+
(`GdnStateCache`) for the GDN layers + the f32 residual thread, per-layer
307+
`fa_idx`/`gdn_idx` indexing identical to the 35B. It reuses the 35B
308+
`GdnBlockPaged`/`FullAttnBlockPaged` + paged machinery VERBATIM via a new
309+
`RunDenseLayerPaged` (a copy of `RunLayerPaged` with `DenseMlpBlock` in place
310+
of `MoeBlock` and `Qwen3_5DenseLayerWeights` in place of the MoE weights). No
311+
dense CUDA-graph driver (the MoE `Qwen3_5DecodeGraph` is an fp4/CUDA decode
312+
optimization; the dense path runs eager, as its GPU GEMM is step 6). Runner
313+
route: `GPUModelRunner` gained a `Qwen3_5DenseWeights` constructor overload;
314+
its MoE reference member became a `{moe,dense}_weights_` pointer pair, and
315+
`execute_model` routes to `Qwen3_5DenseModel::Forward` when `dense_weights_` is
316+
set (the MoE-only fp4 decode-graph fast path stays inert on the dense arch).
317+
`initialize_kv_cache` is unchanged (config-driven; same hybrid backbone).
318+
CPU-validated by `test_qwen27_paged_forward.cpp` (the 27B analogue of the 35B
319+
`test_qwen35_paged_forward.cpp`): paged==dense full-prefill, multi-block
320+
(block_size<T non-contiguous), decode-via-KV-cache, and GDN-state-zeroing on a
321+
garbage-seeded mamba block — all within tolerance (`max|diff|` 0 on the
322+
zeroing/mixed-batch gate). Deviations recorded in porting-inventory §9.
323+
Full LoadedEngine dense loading (config-arch dispatch in `model_loader.cpp`
324+
`LoadQwen3_5Dense` + a dense `LoadedEngine`/executor path) is NOT wired here —
325+
the executor/engine stack is MoE-typed; that end-to-end plumbing is a small
326+
follow-up once the GPU GEMM (step 6) makes a full 27B run meaningful.
293327

294328
4. **[CPU] ✅ DONE — greedy-parity gate scaffold.**
295329
`test_qwen27_paged_engine.cpp` resolves the `unsloth/Qwen3.6-27B-NVFP4`
296330
snapshot and SKIPS (checkpoint-gated + `kW4A4ForwardReady=false`). Compiles
297-
+ links on CPU; flip the flag when steps 3+6 land.
331+
+ links on CPU; flip the flag when step 6 lands (the paged forward is now
332+
ready — step 3b).
298333

299334
5. **[GPU] Capture the pip-vLLM oracle greedy golden** (AGENTS.md STANDING
300335
DIRECTIVE; gates.md §PROTOCOL). Run pip-vLLM (`~/venvs/vllm-oracle`) on the

include/vllm/model_executor/models/qwen3_5_dense.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <string>
2929
#include <vector>
3030

31+
#include "vllm/model_executor/models/qwen3_5.h" // PagedKvCache, GdnStateCache + v1 attention metadata
3132
#include "vllm/model_executor/models/qwen3_5_weights.h" // OwnedTensor, Gdn/FullAttn weights, TensorResolver
3233
#include "vllm/transformers_utils/hf_config.h"
3334
#include "vt/device.h"
@@ -98,6 +99,26 @@ Qwen3_5DenseWeights LoadQwen3_5Dense(const std::vector<SafetensorsFile>& shards,
9899
// logits [T, vocab] f32 (T = token_ids.size()). CPU or CUDA per `queue`.
99100
class Qwen3_5DenseModel {
100101
public:
102+
// Batched PAGED dense forward — the 27B analogue of Qwen3_5Model::Forward.
103+
// Same signature/structure (paged KV cache for the full-attn layers, batched
104+
// GDN recurrent state for the GDN layers, the f32 residual thread), reusing the
105+
// 35B GDN/FullAttn paged machinery VERBATIM with the dense SwiGLU MLP
106+
// (RunDenseLayerPaged) in place of the MoE block. One PagedKvCache per full-attn
107+
// layer + one GdnStateCache per GDN layer, in layer order. Returns
108+
// [num_actual_tokens, vocab] f32 logits (lm_head applied). Runs on `queue`'s
109+
// device. See qwen3_5.h::Qwen3_5Model::Forward for the metadata contract.
110+
static std::vector<float> Forward(const std::vector<int32_t>& token_ids,
111+
const std::vector<int32_t>& positions,
112+
const v1::CommonAttentionMetadata& attn_meta,
113+
const v1::GDNAttentionMetadata& gdn_meta,
114+
const std::vector<PagedKvCache>& attn_kv,
115+
const std::vector<GdnStateCache>& gdn_state,
116+
const Qwen3_5DenseWeights& weights,
117+
const HfConfig& config, vt::Queue& queue);
118+
119+
// Dense single-sequence reference forward (M0.9 anchor). Runs the whole model
120+
// for a single non-paged sequence and returns logits [T, vocab] f32 (T =
121+
// token_ids.size()). Retained as the paged==dense parity reference.
101122
static std::vector<float> ForwardDense(const std::vector<int32_t>& token_ids,
102123
const std::vector<int32_t>& positions,
103124
const Qwen3_5DenseWeights& weights,

include/vllm/v1/worker/gpu/runner.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include <vector>
6161

6262
#include "vllm/model_executor/models/qwen3_5.h"
63+
#include "vllm/model_executor/models/qwen3_5_dense.h"
6364
#include "vllm/model_executor/models/qwen3_5_weights.h"
6465
#include "vllm/transformers_utils/hf_config.h"
6566
#include "vllm/v1/attention/backend.h"
@@ -125,6 +126,17 @@ class GPUModelRunner final : public ModelRunnerBase {
125126
int max_num_reqs, int max_model_len,
126127
int max_num_batched_tokens);
127128

129+
// DENSE-arch overload (27B, Qwen3_5ForConditionalGeneration / num_experts==0).
130+
// Identical to the MoE constructor except the model runs through the dense
131+
// weights + the paged dense forward (Qwen3_5DenseModel::Forward). The KV-cache
132+
// layout is config-driven (same GDN + full-attn hybrid backbone), so
133+
// initialize_kv_cache is unchanged. `config` and `weights` must outlive the
134+
// runner. The MoE-only fp4 decode-graph fast path stays inert on this arch.
135+
GPUModelRunner(const HfConfig& config, const Qwen3_5DenseWeights& weights,
136+
const KVCacheConfig& kv_cache_config, vt::Queue queue,
137+
int max_num_reqs, int max_model_len,
138+
int max_num_batched_tokens);
139+
128140
// ModelRunnerBase (the MRV2 execute_model / sample_tokens split).
129141
std::optional<ModelRunnerOutput> execute_model(
130142
const SchedulerOutput& scheduler_output) override;
@@ -158,7 +170,11 @@ class GPUModelRunner final : public ModelRunnerBase {
158170
int* num_cols) const;
159171

160172
const HfConfig& config_;
161-
const Qwen3_5MoeWeights& weights_;
173+
// Exactly one of {moe_weights_, dense_weights_} is non-null, selecting the MoE
174+
// (35B) or dense (27B) forward. Held by pointer (not reference) so the single
175+
// runner class carries either arch; both are borrowed (must outlive the runner).
176+
const Qwen3_5MoeWeights* moe_weights_ = nullptr;
177+
const Qwen3_5DenseWeights* dense_weights_ = nullptr;
162178
vt::Queue queue_;
163179
InputBatch input_batch_;
164180
Sampler sampler_;

src/vllm/model_executor/models/qwen3_5.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,44 @@ void RunLayerPaged(Dev d, const Qwen3_5MoeLayerWeights& layer, const HfConfig& c
11651165
hidden = MoeBlock(d, layer.moe, cfg, dh2.t(), T);
11661166
}
11671167

1168+
// Batched PAGED dense decoder layer (27B; notes §5). Identical residual/norm
1169+
// thread + paged attention wiring as RunLayerPaged, but the MoE block is swapped
1170+
// for the dense SwiGLU MLP (DenseMlpBlock) and the layer carries dense weights.
1171+
// The GDN / full-attn paged blocks are the 35B helpers reused VERBATIM. Exactly
1172+
// one of {attn_kv, gdn_state} is non-null (per layer type).
1173+
void RunDenseLayerPaged(Dev d, const Qwen3_5DenseLayerWeights& layer,
1174+
const HfConfig& cfg, DBuf& hidden, DBuf& res,
1175+
const std::vector<int32_t>& positions,
1176+
const CommonAttentionMetadata& attn_meta,
1177+
const GDNAttentionMetadata& gdn_meta,
1178+
const PagedKvCache* attn_kv,
1179+
const GdnStateCache* gdn_state, int64_t T) {
1180+
const int64_t H = cfg.hidden_size;
1181+
const float eps = static_cast<float>(cfg.rms_norm_eps);
1182+
1183+
Tensor dw_in = ResidentWeight(d, layer.input_layernorm, {H});
1184+
DBuf dhn(d, DType::kBF16, {T, H});
1185+
vt::RmsNorm(d.q, dhn.t(), hidden.t(), dw_in, vt::RmsNormArgs{eps, true}, &res.t());
1186+
1187+
DBuf attn = [&] {
1188+
if (layer.is_linear_attention) {
1189+
VT_CHECK(gdn_state != nullptr,
1190+
"paged dense layer: GDN layer needs a GdnStateCache");
1191+
return GdnBlockPaged(d, layer.gdn, cfg, dhn.t(), gdn_meta, *gdn_state, T);
1192+
}
1193+
VT_CHECK(attn_kv != nullptr,
1194+
"paged dense layer: full-attn layer needs a PagedKvCache");
1195+
return FullAttnBlockPaged(d, layer.attn, cfg, dhn.t(), positions, attn_meta,
1196+
*attn_kv, T);
1197+
}();
1198+
1199+
Tensor dw_post = ResidentWeight(d, layer.post_attention_layernorm, {H});
1200+
DBuf dh2(d, DType::kBF16, {T, H});
1201+
vt::RmsNorm(d.q, dh2.t(), attn.t(), dw_post, vt::RmsNormArgs{eps, true}, &res.t());
1202+
1203+
hidden = DenseMlpBlock(d, layer.mlp, cfg, dh2.t(), T);
1204+
}
1205+
11681206
} // namespace
11691207

11701208
// Embed: hidden[T,H] bf16 = embed_tokens[token_ids] (device-resident table).
@@ -1384,6 +1422,74 @@ std::vector<float> Qwen3_5DenseModel::ForwardDense(
13841422
return logits;
13851423
}
13861424

1425+
std::vector<float> Qwen3_5DenseModel::Forward(
1426+
const std::vector<int32_t>& token_ids, const std::vector<int32_t>& positions,
1427+
const CommonAttentionMetadata& attn_meta, const GDNAttentionMetadata& gdn_meta,
1428+
const std::vector<PagedKvCache>& attn_kv,
1429+
const std::vector<GdnStateCache>& gdn_state,
1430+
const Qwen3_5DenseWeights& weights, const HfConfig& config,
1431+
vt::Queue& queue) {
1432+
// Same shape/count contract as Qwen3_5Model::Forward (CheckPagedForward), over
1433+
// the dense weights. One PagedKvCache per full-attn layer, one GdnStateCache
1434+
// per GDN layer, in layer order.
1435+
const int64_t T = static_cast<int64_t>(token_ids.size());
1436+
const int64_t H = config.hidden_size;
1437+
const int64_t vocab = config.vocab_size;
1438+
VT_CHECK(T > 0, "qwen3_5 dense paged forward: empty token_ids");
1439+
VT_CHECK(static_cast<int64_t>(positions.size()) == T,
1440+
"qwen3_5 dense paged forward: positions length must equal token count");
1441+
VT_CHECK(static_cast<int64_t>(weights.layers.size()) == config.num_hidden_layers,
1442+
"qwen3_5 dense paged forward: weights.layers size must equal "
1443+
"num_hidden_layers");
1444+
VT_CHECK(attn_meta.num_actual_tokens == T,
1445+
"qwen3_5 dense paged forward: attn_meta.num_actual_tokens must equal T");
1446+
int64_t n_full = 0, n_gdn = 0;
1447+
for (const auto& l : weights.layers) (l.is_linear_attention ? n_gdn : n_full) += 1;
1448+
VT_CHECK(static_cast<int64_t>(attn_kv.size()) == n_full,
1449+
"qwen3_5 dense paged forward: attn_kv count must equal full-attn layers");
1450+
VT_CHECK(static_cast<int64_t>(gdn_state.size()) == n_gdn,
1451+
"qwen3_5 dense paged forward: gdn_state count must equal GDN layers");
1452+
1453+
Dev d{vt::GetBackend(queue.device.type), queue};
1454+
const float eps = static_cast<float>(config.rms_norm_eps);
1455+
1456+
// Embed: hidden = embed_tokens[token_ids] (bf16, device-resident). res = 0.
1457+
// Text-only: the three mRoPE streams coincide so partial NeoX RoPE degenerates
1458+
// to 1-D RoPE over `positions` (notes §2); the vision tower is DEFERRED.
1459+
Tensor dtab = ResidentWeight(d, weights.embed_tokens, {vocab, H});
1460+
DBuf dids(d, DType::kI32, {T}, token_ids.data());
1461+
DBuf hidden(d, DType::kBF16, {T, H});
1462+
vt::Embedding(d.q, hidden.t(), dtab, dids.t());
1463+
1464+
DBuf res(d, DType::kF32, {T, H});
1465+
res.Zero(d);
1466+
1467+
// N paged decoder layers: full-attn layers read/write attn_kv[fa_idx], GDN
1468+
// layers the persistent gdn_state[gdn_idx] (same layer-order indexing as the
1469+
// 35B paged forward).
1470+
int64_t fa_idx = 0, gdn_idx = 0;
1471+
for (int64_t l = 0; l < config.num_hidden_layers; ++l) {
1472+
const Qwen3_5DenseLayerWeights& layer = weights.layers[static_cast<size_t>(l)];
1473+
const PagedKvCache* kv =
1474+
layer.is_linear_attention ? nullptr : &attn_kv[static_cast<size_t>(fa_idx++)];
1475+
const GdnStateCache* gs =
1476+
layer.is_linear_attention ? &gdn_state[static_cast<size_t>(gdn_idx++)] : nullptr;
1477+
RunDenseLayerPaged(d, layer, config, hidden, res, positions, attn_meta,
1478+
gdn_meta, kv, gs, T);
1479+
}
1480+
1481+
// Final RMSNorm over the fused stream (res += hidden; norm), then lm_head.
1482+
Tensor dfn = ResidentWeight(d, weights.final_norm, {H});
1483+
DBuf dnorm(d, DType::kBF16, {T, H});
1484+
vt::RmsNorm(d.q, dnorm.t(), hidden.t(), dfn, vt::RmsNormArgs{eps, true}, &res.t());
1485+
1486+
// lm_head is unquantized bf16 in the 27B (notes §3.6): the one host Download.
1487+
DBuf dlogits = MatmulF32D(d, dnorm.t(), weights.lm_head);
1488+
std::vector<float> logits(static_cast<size_t>(T) * vocab);
1489+
dlogits.Download(d, logits.data());
1490+
return logits;
1491+
}
1492+
13871493
std::vector<float> Qwen3_5ReplayLayer(const Qwen3_5MoeLayerWeights& layer,
13881494
const HfConfig& config,
13891495
const std::vector<float>& hidden_in,

0 commit comments

Comments
 (0)