Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion modules/dasLLAMA/dasllama/dasllama_common.das
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,13 @@ def private rdec_hydrate_host(t : Model; var s : Session) {
}
let private RDEC_MIN_CTX = 4096l // smallest ctx worth arming a capped mirror at

// the auto-cap floor, env-overridable: DASLLAMA_GPU_MIN_CTX lets a short-context session arm a
// model whose capped mirror lands under the default floor (e.g. a 3B on an 8GB card fits at 3429)
def private rdec_min_ctx : int64 {
let e = get_env_variable("DASLLAMA_GPU_MIN_CTX")
return empty(e) ? RDEC_MIN_CTX : max(int64(to_int(e)), 256l)
}
Comment on lines +2057 to +2061

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 41440a1 - non-numeric (to_int -> 0) now keeps the default floor; only positive values clamp to >=256.


// gather one prepared plane into device-layout bytes and place it in its format's arena; returns
// the arena block index. Same gather the per-op tier uses (moe_gpu_gather_stack / _kq), then place
// instead of upload_stack.
Expand Down Expand Up @@ -2538,7 +2545,7 @@ def moe_gpu_upload_resident(var t : Model) {
// weights are fixed, the KV mirror is not: retry at the context that fits (the plan's
// own remedy), as long as it stays a usable window. The driver guards decode past the cap.
let fit_ctx = (plan.budget_bytes - plan.weight_bytes) / (plan.kv_bytes / seq_cap)
if (fit_ctx >= RDEC_MIN_CTX) {
if (fit_ctx >= rdec_min_ctx()) {
seq_cap = fit_ctx
plan = resident_plan(t, seq_cap, KVDtype.f32, KVDtype.f32)
}
Expand Down
110 changes: 83 additions & 27 deletions modules/dasLLAMA/dasllama/dasllama_math_vulkan.das
Original file line number Diff line number Diff line change
Expand Up @@ -2948,6 +2948,15 @@ var private g_gpu : GpuState?
var private g_init_failed = false
var private g_vk_prof = false // DASLLAMA_GPU_PROF=1: per-piece batch-chain timing to log
var private g_vk_prof_probed = false
var private g_vk_dispatches = 0ul
var private g_vk_submits = 0ul

//! Lifetime vkCmdDispatch calls the tier RECORDS (not executions — a re-submitted pre-recorded
//! cmd does not advance it). Pair with vk_submit_count for per-step encode-cost deltas.
def vk_dispatch_count : uint64 => g_vk_dispatches

//! Lifetime queue submissions (real cmds + staging round-trips).
def vk_submit_count : uint64 => g_vk_submits

def private vk_prof : bool {
if (!g_vk_prof_probed) {
Expand Down Expand Up @@ -3230,6 +3239,7 @@ def private staged_upload(dst : uint64; dst_off : int64; bytes : int64) {
var regions : array<BufferCopy>
let rc = BufferCopy(srcOffset = 0ul, dstOffset = uint64(dst_off), size = uint64(bytes))
regions |> push(rc)
g_vk_submits++
run_cmd_sync(g_gpu.device, g_gpu.pool, g_gpu.queue) $(cmd) {
cmd_copy_buffer(cmd, nonowning_buf(g_gpu.staging.buf), nonowning_buf(dst), regions)
}
Expand Down Expand Up @@ -3536,9 +3546,13 @@ def private arena_reserve(fmt : int; cap_blocks : int64) : bool {
let bb = arena_block_bytes(fmt)
let qbytes = cap_blocks * bb.wq
let sbytes = cap_blocks * bb.ws
// maxStorageBufferRange, not uint32 indexing (that reaches 16GiB), is the real ceiling
if (qbytes > 2_147_483_648l || sbytes > 2_147_483_648l) {
to_log(LOG_WARNING, "dasLLAMA vulkan arena: fmt {fmt} needs {qbytes} quant bytes — over the 2GiB shard cap\n")
// maxStorageBufferRange, not uint32 indexing (that reaches 16GiB), is the real ceiling — ask
// the device (NV/AMD report the full 4GiB, where a 3B's single-format q8 plane fits)
var lp : VkPhysicalDeviceProperties
vkGetPhysicalDeviceProperties(g_gpu.phys, lp)
let shard_cap = int64(lp.limits.maxStorageBufferRange)
if (qbytes > shard_cap || sbytes > shard_cap) {
to_log(LOG_WARNING, "dasLLAMA vulkan arena: fmt {fmt} needs {qbytes} quant bytes — over the device's {shard_cap}B storage-range cap\n")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in e190f99 - the warning now reports both quant and scale byte counts.

return false
}
var a = ArenaFmt(fmt = fmt, cap_blocks = cap_blocks, wqbytes = qbytes, wsbytes = sbytes,
Expand Down Expand Up @@ -3943,6 +3957,9 @@ struct private RDec {
cls_fmt_marker : int
layers : array<RLayer>
cmd : VkCommandBuffer
tok_recorded : bool // cmd holds the whole-token chain, re-submittable as-is
cos_host : HostBuf // this token's rope row, copied to cos_dev in-cmd
ffn_meta_h : HostBuf // decode-owned actrq meta source (g_gpu.ffn_meta is shared with the batch rails)
// prefill batch buffers (lazy — sized MAX_NPOS x their width) + per-layer prefill sets
pf_ready : bool
pf_np : int64 // MAX_NPOS the buffers were sized for
Expand Down Expand Up @@ -4025,6 +4042,8 @@ def vk_rdec_prepare(n_layers, dim, qd, kv_dim, head_size, n_heads, hidden, vocab
r.logits_dev = make_device_buf(vocab * 4l)
r.dummy = make_device_buf(256l)
r.x_host = make_host_buf(dim * 4l, false, [cached = true])
r.cos_host = make_host_buf(head_size * 4l, false, [cached = true])
r.ffn_meta_h = make_host_buf(FFN_META_BYTES, false)
r.logits_host = make_host_buf(vocab * 4l, false, [cached = true])
r.kv_readback = make_host_buf(2l * kv_dim * 4l * KV_READBACK_ROWS, false, [cached = true])
ensure_ffn_state() // hq_dev/hs_dev for the FFN act+requant
Expand Down Expand Up @@ -4112,6 +4131,7 @@ def vk_rdec_set_layer(l : int64; bq, bk, bv, bo, b1, b3, b2 : int64; fq, fk, fv,
dim * 4l, dim * 4l, META_BYTES, (g_rd.n_layers * 2l * dim + dim) * 4l, 64l, dim * 4l)
L.made = true
}
g_rd.tok_recorded = false // the recorded chain references the replaced sets
}

//! Register the classifier plane (arena block + format) and build the prologue / final-norm / cls
Expand Down Expand Up @@ -4145,6 +4165,7 @@ def vk_rdec_set_cls(cls_block : int64; cls_fmt : int) {
mp[5] = 0u
staged_upload(g_rd.cls_meta_dev, 0l, CLS_META_BYTES)
g_rd.cls_fmt_marker = cls_fmt
g_rd.tok_recorded = false // the recorded chain references the replaced cls/prologue sets
g_rd.ready = true
}
}
Expand Down Expand Up @@ -4244,44 +4265,50 @@ def private pfq_us(i : uint) : double {
}
}

def private rd_dispatch(raw : VkCommandBuffer; pipe : Pipeline; var set : VkDescriptorSet; groups : uint) {
// bar = false joins the NEXT dispatch into one barrier level — the caller owns the disjointness proof
def private rd_dispatch(raw : VkCommandBuffer; pipe : Pipeline; var set : VkDescriptorSet; groups : uint; bar : bool = true) {
cmd_bind_pipeline(vk_value_to_boost(raw), pipe, VkPipelineBindPoint.COMPUTE)
cmd_bind_dispatch(raw, set, groups)
comp_barrier(raw)
if (bar) {
comp_barrier(raw)
}
pfq_ts(raw)
}

// GEMV dispatch — the per-format kernel selected off the plane format
def private rd_dispatch_gemv(raw : VkCommandBuffer; fmt : int; var set : VkDescriptorSet; groups : uint) {
def private rd_dispatch_gemv(raw : VkCommandBuffer; fmt : int; var set : VkDescriptorSet; groups : uint; bar : bool = true) {
bind_gemv_pipe(raw, fmt)
cmd_bind_dispatch(raw, set, groups)
comp_barrier(raw)
if (bar) {
comp_barrier(raw)
}
pfq_ts(raw)
}

//! Resident-driver token: run the whole stack on device from the embedded residual `x` and this
//! position's rope row (cos[half]+sin[half]) -> logits. `cnt` = cached positions incl. this one;
//! K/V for `pos` get stored into the mirror as a side effect.
def vk_rdec_token(x : array<float>; cossin : array<float>; pos, cnt : int64; var logits : array<float>) {
assert(g_rd != null && g_rd.ready, "vk_rdec_token before prepare/set_cls")
// Record the whole-token cmd ONCE per set_layer/set_cls epoch: constant metas + scalars fill here;
// per-token state is only x/cos (in-cmd copies) and the rope/attn position words (host-visible)
def private rd_record_token {
let dim = g_rd.dim
let qd = g_rd.qd
let kvd = g_rd.kv_dim
let hid = g_rd.hidden
let nlfin = g_rd.n_layers * 2l * dim // rms_final offset in the norms buffer (dim units later)
unsafe {
// per-token host state — attn scalars ([0]=scale) and add_rms scalars ([0]=eps, [1]=ascale)
// constant scalars — attn ([0]=scale) and add_rms ([0]=eps, [1]=ascale)
var sp = reinterpret<float?>(g_gpu.staging.mapped)
sp[0] = g_rd.scale
staged_upload(g_rd.scal_dev, 0l, 64l)
sp[0] = g_rd.eps
sp[1] = 1.0
staged_upload(g_rd.ar_scal_dev, 0l, 64l)
upload_region_at(g_rd.x_dev, 0l, addr<uint8 const?>(x[0]), dim * 4l)
upload_region_at(g_rd.cos_dev, 0l, addr<uint8 const?>(cossin[0]), g_rd.head_size * 4l)
// fill the FFN act+requant meta once (gelu off; nfe blocks = hidden/32)
fill_ffn_meta(hid, hid / 32l, false)
// fill all metas
// decode-owned actrq meta — the in-cmd copy re-establishes ffn_meta_dev every submit (batch rails share it)
var fp = reinterpret<uint?>(g_rd.ffn_meta_h.mapped)
fp[0] = uint(hid)
fp[1] = 0u
fp[2] = uint(hid / 32l)
for (i in range(5)) {
fp[3 + i] = 0u
}
fill_addrms_meta(g_rd.m_prologue, dim, 0l, false) // rms_att[0] at offset 0
for (l in range64(g_rd.n_layers)) {
var L & = g_rd.layers[l]
Expand All @@ -4292,9 +4319,8 @@ def vk_rdec_token(x : array<float>; cossin : array<float>; pos, cnt : int64; var
var mr = reinterpret<uint?>(L.m_rope.mapped)
mr[0] = uint(qd); mr[1] = uint(kvd); mr[2] = uint(g_rd.head_size); mr[3] = uint(g_rd.head_size / 2l)
mr[4] = g_rd.neox ? 1u : 0u; mr[5] = uint(qd / 2l); mr[6] = uint(qd / 2l + kvd / 2l)
mr[7] = uint(l * g_rd.seq_cap * kvd + pos * kvd); mr[8] = uint(l * g_rd.seq_cap * kvd + pos * kvd)
var ma = reinterpret<uint?>(L.m_attn.mapped)
ma[0] = uint(cnt); ma[2] = uint(kvd); ma[3] = uint(g_rd.head_size); ma[4] = uint(g_rd.kv_mul)
ma[2] = uint(kvd); ma[3] = uint(g_rd.head_size); ma[4] = uint(g_rd.kv_mul)
ma[5] = uint(l * g_rd.seq_cap * kvd); ma[6] = uint(l * g_rd.seq_cap * kvd)
fill_quant_meta(L.m_quant_at, qd / 32l)
fill_gemv_meta(L.m_wo, qd, dim, 0l, L.bo)
Expand All @@ -4307,38 +4333,66 @@ def vk_rdec_token(x : array<float>; cossin : array<float>; pos, cnt : int64; var
fill_addrms_meta(L.m_addr_next, dim, nxt, true)
}
fill_quant_meta(g_rd.m_quant_final, dim / 32l)
// record the whole token
var raw = g_rd.cmd
let rf : VkCommandBufferResetFlags
vk_check(vkResetCommandBuffer(raw, rf), null)
let begin = VkCommandBufferBeginInfo()
vk_check(vkBeginCommandBuffer(raw, begin), null)
cmd_copy_whole(raw, g_gpu.ffn_meta.buf, g_gpu.ffn_meta_dev, FFN_META_BYTES)
comp_barrier(raw)
cmd_copy_whole(raw, g_rd.x_host.buf, g_rd.x_dev, dim * 4l)
cmd_copy_whole(raw, g_rd.cos_host.buf, g_rd.cos_dev, g_rd.head_size * 4l)
cmd_copy_whole(raw, g_rd.ffn_meta_h.buf, g_gpu.ffn_meta_dev, FFN_META_BYTES)
batch_barrier(raw, true) // transfer -> compute (a compute-only barrier under-orders these copies)
rd_dispatch(raw, g_gpu.ar_pipeline, g_rd.s_prologue, 1u) // xb = rms_att[0](x)
for (l in range64(g_rd.n_layers)) {
var L & = g_rd.layers[l]
rd_dispatch(raw, g_gpu.dn_rq_pipeline, L.s_quant_xb, uint((dim / 32l + 255l) / 256l))
rd_dispatch_gemv(raw, L.fq, L.s_q, rd_gemv_wg(qd))
rd_dispatch_gemv(raw, L.fk, L.s_k, rd_gemv_wg(kvd))
// q|k|v: one level — all read xq/xs; q writes q_dev, k/v disjoint kv_dev ranges
rd_dispatch_gemv(raw, L.fq, L.s_q, rd_gemv_wg(qd), [bar = false])
rd_dispatch_gemv(raw, L.fk, L.s_k, rd_gemv_wg(kvd), [bar = false])
rd_dispatch_gemv(raw, L.fv, L.s_v, rd_gemv_wg(kvd))
rd_dispatch(raw, g_gpu.rks_pipeline, L.s_rope, uint((qd / 2l + kvd / 2l + int64(WG_X) - 1l) / int64(WG_X)))
rd_dispatch(raw, g_gpu.da_pipeline, L.s_attn, uint(g_rd.n_heads))
rd_dispatch(raw, g_gpu.dn_rq_pipeline, L.s_quant_at, uint((qd / 32l + 255l) / 256l))
rd_dispatch_gemv(raw, L.fo, L.s_wo, rd_gemv_wg(dim))
rd_dispatch(raw, g_gpu.ar_pipeline, L.s_addr_ffn, 1u)
rd_dispatch(raw, g_gpu.dn_rq_pipeline, L.s_quant_xb2, uint((dim / 32l + 255l) / 256l))
rd_dispatch_gemv(raw, L.f1, L.s_gate, rd_gemv_wg(hid))
// gate|up: one level — both read xq/xs, write gate_dev / up_dev
rd_dispatch_gemv(raw, L.f1, L.s_gate, rd_gemv_wg(hid), [bar = false])
rd_dispatch_gemv(raw, L.f3, L.s_up, rd_gemv_wg(hid))
rd_dispatch(raw, g_gpu.actrq_pipeline, L.s_actrq, uint((hid / 32l + 255l) / 256l))
rd_dispatch_gemv(raw, L.f2, L.s_down, rd_gemv_wg(dim))
rd_dispatch(raw, g_gpu.ar_pipeline, L.s_addr_next, 1u)
}
rd_dispatch(raw, g_gpu.dn_rq_pipeline, g_rd.s_quant_final, uint((dim / 32l + 255l) / 256l))
rd_dispatch_gemv(raw, g_rd.cls_fmt_marker, g_rd.cls_set, rd_gemv_wg(g_rd.vocab))
batch_barrier(raw, false) // compute -> transfer for the logits DMA
cmd_copy_whole(raw, g_rd.logits_dev, g_rd.logits_host.buf, g_rd.vocab * 4l)
vk_check(vkEndCommandBuffer(raw), null)
submit_wait(raw)
}
g_rd.tok_recorded = true
}

//! Resident-driver token: run the whole stack on device from the embedded residual `x` and this
//! position's rope row (cos[half]+sin[half]) -> logits. `cnt` = cached positions incl. this one; K/V
//! for `pos` store into the mirror. Recorded once — a step is two memcpys + position words + one submit.
def vk_rdec_token(x : array<float>; cossin : array<float>; pos, cnt : int64; var logits : array<float>) {
assert(g_rd != null && g_rd.ready, "vk_rdec_token before prepare/set_cls")
if (!g_rd.tok_recorded) {
rd_record_token()
}
let kvd = g_rd.kv_dim
unsafe {
memcpy(g_rd.x_host.mapped, reinterpret<void? -const>(addr<uint8 const?>(x[0])), int(g_rd.dim * 4l))
memcpy(g_rd.cos_host.mapped, reinterpret<void? -const>(addr<uint8 const?>(cossin[0])), int(g_rd.head_size * 4l))
Comment thread
Copilot marked this conversation as resolved.
Outdated
for (l in range64(g_rd.n_layers)) {
var L & = g_rd.layers[l]
var mr = reinterpret<uint?>(L.m_rope.mapped)
mr[7] = uint(l * g_rd.seq_cap * kvd + pos * kvd)
mr[8] = uint(l * g_rd.seq_cap * kvd + pos * kvd)
var ma = reinterpret<uint?>(L.m_attn.mapped)
ma[0] = uint(cnt)
}
submit_wait(g_rd.cmd)
memcpy(addr<void?>(logits[0]), g_rd.logits_host.mapped, int(g_rd.vocab * 4l))
}
}
Expand Down Expand Up @@ -4827,6 +4881,7 @@ def private cmd_bind_dispatch(raw : VkCommandBuffer; var set_ : VkDescriptorSet;
unsafe {
vkCmdBindDescriptorSets(raw, VkPipelineBindPoint.COMPUTE, boost_value_to_vk(g_gpu.pipe_layout), 0u, 1u, addr(set_), 0u, null)
}
g_vk_dispatches++
vkCmdDispatch(raw, groups, 1u, 1u)
}

Expand Down Expand Up @@ -4881,6 +4936,7 @@ def private bind_batch_pipe(raw : VkCommandBuffer; fmt : int) {
}

def private submit_nowait(var raw : VkCommandBuffer) {
g_vk_submits++
var submit = VkSubmitInfo()
submit.commandBufferCount = 1u
let fence = g_gpu.fence
Expand Down
12 changes: 12 additions & 0 deletions modules/dasSpirv/spirv/spirv_builtins.das
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,18 @@ def public coopmatStore(mat : coopmatAcc_f32_16x16; dst; idx, stride, layout : i
[unused_argument(a, b), sideeffects]
def public coopmatMulAdd(a : coopmatA_f16_16x16; b : coopmatB_f16_16x16; c : coopmatAcc_f32_16x16) : coopmatAcc_f32_16x16 { return c }

// The f16-ACCUMULATOR variant (llama.cpp's NV default): halves accumulator register pressure so
// big-tile kernels (many fragments per subgroup) stay register-resident. coopmatConvert widens the
// accumulated tile to f32 (componentwise OpFConvert) for the f32 store path.
struct coopmatAcc_f16_16x16 {}

[unused_argument(mat, dst, idx, stride, layout), sideeffects]
def public coopmatStore(mat : coopmatAcc_f16_16x16; dst; idx, stride, layout : int) : void {}
[unused_argument(a, b), sideeffects]
def public coopmatMulAdd(a : coopmatA_f16_16x16; b : coopmatB_f16_16x16; c : coopmatAcc_f16_16x16) : coopmatAcc_f16_16x16 { return c }
[unused_argument(dst, src), sideeffects]
def public coopmatConvert(var dst : coopmatAcc_f32_16x16; src : coopmatAcc_f16_16x16) : void {}

// The native INT8 path (s8 x s8 -> s32), the Q8-weight tile: K=32 (double the f16 tile), A is 16x32 and
// B is 32x16, s32 accumulator (16x16). Loading Q8_0 weights as s8 coopmat tiles feeds the tensor cores
// directly, no dequant. The integer MulAdd carries a signed-components operands mask (added by the emitter).
Expand Down
27 changes: 27 additions & 0 deletions modules/dasSpirv/spirv/spirv_emit.das
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ def private coopmat_info(t : TypeDecl?) : tuple<ok : bool; is_float : bool; sign
if (n == "coopmatA_f16_16x16") return (ok = true, is_float = true, signed = false, comp_width = 16u, rows = 16u, cols = 16u, use = uint(SpvCooperativeMatrixUse.MatrixAKHR))
if (n == "coopmatB_f16_16x16") return (ok = true, is_float = true, signed = false, comp_width = 16u, rows = 16u, cols = 16u, use = uint(SpvCooperativeMatrixUse.MatrixBKHR))
if (n == "coopmatAcc_f32_16x16") return (ok = true, is_float = true, signed = false, comp_width = 32u, rows = 16u, cols = 16u, use = uint(SpvCooperativeMatrixUse.MatrixAccumulatorKHR))
if (n == "coopmatAcc_f16_16x16") return (ok = true, is_float = true, signed = false, comp_width = 16u, rows = 16u, cols = 16u, use = uint(SpvCooperativeMatrixUse.MatrixAccumulatorKHR))
if (n == "coopmatA_s8_16x32") return (ok = true, is_float = false, signed = true, comp_width = 8u, rows = 16u, cols = 32u, use = uint(SpvCooperativeMatrixUse.MatrixAKHR))
if (n == "coopmatB_s8_32x16") return (ok = true, is_float = false, signed = true, comp_width = 8u, rows = 32u, cols = 16u, use = uint(SpvCooperativeMatrixUse.MatrixBKHR))
if (n == "coopmatAcc_s32_16x16") return (ok = true, is_float = false, signed = true, comp_width = 32u, rows = 16u, cols = 16u, use = uint(SpvCooperativeMatrixUse.MatrixAccumulatorKHR))
Expand Down Expand Up @@ -4232,6 +4233,32 @@ class SpirvEmit : AstVisitor {
e2id[k] = res
return expr
}
// coopmatConvert(dst_f32_acc, src_f16_acc): componentwise OpFConvert over the tile — the
// f16-accumulate path's widening before an f32 store (llama.cpp's cast-then-coopMatStore)
if (name == "coopmatConvert") {
if (ctx.stage != ShaderStage.Compute) {
errs |> push("coopmatConvert is only valid in a compute shader")
return expr
}
if (length(expr.arguments) != 2) {
errs |> push("coopmatConvert expects (dst, src)")
return expr
}
let dst = coop_local_ptr(expr.arguments[0])
let vs = coop_local_val(expr.arguments[1])
if (dst.id == 0u || dst.pty == 0u || vs == 0u) {
errs |> push("coopmatConvert: both arguments must be coopmat locals")
return expr
}
let res = alloc_id(bm)
var cops <- [dst.pty, res, vs]
emit_n(bm, SEC_FUNCS, SpvOp.FConvert, cops)
delete cops
var sops <- [dst.id, res]
emit_n(bm, SEC_FUNCS, SpvOp.Store, sops)
delete sops
return expr
Comment on lines +4236 to +4267

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 41440a1 - the arm now validates via coopmat_info that dst is an f32 accumulator and src an f16 accumulator of the same tile shape before emitting; golden suite re-run green.

}
// barrier(): control + shared-memory barrier (OpControlBarrier at Workgroup execution+memory
// scope, WorkgroupMemory|AcquireRelease semantics). memoryBarrierShared(): the memory-only
// variant (OpMemoryBarrier, same scope/semantics). memoryBarrierBuffer(): the SSBO variant
Expand Down
Loading
Loading