Skip to content

the whole-model Vulkan driver serves deltanet hybrids: Qwen3.5-9B prefill at parity with llama.cpp, decode at 0.965x, K-quant deltanet planes native, the SPIR-V emitter's lazy operands - #3954

Merged
borisbat merged 22 commits into
masterfrom
bbatkin/vk-hybrid-ladder
Sep 7, 2026
Merged

the whole-model Vulkan driver serves deltanet hybrids: Qwen3.5-9B prefill at parity with llama.cpp, decode at 0.965x, K-quant deltanet planes native, the SPIR-V emitter's lazy operands#3954
borisbat merged 22 commits into
masterfrom
bbatkin/vk-hybrid-ladder

Conversation

@borisbat

@borisbat borisbat commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Behavior change: on a Vulkan box with the whole-model driver, a dense deltanet hybrid now loads its deltanet planes in the file's K-quant formats (no Q8_0 transcode) and mints a new image; the first load of such a model re-mints.

Why. Qwen3.5-9B on the Vulkan tier ran at 0.05x of llama.cpp's prefill and 0.2x of its decode: the whole-model driver declined every hybrid, and the per-op rails streamed 4 GB of FFN per token through the CPU. Two GPU hangs on the way came from the SPIR-V emitter lowering ?: eagerly.

What changes.

  • The whole-model resident driver serves deltanet hybrids: the token command carries recurrent layers through a fused step over per-layer device state, the window chain prefills them (conv, a sequential scan with the state in registers, the out-norm), and gated attention with partial rotary rides the batch kernels.
  • The cm2 flash-attention tile stamps at head 256 with a gated epilogue; the deltanet GEMMs ride the f16-fed cm2 tiles; the beta/alpha rows live on the device as f16; the scan keeps two state columns per lane.
  • The deltanet planes stay in their file formats on this driver (the loader tags them natively, the plan and arena carry the formats); a K-quant out plane declines the layer; off the f16 feed a K-quant qkv/z pair reads the Q8_K activation form.
  • The window chain keeps every window past the first at the conv taps, and a lone shorter window's ring rows read as zero; the scan's idle lanes read a clamped column; the deltanet state width must be a power of two (the lane grouping's assumption); a gated model takes the cm2 flash tiles at head 256 alone.
  • The dasSpirv emitter lowers ?:, && and || lazily when an operand indexes a buffer (a hoisted temp and a branch); pure operands keep OpSelect.
  • Vulkan storage buffers carry the device-address usage, allocation flag and query on the coopmat2 device alone (the other creators never enable the feature), and the coopmat2 device creator names the integer dot product on the 1.3 struct.
  • The runtime copies a log message before its debug agents run, so a hook on the logging context keeps it whole.

Observable behavior.

  • Qwen3.5-9B UD-Q5_K_XL, 5060 Ti, same-day llama-bench control 2484 / 53.7: pp512 132 -> 2526 (1.02x), tg128 11.1 -> 51.8 (0.965x).
  • test_gpu_resident_hybrid: two fixtures (Q8_0 and Q4_K_M 0.8B) hold the forced-feed bar against the all-CPU chain over four prompt shapes (one window, two, one row past a window, two tokens); the parity harness reads 40/40 token-for-token on the Q8_0 file with the driver armed.
  • A hybrid prompt one row past the window, or shorter than the conv taps, prefilled resident: the conv tail read past the window plane -> the two new cells hold the bar.
  • DASLLAMA_COOPMAT=mm on a hybrid (the coopmat1 device, no device addresses): the buffer creation asked for an unenabled feature -> the same ten cells pass on that device.
  • The 9B window under GPU-assisted validation logs no VUID (was 80 device-address reports and one device-create report) and lands the plain run's logits.
  • qk_rms_cls no longer reads 12.6 MB past its binding (the hang); the kernel-unit suite gates the scan, the beta/alpha GEMM and the flash tile at the 9B's geometry, each cell with a NaN sentinel and a poison control.

Where to look. dasllama_vulkan_prefill.das (rd_pf_recurrent, pf_run's window loop, pf_facm2), dasllama_vulkan_classes.das (DnScan, DnBaGemm, DnTail, FaCm2H256T), dasllama_gpu_resident.das (the deltanet plane formats and declines), dasllama_vulkan_common.das (has_bda), spirv_emit.das (SpirvGlobalIndexScan, lazy_merge_value).

Validation, claims, ledger

Validation

  • Parity harness on the Q4_K_M 0.8B: 19/40 with the driver armed, 36/40 on the CPU alone (freeform near-ties on a 4-bit 0.8B); its instrument is the forced-feed cells, which hold a 6% bar (the K-quant chain's own CPU-vs-device noise sits flat at ~4.5% of the max logit).
  • The hang forensics ran under the Khronos layer's GPU-assisted validation in safe mode; the validated numerics became evidence only after the device-address usage landed.
  • Local only: the hybrid cells under DASLLAMA_COOPMAT=mm (10/10 on the coopmat1 device), the 30B-A3B Q4_K_M row (ours 606 / 63.7 vs llama.cpp 593 / 40.7 on 9/5), the per-role window profiles in followup_vulkan.md item 2, and the Vulkan serving census on the qwen35 rows (the 0.8B and the new 9B UD row, DASLLAMA_PARITY_FULL=1): with the census's new f16 session pass the resident driver's deltanet chain, the tail, the beta/alpha GEMM, the f16 router GEMV and the gated f16 head-256 flash stamp all dispatch; the ungated head-256 stamps wait for an ungated head-256 carrier in the full census, and the gated f32-output twin for an out plane off the f16 feed.
  • The review round: seven checklist audits, the TDD audit, the style audit and two codex rounds on the branch; every real finding is fixed in this diff, the checklist self-review repairs included.
  • The local preflight's lanes on this Windows box: tests-cpp, tests-interp and docs green; tests-jit red on two master files (tests/jit_tests/cross_target_folds.das, wasm_idot_lowering.das) whose spawn opened with a quote beside a redirect, the Windows cmd quote-stripping trap - fixed here, both green after; utils-tests red on the ast-fuzz suite alone, which hardcodes bin/daslang and cannot spawn under the MSVC layout (CI-only, every other utils suite green); tests-aot red on tests/module_cache/test_default_cache_path.das alone, an AOT-host-only red outside this diff (its children print nothing under test_aot; the -jit lane runs the same file green; CI's per-PR AOT compiles tests/language only).
  • The dasllama suites under DASLLAMA_GPU=1 on the 5060 Ti: model-free 72 files green (the one red, test_gpu_tier's "auto not requested" cell, reads the knob I exported for the stocked suite and passes 11/11 in CI shape); stocked 40 files with two reds fixed here - test_chat's render-seam cell byte-compares two chains that split between the device prefill and the CPU continuation under the armed tier, so it now drops the model's device state like the scheduler cells do, and test_exe_smoke's artifact exited with STATUS_DLL_NOT_FOUND because the exe links the producing tree's two runtime DLLs and the test ran it from a temp dir with no such dir on PATH, so the test now runs it with that daslang's bin dir on PATH.

Claims - stated, not tested

  • The four columns per lane scan form measured 15.3 ms against 10.9 (64 workgroups starve 36 SMs) and was reverted; the two-column form is what ships.
  • The tier's kernels assume a 32-lane subgroup (followup_vulkan.md item 40e); a wave64 device runs those arms unmeasured.
  • A debug agent installed from a context that dies before the process (a dastest file's context) leaves a dangling adapter in the runtime's agent list; the logger hook's probe therefore runs in a child process.
  • A gated model at head 64 or 128 now takes the batch attention kernels (the gated cm2 flash stamps exist at head 256 alone); no such model is stocked, so the pick is read, not run. A deltanet state width that is not a power of two (96 passes the old % 32 guard) now declines the resident driver and asserts on the per-op chain; no such model exists.

Not done

  • followup_vulkan.md item 2: the k5/k6 cm2 tiles run the deltanet GEMMs slower than the q8 tile (5 ms of the 9B window); the fused add+rms+requant twin stays off on hybrids; the scheduler's head-of-line wait; per-session device slots for the deltanet step.
  • followup_vulkan.md item 40: the coopmat load stride VUID on q8_batch_mm_a_cls, debug-utils names on class pipelines, the validation-layer exit wedge, the subgroup-32 assumption.
  • followup_vulkan.md item 41: the kernel-unit file's device-absent cells feint instead of skipping.
  • The board cell for the hybrid route waits for decode parity.

Copilot AI lite review requested due to automatic review settings September 6, 2026 21:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It introduces broad, high-impact changes across Vulkan serving, SPIR-V emission semantics, runtime logging, and extensive test scaffolding that warrant final human validation of end-to-end correctness and GPU safety.

Pull request overview

This PR expands daScript’s Vulkan whole-model (GPU-resident) execution path to serve Qwen3.5-class DeltaNet hybrid models efficiently and safely, while tightening correctness and validation in the SPIR-V emitter, Vulkan device/buffer setup, and runtime logging hooks.

Changes:

  • Enable the Vulkan whole-model resident driver to serve DeltaNet hybrids end-to-end (prefill + decode), including gated attention / partial rotary and device-side recurrent state handling.
  • Keep DeltaNet planes in their on-disk K-quant formats when the whole-model driver will be attempted (avoids Q8_0 transcode) and propagate that choice through residency/image identity.
  • Fix/guard GPU correctness hazards: lazy lowering for ?:, &&, || when operands index global-rooted buffers; enable/propagate buffer-device-address usage + allocation flags + address queries where required; ensure log messages survive debug-agent temp-string churn; add/extend test coverage.
File summaries
File Description
tutorials/integration/cpp/class_adapters_module.das.inc Regenerated embedded tutorial payload (no logic changes).
tests/spirv/test_ternary.das Adds coverage for lazy operand lowering behavior.
tests/spirv/test_coopmat2_fa256.das New SPIR-V validation test for coopmat2 head-256 flash-attention tile shapes.
tests/spirv/test_census.das Extends opcode census to include new fixtures.
tests/spirv/_spirv_common.das Adds new SPIR-V fixtures for lazy operands and coopmat2 FA256 kernel.
tests/jit_tests/wasm_idot_lowering.das Windows quoting fix for popen-based child spawn.
tests/jit_tests/cross_target_folds.das Windows quoting fix for popen-based child spawn.
tests/daslib/test_logger.das Adds child-process-based test for logger hook at field formatting.
tests/daslib/_logger_hook_child.das Child probe used by logger hook test.
tests-cpp/small/test_to_out_agent_temp_strings.das Minimal script compiled/executed by new C++ test.
tests-cpp/small/test_to_out_agent_temp_strings.cpp New C++ regression test for Context::to_out message stability across agent allocations.
src/runtime/context.cpp Copies log message before invoking debug agents to prevent corruption.
src/parser/lex.yy.h Regenerated lexer header (line directives).
src/parser/ds_lexer.cpp Regenerated lexer output (line directives).
plans/vulkan_hybrid_ladder.md New plan/arc doc capturing the hybrid ladder work.
modules/REVIEW_SHADER_EMITTERS.md Adds review rule clarifying operand/global indexing expectations in shader bodies.
modules/dasVulkan/daslib/vulkan_boost.das Fixes feature chaining: integer dot product feature placement under Vulkan 1.3 vs standalone struct.
modules/dasVulkan/ARCHITECTURE.md Documents subgroup/full-subgroup pairing and dot-product feature chaining constraints.
modules/dasUnitTest/unit_test.das.inc Regenerated embedded module payload (no logic changes).
modules/dasSpirv/spirv/spirv_emit.das Implements lazy lowering for ?:, &&, `
modules/dasSpirv/spirv/spirv_builtins.das Adds cooperative-matrix marker types and mul-add signatures for head-256 tiles.
modules/dasSpirv/ARCHITECTURE.md Documents operand laziness design and rationale.
modules/dasLLAMA/tests/test_vulkan_kernels.das Expands Vulkan kernel-unit suite (FA h256 gated, dn scan/ba coverage, sentinels, updated rails).
modules/dasLLAMA/tests/test_kernel_coverage.das Extends Vulkan kernel coverage census (adds f16 session path, adds 9B hybrid).
modules/dasLLAMA/tests/test_gpu_tier.das Extends resident-driver contract tests (hybrid seats + dn_native tag bit).
modules/dasLLAMA/tests/test_gpu_serving_declines.das New tests for whole-model driver decline reasons and per-layer decline text.
modules/dasLLAMA/tests/test_gpu_resident_hybrid.das New end-to-end forced-feed parity tests for resident hybrid prefill+decode (Q8 + K-quant fixture).
modules/dasLLAMA/tests/test_exe_smoke.das Ensures produced runtime DLLs can be found via PATH when executing from temp dir.
modules/dasLLAMA/tests/test_chat.das Forces CPU path for render seam test to avoid mixed GPU/CPU prefill behavior under DASLLAMA_GPU=1.
modules/dasLLAMA/tests/run.das Wires new dasLLAMA test files into suites/areas.
modules/dasLLAMA/tests/CLAUDE.md Updates test-suite documentation for new hybrid/decline tests.
modules/dasLLAMA/REVIEW.das Updates image layout stamp hash.
modules/dasLLAMA/REVIEW_MEASUREMENT.md Refines measurement/provenance rules and terminology.
modules/dasLLAMA/REVIEW_GPU.md Tightens Vulkan parity/coverage rules; documents hybrid seat install asymmetry.
modules/dasLLAMA/REVIEW_GPU_VULKAN.md Adds Vulkan-tier checklist items for timestamp accounting + decline logging.
modules/dasLLAMA/README.md Splits Vulkan architecture docs (adds residency companion doc).
modules/dasLLAMA/performance/model_specs.das Adds K-quant hybrid fixture + 9B UD model spec metadata for stocked runs.
modules/dasLLAMA/followup_vulkan.md Updates Vulkan follow-up ledger with the hybrid ladder completion and validation notes.
modules/dasLLAMA/dasllama/dasllama_vulkan_common.das Adds dn scan/ba workgroup helpers; adds BDA enablement/alloc/query path; updates dn workspace layout and hybrid fields.
modules/dasLLAMA/dasllama/dasllama_math_vulkan.das Plumbs dn_native config bit; installs hybrid resident seats.
modules/dasLLAMA/dasllama/dasllama_load.das Tags DeltaNet K-quant formats when the whole-model driver is to be attempted (dn_native).
modules/dasLLAMA/dasllama/dasllama_gpu_tier.das Extends resident-driver SPI: new prepare signature, per-layer mirror idx, hybrid seat install + hooks.
modules/dasLLAMA/dasllama/dasllama_config.das Adds dn_native to Vulkan image identity/tagging config.
modules/dasLLAMA/dasllama/dasllama_common.das Adjusts session state fields around resident mirror ownership tracking.
modules/dasLLAMA/ARCHITECTURE.md Updates architecture doc index to reflect Vulkan doc split and hybrid coverage.
modules/dasLLAMA/ARCHITECTURE_GPU.md Updates Vulkan notes (decline logging expectations, hybrid seats as Vulkan-only asymmetry).
modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN.md Moves residency sections out; extends prefill section to document recurrent-window block + h256 FA.
modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN_RESIDENCY.md New doc splitting out residency plan + marks swap details.
modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN_DECODE.md Adds whole-model hybrid token-command section (decode + prefill-to-decode handoff).
daslib/logger.das Ensures logger hook’s at line number prints as decimal (not hex-formatted).
Review details
  • Files reviewed: 52/54 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests-cpp/small/test_to_out_agent_temp_strings.cpp
Copilot AI review requested due to automatic review settings September 6, 2026 21:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

One new test (tests/daslib/test_logger.das) can cascade into misleading failures because it continues file IO after a failed child-process spawn instead of returning early.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

tests/daslib/test_logger.das:217

  • After asserting the child process exit code, the test continues and unconditionally reads/removes the log file. Since equal(...) does not abort on failure, a failed spawn can cascade into a secondary failure (or exception) in read_lines(p) that hides the real cause. Guard the file IO behind a successful rc == 0 check (or return early on failure).
  • Files reviewed: 53/55 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 6, 2026 22:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It spans core Vulkan residency/dispatch behavior, SPIR-V codegen control-flow changes, and runtime logging internals, which warrants final human review despite strong test coverage additions.

Review details
  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

borisbat and others added 21 commits September 6, 2026 17:03
…driver names every missing arm in one line, each per-op rail reports the layers it left on the CPU and its VRAM-budget stop, a dense model's FFN gets its own sized line, and the per-call overrides say each pass-to-CPU reason once per armed model

REVIEW_GPU.md carries the rule (a serving gate that leaves work off the GPU without logging the concrete reason is a defect); ARCHITECTURE_GPU.md 1.5 says how the declines speak; tests/test_gpu_serving_declines.das covers the reason builders model-free.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rs ride the recorded token command through the fused step over per-layer state slots, gated attention and partial rotary ride the fused qk-norm+rope and decode attention kernels, the K/V mirror keeps one slot per attention layer, and the nextn block stops declining

A hybrid's prompt still prefills on the CPU (the window chain has no recurrent arm yet); the decode takes the mirror over from the host cache, which also serves a session another prefill superseded. The auto VRAM plan leaves max(2 GiB, 27% of the cap) unfilled: on the 16 GB card a 12.5 GB plan was WDDM-demoted to 6.8 tok/s, 11 GB held 49.8. Qwen3.5-9B-MTP-UD-Q5_K_XL decodes resident at tg128 49.7 (was 11.1; upstream 56.7).

Found and fixed on the way: the arena tally and placement used qd rows for a gated q plane whose layout is 2 x qd ([q | gate] per head), so half the plane was never placed.

Docs: ARCHITECTURE_GPU_VULKAN_DECODE.md 2.2v (the hybrid token command) with [arch] on the recorder heads, the dn layer registration, the owner bind and the mirror takeover; ARCHITECTURE_GPU_VULKAN.md 2.2n (the headroom share); followup_vulkan item 2; plans/vulkan_hybrid_ladder.md. Tests: tests/test_gpu_resident_hybrid.das (stocked, DASLLAMA_GPU=1: the 0.8B forced-feed against the CPU chain), test_gpu_tier.das's doubles on the widened SPI and the hybrid seats.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, so a hook installed on the logging context keeps it whole; the logger's "at" field prints the line in decimal

A das debug agent installed through install_new_debug_agent runs its onLog on the context that logged, and the first [temp_string_result] it produces there reclaims the one-slot dispose queue - which holds the caller's interpolated message. In tee mode the runtime then printed whatever string reused that slot: the timestamp, or the pretty JSON of the record's fields, glued in front of the next line, with the message itself gone. Context::to_out now copies the message before the first agent sees it and prints the copy. The copy lives at function scope: an SSO-sized message dies with a block-scoped string before the print.

The hook's "at" field interpolated LineInfo.line as uint, which prints hex ("main.das:0x373"); it is int(at.line) now.

Test: tests-cpp/small/test_to_out_agent_temp_strings.cpp - a C++ agent does the temp-string traffic on the logging context and asserts the message it read is intact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r's window block (qkv/z GEMMs, beta/alpha rows, conv, the two-phase scan over the layer's device state, the out GEMM) rides the window command beside the attention head, gated attention and partial rotary ride the batch kernels, and the state comes home for the decode's upload

The recurrent block replaces the attention head per window and shares the FFN tail; its conv history crosses windows position-major in ring image 0 and transposes into the decode step's per-channel ring on the last window (dn_tail_cls). Gated q rides a per-head q stride (qhs = 2 x hs: the q GEMM writes [q | gate] per head, qk-rms and rope read q head-strided in place, the mirror attention gates its output on the sigmoid of the gate half); partial rotary is the half = rot / 2 word with the pairs past it passing through. The f32 beta/alpha arm is the router GEMV grown a row stride; the q8 arm a batch GEMM and a device copy. After the last window every recurrent layer's state and conv history flush home (vk_rdec_prefill_dn_flush, a new tier seat) and the slot goes invalid, so the first decode's owner bind uploads the host copy the ordinary way. resident_prefill_shape_ok and the hybrid_prefill pass kind are gone: a hybrid's prompt no longer prefills on the CPU.

Found on the way: the prefill addressed the K/V mirror by layer index (mirb = l * seq_cap * kvd) and sized its sets by n_layers - the hybrid mirror has one slot per attention layer (RLayer.mir_idx, n_attn); and the rope/attention/qk-rms sets declared the q plane at qd rows while a gated q row is 2 x qd, so rows past 256 fell outside the bound range (256 tokens passed, 384 failed).

Gate: tests/test_gpu_resident_hybrid.das on Qwen3.5-0.8B-Q8_0 under DASLLAMA_GPU=1 - the prefill logits within 0.19-0.24 of the CPU chain on a bar of ~0.5 (one window at 40 tokens; two windows at 600), every fed step within 0.23-0.31, argmax equal, one-step-off controls red, armed + served-prefill witnesses. Qwen3.5-9B-MTP-UD-Q5_K_XL on the resident driver: pp512 1365.0 +- 4.6 (was 95.7 on the CPU prefill; upstream 2527, 0.54x - a gap remains), tg128 53.3 +- 0.1 (was 49.7; upstream 56.7).

Docs: ARCHITECTURE_GPU_VULKAN.md 2.2j (the recurrent window block, the gated/partial arms), ARCHITECTURE_GPU_VULKAN_DECODE.md 2.2v (the prefill's three arms and the state handoff), ARCHITECTURE.md index, followup_vulkan item 2, plans/vulkan_hybrid_ladder.md, tests/CLAUDE.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the out-norm as a phase 3, and the staged GEMM both phases share computes a 2x4 register tile per invocation; the prefill and decode profilers table a hybrid's recurrent head by role

Phase 2's four GEMMs are independent per C column, so DN_NSP (4) workgroups per head each own a column slice of the state, of d and of the raw o rows - 64 workgroups on the 5060 Ti's 36 SMs where one per head left 20 idle - and the raw o rows land in the o plane; the gated out-norm needs whole rows and moves to dn_scan_p3_cls, one workgroup per position. The staged GEMM kept its C tile in a dynamically indexed private array and did two shared loads per FMA; each invocation now accumulates a 2x4 tile in registers (one A pair and one B quad per 8 FMAs), a GEMM past 256 tiles running in waves; every C element still sums k in the same order, so the kernels are bit-exact with the shipped ones (the kernel-unit cell's oracle and the 0.8B parity cells agree to the digit). On the 9B UD file the scan went from 141 ms to 63 per 512-row window; pp512 1365 -> 1710.6 +- 15.6 (upstream 2527).

The prefill profiler's per-role table was silent on a hybrid (the recurrent head stamps its own count); pf_prof_report now walks the layers by head type and prints an attention line and a recurrent line. The decode profiler (rdq_sample) does the same for the token command (a second table for the recurrent head, and the split-rail attention count with the fused qk-norm+rope a gated/partial-rope model forces).

Docs: ARCHITECTURE_GPU_VULKAN.md 2.2j (the scan's three phases); followup_vulkan item 2 carries both rows' per-role profile and the ranked gaps (P1 attention at head size 256 on the scalar tile, P2 the f32 beta/alpha per-position GEMV, P3b a coopmat scan, D1 the Q8 transcode of the deltanet planes on decode); plans/vulkan_hybrid_ladder.md. Tests: test_vulkan_kernels' dn family cell dispatches phases 2 and 3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…perand indexes a global-rooted array: that operand lowers as a branch through a hoisted Function temp instead of an eager OpSelect, so the load a condition guards never reaches the device out of range; pure operands stay branchless

The eager form read `krows` at every q-row offset in the qk-norm kernel - 12.6 MB past a 4 MiB
binding on the 9B's 512-row window - and faulted the card once the overshoot left mapped VRAM.
SpirvTempAlloc hoists one temp per such ternary or short-circuit; the Left/Right hooks bracket the
operands with the selection, and the merge reloads the temp as the value. tests/spirv/test_ternary
gains the lazy-operand fixture; modules/dasSpirv/ARCHITECTURE.md sec.3 states the rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…state in registers - one 32-lane subgroup per (head, column group), sixteen state rows per lane, the window's tokens looping inside the kernel - the f32 beta/alpha rows ride a 16-position tile GEMM, the chunked two-phase scan and its slab workspace are gone, the kernel-unit suite gates both kernels at the 9B geometry, and the top-k router hoists its shared reads so the emitter's lazy rule keeps them branchless

Qwen3.5-9B UD-Q5_K_XL on the 5060 Ti, last 512-row window: scan 63 ms -> 17.7 + 1.2 (out-norm),
beta/alpha 15.5 -> 11.7, window 291 -> 221 ms; pp512 1710 -> 2010 (0.80x of llama.cpp's 2527),
tg128 52.6 (0.93x). The scan workspace shrinks from ~155 MB to the raw o rows (16 MB).
followup_vulkan item 2 carries the remaining ledger, item 40 the hang hunt's residue.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… zeroes every recurrent layer's state and ring image with transfer fills inside the window command, and after the last window each layer's slot is handed to the session - valid, dirty, owned - so the first decode's owner bind finds the state in place and a host read syncs it down through dn_flush_layer

Before, window 0 paid 24 record-and-wait round trips through a staging buffer and the end of
the prefill 24 more that pulled 2 MiB per layer home only for the first decode to upload it
again. Qwen3.5-9B UD-Q5_K_XL on the 5060 Ti, 3 reps: pp512 2049 -> 2282 (0.90x of llama.cpp's
2527), tg128 53.6 (0.95x). The hybrid parity cells hold at every step after the prefill.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hen their formats admit them: the block converts its normed rows to f16 like the attention layers do, the out GEMM reads an f16 copy of the scan's o rows, and only the q8 arm's beta/alpha GEMMs (nvh columns, under the tile) keep the q8 image

Qwen3.5-9B UD-Q5_K_XL on the 5060 Ti, last 512-row window: qkv 21.0 -> 15.4 ms, z 10.2 -> 7.1,
out 10.4 -> 9.2, the two f16 converts +0.5; pp512 2282 -> 2361 (0.93x of llama.cpp's 2527), two
3-rep pairs. The hybrid parity cells hold on both arms (the 0.8B records both feeds).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… four-subgroup workgroup: a lane issues five loads per token instead of 35 and a head's rows cross L2 once per 16 columns instead of once per 4, the state stays in registers

Qwen3.5-9B UD-Q5_K_XL on the 5060 Ti, last 512-row window: scan 17.5 -> 15.3 ms; pp512 2361 -> 2387
(0.94x of llama.cpp's 2527). Two further variants measured no faster or worse (the next token's rows
staged a token ahead: 15.7; its scalars and transcendentals a token ahead too: 19.9) - the
remaining cost is the dependent per-token chain, noted on the class.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…refill's tile GEMM and by an f16 twin of the router GEMV (the MoE span keeps the f32 instance through a template split), and the tile GEMM's grid becomes position tiles x 16-output groups, one output per invocation, so a 64-row layer fills the card

Qwen3.5-9B UD-Q5_K_XL on the 5060 Ti, last 512-row window: ba 9.4 -> 6.1 ms (the bytes alone
moved nothing - the old grid was 32 workgroups on 36 SMs); window 204 -> 200 ms; pp512 2387 ->
2439 (0.965x of llama.cpp's 2527), tg128 54.2 (0.956x); the sanity logits move by ~0.1 under the
f16 rows. The kernel-unit suite gains the router GEMV's f16 twin at a row base and an output base,
and the two beta/alpha cells dot the f16-rounded rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…: the h128 loop over 64x256 / 256x32 / 32x256 workgroup-scope shapes (new in dasSpirv, with their multiply-add overloads), the gated twins loading Q at the head's q stride and scaling the normalized output by the sigmoid of the gate half, the resident prefill dispatching them for a Qwen3.5-class window and the decode-side pre-flight making the same pick; the kernel-unit suite gates the plain stamp in the family loop and the gated stamps at a small shape and at the 9B's own geometry

On the 9B the attention role in the 512-token window goes from 21.05 ms to 1.29 ms, the window from 200 to 183 ms, pp512 from 2439 to 2660 (1.05x of llama.cpp's 2527); tg128 is untouched (53.5).

The ledger records the validation layer's finding that the cm2 decode callbacks read weight blocks through physical-storage pointers into buffers created without the device-address usage (VUID 11819): the driver serves the reads, the layer's safe mode zeroes them, so a validated run's logits are not evidence until the usage bit lands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…' decode callbacks reach a plane's blocks through PhysicalStorageBuffer pointers, so the device and pinned-host buffer makers set the shader-device-address usage, chain the device-address allocate flag on their allocations and query the address once after the bind (the validation layer learns a buffer's range only from that query); the coopmat2 device creator names the integer dot product on the Vulkan 1.3 struct when that struct is chained and keeps the standalone struct for a device below 1.3

The 9B pp512 window under GPU-assisted validation logs no VUID (was 80 x PhysicalStorageBuffer64-11819 and the device-create pNext 06532) and lands the plain run's logits, so a validated run's numerics are evidence again; the plain pair is unchanged by the flags (A/B on one build: 2454 vs 2307 pp512, 48.4 vs 47.7 tg128, both inside the box's spread). The followup ledger closes items 40b and 40d and records the exit wedge under the layer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e holds 16 rows of both in registers, the token's staged k and q rows feed both columns, the two per-token chains are independent and interleave, and a workgroup covers twice the columns (the 9B dispatches 128 workgroups instead of 256)

On the 9B window the scan role goes from 17.0 to 10.9 ms and the window from 200 to 189 ms on the same box state; the deltanet kernel cells (family, beta/alpha, both 9B-geometry cells) and the 0.8B hybrid parity hold.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…; four measured 15.3 ms against 10.9 on 64 workgroups) and names the k-native deltanet planes as the one lever left that moves both the prefill and the decode

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the whole-model driver: the loader tags them natively when the driver will be attempted (an image identity bit rides along), the resident plan tallies, reserves and places the triple by format and hands the formats to the layer sets, the decode's per-format GEMVs and the prefill's cm2 tiles serve them, a K-quant out plane declines the layer (the step's o row is Q8_0), and a q8 beta/alpha arm beside K-quant qkv/z re-requantizes the row Q8_0 before its GEMVs; the resident-hybrid test gains the Q4_K_M fixture and the declines test names the out-plane case

On the 9B UD file against a same-day llama-bench control: tg128 50.1 -> 51.8 (0.965x of 53.7; the decode's qkv GEMV 2.21 -> 1.70 ms per token, z 1.13 -> 0.95), pp512 2608 -> 2526 (1.02x of 2484; the k5/k6 tiles run the window's qkv and z GEMMs slower than the q8 tile - ledgered as the next prefill lever), and the plan's context grows from 125655 to 136087 with the smaller planes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…(the four function globals get their boot-restore guards), the tier test installs the prefill handoff seat beside the other three and proves it forwards, and the image layout stamp follows the vulkan config's new identity field

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ments carried land in the architecture documents with their anchors and citations (dasSpirv's operand laziness as section 3.4, the deltanet tag policy and the beta/alpha grid in the Vulkan sections), three rules join the Vulkan tier's checklist (a select never reads a buffer in a kernel arm, a timestamp count change updates its role table, a recurrent override declines out-of-order positions), the names the comments stood in for land as renames (lazy_merge_value, rdec_host_current, WalkSkips.skipped/first_why, rdn_upload_constants, the scan's lanes_per_pair/pairs_per_subgroup/cols_per_group/row_stride, FaCm2Args.qstride, ba_stamp_shift), and a hundred comment lines that restated the code or a landed section go

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ag and query ride only the coopmat2 device (the other creators never enable bufferDeviceAddress), the window chain keeps every window past the first at the conv taps and a lone shorter window's ring rows read as zero (a prompt one past the window or shorter than the taps read the tail past the plane), a K-quant qkv/z pair off the f16 feed reads the Q8_K activation form with the q8 beta/alpha arm re-requantizing behind it, the scan's idle lanes read a clamped column (the last head's idle columns read past the conv plane at a narrow state), the deltanet state width must be a power of two (the lane grouping's assumption; 96 passed the old guard), a gated model takes the cm2 flash tiles at head 256 alone (the head-64/128 stamps carry no gate), the resident plan's decline reason is built before the plane list is freed, the RoPE store reads its clamped table entries above the select, the decode's kq requant loses its stamp so the recurrent role table stays 13 wide behind one fuse-gate helper, the kernel cells gain NaN sentinels and poison controls and a narrow-state scan cell, the hybrid parity file gains the two short-window cells, the serving census runs an f16 session pass so the whole-model driver's kernels count, the K-quant twin and the 9B UD file get provenance rows and the 9B a census row, the logger hook's decimal line (a child-process probe) and the image tag's native-planes bit get cells, the deltanet-resident seats land in the asymmetry list, the Vulkan architecture doc splits its residency sections into a companion, and the touched checklists read as one obligation per rule

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…target tests wrap their spawn on Windows (a command that opens with a quote beside a redirect loses its first and last quote to cmd.exe), the chat render-seam cell drops the model's device state before its byte-equal compare (under the armed tier the two chains split between the device prefill and the CPU continuation), and the exe smoke runs its artifact with the producing daslang's bin dir on PATH (the exe links that tree's runtime DLLs, which a temp dir never holds)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ell rather than the process environment - a PATH read through env_value_of is a raw read of an undeclared knob to the env-knob registry test - and the emitter's eager-operand plan lands in the shader emitter ledger (bool & | ^ operators and a select builtin as language surface, the SPIR-V emitter's purity heuristic dropped, the census sites converted)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lean-first rebuild regenerated them and the fix batch swept them in; none of their inputs changed on this branch

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 7, 2026 00:17
@borisbat
borisbat force-pushed the bbatkin/vk-hybrid-ladder branch from c2c4f14 to dc8574e Compare September 7, 2026 00:17
@borisbat
borisbat merged commit 245481f into master Sep 7, 2026
34 checks passed
@borisbat
borisbat deleted the bbatkin/vk-hybrid-ladder branch September 7, 2026 00:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It makes large, cross-cutting changes across Vulkan residency/serving, SPIR-V emission semantics, and runtime logging, which warrants final human review despite the added test coverage.

Review details
  • Files reviewed: 50/50 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants