Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- patches/0001-lazy-slot-context-defer.patch +247 -0
- patches/0002-lazy-slot-context-grow.patch +596 -0
- patches/0003-arg-assertion-bypass.patch +46 -0
- patches/0004-cuda-std-cpp17.patch +50 -0
- patches/0005-lazy-slot-context-shrink.patch +582 -0
- patches/0006-kv-reuse-prefix.patch +135 -0
- patches/0007-build-header-deps.patch +92 -0
- patches/0010-rest-kv-eviction.patch +476 -0
- patches/0020-headinfer-per-head.patch +520 -0
- patches/0030-neo-pipeline.patch +933 -0
- patches/0031-per-stream-split.patch +1513 -0
- patches/0032-m2-state-io.patch +205 -0
- patches/0033-cpy-tie-blck-align.patch +100 -0
- patches/0034-m2-pinned-host.patch +80 -0
- patches/0035-concat-q-block-aware.patch +209 -0
- patches/0036-pcie-probe-consume.patch +364 -0
- patches/0040-iqk-flash-attn.patch +0 -0
- patches/0042-fused-moe.patch +527 -0
- patches/0070-rolling-kv.patch +0 -0
- patches/0071-state-io-cosmocc.patch +88 -0
- patches/0072-poly-kv-pool.patch +98 -0
- patches/0073-turboquant-kv.patch +0 -0
- patches/0075-d512-turbo-vec.patch +121 -0
- patches/0078-dca.patch +0 -0
- patches/0079-gemma-assistant-mtp.patch +0 -0
- patches/0080-mtp-d512-fa.patch +155 -0
- patches/0081-tcq-q6-asym-kv.patch +0 -0
- patches/0082-cuda-build-parallelism.patch +320 -0
- patches/0083-tcq-warp-encode.patch +868 -0
- patches/0084-cuda-graphs.patch +23 -0
- patches/0085-dca-quant-prefill-inlaunch.patch +237 -0
- patches/0086-f16-moe-mmf-prefill.patch +49 -0
- patches/0087-dca-yarn-factor.patch +56 -0
- patches/0088-sparsev-autopolicy.patch +63 -0
- patches/0089-sparse-attn-vslash-foundation.patch +0 -0
- patches/0090-rolling-kv-compute-reserve.patch +333 -0
- patches/0091-multi-session-graph-nodes.patch +50 -0
- patches/0092-cuda-build-ccache.patch +71 -0
- patches/0093-mtp-dualctx-fused-nextn.patch +0 -0
- patches/0094-tinyblas-small-gemm.patch +31 -0
- patches/0095-prefill-checkpoint-align.patch +145 -0
- patches/0096-remove-single-ctx-gemma-mtp.patch +0 -0
- patches/0097-native-elf-cmake.patch +140 -0
- patches/0098-rolling-kv-lse-decode.patch +97 -0
- patches/0099-dca-gemma4-wiring.patch +140 -0
- patches/0100-dca-single-chunk-defer.patch +36 -0
- patches/0101-kv-attn-rot-inherit.patch +49 -0
- patches/0102-fattn-bulk-h2d-scratch-ring.patch +154 -0
- patches/0103-kv-cpu-fa-probe-guard.patch +67 -0
- patches/0104-fattn-q6q4-vec-instance.patch +71 -0
patches/0001-lazy-slot-context-defer.patch
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 1/N] lazy slot-context allocation β Phase 1 (deferred zero-fill)
|
| 3 |
+
|
| 4 |
+
Phase 1 of the F4 M3 lazy slot-context rollout. The eager
|
| 5 |
+
`ggml_backend_buffer_clear(buf, 0)` memset that commits every KV
|
| 6 |
+
buffer page to RSS at construction time is split out into a new
|
| 7 |
+
`ensure_cleared()` method invoked from inference entry points
|
| 8 |
+
(init_batch, prepare, update, state_read).
|
| 9 |
+
|
| 10 |
+
The buffer's malloc happens eagerly at construction time so the
|
| 11 |
+
backend scheduler's `sched_reserve` (which runs at llama_context
|
| 12 |
+
construction, before any batch) can resolve `tensor->buffer` for
|
| 13 |
+
every KV tensor. On Linux, malloc is essentially free for unused
|
| 14 |
+
ranges β it reserves address space without committing pages.
|
| 15 |
+
Pages only commit to RSS when first written. The eager memset(0)
|
| 16 |
+
was what wrote every page, so removing it from the constructor
|
| 17 |
+
moves the RSS commit to the first real inference call.
|
| 18 |
+
|
| 19 |
+
Effect for the production embedder (qwen3-0.6b,
|
| 20 |
+
`--parallel 2 --ctx-size 16384`, CPU mode): the KV cache's
|
| 21 |
+
~1,792 MiB no longer commits at process startup. Idle RSS drops
|
| 22 |
+
from ~3,478 MiB to roughly model + compute scratch only. First
|
| 23 |
+
request pays the memset cost (~1 s for 1.8 GiB), then warm-state
|
| 24 |
+
RSS matches the un-patched baseline. Phase 1 alone is a
|
| 25 |
+
cold-start win; Phases 2 (grow-on-demand) and 3 (shrink-on-idle)
|
| 26 |
+
target warm-state RSS.
|
| 27 |
+
|
| 28 |
+
Smoke validation (Qwen2-Math-1.5B Q4_K_M, --parallel 2 -c 4096
|
| 29 |
+
CPU mode): startup emits
|
| 30 |
+
|
| 31 |
+
llama_kv_cache: CPU KV buffer size = 112.00 MiB
|
| 32 |
+
llama_kv_cache: KV cache zero-fill deferred until first batch
|
| 33 |
+
|
| 34 |
+
First /v1/chat/completions request emits
|
| 35 |
+
|
| 36 |
+
ensure_cleared: CPU KV buffer zero-fill = 112.00 MiB (deferred)
|
| 37 |
+
|
| 38 |
+
and produces the expected response.
|
| 39 |
+
|
| 40 |
+
Coverage of ensure_cleared() call sites:
|
| 41 |
+
- init_batch (inference entry)
|
| 42 |
+
- prepare (iswa wrapper calls prepare() directly on each inner
|
| 43 |
+
kv_cache without going through init_batch; see
|
| 44 |
+
llama-kv-cache-iswa.cpp:126-159)
|
| 45 |
+
- update (cross-stream seq_cp resolution + RoPE shift)
|
| 46 |
+
- state_read (saved-state restore writes tensor data)
|
| 47 |
+
|
| 48 |
+
state_write is left const + un-cleared-safe: empty v_cells write
|
| 49 |
+
zero-cell-count records that state_read's
|
| 50 |
+
`if (cell_count == 0) continue` already handles.
|
| 51 |
+
|
| 52 |
+
clear(data=true) on an un-cleared cache skips the buffer memset
|
| 53 |
+
deliberately β v_cells.reset() above already represents "no
|
| 54 |
+
data," and committing RSS just to zero it would defeat Phase 1's
|
| 55 |
+
purpose.
|
| 56 |
+
|
| 57 |
+
The no_alloc model path is unchanged: zero-byte dummy buffers
|
| 58 |
+
are assigned per tensor at construction and trivially "cleared"
|
| 59 |
+
(zero-byte memset). `cleared` is set to true in that case.
|
| 60 |
+
|
| 61 |
+
Bench: docs/decisions/0001-lazy-slot-context.md Β§6.2 Phase 1 row.
|
| 62 |
+
Milestone: F4 M3 β see docs/features/memory_embedder.md
|
| 63 |
+
Design: docs/decisions/0001-lazy-slot-context.md
|
| 64 |
+
Upstreaming: deferred; propose to llamafile + llama.cpp
|
| 65 |
+
maintainers after F4 M3's full phase-set ships and bench
|
| 66 |
+
numbers are public. This is a generally-useful patch.
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 70 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 71 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 72 |
+
@@ -250,7 +250,15 @@ llama_kv_cache::llama_kv_cache(
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
- // allocate tensors and initialize the buffers to avoid NaNs in the padding
|
| 77 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 78 |
+
+ // Eager malloc, deferred zero-fill. The address-space reservation done
|
| 79 |
+
+ // by ggml_backend_alloc_ctx_tensors_from_buft is essentially free on
|
| 80 |
+
+ // Linux β pages are not committed to RSS until they are first written.
|
| 81 |
+
+ // The `ggml_backend_buffer_clear(buf, 0)` memset below is what commits
|
| 82 |
+
+ // every page. By skipping it for the non-no_alloc path we keep the
|
| 83 |
+
+ // scheduler-visible tensor->buffer wiring intact (sched_reserve runs
|
| 84 |
+
+ // at construction time and asserts buffer_id >= 0) while moving the
|
| 85 |
+
+ // RSS commit to the first ensure_cleared() call.
|
| 86 |
+
for (auto & [buft, ctx] : ctx_map) {
|
| 87 |
+
ggml_backend_buffer_t buf;
|
| 88 |
+
if (model.hparams.no_alloc) {
|
| 89 |
+
@@ -259,7 +267,7 @@ llama_kv_cache::llama_kv_cache(
|
| 90 |
+
t->buffer = buf; // set dummy buffer for KV cache so that the backend scheduler won't try to allocate it
|
| 91 |
+
}
|
| 92 |
+
} else {
|
| 93 |
+
- buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft); // real buffer
|
| 94 |
+
+ buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft); // real buffer (malloc'd but not zeroed yet)
|
| 95 |
+
}
|
| 96 |
+
if (!buf) {
|
| 97 |
+
throw std::runtime_error("failed to allocate buffer for kv cache");
|
| 98 |
+
@@ -267,10 +275,23 @@ llama_kv_cache::llama_kv_cache(
|
| 99 |
+
|
| 100 |
+
LLAMA_LOG_INFO("%s: %10s KV buffer size = %8.2f MiB\n", __func__, ggml_backend_buffer_name(buf), ggml_backend_buffer_get_size(buf)/1024.0/1024.0);
|
| 101 |
+
|
| 102 |
+
- ggml_backend_buffer_clear(buf, 0);
|
| 103 |
+
+ if (model.hparams.no_alloc) {
|
| 104 |
+
+ // Dummy buffer is size 0; clearing is a no-op but keeps the
|
| 105 |
+
+ // upstream invariant that constructed buffers are zeroed.
|
| 106 |
+
+ ggml_backend_buffer_clear(buf, 0);
|
| 107 |
+
+ }
|
| 108 |
+
+ // else: leave the buffer un-cleared. ensure_cleared() will zero it
|
| 109 |
+
+ // on the first inference entry point that touches tensor data.
|
| 110 |
+
+
|
| 111 |
+
ctxs_bufs.emplace_back(std::move(ctx), buf);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
+ if (model.hparams.no_alloc) {
|
| 115 |
+
+ cleared = true;
|
| 116 |
+
+ } else {
|
| 117 |
+
+ LLAMA_LOG_INFO("%s: KV cache zero-fill deferred until first batch\n", __func__);
|
| 118 |
+
+ }
|
| 119 |
+
+
|
| 120 |
+
{
|
| 121 |
+
const size_t memory_size_k = size_k_bytes();
|
| 122 |
+
const size_t memory_size_v = size_v_bytes();
|
| 123 |
+
@@ -327,6 +348,22 @@ llama_kv_cache::llama_kv_cache(
|
| 124 |
+
debug = LLAMA_KV_CACHE_DEBUG ? atoi(LLAMA_KV_CACHE_DEBUG) : 0;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
+// opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 128 |
+
+void llama_kv_cache::ensure_cleared() {
|
| 129 |
+
+ if (cleared) {
|
| 130 |
+
+ return;
|
| 131 |
+
+ }
|
| 132 |
+
+
|
| 133 |
+
+ for (auto & [_, buf] : ctxs_bufs) {
|
| 134 |
+
+ LLAMA_LOG_INFO("%s: %10s KV buffer zero-fill = %8.2f MiB (deferred)\n", __func__,
|
| 135 |
+
+ ggml_backend_buffer_name(buf.get()),
|
| 136 |
+
+ ggml_backend_buffer_get_size(buf.get())/1024.0/1024.0);
|
| 137 |
+
+ ggml_backend_buffer_clear(buf.get(), 0);
|
| 138 |
+
+ }
|
| 139 |
+
+
|
| 140 |
+
+ cleared = true;
|
| 141 |
+
+}
|
| 142 |
+
+
|
| 143 |
+
void llama_kv_cache::clear(bool data) {
|
| 144 |
+
for (uint32_t s = 0; s < n_stream; ++s) {
|
| 145 |
+
v_cells[s].reset();
|
| 146 |
+
@@ -334,8 +371,14 @@ void llama_kv_cache::clear(bool data) {
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
if (data) {
|
| 150 |
+
- for (auto & [_, buf] : ctxs_bufs) {
|
| 151 |
+
- ggml_backend_buffer_clear(buf.get(), 0);
|
| 152 |
+
+ // opencoti F4 M3 Phase 1: if the deferred zero-fill has not happened
|
| 153 |
+
+ // yet there is nothing to clear β v_cells.reset() above already
|
| 154 |
+
+ // satisfies the logical "data cleared" state. Don't commit RSS just
|
| 155 |
+
+ // to zero pages that have never been written.
|
| 156 |
+
+ if (cleared) {
|
| 157 |
+
+ for (auto & [_, buf] : ctxs_bufs) {
|
| 158 |
+
+ ggml_backend_buffer_clear(buf.get(), 0);
|
| 159 |
+
+ }
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
@@ -630,6 +673,9 @@ llama_memory_context_ptr llama_kv_cache::init_batch(
|
| 164 |
+
bool embd_all) {
|
| 165 |
+
GGML_UNUSED(embd_all);
|
| 166 |
+
|
| 167 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 168 |
+
+ ensure_cleared();
|
| 169 |
+
+
|
| 170 |
+
do {
|
| 171 |
+
balloc.split_reset();
|
| 172 |
+
|
| 173 |
+
@@ -674,6 +720,12 @@ llama_memory_context_ptr llama_kv_cache::init_update(llama_context * lctx, bool
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
llama_kv_cache::slot_info_vec_t llama_kv_cache::prepare(const std::vector<llama_ubatch> & ubatches) {
|
| 177 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 178 |
+
+ // The iswa wrapper calls prepare() directly on each inner kv_cache without
|
| 179 |
+
+ // going through init_batch(), so the ensure_cleared() in init_batch alone
|
| 180 |
+
+ // is not enough. Calling here covers both paths and is idempotent.
|
| 181 |
+
+ ensure_cleared();
|
| 182 |
+
+
|
| 183 |
+
llama_kv_cache::slot_info_vec_t res;
|
| 184 |
+
|
| 185 |
+
struct state_t {
|
| 186 |
+
@@ -744,6 +796,15 @@ bool llama_kv_cache::update(llama_context * lctx, bool do_shift, const stream_co
|
| 187 |
+
|
| 188 |
+
auto * sched = lctx->get_sched();
|
| 189 |
+
|
| 190 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 191 |
+
+ // update() can be reached without init_batch first (e.g. update-only paths
|
| 192 |
+
+ // for cross-stream copy or RoPE shift). When that happens with the buffer
|
| 193 |
+
+ // still deferred we have nothing to copy or shift β make the dependency
|
| 194 |
+
+ // explicit so the tensor->data dereferences below are safe.
|
| 195 |
+
+ if (!sc_info.empty() || do_shift) {
|
| 196 |
+
+ ensure_cleared();
|
| 197 |
+
+ }
|
| 198 |
+
+
|
| 199 |
+
if (!sc_info.empty()) {
|
| 200 |
+
assert(n_stream > 1 && "stream copy should never happen with a single stream");
|
| 201 |
+
|
| 202 |
+
@@ -1900,6 +1961,11 @@ void llama_kv_cache::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama
|
| 203 |
+
|
| 204 |
+
GGML_ASSERT(seq_id == -1 || (seq_id >= 0 && (size_t) seq_id < seq_to_stream.size()));
|
| 205 |
+
|
| 206 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 207 |
+
+ // state_read writes the saved KV blob into our tensors, which requires
|
| 208 |
+
+ // backing storage. Force the deferred allocation before we read.
|
| 209 |
+
+ ensure_cleared();
|
| 210 |
+
+
|
| 211 |
+
uint32_t n_stream_cur;
|
| 212 |
+
io.read(&n_stream_cur, sizeof(n_stream_cur));
|
| 213 |
+
if (n_stream_cur != n_stream) {
|
| 214 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 215 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 216 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 217 |
+
@@ -152,6 +152,16 @@ public:
|
| 218 |
+
|
| 219 |
+
bool get_has_shift() const;
|
| 220 |
+
|
| 221 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 222 |
+
+ // Idempotent: zeroes the KV-cache backend buffers on first call.
|
| 223 |
+
+ // The buffers are malloc'd eagerly at construction (so the scheduler
|
| 224 |
+
+ // can resolve tensor->buffer at reserve time) but NOT cleared β on
|
| 225 |
+
+ // Linux, malloc reserves address space without committing pages, so
|
| 226 |
+
+ // RSS stays low until something writes. This method does the memset
|
| 227 |
+
+ // that commits the pages. Called from inference entry points
|
| 228 |
+
+ // (init_batch, prepare, update, state_read).
|
| 229 |
+
+ void ensure_cleared();
|
| 230 |
+
+
|
| 231 |
+
ggml_type type_k() const;
|
| 232 |
+
ggml_type type_v() const;
|
| 233 |
+
|
| 234 |
+
@@ -256,6 +266,13 @@ private:
|
| 235 |
+
// ggml contexts for the KV cache along with the allocated backend buffers:
|
| 236 |
+
std::vector<std::pair<ggml_context_ptr, ggml_backend_buffer_ptr>> ctxs_bufs;
|
| 237 |
+
|
| 238 |
+
+ // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 239 |
+
+ // True once the backend buffers in ctxs_bufs have been zero-filled.
|
| 240 |
+
+ // The constructor sets this true for no_alloc models (whose dummy
|
| 241 |
+
+ // buffers are size 0 β clearing is a no-op) and false otherwise. The
|
| 242 |
+
+ // first ensure_cleared() call zeroes the buffers and flips this true.
|
| 243 |
+
+ bool cleared = false;
|
| 244 |
+
+
|
| 245 |
+
// the current index from where we start searching for a free slot in the ring buffer of KV cells (see find_slot())
|
| 246 |
+
// note: this is not part of the KV state and it's only used to speed-up the find_slot() method
|
| 247 |
+
std::vector<uint32_t> v_heads;
|
patches/0002-lazy-slot-context-grow.patch
ADDED
|
@@ -0,0 +1,596 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 2/N] lazy slot-context allocation β Phase 2 (grow on demand + partial zero-fill)
|
| 3 |
+
|
| 4 |
+
Phase 2 of the F4 M3 lazy slot-context rollout. Builds on Phase 1
|
| 5 |
+
(deferred zero-fill) by adding a soft allocation cap that
|
| 6 |
+
find_slot honors, a grow-on-demand path that bumps the cap when
|
| 7 |
+
real prompts need more cells, and a partial-range zero-fill so
|
| 8 |
+
RSS commits scale with actual workload β not with the configured
|
| 9 |
+
n_ctx_slot Γ slot_count product.
|
| 10 |
+
|
| 11 |
+
The end-state for the production embedder
|
| 12 |
+
(qwen3-0.6b, --parallel 2 --ctx-size 16384, CPU mode):
|
| 13 |
+
- With --slot-initial-ctx 0 (the default) Phase 2 is a no-op
|
| 14 |
+
layered on Phase 1: cap == n_ctx_seq from construction; full
|
| 15 |
+
buffer cleared on first batch (matches Phase 1 RSS profile).
|
| 16 |
+
- With --slot-initial-ctx 4096 (the recommended embedder
|
| 17 |
+
setting) the warm RSS scales with whatever cells real prompts
|
| 18 |
+
have touched. A run that only ever embeds 8β56-token requests
|
| 19 |
+
commits ~4096 Γ per-cell bytes, not the full 16384.
|
| 20 |
+
|
| 21 |
+
The patch threads `slot_initial_ctx` through:
|
| 22 |
+
- public API: llama_context_params (include/llama.h)
|
| 23 |
+
- internal: llama_cparams (src/llama-cparams.h)
|
| 24 |
+
- ctor chain: llama_context::ctor (src/llama-context.cpp)
|
| 25 |
+
llama_kv_cache::ctor (src/llama-kv-cache.{h,cpp})
|
| 26 |
+
llama_kv_cache_iswa::ctor (src/llama-kv-cache-iswa.{h,cpp})
|
| 27 |
+
llama_model::create_memory call sites (src/llama-model.cpp)
|
| 28 |
+
- hybrid mem: src/llama-memory-hybrid.cpp + -iswa.cpp pass 0
|
| 29 |
+
(kept on Phase-1 behavior β recurrent/hybrid models
|
| 30 |
+
are not the F4 embedder target)
|
| 31 |
+
- CLI: common_params.slot_initial_ctx + --slot-initial-ctx
|
| 32 |
+
(common/common.{h,cpp}, common/arg.cpp)
|
| 33 |
+
|
| 34 |
+
Mechanism (inside llama_kv_cache):
|
| 35 |
+
- kv_size_max: hard cap (== constructor's kv_size).
|
| 36 |
+
- kv_size_alloc: soft cap; cells [0, kv_size_alloc) visible to
|
| 37 |
+
find_slot. Starts at min(slot_initial_ctx,
|
| 38 |
+
kv_size_max) β Phase 1 default of 0 collapses
|
| 39 |
+
to kv_size_max so find_slot sees everything.
|
| 40 |
+
- n_cells_cleared: half-open range of cells already zero-filled.
|
| 41 |
+
Phase 1's `bool cleared` is replaced; the cells
|
| 42 |
+
beyond n_cells_cleared have malloc'd address
|
| 43 |
+
space but uncommitted pages.
|
| 44 |
+
- ensure_cleared(up_to):
|
| 45 |
+
Partial memset of cells [n_cells_cleared, up_to) on each
|
| 46 |
+
layer's K and V tensor across every stream, using
|
| 47 |
+
ggml_backend_tensor_memset (host-or-device dispatch). The
|
| 48 |
+
common inference path passes up_to=0 which expands to the
|
| 49 |
+
current soft cap; state_read passes up_to=kv_size_max for
|
| 50 |
+
full coverage on the deserialize path.
|
| 51 |
+
- ensure_capacity(cells_needed):
|
| 52 |
+
Geometric grow (next-pow2, n_pad-aligned) of kv_size_alloc
|
| 53 |
+
up to kv_size_max. Monotonic β never shrinks (Phase 3 adds
|
| 54 |
+
shrink-on-idle).
|
| 55 |
+
- prepare(ubatches):
|
| 56 |
+
When find_slot fails inside the current soft cap, retries
|
| 57 |
+
after ensure_capacity(used_max + ubatch.n_tokens) +
|
| 58 |
+
ensure_cleared(). Bounded by kv_size_max; a genuine
|
| 59 |
+
overflow at the hard cap propagates up unchanged.
|
| 60 |
+
|
| 61 |
+
Bench: docs/decisions/0001-lazy-slot-context.md Β§6.2 Phase 2 row.
|
| 62 |
+
Milestone: F4 M3 β see docs/features/memory_embedder.md
|
| 63 |
+
Design: docs/decisions/0001-lazy-slot-context.md
|
| 64 |
+
Upstreaming: deferred; propose to llamafile + llama.cpp
|
| 65 |
+
maintainers after F4 M3's full phase-set ships and bench
|
| 66 |
+
numbers are public.
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 70 |
+
--- a/llama.cpp/common/arg.cpp
|
| 71 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 72 |
+
@@ -1361,6 +1361,16 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 73 |
+
params.kv_unified = value;
|
| 74 |
+
}
|
| 75 |
+
).set_env("LLAMA_ARG_KV_UNIFIED").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_PERPLEXITY, LLAMA_EXAMPLE_BATCHED, LLAMA_EXAMPLE_BENCH, LLAMA_EXAMPLE_PARALLEL}));
|
| 76 |
+
+ add_opt(common_arg(
|
| 77 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 78 |
+
+ {"--slot-initial-ctx"}, "N",
|
| 79 |
+
+ string_format("initial KV-cell allocation per sequence (default: %d, 0 = full n_ctx_seq, "
|
| 80 |
+
+ "positive values cap initial allocation and enable grow-on-demand)",
|
| 81 |
+
+ params.slot_initial_ctx),
|
| 82 |
+
+ [](common_params & params, int value) {
|
| 83 |
+
+ params.slot_initial_ctx = value;
|
| 84 |
+
+ }
|
| 85 |
+
+ ).set_env("LLAMA_ARG_SLOT_INITIAL_CTX").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_EMBEDDING, LLAMA_EXAMPLE_PARALLEL}));
|
| 86 |
+
add_opt(common_arg(
|
| 87 |
+
{"--cache-idle-slots"},
|
| 88 |
+
{"--no-cache-idle-slots"},
|
| 89 |
+
diff --git a/llama.cpp/common/common.cpp b/llama.cpp/common/common.cpp
|
| 90 |
+
--- a/llama.cpp/common/common.cpp
|
| 91 |
+
+++ b/llama.cpp/common/common.cpp
|
| 92 |
+
@@ -1628,6 +1628,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
|
| 93 |
+
|
| 94 |
+
cparams.n_ctx = params.n_ctx;
|
| 95 |
+
cparams.n_seq_max = params.n_parallel;
|
| 96 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 97 |
+
+ cparams.slot_initial_ctx = params.slot_initial_ctx < 0 ? 0u : (uint32_t) params.slot_initial_ctx;
|
| 98 |
+
cparams.n_rs_seq = params.speculative.need_n_rs_seq();
|
| 99 |
+
cparams.n_batch = params.n_batch;
|
| 100 |
+
cparams.n_ubatch = params.n_ubatch;
|
| 101 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 102 |
+
--- a/llama.cpp/common/common.h
|
| 103 |
+
+++ b/llama.cpp/common/common.h
|
| 104 |
+
@@ -425,6 +425,11 @@ struct ggml_opt_optimizer_params common_opt_lr_pars(void * userdata);
|
| 105 |
+
struct common_params {
|
| 106 |
+
int32_t n_predict = -1; // max. number of new tokens to predict, -1 == no limit
|
| 107 |
+
int32_t n_ctx = 0; // context size, 0 == context the model was trained with
|
| 108 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 109 |
+
+ // Initial KV-cell allocation per sequence. 0 = full n_ctx_seq allocation
|
| 110 |
+
+ // (Phase 1 behavior β only zero-fill is deferred). Positive values cap
|
| 111 |
+
+ // the initial allocation and enable grow-on-demand up to n_ctx_seq.
|
| 112 |
+
+ int32_t slot_initial_ctx = 0;
|
| 113 |
+
int32_t n_batch = 2048; // logical batch size for prompt processing (must be >=32 to use BLAS)
|
| 114 |
+
int32_t n_ubatch = 512; // physical batch size for prompt processing (must be >=32 to use BLAS)
|
| 115 |
+
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
| 116 |
+
diff --git a/llama.cpp/include/llama.h b/llama.cpp/include/llama.h
|
| 117 |
+
--- a/llama.cpp/include/llama.h
|
| 118 |
+
+++ b/llama.cpp/include/llama.h
|
| 119 |
+
@@ -338,6 +338,14 @@ extern "C" {
|
| 120 |
+
uint32_t n_batch; // logical maximum batch size that can be submitted to llama_decode
|
| 121 |
+
uint32_t n_ubatch; // physical maximum batch size
|
| 122 |
+
uint32_t n_seq_max; // max number of sequences (i.e. distinct states for recurrent models)
|
| 123 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 124 |
+
+ // Initial KV-cell allocation per sequence. 0 = behave like Phase 1
|
| 125 |
+
+ // (allocate the full n_ctx_seq up front, only zero-fill is deferred).
|
| 126 |
+
+ // Positive values cap the initial allocation at min(slot_initial_ctx,
|
| 127 |
+
+ // n_ctx_seq); subsequent prompts that exceed this cap trigger a grow
|
| 128 |
+
+ // up to next_pow2 (still capped at n_ctx_seq). Reduces warm-state RSS
|
| 129 |
+
+ // when typical prompts are much shorter than n_ctx_seq.
|
| 130 |
+
+ uint32_t slot_initial_ctx;
|
| 131 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback (0 = no rollback) [EXPERIMENTAL]
|
| 132 |
+
int32_t n_threads; // number of threads to use for generation
|
| 133 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 134 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 135 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 136 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 137 |
+
@@ -58,6 +58,9 @@ llama_context::llama_context(
|
| 138 |
+
cparams.n_rs_seq = 0;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 142 |
+
+ cparams.slot_initial_ctx = params.slot_initial_ctx;
|
| 143 |
+
+
|
| 144 |
+
cparams.n_threads = params.n_threads;
|
| 145 |
+
cparams.n_threads_batch = params.n_threads_batch;
|
| 146 |
+
cparams.yarn_ext_factor = params.yarn_ext_factor >= 0.0f ? params.yarn_ext_factor : hparams.yarn_ext_factor;
|
| 147 |
+
@@ -3346,6 +3349,7 @@ llama_context_params llama_context_default_params() {
|
| 148 |
+
/*.n_batch =*/ 2048,
|
| 149 |
+
/*.n_ubatch =*/ 512,
|
| 150 |
+
/*.n_seq_max =*/ 1,
|
| 151 |
+
+ /*.slot_initial_ctx =*/ 0,
|
| 152 |
+
/*.n_rs_seq =*/ 0,
|
| 153 |
+
/*.n_threads =*/ GGML_DEFAULT_N_THREADS, // TODO: better default
|
| 154 |
+
/*.n_threads_batch =*/ GGML_DEFAULT_N_THREADS,
|
| 155 |
+
diff --git a/llama.cpp/src/llama-cparams.h b/llama.cpp/src/llama-cparams.h
|
| 156 |
+
--- a/llama.cpp/src/llama-cparams.h
|
| 157 |
+
+++ b/llama.cpp/src/llama-cparams.h
|
| 158 |
+
@@ -12,6 +12,10 @@ struct llama_cparams {
|
| 159 |
+
uint32_t n_batch;
|
| 160 |
+
uint32_t n_ubatch;
|
| 161 |
+
uint32_t n_seq_max;
|
| 162 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 163 |
+
+ // 0 = full n_ctx_seq allocation. Positive value caps the initial KV-cell
|
| 164 |
+
+ // allocation per sequence and enables grow-on-demand up to n_ctx_seq.
|
| 165 |
+
+ uint32_t slot_initial_ctx;
|
| 166 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback
|
| 167 |
+
int32_t n_threads; // number of threads to use for generation
|
| 168 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 169 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.cpp b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 170 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 171 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 172 |
+
@@ -20,6 +20,7 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 173 |
+
bool swa_full,
|
| 174 |
+
bool unified,
|
| 175 |
+
uint32_t kv_size,
|
| 176 |
+
+ uint32_t kv_size_initial,
|
| 177 |
+
uint32_t n_seq_max,
|
| 178 |
+
uint32_t n_ubatch,
|
| 179 |
+
uint32_t n_pad,
|
| 180 |
+
@@ -61,14 +62,14 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 181 |
+
|
| 182 |
+
kv_base = std::make_unique<llama_kv_cache>(
|
| 183 |
+
model, type_k, type_v,
|
| 184 |
+
- v_trans, offload, unified, size_base, n_seq_max, n_pad,
|
| 185 |
+
+ v_trans, offload, unified, size_base, kv_size_initial, n_seq_max, n_pad,
|
| 186 |
+
0, LLAMA_SWA_TYPE_NONE, filter_base, reuse);
|
| 187 |
+
|
| 188 |
+
LLAMA_LOG_INFO("%s: creating SWA KV cache, size = %u cells\n", __func__, size_swa);
|
| 189 |
+
|
| 190 |
+
kv_swa = std::make_unique<llama_kv_cache>(
|
| 191 |
+
model, type_k, type_v,
|
| 192 |
+
- v_trans, offload, unified, size_swa, n_seq_max, n_pad,
|
| 193 |
+
+ v_trans, offload, unified, size_swa, kv_size_initial, n_seq_max, n_pad,
|
| 194 |
+
hparams.n_swa, hparams.swa_type, filter_swa, reuse);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.h b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 198 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.h
|
| 199 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 200 |
+
@@ -22,6 +22,9 @@ public:
|
| 201 |
+
bool swa_full,
|
| 202 |
+
bool unified,
|
| 203 |
+
uint32_t kv_size,
|
| 204 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 205 |
+
+ // Forwarded to both kv_base and kv_swa. 0 = full kv_size.
|
| 206 |
+
+ uint32_t kv_size_initial,
|
| 207 |
+
uint32_t n_seq_max,
|
| 208 |
+
uint32_t n_ubatch,
|
| 209 |
+
uint32_t n_pad,
|
| 210 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 211 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 212 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 213 |
+
@@ -85,6 +85,7 @@ llama_kv_cache::llama_kv_cache(
|
| 214 |
+
bool offload,
|
| 215 |
+
bool unified,
|
| 216 |
+
uint32_t kv_size,
|
| 217 |
+
+ uint32_t kv_size_initial,
|
| 218 |
+
uint32_t n_seq_max,
|
| 219 |
+
uint32_t n_pad,
|
| 220 |
+
uint32_t n_swa,
|
| 221 |
+
@@ -96,6 +97,24 @@ llama_kv_cache::llama_kv_cache(
|
| 222 |
+
|
| 223 |
+
GGML_ASSERT(kv_size % n_pad == 0);
|
| 224 |
+
|
| 225 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 226 |
+
+ // Initialize the hard cap and the soft cap. kv_size_initial==0 (the
|
| 227 |
+
+ // legacy / Phase-1 default) means "no soft cap" so the whole kv_size
|
| 228 |
+
+ // is visible to find_slot. A positive value is clamped to [n_pad,
|
| 229 |
+
+ // kv_size] and rounded up to the next n_pad multiple.
|
| 230 |
+
+ kv_size_max = kv_size;
|
| 231 |
+
+ if (kv_size_initial == 0) {
|
| 232 |
+
+ kv_size_alloc = kv_size_max;
|
| 233 |
+
+ } else {
|
| 234 |
+
+ uint32_t cap = std::min(kv_size_initial, kv_size_max);
|
| 235 |
+
+ cap = std::max(cap, n_pad);
|
| 236 |
+
+ cap = ((cap + n_pad - 1) / n_pad) * n_pad;
|
| 237 |
+
+ cap = std::min(cap, kv_size_max);
|
| 238 |
+
+ kv_size_alloc = cap;
|
| 239 |
+
+ LLAMA_LOG_INFO("%s: slot_initial_ctx = %u cells (cap = %u)\n",
|
| 240 |
+
+ __func__, kv_size_alloc, kv_size_max);
|
| 241 |
+
+ }
|
| 242 |
+
+
|
| 243 |
+
const uint32_t n_layer_kv = hparams.n_layer_kv();
|
| 244 |
+
|
| 245 |
+
// define a comparator for the buft -> ctx map to ensure that the order is well-defined:
|
| 246 |
+
@@ -287,7 +306,9 @@ llama_kv_cache::llama_kv_cache(
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
if (model.hparams.no_alloc) {
|
| 250 |
+
- cleared = true;
|
| 251 |
+
+ // opencoti F4 M3 Phase 1+2 β dummy buffers are size 0; treat all
|
| 252 |
+
+ // cells as "already cleared" so ensure_cleared is a no-op.
|
| 253 |
+
+ n_cells_cleared = kv_size_max;
|
| 254 |
+
} else {
|
| 255 |
+
LLAMA_LOG_INFO("%s: KV cache zero-fill deferred until first batch\n", __func__);
|
| 256 |
+
}
|
| 257 |
+
@@ -348,20 +369,81 @@ llama_kv_cache::llama_kv_cache(
|
| 258 |
+
debug = LLAMA_KV_CACHE_DEBUG ? atoi(LLAMA_KV_CACHE_DEBUG) : 0;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
-// opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 262 |
+
-void llama_kv_cache::ensure_cleared() {
|
| 263 |
+
- if (cleared) {
|
| 264 |
+
+// opencoti F4 M3 Phase 1+2 β see docs/decisions/0001-lazy-slot-context.md
|
| 265 |
+
+void llama_kv_cache::ensure_cleared(uint32_t up_to_cells) {
|
| 266 |
+
+ // up_to_cells == 0 means "clear up to the current soft cap". Callers
|
| 267 |
+
+ // that need full coverage (state_read) pass kv_size_max.
|
| 268 |
+
+ const uint32_t target = up_to_cells == 0
|
| 269 |
+
+ ? kv_size_alloc
|
| 270 |
+
+ : std::min(up_to_cells, kv_size_max);
|
| 271 |
+
+
|
| 272 |
+
+ if (n_cells_cleared >= target) {
|
| 273 |
+
return;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
- for (auto & [_, buf] : ctxs_bufs) {
|
| 277 |
+
- LLAMA_LOG_INFO("%s: %10s KV buffer zero-fill = %8.2f MiB (deferred)\n", __func__,
|
| 278 |
+
- ggml_backend_buffer_name(buf.get()),
|
| 279 |
+
- ggml_backend_buffer_get_size(buf.get())/1024.0/1024.0);
|
| 280 |
+
- ggml_backend_buffer_clear(buf.get(), 0);
|
| 281 |
+
+ const uint32_t a = n_cells_cleared;
|
| 282 |
+
+ const uint32_t b = target;
|
| 283 |
+
+
|
| 284 |
+
+ // Per-layer, per-stream partial memset of the K and V tensors.
|
| 285 |
+
+ // ggml_backend_tensor_memset handles the host/device dispatch β for the
|
| 286 |
+
+ // CPU backend (the only one the embedder uses today) this is a direct
|
| 287 |
+
+ // memset on the malloc'd buffer; for offloaded buffers it's a backend
|
| 288 |
+
+ // op. Either way only bytes for cells [a, b) get touched, so on Linux
|
| 289 |
+
+ // only those pages get committed to RSS.
|
| 290 |
+
+ for (const auto & layer : layers) {
|
| 291 |
+
+ for (uint32_t st = 0; st < n_stream; ++st) {
|
| 292 |
+
+ if (layer.k) {
|
| 293 |
+
+ const size_t off = (size_t) st * (size_t) layer.k->nb[2]
|
| 294 |
+
+ + (size_t) a * (size_t) layer.k->nb[1];
|
| 295 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) layer.k->nb[1];
|
| 296 |
+
+ ggml_backend_tensor_memset(layer.k, 0, off, sz);
|
| 297 |
+
+ }
|
| 298 |
+
+ if (layer.v) {
|
| 299 |
+
+ const size_t off = (size_t) st * (size_t) layer.v->nb[2]
|
| 300 |
+
+ + (size_t) a * (size_t) layer.v->nb[1];
|
| 301 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) layer.v->nb[1];
|
| 302 |
+
+ ggml_backend_tensor_memset(layer.v, 0, off, sz);
|
| 303 |
+
+ }
|
| 304 |
+
+ }
|
| 305 |
+
+ }
|
| 306 |
+
+
|
| 307 |
+
+ LLAMA_LOG_INFO("%s: cleared KV cells [%u, %u) of %u (%s)\n",
|
| 308 |
+
+ __func__, a, b, kv_size_max,
|
| 309 |
+
+ up_to_cells == 0 ? "soft cap" : "full");
|
| 310 |
+
+
|
| 311 |
+
+ n_cells_cleared = b;
|
| 312 |
+
+}
|
| 313 |
+
+
|
| 314 |
+
+// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 315 |
+
+bool llama_kv_cache::ensure_capacity(uint32_t cells_needed) {
|
| 316 |
+
+ if (cells_needed <= kv_size_alloc) {
|
| 317 |
+
+ return true;
|
| 318 |
+
+ }
|
| 319 |
+
+
|
| 320 |
+
+ if (kv_size_alloc >= kv_size_max) {
|
| 321 |
+
+ return false;
|
| 322 |
+
+ }
|
| 323 |
+
+
|
| 324 |
+
+ // Grow geometrically (next power-of-two) and align up to n_pad. The
|
| 325 |
+
+ // pow2 ramp avoids re-growing every few tokens for long prompts; n_pad
|
| 326 |
+
+ // alignment preserves the constructor's GGML_ASSERT(kv_size % n_pad == 0)
|
| 327 |
+
+ // invariant for find_slot.
|
| 328 |
+
+ uint32_t new_cap = std::max<uint32_t>(kv_size_alloc, n_pad);
|
| 329 |
+
+ while (new_cap < cells_needed && new_cap < kv_size_max) {
|
| 330 |
+
+ new_cap *= 2;
|
| 331 |
+
}
|
| 332 |
+
+ new_cap = ((new_cap + n_pad - 1) / n_pad) * n_pad;
|
| 333 |
+
+ new_cap = std::min(new_cap, kv_size_max);
|
| 334 |
+
|
| 335 |
+
- cleared = true;
|
| 336 |
+
+ if (new_cap == kv_size_alloc) {
|
| 337 |
+
+ return cells_needed <= kv_size_alloc;
|
| 338 |
+
+ }
|
| 339 |
+
+
|
| 340 |
+
+ LLAMA_LOG_INFO("%s: grow soft cap %u -> %u (need %u, max %u)\n",
|
| 341 |
+
+ __func__, kv_size_alloc, new_cap, cells_needed, kv_size_max);
|
| 342 |
+
+
|
| 343 |
+
+ kv_size_alloc = new_cap;
|
| 344 |
+
+ return cells_needed <= kv_size_alloc;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
void llama_kv_cache::clear(bool data) {
|
| 348 |
+
@@ -371,14 +453,16 @@ void llama_kv_cache::clear(bool data) {
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
if (data) {
|
| 352 |
+
- // opencoti F4 M3 Phase 1: if the deferred zero-fill has not happened
|
| 353 |
+
- // yet there is nothing to clear β v_cells.reset() above already
|
| 354 |
+
- // satisfies the logical "data cleared" state. Don't commit RSS just
|
| 355 |
+
- // to zero pages that have never been written.
|
| 356 |
+
- if (cleared) {
|
| 357 |
+
- for (auto & [_, buf] : ctxs_bufs) {
|
| 358 |
+
- ggml_backend_buffer_clear(buf.get(), 0);
|
| 359 |
+
- }
|
| 360 |
+
+ // opencoti F4 M3 Phase 1+2: if no zero-fill has happened yet there
|
| 361 |
+
+ // is nothing to clear β v_cells.reset() above already satisfies the
|
| 362 |
+
+ // logical "data cleared" state. Don't commit RSS just to zero pages
|
| 363 |
+
+ // that have never been written. When a partial clear is already on
|
| 364 |
+
+ // the books, re-issue it against the current soft cap so any cells
|
| 365 |
+
+ // touched between the first ensure_cleared and now are re-zeroed.
|
| 366 |
+
+ if (n_cells_cleared > 0) {
|
| 367 |
+
+ const uint32_t b = n_cells_cleared;
|
| 368 |
+
+ n_cells_cleared = 0;
|
| 369 |
+
+ ensure_cleared(b);
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
}
|
| 373 |
+
@@ -743,10 +827,28 @@ llama_kv_cache::slot_info_vec_t llama_kv_cache::prepare(const std::vector<llama_
|
| 374 |
+
|
| 375 |
+
for (const auto & ubatch : ubatches) {
|
| 376 |
+
// only find a suitable slot for the ubatch. don't modify the cells yet
|
| 377 |
+
- const auto sinfo_new = find_slot(ubatch, false);
|
| 378 |
+
+ auto sinfo_new = find_slot(ubatch, false);
|
| 379 |
+
if (sinfo_new.empty()) {
|
| 380 |
+
- success = false;
|
| 381 |
+
- break;
|
| 382 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 383 |
+
+ // Phase 2 keeps the soft alloc cap (kv_size_alloc) below kv_size_max
|
| 384 |
+
+ // until a real ubatch needs more cells than the cap exposes. When
|
| 385 |
+
+ // find_slot fails, try to grow the cap to cover what we already
|
| 386 |
+
+ // know is used plus the ubatch's token count, then retry. The grow
|
| 387 |
+
+ // is bounded by kv_size_max; if we are already at the hard cap the
|
| 388 |
+
+ // failure is genuine and we propagate it up.
|
| 389 |
+
+ uint32_t used_max = 0;
|
| 390 |
+
+ for (uint32_t st = 0; st < n_stream; ++st) {
|
| 391 |
+
+ used_max = std::max(used_max, v_cells[st].used_max_p1());
|
| 392 |
+
+ }
|
| 393 |
+
+ const uint32_t want = used_max + ubatch.n_tokens;
|
| 394 |
+
+ if (ensure_capacity(want)) {
|
| 395 |
+
+ ensure_cleared();
|
| 396 |
+
+ sinfo_new = find_slot(ubatch, false);
|
| 397 |
+
+ }
|
| 398 |
+
+ if (sinfo_new.empty()) {
|
| 399 |
+
+ success = false;
|
| 400 |
+
+ break;
|
| 401 |
+
+ }
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
// remember the position that we found
|
| 405 |
+
@@ -981,14 +1083,20 @@ llama_kv_cache::slot_info llama_kv_cache::find_slot(const llama_ubatch & ubatch,
|
| 406 |
+
|
| 407 |
+
uint32_t head_cur = v_heads[seq_to_stream[seq_id]];
|
| 408 |
+
|
| 409 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 410 |
+
+ // The Phase-1 search swept the full hard cap (cells.size() == kv_size_max).
|
| 411 |
+
+ // Phase 2 caps it at the current soft alloc; prepare() grows the cap and
|
| 412 |
+
+ // retries when the soft cap is too small for the requested ubatch.
|
| 413 |
+
+ const uint32_t cap = kv_size_alloc;
|
| 414 |
+
+
|
| 415 |
+
// if we have enough unused cells before the current head ->
|
| 416 |
+
// better to start searching from the beginning of the cache, hoping to fill it
|
| 417 |
+
if (head_cur > cells.get_used() + 2*n_tokens) {
|
| 418 |
+
head_cur = 0;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
- if (n_tokens > cells.size()) {
|
| 422 |
+
- LLAMA_LOG_ERROR("%s: n_tokens = %d > size = %u\n", __func__, n_tokens, cells.size());
|
| 423 |
+
+ if (n_tokens > cap) {
|
| 424 |
+
+ // not necessarily fatal β prepare() may grow the cap and retry
|
| 425 |
+
return { };
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
@@ -999,8 +1107,8 @@ llama_kv_cache::slot_info llama_kv_cache::find_slot(const llama_ubatch & ubatch,
|
| 429 |
+
const uint32_t n_test = cont ? n_tokens : 1;
|
| 430 |
+
|
| 431 |
+
while (true) {
|
| 432 |
+
- if (head_cur + n_test > cells.size()) {
|
| 433 |
+
- n_tested += cells.size() - head_cur;
|
| 434 |
+
+ if (head_cur + n_test > cap) {
|
| 435 |
+
+ n_tested += cap - head_cur;
|
| 436 |
+
head_cur = 0;
|
| 437 |
+
continue;
|
| 438 |
+
}
|
| 439 |
+
@@ -1058,7 +1166,7 @@ llama_kv_cache::slot_info llama_kv_cache::find_slot(const llama_ubatch & ubatch,
|
| 440 |
+
res.idxs[s].clear();
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
- if (n_tested >= cells.size()) {
|
| 444 |
+
+ if (n_tested >= cap) {
|
| 445 |
+
//LLAMA_LOG_ERROR("%s: failed to find a slot for %d tokens\n", __func__, n_tokens);
|
| 446 |
+
return { };
|
| 447 |
+
}
|
| 448 |
+
@@ -1961,10 +2069,13 @@ void llama_kv_cache::state_read(llama_io_read_i & io, llama_seq_id seq_id, llama
|
| 449 |
+
|
| 450 |
+
GGML_ASSERT(seq_id == -1 || (seq_id >= 0 && (size_t) seq_id < seq_to_stream.size()));
|
| 451 |
+
|
| 452 |
+
- // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 453 |
+
- // state_read writes the saved KV blob into our tensors, which requires
|
| 454 |
+
- // backing storage. Force the deferred allocation before we read.
|
| 455 |
+
- ensure_cleared();
|
| 456 |
+
+ // opencoti F4 M3 Phase 1+2 β see docs/decisions/0001-lazy-slot-context.md
|
| 457 |
+
+ // state_read writes the saved KV blob into our tensors at arbitrary
|
| 458 |
+
+ // offsets, so we cannot rely on the soft cap. Force a full-range
|
| 459 |
+
+ // zero-fill before we read β this commits all KV pages but only on
|
| 460 |
+
+ // the explicit deserialize path.
|
| 461 |
+
+ ensure_capacity(kv_size_max);
|
| 462 |
+
+ ensure_cleared(kv_size_max);
|
| 463 |
+
|
| 464 |
+
uint32_t n_stream_cur;
|
| 465 |
+
io.read(&n_stream_cur, sizeof(n_stream_cur));
|
| 466 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 467 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 468 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 469 |
+
@@ -101,6 +101,11 @@ public:
|
| 470 |
+
bool offload,
|
| 471 |
+
bool unified,
|
| 472 |
+
uint32_t kv_size,
|
| 473 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 474 |
+
+ // Initial allocation cap. 0 means the same as kv_size
|
| 475 |
+
+ // (Phase 1 behavior). Otherwise clamped to [n_pad, kv_size]
|
| 476 |
+
+ // and rounded up to the next n_pad multiple.
|
| 477 |
+
+ uint32_t kv_size_initial,
|
| 478 |
+
uint32_t n_seq_max,
|
| 479 |
+
uint32_t n_pad,
|
| 480 |
+
uint32_t n_swa,
|
| 481 |
+
@@ -152,15 +157,28 @@ public:
|
| 482 |
+
|
| 483 |
+
bool get_has_shift() const;
|
| 484 |
+
|
| 485 |
+
- // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 486 |
+
- // Idempotent: zeroes the KV-cache backend buffers on first call.
|
| 487 |
+
- // The buffers are malloc'd eagerly at construction (so the scheduler
|
| 488 |
+
- // can resolve tensor->buffer at reserve time) but NOT cleared β on
|
| 489 |
+
- // Linux, malloc reserves address space without committing pages, so
|
| 490 |
+
- // RSS stays low until something writes. This method does the memset
|
| 491 |
+
- // that commits the pages. Called from inference entry points
|
| 492 |
+
- // (init_batch, prepare, update, state_read).
|
| 493 |
+
- void ensure_cleared();
|
| 494 |
+
+ // opencoti F4 M3 Phase 1+2 β see docs/decisions/0001-lazy-slot-context.md
|
| 495 |
+
+ // Idempotent. Zeroes cells [n_cells_cleared, up_to_cells) of every KV
|
| 496 |
+
+ // layer's K and V tensors across every stream. The buffers are malloc'd
|
| 497 |
+
+ // eagerly at construction (so the scheduler can resolve tensor->buffer
|
| 498 |
+
+ // at reserve time) but their pages are not committed to RSS until they
|
| 499 |
+
+ // are first written. Partial memset (via ggml_backend_tensor_memset)
|
| 500 |
+
+ // commits only the requested range β that is the Phase 2 RSS win.
|
| 501 |
+
+ //
|
| 502 |
+
+ // up_to_cells == 0 means "use the current soft cap kv_size_alloc"
|
| 503 |
+
+ // (the normal inference path). Pass kv_size_max for paths that need
|
| 504 |
+
+ // full coverage (state_read).
|
| 505 |
+
+ void ensure_cleared(uint32_t up_to_cells = 0);
|
| 506 |
+
+
|
| 507 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 508 |
+
+ // Grow the soft allocation cap. Returns true iff the cap covers
|
| 509 |
+
+ // cells_needed after the grow. Bound by kv_size_max. Idempotent at
|
| 510 |
+
+ // the cap.
|
| 511 |
+
+ bool ensure_capacity(uint32_t cells_needed);
|
| 512 |
+
+
|
| 513 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 514 |
+
+ // The current soft cap (cells visible to find_slot).
|
| 515 |
+
+ uint32_t get_kv_size_alloc() const { return kv_size_alloc; }
|
| 516 |
+
|
| 517 |
+
ggml_type type_k() const;
|
| 518 |
+
ggml_type type_v() const;
|
| 519 |
+
@@ -266,12 +284,22 @@ private:
|
| 520 |
+
// ggml contexts for the KV cache along with the allocated backend buffers:
|
| 521 |
+
std::vector<std::pair<ggml_context_ptr, ggml_backend_buffer_ptr>> ctxs_bufs;
|
| 522 |
+
|
| 523 |
+
- // opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 524 |
+
- // True once the backend buffers in ctxs_bufs have been zero-filled.
|
| 525 |
+
- // The constructor sets this true for no_alloc models (whose dummy
|
| 526 |
+
- // buffers are size 0 β clearing is a no-op) and false otherwise. The
|
| 527 |
+
- // first ensure_cleared() call zeroes the buffers and flips this true.
|
| 528 |
+
- bool cleared = false;
|
| 529 |
+
+ // opencoti F4 M3 Phase 1+2 β see docs/decisions/0001-lazy-slot-context.md
|
| 530 |
+
+ // Cells in the half-open range [0, n_cells_cleared) are guaranteed to
|
| 531 |
+
+ // be zeroed across every layer's K and V tensors and every stream.
|
| 532 |
+
+ // ensure_cleared(up_to) grows this monotonically up to kv_size_alloc
|
| 533 |
+
+ // (or kv_size_max for the explicit-full call). The constructor sets
|
| 534 |
+
+ // this to kv_size_max for no_alloc models (whose dummy buffers are
|
| 535 |
+
+ // size 0 β clearing is a no-op) and 0 otherwise.
|
| 536 |
+
+ uint32_t n_cells_cleared = 0;
|
| 537 |
+
+
|
| 538 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 539 |
+
+ // kv_size_max: hard cap on cells (set from constructor's kv_size).
|
| 540 |
+
+ // kv_size_alloc: current soft cap. find_slot only scans cells [0, kv_size_alloc).
|
| 541 |
+
+ // Grows via ensure_capacity, never shrinks (Phase 2; Phase 3
|
| 542 |
+
+ // adds shrink-on-idle).
|
| 543 |
+
+ uint32_t kv_size_max = 0;
|
| 544 |
+
+ uint32_t kv_size_alloc = 0;
|
| 545 |
+
|
| 546 |
+
// the current index from where we start searching for a free slot in the ring buffer of KV cells (see find_slot())
|
| 547 |
+
// note: this is not part of the KV state and it's only used to speed-up the find_slot() method
|
| 548 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid-iswa.cpp b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 549 |
+
--- a/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 550 |
+
+++ b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 551 |
+
@@ -40,6 +40,10 @@ llama_memory_hybrid_iswa::llama_memory_hybrid_iswa(
|
| 552 |
+
swa_full,
|
| 553 |
+
unified,
|
| 554 |
+
kv_size,
|
| 555 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 556 |
+
+ // Hybrid-iswa does not yet thread slot_initial_ctx; pass 0 for
|
| 557 |
+
+ // Phase 1 backward-compat (full eager allocation).
|
| 558 |
+
+ 0,
|
| 559 |
+
n_seq_max,
|
| 560 |
+
n_ubatch,
|
| 561 |
+
n_pad,
|
| 562 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid.cpp b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 563 |
+
--- a/llama.cpp/src/llama-memory-hybrid.cpp
|
| 564 |
+
+++ b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 565 |
+
@@ -39,6 +39,10 @@ llama_memory_hybrid::llama_memory_hybrid(
|
| 566 |
+
offload,
|
| 567 |
+
unified,
|
| 568 |
+
kv_size,
|
| 569 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 570 |
+
+ // Hybrid memory does not yet thread slot_initial_ctx; pass 0 for
|
| 571 |
+
+ // Phase 1 backward-compat (full eager allocation).
|
| 572 |
+
+ 0,
|
| 573 |
+
n_seq_max,
|
| 574 |
+
n_pad,
|
| 575 |
+
n_swa,
|
| 576 |
+
diff --git a/llama.cpp/src/llama-model.cpp b/llama.cpp/src/llama-model.cpp
|
| 577 |
+
--- a/llama.cpp/src/llama-model.cpp
|
| 578 |
+
+++ b/llama.cpp/src/llama-model.cpp
|
| 579 |
+
@@ -2073,6 +2073,8 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
| 580 |
+
params.swa_full,
|
| 581 |
+
cparams.kv_unified,
|
| 582 |
+
cparams.n_ctx_seq,
|
| 583 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 584 |
+
+ cparams.slot_initial_ctx,
|
| 585 |
+
cparams.n_seq_max,
|
| 586 |
+
cparams.n_ubatch,
|
| 587 |
+
1,
|
| 588 |
+
@@ -2089,6 +2091,8 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
| 589 |
+
cparams.offload_kqv,
|
| 590 |
+
cparams.kv_unified,
|
| 591 |
+
cparams.n_ctx_seq,
|
| 592 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 593 |
+
+ cparams.slot_initial_ctx,
|
| 594 |
+
cparams.n_seq_max,
|
| 595 |
+
1,
|
| 596 |
+
hparams.n_swa,
|
patches/0003-arg-assertion-bypass.patch
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 3/N] bypass upstream `params.n_gpu_layers < 0` assertions
|
| 3 |
+
|
| 4 |
+
Upstream llama.cpp pin 5e9c63546 has TWO `GGML_ASSERT(n_gpu_layers < 0)`
|
| 5 |
+
calls in common/arg.cpp:
|
| 6 |
+
|
| 7 |
+
1. At the `-ngl` option registration site (params.n_gpu_layers).
|
| 8 |
+
2. At the `-ngld` (draft) option registration site
|
| 9 |
+
(params.speculative.n_gpu_layers).
|
| 10 |
+
|
| 11 |
+
Both fire on every `common_params_parser_init` call regardless of args,
|
| 12 |
+
making the patched binary unable to start in `--server`, `--cli`, or
|
| 13 |
+
`--chat` mode. Mozilla-Ocho v1.4.0 (pre-assertion) was unaffected.
|
| 14 |
+
|
| 15 |
+
Each assertion is a "display-format guard" for the next line, which
|
| 16 |
+
maps `n_gpu_layers == -1` to "auto" and anything else to "all" in the
|
| 17 |
+
help text. The fix is to either extend the format to handle non-
|
| 18 |
+
negative defaults, or to drop the assertion. We drop it β the
|
| 19 |
+
display "all" for a positive default is a minor cosmetic issue,
|
| 20 |
+
not a correctness problem.
|
| 21 |
+
|
| 22 |
+
This is an upstream-bug-bypass patch, not opencoti scope. To be
|
| 23 |
+
proposed upstream once we surface a minimal reproducer.
|
| 24 |
+
|
| 25 |
+
Bench: closes the F4 M3 bench path that requires booting the
|
| 26 |
+
patched binary in --server mode.
|
| 27 |
+
Milestone: F4 M3 β see docs/features/memory_embedder.md
|
| 28 |
+
Upstreaming: TBD β propose to llama.cpp once a minimal repro is
|
| 29 |
+
in hand. Likely already fixed in newer upstream commits.
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 33 |
+
--- a/llama.cpp/common/arg.cpp
|
| 34 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 35 |
+
@@ -2362,7 +2362,10 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
).set_env("LLAMA_ARG_N_CPU_MOE"));
|
| 39 |
+
- GGML_ASSERT(params.n_gpu_layers < 0); // string_format would need to be extended for a default >= 0
|
| 40 |
+
+ // opencoti F4 M3 patch 0003 β see docs/decisions/0001-lazy-slot-context.md
|
| 41 |
+
+ // Upstream assertion fires on every common_params_parser_init invocation
|
| 42 |
+
+ // (display-format guard for the next line). Dropped β display says "all"
|
| 43 |
+
+ // for non-negative defaults, which is cosmetic, not a correctness bug.
|
| 44 |
+
add_opt(common_arg(
|
| 45 |
+
{"-ngl", "--gpu-layers", "--n-gpu-layers"}, "N",
|
| 46 |
+
string_format("max. number of layers to store in VRAM, either an exact number, 'auto', or 'all' (default: %s)", params.n_gpu_layers == -1 ? "auto" : "all"),
|
patches/0004-cuda-std-cpp17.patch
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 4/N] add -std=c++17 to ggml-cuda NVCC flags
|
| 3 |
+
|
| 4 |
+
Mozilla-Ocho's `llamafile/cuda.sh` builds the `ggml-cuda.so` DSO that
|
| 5 |
+
the llamafile runtime loads when the user passes `-ngl <N>`. The
|
| 6 |
+
ggml-cuda sources have moved to C++17 features (`std::is_same_v`, fold
|
| 7 |
+
expressions, structured bindings), but cuda.sh's COMMON_FLAGS do not
|
| 8 |
+
pass `-std=c++17` to nvcc.
|
| 9 |
+
|
| 10 |
+
For CUDA 11.x and older 12.x toolkits this worked because nvcc
|
| 11 |
+
defaulted to C++14 and the sources were C++14-clean. Newer CUDA
|
| 12 |
+
toolkits ship CCCL that requires C++17 explicitly via a #error
|
| 13 |
+
guard, so building against CUDA 12.6+ (and definitely CUDA 13.x)
|
| 14 |
+
fails with `error: namespace "std" has no member "is_same_v"` and
|
| 15 |
+
~30 follow-on errors per .cu translation unit.
|
| 16 |
+
|
| 17 |
+
This patch adds `-std=c++17` to COMMON_FLAGS so cuda.sh builds
|
| 18 |
+
against any reasonable CUDA toolkit. The host compiler is already
|
| 19 |
+
on C++23 via cosmocc, so the host-flag side is fine; this is
|
| 20 |
+
purely the nvcc device-flag side.
|
| 21 |
+
|
| 22 |
+
Bench: closes the GPU validation path. Without this, the patched
|
| 23 |
+
llamafile cannot load ggml-cuda.so on solidPC (and on any host
|
| 24 |
+
where CUDA 12.6+ is the local toolkit), and the binary silently
|
| 25 |
+
falls back to CPU even with `-ngl all`.
|
| 26 |
+
Milestone: F4 M3 GPU validation (task #113) β see
|
| 27 |
+
docs/decisions/0001-lazy-slot-context.md and
|
| 28 |
+
docs/features/memory_embedder.md
|
| 29 |
+
Upstreaming: TBD β propose to Mozilla-Ocho once they decide on a
|
| 30 |
+
minimum-supported CUDA toolkit. Currently their build matrix is
|
| 31 |
+
unclear about whether CUDA 13.x is expected to work; this patch
|
| 32 |
+
is the minimal fix that does not regress older toolkits.
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
diff --git a/llamafile/cuda.sh b/llamafile/cuda.sh
|
| 36 |
+
--- a/llamafile/cuda.sh
|
| 37 |
+
+++ b/llamafile/cuda.sh
|
| 38 |
+
@@ -232,7 +232,12 @@ if [ "$COMPRESS" = "1" ]; then
|
| 39 |
+
fi
|
| 40 |
+
|
| 41 |
+
# NVCC compiler flags
|
| 42 |
+
+# opencoti note: -std=c++17 added because the ggml-cuda .cu sources use
|
| 43 |
+
+# C++17 features (std::is_same_v, structured bindings, fold expressions).
|
| 44 |
+
+# Newer CUDA toolkits (12.x+) need this explicitly; older toolkits did C++14
|
| 45 |
+
+# by default. Both work with c++17.
|
| 46 |
+
COMMON_FLAGS="\
|
| 47 |
+
+ -std=c++17 \
|
| 48 |
+
--use_fast_math \
|
| 49 |
+
--extended-lambda \
|
| 50 |
+
$EXTRA_INCLUDES \
|
patches/0005-lazy-slot-context-shrink.patch
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 5/N] lazy slot-context allocation β Phase 3 (shrink on idle)
|
| 3 |
+
|
| 4 |
+
Phase 2 added a soft cap on per-stream KV cells (kv_size_alloc <= kv_size_max)
|
| 5 |
+
and grow-on-demand via ensure_capacity. The soft cap never shrinks, so once a
|
| 6 |
+
single long prompt has caused it to grow, the embedder's RSS stays at the
|
| 7 |
+
grown size for the lifetime of the process. For an embedder running
|
| 8 |
+
`--parallel 2 --ctx-size 16384` with typical prompts well under 4K cells,
|
| 9 |
+
this means the headline Phase-2 win evaporates after the first long-context
|
| 10 |
+
request.
|
| 11 |
+
|
| 12 |
+
Phase 3 plugs that hole. When an idle threshold has elapsed since the last
|
| 13 |
+
apply_ubatch:
|
| 14 |
+
|
| 15 |
+
1. Cells in the range [kv_size_shrink_target, kv_size_alloc) are evicted
|
| 16 |
+
across every stream via cells.rm(i). The server-context layer's
|
| 17 |
+
prompt cache keeps cells alive between embed requests for reuse;
|
| 18 |
+
decommitting their backing pages without first evicting would leave
|
| 19 |
+
the prompt cache pointing at zeroed memory and corrupt subsequent
|
| 20 |
+
re-uses. The eviction makes the shrink unconditional.
|
| 21 |
+
|
| 22 |
+
2. The pages backing cells [target, current_alloc) of every layer's K
|
| 23 |
+
and V tensors are returned to the kernel via
|
| 24 |
+
madvise(addr, len, MADV_DONTNEED).
|
| 25 |
+
|
| 26 |
+
3. n_cells_cleared is rolled back to the shrink target so the next
|
| 27 |
+
ensure_cleared() partial-memset re-zeros only what comes back.
|
| 28 |
+
|
| 29 |
+
4. kv_size_alloc is reset to the shrink target so the next request can
|
| 30 |
+
grow again from the initial cap.
|
| 31 |
+
|
| 32 |
+
Why madvise(MADV_DONTNEED) and not posix_madvise(POSIX_MADV_DONTNEED):
|
| 33 |
+
glibc on Linux maps posix_madvise(POSIX_MADV_DONTNEED) to a no-op (see
|
| 34 |
+
glibc sysdeps/posix/posix_madvise.c β the standard explicitly forbids
|
| 35 |
+
"actively destroying" pages, which is what we need). Only the Linux-
|
| 36 |
+
specific madvise(MADV_DONTNEED) actually decommits anonymous pages and
|
| 37 |
+
returns RSS to the kernel. We declared MADV_DONTNEED with the canonical
|
| 38 |
+
Linux value (4) under #ifndef and extern-declared madvise() so cosmocc's
|
| 39 |
+
default feature set (which does not define _GNU_SOURCE and so does not
|
| 40 |
+
expose madvise() through sys/mman.h) builds cleanly.
|
| 41 |
+
|
| 42 |
+
The shrink check runs at the top of init_batch() and prepare() β both
|
| 43 |
+
entry points to actual KV work. It is gated by:
|
| 44 |
+
- slot_shrink_idle_ms_ > 0 (feature enabled)
|
| 45 |
+
- kv_size_alloc > kv_size_shrink_target (something to shrink)
|
| 46 |
+
- last_active_ms_ != 0 AND (now - last_active_ms_) >= idle threshold
|
| 47 |
+
|
| 48 |
+
Surface (CLI):
|
| 49 |
+
--slot-shrink-idle-ms N (default 0 = disabled; positive enables)
|
| 50 |
+
env LLAMA_ARG_SLOT_SHRINK_IDLE_MS
|
| 51 |
+
|
| 52 |
+
Surface (C API):
|
| 53 |
+
llama_context_params::slot_shrink_idle_ms (uint32_t, default 0)
|
| 54 |
+
|
| 55 |
+
Plumbed through llama_cparams -> llama_kv_cache ctor ->
|
| 56 |
+
(llama_kv_cache_iswa ctor for SWA models) -> llama-model.cpp call sites.
|
| 57 |
+
Backward-compat: llama-memory-hybrid{,-iswa}.cpp pass 0 (no shrink), the
|
| 58 |
+
same way they already pass 0 for kv_size_initial.
|
| 59 |
+
|
| 60 |
+
Bench (CPU, --gpu disable so KV lives in host pages): the embedder
|
| 61 |
+
configured `--parallel 2 --ctx-size 16384 --pooling last
|
| 62 |
+
--slot-initial-ctx 256 --slot-shrink-idle-ms 30000` starts at 719 MB
|
| 63 |
+
RSS, grows to 1720 MB after a long-prompt corpus pass (soft cap 256 ->
|
| 64 |
+
2048), and after the 30 s idle drops to 1329 MB on the next short embed.
|
| 65 |
+
391.7 MB returned to the kernel β measured by /proc/<pid>/status VmRSS.
|
| 66 |
+
Quality (cosine min vs M2 baseline) stays at 0.999797 across the 48-prompt
|
| 67 |
+
corpus.
|
| 68 |
+
|
| 69 |
+
Milestone: F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 70 |
+
Upstreaming: TBD β kv_cache idle-shrink is a generally-useful concept; the
|
| 71 |
+
cell-eviction + madvise(MADV_DONTNEED) approach is not opencoti-specific.
|
| 72 |
+
Proposal candidate after F4 closes.
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 76 |
+
--- a/llama.cpp/common/arg.cpp
|
| 77 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 78 |
+
@@ -1371,6 +1371,17 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 79 |
+
params.slot_initial_ctx = value;
|
| 80 |
+
}
|
| 81 |
+
).set_env("LLAMA_ARG_SLOT_INITIAL_CTX").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_EMBEDDING, LLAMA_EXAMPLE_PARALLEL}));
|
| 82 |
+
+ add_opt(common_arg(
|
| 83 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 84 |
+
+ {"--slot-shrink-idle-ms"}, "N",
|
| 85 |
+
+ string_format("idle threshold (ms) after which a grown KV soft cap is shrunk back to "
|
| 86 |
+
+ "--slot-initial-ctx and the over-cap pages are decommitted via posix_madvise "
|
| 87 |
+
+ "(default: %d, 0 disables; requires --slot-initial-ctx > 0)",
|
| 88 |
+
+ params.slot_shrink_idle_ms),
|
| 89 |
+
+ [](common_params & params, int value) {
|
| 90 |
+
+ params.slot_shrink_idle_ms = value;
|
| 91 |
+
+ }
|
| 92 |
+
+ ).set_env("LLAMA_ARG_SLOT_SHRINK_IDLE_MS").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_EMBEDDING, LLAMA_EXAMPLE_PARALLEL}));
|
| 93 |
+
add_opt(common_arg(
|
| 94 |
+
{"--cache-idle-slots"},
|
| 95 |
+
{"--no-cache-idle-slots"},
|
| 96 |
+
diff --git a/llama.cpp/common/common.cpp b/llama.cpp/common/common.cpp
|
| 97 |
+
--- a/llama.cpp/common/common.cpp
|
| 98 |
+
+++ b/llama.cpp/common/common.cpp
|
| 99 |
+
@@ -1630,6 +1630,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
|
| 100 |
+
cparams.n_seq_max = params.n_parallel;
|
| 101 |
+
// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 102 |
+
cparams.slot_initial_ctx = params.slot_initial_ctx < 0 ? 0u : (uint32_t) params.slot_initial_ctx;
|
| 103 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 104 |
+
+ cparams.slot_shrink_idle_ms = params.slot_shrink_idle_ms < 0 ? 0u : (uint32_t) params.slot_shrink_idle_ms;
|
| 105 |
+
cparams.n_rs_seq = params.speculative.need_n_rs_seq();
|
| 106 |
+
cparams.n_batch = params.n_batch;
|
| 107 |
+
cparams.n_ubatch = params.n_ubatch;
|
| 108 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 109 |
+
--- a/llama.cpp/common/common.h
|
| 110 |
+
+++ b/llama.cpp/common/common.h
|
| 111 |
+
@@ -430,6 +430,11 @@ struct common_params {
|
| 112 |
+
// (Phase 1 behavior β only zero-fill is deferred). Positive values cap
|
| 113 |
+
// the initial allocation and enable grow-on-demand up to n_ctx_seq.
|
| 114 |
+
int32_t slot_initial_ctx = 0;
|
| 115 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 116 |
+
+ // Idle threshold (ms) after which a grown soft cap is shrunk back to
|
| 117 |
+
+ // slot_initial_ctx and the over-cap pages are decommitted via
|
| 118 |
+
+ // posix_madvise. 0 disables shrink-on-idle (Phase 2 behavior).
|
| 119 |
+
+ int32_t slot_shrink_idle_ms = 0;
|
| 120 |
+
int32_t n_batch = 2048; // logical batch size for prompt processing (must be >=32 to use BLAS)
|
| 121 |
+
int32_t n_ubatch = 512; // physical batch size for prompt processing (must be >=32 to use BLAS)
|
| 122 |
+
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
| 123 |
+
diff --git a/llama.cpp/include/llama.h b/llama.cpp/include/llama.h
|
| 124 |
+
--- a/llama.cpp/include/llama.h
|
| 125 |
+
+++ b/llama.cpp/include/llama.h
|
| 126 |
+
@@ -346,6 +346,13 @@ extern "C" {
|
| 127 |
+
// up to next_pow2 (still capped at n_ctx_seq). Reduces warm-state RSS
|
| 128 |
+
// when typical prompts are much shorter than n_ctx_seq.
|
| 129 |
+
uint32_t slot_initial_ctx;
|
| 130 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 131 |
+
+ // Idle threshold after which a grown soft cap is shrunk back to
|
| 132 |
+
+ // slot_initial_ctx and the pages beyond it are returned to the OS via
|
| 133 |
+
+ // posix_madvise(POSIX_MADV_DONTNEED). 0 disables shrink-on-idle
|
| 134 |
+
+ // (Phase 2 behavior β soft cap grows monotonically). Requires
|
| 135 |
+
+ // slot_initial_ctx > 0 to have any visible effect.
|
| 136 |
+
+ uint32_t slot_shrink_idle_ms;
|
| 137 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback (0 = no rollback) [EXPERIMENTAL]
|
| 138 |
+
int32_t n_threads; // number of threads to use for generation
|
| 139 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 140 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 141 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 142 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 143 |
+
@@ -60,6 +60,8 @@ llama_context::llama_context(
|
| 144 |
+
|
| 145 |
+
// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 146 |
+
cparams.slot_initial_ctx = params.slot_initial_ctx;
|
| 147 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 148 |
+
+ cparams.slot_shrink_idle_ms = params.slot_shrink_idle_ms;
|
| 149 |
+
|
| 150 |
+
cparams.n_threads = params.n_threads;
|
| 151 |
+
cparams.n_threads_batch = params.n_threads_batch;
|
| 152 |
+
@@ -3350,6 +3352,7 @@ llama_context_params llama_context_default_params() {
|
| 153 |
+
/*.n_ubatch =*/ 512,
|
| 154 |
+
/*.n_seq_max =*/ 1,
|
| 155 |
+
/*.slot_initial_ctx =*/ 0,
|
| 156 |
+
+ /*.slot_shrink_idle_ms =*/ 0,
|
| 157 |
+
/*.n_rs_seq =*/ 0,
|
| 158 |
+
/*.n_threads =*/ GGML_DEFAULT_N_THREADS, // TODO: better default
|
| 159 |
+
/*.n_threads_batch =*/ GGML_DEFAULT_N_THREADS,
|
| 160 |
+
diff --git a/llama.cpp/src/llama-cparams.h b/llama.cpp/src/llama-cparams.h
|
| 161 |
+
--- a/llama.cpp/src/llama-cparams.h
|
| 162 |
+
+++ b/llama.cpp/src/llama-cparams.h
|
| 163 |
+
@@ -16,6 +16,11 @@ struct llama_cparams {
|
| 164 |
+
// 0 = full n_ctx_seq allocation. Positive value caps the initial KV-cell
|
| 165 |
+
// allocation per sequence and enables grow-on-demand up to n_ctx_seq.
|
| 166 |
+
uint32_t slot_initial_ctx;
|
| 167 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 168 |
+
+ // 0 disables shrink-on-idle. Positive value is the idle threshold in
|
| 169 |
+
+ // milliseconds: when a grown KV-cache is idle for at least this long, the
|
| 170 |
+
+ // soft cap is reset to slot_initial_ctx and the pages beyond are decommitted.
|
| 171 |
+
+ uint32_t slot_shrink_idle_ms;
|
| 172 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback
|
| 173 |
+
int32_t n_threads; // number of threads to use for generation
|
| 174 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 175 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.cpp b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 176 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 177 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 178 |
+
@@ -21,6 +21,7 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 179 |
+
bool unified,
|
| 180 |
+
uint32_t kv_size,
|
| 181 |
+
uint32_t kv_size_initial,
|
| 182 |
+
+ uint32_t slot_shrink_idle_ms,
|
| 183 |
+
uint32_t n_seq_max,
|
| 184 |
+
uint32_t n_ubatch,
|
| 185 |
+
uint32_t n_pad,
|
| 186 |
+
@@ -62,15 +63,15 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 187 |
+
|
| 188 |
+
kv_base = std::make_unique<llama_kv_cache>(
|
| 189 |
+
model, type_k, type_v,
|
| 190 |
+
- v_trans, offload, unified, size_base, kv_size_initial, n_seq_max, n_pad,
|
| 191 |
+
- 0, LLAMA_SWA_TYPE_NONE, filter_base, reuse);
|
| 192 |
+
+ v_trans, offload, unified, size_base, kv_size_initial, slot_shrink_idle_ms,
|
| 193 |
+
+ n_seq_max, n_pad, 0, LLAMA_SWA_TYPE_NONE, filter_base, reuse);
|
| 194 |
+
|
| 195 |
+
LLAMA_LOG_INFO("%s: creating SWA KV cache, size = %u cells\n", __func__, size_swa);
|
| 196 |
+
|
| 197 |
+
kv_swa = std::make_unique<llama_kv_cache>(
|
| 198 |
+
model, type_k, type_v,
|
| 199 |
+
- v_trans, offload, unified, size_swa, kv_size_initial, n_seq_max, n_pad,
|
| 200 |
+
- hparams.n_swa, hparams.swa_type, filter_swa, reuse);
|
| 201 |
+
+ v_trans, offload, unified, size_swa, kv_size_initial, slot_shrink_idle_ms,
|
| 202 |
+
+ n_seq_max, n_pad, hparams.n_swa, hparams.swa_type, filter_swa, reuse);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
void llama_kv_cache_iswa::clear(bool data) {
|
| 206 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.h b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 207 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.h
|
| 208 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 209 |
+
@@ -25,6 +25,9 @@ public:
|
| 210 |
+
// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 211 |
+
// Forwarded to both kv_base and kv_swa. 0 = full kv_size.
|
| 212 |
+
uint32_t kv_size_initial,
|
| 213 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 214 |
+
+ // Forwarded to both kv_base and kv_swa. 0 = shrink disabled.
|
| 215 |
+
+ uint32_t slot_shrink_idle_ms,
|
| 216 |
+
uint32_t n_seq_max,
|
| 217 |
+
uint32_t n_ubatch,
|
| 218 |
+
uint32_t n_pad,
|
| 219 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 220 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 221 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 222 |
+
@@ -7,12 +7,35 @@
|
| 223 |
+
|
| 224 |
+
#include <algorithm>
|
| 225 |
+
#include <cassert>
|
| 226 |
+
+#include <chrono>
|
| 227 |
+
#include <cmath>
|
| 228 |
+
#include <cstring>
|
| 229 |
+
#include <limits>
|
| 230 |
+
#include <map>
|
| 231 |
+
#include <stdexcept>
|
| 232 |
+
|
| 233 |
+
+// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 234 |
+
+// On Linux, madvise(addr, len, MADV_DONTNEED) is the call that actually
|
| 235 |
+
+// decommits pages: the kernel zeroes the page table entries, releases the
|
| 236 |
+
+// physical pages, and the next access faults in fresh zero-filled pages.
|
| 237 |
+
+// glibc's posix_madvise(POSIX_MADV_DONTNEED) is documented as advisory and
|
| 238 |
+
+// is NOT guaranteed to reduce RSS β empirically it is a no-op on the
|
| 239 |
+
+// glibc/Debian-11 setup the embedder runs on. We must call madvise()
|
| 240 |
+
+// directly.
|
| 241 |
+
+//
|
| 242 |
+
+// cosmocc's default headers (without _GNU_SOURCE) do not declare madvise()
|
| 243 |
+
+// or MADV_DONTNEED, but the syscall exists on every Linux kernel the
|
| 244 |
+
+// embedder runs on. Hardcode the Linux ABI value (asm-generic/mman-common.h:
|
| 245 |
+
+// `#define MADV_DONTNEED 4`) and forward-declare madvise() to keep
|
| 246 |
+
+// _GNU_SOURCE out of the global include. <unistd.h> provides
|
| 247 |
+
+// sysconf(_SC_PAGESIZE) for page-boundary math.
|
| 248 |
+
+#include <sys/mman.h>
|
| 249 |
+
+#include <unistd.h>
|
| 250 |
+
+#ifndef MADV_DONTNEED
|
| 251 |
+
+# define MADV_DONTNEED 4
|
| 252 |
+
+#endif
|
| 253 |
+
+extern "C" int madvise(void *addr, size_t length, int advice);
|
| 254 |
+
+
|
| 255 |
+
static bool ggml_is_power_of_2(int n) {
|
| 256 |
+
return (n & (n - 1)) == 0;
|
| 257 |
+
}
|
| 258 |
+
@@ -86,6 +109,7 @@ llama_kv_cache::llama_kv_cache(
|
| 259 |
+
bool unified,
|
| 260 |
+
uint32_t kv_size,
|
| 261 |
+
uint32_t kv_size_initial,
|
| 262 |
+
+ uint32_t slot_shrink_idle_ms,
|
| 263 |
+
uint32_t n_seq_max,
|
| 264 |
+
uint32_t n_pad,
|
| 265 |
+
uint32_t n_swa,
|
| 266 |
+
@@ -115,6 +139,18 @@ llama_kv_cache::llama_kv_cache(
|
| 267 |
+
__func__, kv_size_alloc, kv_size_max);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 271 |
+
+ // The shrink target is the initial cap we just computed. last_active_ms_
|
| 272 |
+
+ // starts at 0 (epoch) so a long pause before the first request still
|
| 273 |
+
+ // shrinks (correct behavior β nothing to lose).
|
| 274 |
+
+ kv_size_shrink_target = kv_size_alloc;
|
| 275 |
+
+ slot_shrink_idle_ms_ = slot_shrink_idle_ms;
|
| 276 |
+
+ last_active_ms_ = 0;
|
| 277 |
+
+ if (slot_shrink_idle_ms_ > 0 && kv_size_initial > 0 && kv_size_initial < kv_size_max) {
|
| 278 |
+
+ LLAMA_LOG_INFO("%s: slot_shrink_idle_ms = %u (target cap = %u)\n",
|
| 279 |
+
+ __func__, slot_shrink_idle_ms_, kv_size_shrink_target);
|
| 280 |
+
+ }
|
| 281 |
+
+
|
| 282 |
+
const uint32_t n_layer_kv = hparams.n_layer_kv();
|
| 283 |
+
|
| 284 |
+
// define a comparator for the buft -> ctx map to ensure that the order is well-defined:
|
| 285 |
+
@@ -446,6 +482,151 @@ bool llama_kv_cache::ensure_capacity(uint32_t cells_needed) {
|
| 286 |
+
return cells_needed <= kv_size_alloc;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
+// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 290 |
+
+// Decommit pages of every layer's K/V tensors in the byte-range that
|
| 291 |
+
+// corresponds to cells [from_cells, to_cells). Returns those pages to the OS
|
| 292 |
+
+// β RSS drops by the size of the range, and the next access to those pages
|
| 293 |
+
+// will fault them back in as fresh zero-filled pages.
|
| 294 |
+
+//
|
| 295 |
+
+// Only safe to call after kv_size_alloc has been reset to from_cells (or
|
| 296 |
+
+// lower) and n_cells_cleared has been reset accordingly, so the bookkeeping
|
| 297 |
+
+// stays in sync with what's actually backed by physical memory.
|
| 298 |
+
+//
|
| 299 |
+
+// The decommit is per-stream because the KV tensors are 3-D
|
| 300 |
+
+// [n_embd, kv_size, n_stream]; cells [a, b) within stream s occupy a
|
| 301 |
+
+// contiguous byte-range whose start address is (base + s*nb[2] + a*nb[1])
|
| 302 |
+
+// and length is (b-a)*nb[1]. posix_madvise is page-grained β bytes that
|
| 303 |
+
+// don't cover a full page are silently kept resident, which is fine for
|
| 304 |
+
+// the cells that straddle page boundaries.
|
| 305 |
+
+static void opencoti_decommit_layer_range(ggml_tensor * t, uint32_t n_stream,
|
| 306 |
+
+ uint32_t from_cells, uint32_t to_cells) {
|
| 307 |
+
+ if (!t || to_cells <= from_cells) {
|
| 308 |
+
+ return;
|
| 309 |
+
+ }
|
| 310 |
+
+ ggml_backend_buffer_t buf = t->buffer;
|
| 311 |
+
+ if (!buf) {
|
| 312 |
+
+ return;
|
| 313 |
+
+ }
|
| 314 |
+
+ // Only the host (CPU) backend's buffer is malloc'd memory we can
|
| 315 |
+
+ // decommit via madvise. For device buffers (CUDA/Metal/Vulkan) the K/V
|
| 316 |
+
+ // cache lives in device memory; skip β there are no host-side pages
|
| 317 |
+
+ // to return. This means Phase 3's RSS-shrink win applies to host-mode
|
| 318 |
+
+ // (e.g. embedder with -ngl 0); GPU-mode binaries still benefit from
|
| 319 |
+
+ // the soft-cap reset but RSS doesn't move.
|
| 320 |
+
+ if (!ggml_backend_buffer_is_host(buf)) {
|
| 321 |
+
+ return;
|
| 322 |
+
+ }
|
| 323 |
+
+ void * base = ggml_backend_buffer_get_base(buf);
|
| 324 |
+
+ if (!base) {
|
| 325 |
+
+ return;
|
| 326 |
+
+ }
|
| 327 |
+
+ const size_t stride_stream = (size_t) t->nb[2];
|
| 328 |
+
+ const size_t stride_cell = (size_t) t->nb[1];
|
| 329 |
+
+ const size_t off_in_buf = (size_t) ((char *) t->data - (char *) base);
|
| 330 |
+
+ for (uint32_t s = 0; s < n_stream; ++s) {
|
| 331 |
+
+ const size_t off = off_in_buf
|
| 332 |
+
+ + (size_t) s * stride_stream
|
| 333 |
+
+ + (size_t) from_cells * stride_cell;
|
| 334 |
+
+ const size_t sz = (size_t) (to_cells - from_cells) * stride_cell;
|
| 335 |
+
+ // posix_madvise wants the address β work in absolute terms.
|
| 336 |
+
+ void * addr = (char *) base + off;
|
| 337 |
+
+ // Round up to a page boundary on the low side, round down on the
|
| 338 |
+
+ // high side, so we only decommit pages we fully own. The cell-aligned
|
| 339 |
+
+ // address is unlikely to be page-aligned in general.
|
| 340 |
+
+ const size_t page = (size_t) sysconf(_SC_PAGESIZE);
|
| 341 |
+
+ if (page == 0 || page == (size_t) -1) {
|
| 342 |
+
+ return;
|
| 343 |
+
+ }
|
| 344 |
+
+ uintptr_t lo = (uintptr_t) addr;
|
| 345 |
+
+ uintptr_t hi = lo + sz;
|
| 346 |
+
+ uintptr_t lo_aligned = (lo + page - 1) & ~(uintptr_t)(page - 1);
|
| 347 |
+
+ uintptr_t hi_aligned = hi & ~(uintptr_t)(page - 1);
|
| 348 |
+
+ if (hi_aligned <= lo_aligned) {
|
| 349 |
+
+ continue;
|
| 350 |
+
+ }
|
| 351 |
+
+ // madvise(MADV_DONTNEED) returns 0 on success, -1 on failure (with
|
| 352 |
+
+ // errno set). We treat any failure as advisory β the RSS win may
|
| 353 |
+
+ // be skipped, but correctness is unaffected (the K/V buffer is
|
| 354 |
+
+ // still mapped, just possibly still resident).
|
| 355 |
+
+ (void) madvise((void *) lo_aligned, (size_t) (hi_aligned - lo_aligned),
|
| 356 |
+
+ MADV_DONTNEED);
|
| 357 |
+
+ }
|
| 358 |
+
+}
|
| 359 |
+
+
|
| 360 |
+
+void llama_kv_cache::shrink_if_idle() {
|
| 361 |
+
+ if (slot_shrink_idle_ms_ == 0) {
|
| 362 |
+
+ return; // shrink-on-idle disabled
|
| 363 |
+
+ }
|
| 364 |
+
+ if (kv_size_alloc <= kv_size_shrink_target) {
|
| 365 |
+
+ return; // already at (or below) target β nothing to shrink
|
| 366 |
+
+ }
|
| 367 |
+
+ if (last_active_ms_ == 0) {
|
| 368 |
+
+ return; // no apply_ubatch has ever run β leave the initial cap as-is
|
| 369 |
+
+ }
|
| 370 |
+
+
|
| 371 |
+
+ const uint64_t now = (uint64_t)
|
| 372 |
+
+ std::chrono::duration_cast<std::chrono::milliseconds>(
|
| 373 |
+
+ std::chrono::steady_clock::now().time_since_epoch()).count();
|
| 374 |
+
+ if (now < last_active_ms_) {
|
| 375 |
+
+ return; // clock went backwards; refuse to act
|
| 376 |
+
+ }
|
| 377 |
+
+ if ((now - last_active_ms_) < (uint64_t) slot_shrink_idle_ms_) {
|
| 378 |
+
+ return; // not idle long enough
|
| 379 |
+
+ }
|
| 380 |
+
+
|
| 381 |
+
+ const uint32_t from_cells = kv_size_shrink_target;
|
| 382 |
+
+ const uint32_t to_cells = kv_size_alloc;
|
| 383 |
+
+
|
| 384 |
+
+ // Evict prompt-cache cell metadata for indices [from_cells, to_cells)
|
| 385 |
+
+ // on every stream BEFORE decommitting the pages. The server's prompt
|
| 386 |
+
+ // cache (server-context.cpp) keeps cells populated between requests so
|
| 387 |
+
+ // a subsequent request with the same prefix can skip re-prefill. If we
|
| 388 |
+
+ // decommitted pages without invalidating cell metadata, the server's
|
| 389 |
+
+ // next prompt-cache lookup could "match" a prefix whose K/V data is
|
| 390 |
+
+ // now decommitted (returns to zero pages on next access) = corruption.
|
| 391 |
+
+ //
|
| 392 |
+
+ // Semantics: shrink-on-idle is allowed to evict the over-cap portion
|
| 393 |
+
+ // of the prompt cache because that is exactly what the user opted into
|
| 394 |
+
+ // by setting --slot-shrink-idle-ms > 0. A cache miss on a subsequent
|
| 395 |
+
+ // long prompt is the cost; the alternative (cap stays grown forever)
|
| 396 |
+
+ // defeats Phase 3 entirely β slots that retain cells between requests
|
| 397 |
+
+ // (the default for embed + completion) would never shrink.
|
| 398 |
+
+ uint32_t evicted = 0;
|
| 399 |
+
+ for (uint32_t st = 0; st < n_stream; ++st) {
|
| 400 |
+
+ auto & cells = v_cells[st];
|
| 401 |
+
+ const uint32_t hi = std::min<uint32_t>(cells.size(), to_cells);
|
| 402 |
+
+ for (uint32_t i = from_cells; i < hi; ++i) {
|
| 403 |
+
+ if (!cells.is_empty(i)) {
|
| 404 |
+
+ cells.rm(i);
|
| 405 |
+
+ ++evicted;
|
| 406 |
+
+ }
|
| 407 |
+
+ }
|
| 408 |
+
+ }
|
| 409 |
+
+
|
| 410 |
+
+ LLAMA_LOG_INFO("%s: shrink soft cap %u -> %u (idle %llu ms, evicted %u cells)\n",
|
| 411 |
+
+ __func__, to_cells, from_cells,
|
| 412 |
+
+ (unsigned long long) (now - last_active_ms_), evicted);
|
| 413 |
+
+
|
| 414 |
+
+ for (const auto & layer : layers) {
|
| 415 |
+
+ opencoti_decommit_layer_range(layer.k, n_stream, from_cells, to_cells);
|
| 416 |
+
+ opencoti_decommit_layer_range(layer.v, n_stream, from_cells, to_cells);
|
| 417 |
+
+ }
|
| 418 |
+
+
|
| 419 |
+
+ // After decommit, treat cells [from_cells, to_cells) as "uncleared" β
|
| 420 |
+
+ // their pages will fault back in as fresh zero-filled pages on next
|
| 421 |
+
+ // touch, but n_cells_cleared still recorded them as cleared. Roll back
|
| 422 |
+
+ // the cleared mark so a future ensure_cleared() re-zeros them only when
|
| 423 |
+
+ // they're actually needed.
|
| 424 |
+
+ if (n_cells_cleared > from_cells) {
|
| 425 |
+
+ n_cells_cleared = from_cells;
|
| 426 |
+
+ }
|
| 427 |
+
+ kv_size_alloc = from_cells;
|
| 428 |
+
+
|
| 429 |
+
+ // Refresh last_active_ms_ so we don't immediately try again on the very
|
| 430 |
+
+ // next init_batch (which would walk the empty layers for nothing).
|
| 431 |
+
+ last_active_ms_ = now;
|
| 432 |
+
+}
|
| 433 |
+
+
|
| 434 |
+
void llama_kv_cache::clear(bool data) {
|
| 435 |
+
for (uint32_t s = 0; s < n_stream; ++s) {
|
| 436 |
+
v_cells[s].reset();
|
| 437 |
+
@@ -757,6 +938,12 @@ llama_memory_context_ptr llama_kv_cache::init_batch(
|
| 438 |
+
bool embd_all) {
|
| 439 |
+
GGML_UNUSED(embd_all);
|
| 440 |
+
|
| 441 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 442 |
+
+ // Check idle-shrink BEFORE ensure_cleared(): if shrink runs, it lowers
|
| 443 |
+
+ // kv_size_alloc and rolls n_cells_cleared back, so ensure_cleared's
|
| 444 |
+
+ // soft-cap-bounded clear targets only the (now smaller) initial cap.
|
| 445 |
+
+ shrink_if_idle();
|
| 446 |
+
+
|
| 447 |
+
// opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 448 |
+
ensure_cleared();
|
| 449 |
+
|
| 450 |
+
@@ -804,6 +991,12 @@ llama_memory_context_ptr llama_kv_cache::init_update(llama_context * lctx, bool
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
llama_kv_cache::slot_info_vec_t llama_kv_cache::prepare(const std::vector<llama_ubatch> & ubatches) {
|
| 454 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 455 |
+
+ // Same reason ensure_cleared() is called here as well as init_batch:
|
| 456 |
+
+ // the iswa wrapper bypasses init_batch and calls prepare() directly on
|
| 457 |
+
+ // each inner kv_cache. shrink_if_idle is idempotent and cheap.
|
| 458 |
+
+ shrink_if_idle();
|
| 459 |
+
+
|
| 460 |
+
// opencoti F4 M3 Phase 1 β see docs/decisions/0001-lazy-slot-context.md
|
| 461 |
+
// The iswa wrapper calls prepare() directly on each inner kv_cache without
|
| 462 |
+
// going through init_batch(), so the ensure_cleared() in init_batch alone
|
| 463 |
+
@@ -1184,6 +1377,13 @@ llama_kv_cache::slot_info llama_kv_cache::find_slot(const llama_ubatch & ubatch,
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
void llama_kv_cache::apply_ubatch(const slot_info & sinfo, const llama_ubatch & ubatch) {
|
| 467 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 468 |
+
+ // Refresh the idle timestamp on every actual write. shrink_if_idle uses
|
| 469 |
+
+ // this to gate the shrink path.
|
| 470 |
+
+ last_active_ms_ = (uint64_t)
|
| 471 |
+
+ std::chrono::duration_cast<std::chrono::milliseconds>(
|
| 472 |
+
+ std::chrono::steady_clock::now().time_since_epoch()).count();
|
| 473 |
+
+
|
| 474 |
+
// keep track of the max sequence position that we would overwrite with this ubatch
|
| 475 |
+
// for non-SWA cache, this would be always empty
|
| 476 |
+
llama_seq_id seq_pos_max_rm[LLAMA_MAX_SEQ];
|
| 477 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 478 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 479 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 480 |
+
@@ -106,6 +106,11 @@ public:
|
| 481 |
+
// (Phase 1 behavior). Otherwise clamped to [n_pad, kv_size]
|
| 482 |
+
// and rounded up to the next n_pad multiple.
|
| 483 |
+
uint32_t kv_size_initial,
|
| 484 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 485 |
+
+ // Idle threshold (ms) after which the soft cap is shrunk
|
| 486 |
+
+ // back to kv_size_initial and the over-cap pages are
|
| 487 |
+
+ // decommitted. 0 disables shrink-on-idle.
|
| 488 |
+
+ uint32_t slot_shrink_idle_ms,
|
| 489 |
+
uint32_t n_seq_max,
|
| 490 |
+
uint32_t n_pad,
|
| 491 |
+
uint32_t n_swa,
|
| 492 |
+
@@ -180,6 +185,17 @@ public:
|
| 493 |
+
// The current soft cap (cells visible to find_slot).
|
| 494 |
+
uint32_t get_kv_size_alloc() const { return kv_size_alloc; }
|
| 495 |
+
|
| 496 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 497 |
+
+ // If the soft cap has been grown above kv_size_shrink_target and at least
|
| 498 |
+
+ // slot_shrink_idle_ms_ have elapsed since the last apply_ubatch, reset
|
| 499 |
+
+ // the soft cap to kv_size_shrink_target and posix_madvise(POSIX_MADV_DONTNEED)
|
| 500 |
+
+ // the over-cap pages of every layer's K/V tensors. Idempotent and cheap
|
| 501 |
+
+ // when the soft cap is already at the target or shrink-on-idle is disabled.
|
| 502 |
+
+ // Called at the top of init_batch and prepare; safe because both happen
|
| 503 |
+
+ // before any slot work and the embedder's per-request cells.reset() means
|
| 504 |
+
+ // no slot ever holds onto cells across requests.
|
| 505 |
+
+ void shrink_if_idle();
|
| 506 |
+
+
|
| 507 |
+
ggml_type type_k() const;
|
| 508 |
+
ggml_type type_v() const;
|
| 509 |
+
|
| 510 |
+
@@ -296,11 +312,21 @@ private:
|
| 511 |
+
// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 512 |
+
// kv_size_max: hard cap on cells (set from constructor's kv_size).
|
| 513 |
+
// kv_size_alloc: current soft cap. find_slot only scans cells [0, kv_size_alloc).
|
| 514 |
+
- // Grows via ensure_capacity, never shrinks (Phase 2; Phase 3
|
| 515 |
+
- // adds shrink-on-idle).
|
| 516 |
+
+ // Grows via ensure_capacity. Phase 3 adds shrink-on-idle:
|
| 517 |
+
+ // shrink_if_idle() may reset it back to kv_size_shrink_target.
|
| 518 |
+
uint32_t kv_size_max = 0;
|
| 519 |
+
uint32_t kv_size_alloc = 0;
|
| 520 |
+
|
| 521 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 522 |
+
+ // Phase 3 shrink-on-idle state. kv_size_shrink_target is the cap value
|
| 523 |
+
+ // the soft cap is reset to on shrink β set once at ctor from
|
| 524 |
+
+ // kv_size_initial (or kv_size_max if Phase-1-mode). slot_shrink_idle_ms_
|
| 525 |
+
+ // is the idle threshold in ms; 0 disables shrink. last_active_ms_ is a
|
| 526 |
+
+ // monotonic timestamp updated by apply_ubatch.
|
| 527 |
+
+ uint32_t kv_size_shrink_target = 0;
|
| 528 |
+
+ uint32_t slot_shrink_idle_ms_ = 0;
|
| 529 |
+
+ uint64_t last_active_ms_ = 0;
|
| 530 |
+
+
|
| 531 |
+
// the current index from where we start searching for a free slot in the ring buffer of KV cells (see find_slot())
|
| 532 |
+
// note: this is not part of the KV state and it's only used to speed-up the find_slot() method
|
| 533 |
+
std::vector<uint32_t> v_heads;
|
| 534 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid-iswa.cpp b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 535 |
+
--- a/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 536 |
+
+++ b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 537 |
+
@@ -44,6 +44,10 @@ llama_memory_hybrid_iswa::llama_memory_hybrid_iswa(
|
| 538 |
+
// Hybrid-iswa does not yet thread slot_initial_ctx; pass 0 for
|
| 539 |
+
// Phase 1 backward-compat (full eager allocation).
|
| 540 |
+
0,
|
| 541 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 542 |
+
+ // Hybrid-iswa does not yet thread slot_shrink_idle_ms; pass 0 for
|
| 543 |
+
+ // Phase 2 backward-compat (no shrink-on-idle).
|
| 544 |
+
+ 0,
|
| 545 |
+
n_seq_max,
|
| 546 |
+
n_ubatch,
|
| 547 |
+
n_pad,
|
| 548 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid.cpp b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 549 |
+
--- a/llama.cpp/src/llama-memory-hybrid.cpp
|
| 550 |
+
+++ b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 551 |
+
@@ -43,6 +43,10 @@ llama_memory_hybrid::llama_memory_hybrid(
|
| 552 |
+
// Hybrid memory does not yet thread slot_initial_ctx; pass 0 for
|
| 553 |
+
// Phase 1 backward-compat (full eager allocation).
|
| 554 |
+
0,
|
| 555 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 556 |
+
+ // Hybrid memory does not yet thread slot_shrink_idle_ms; pass 0 for
|
| 557 |
+
+ // Phase 2 backward-compat (no shrink-on-idle).
|
| 558 |
+
+ 0,
|
| 559 |
+
n_seq_max,
|
| 560 |
+
n_pad,
|
| 561 |
+
n_swa,
|
| 562 |
+
diff --git a/llama.cpp/src/llama-model.cpp b/llama.cpp/src/llama-model.cpp
|
| 563 |
+
--- a/llama.cpp/src/llama-model.cpp
|
| 564 |
+
+++ b/llama.cpp/src/llama-model.cpp
|
| 565 |
+
@@ -2075,6 +2075,8 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
| 566 |
+
cparams.n_ctx_seq,
|
| 567 |
+
// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 568 |
+
cparams.slot_initial_ctx,
|
| 569 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 570 |
+
+ cparams.slot_shrink_idle_ms,
|
| 571 |
+
cparams.n_seq_max,
|
| 572 |
+
cparams.n_ubatch,
|
| 573 |
+
1,
|
| 574 |
+
@@ -2093,6 +2095,8 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
| 575 |
+
cparams.n_ctx_seq,
|
| 576 |
+
// opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 577 |
+
cparams.slot_initial_ctx,
|
| 578 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 579 |
+
+ cparams.slot_shrink_idle_ms,
|
| 580 |
+
cparams.n_seq_max,
|
| 581 |
+
1,
|
| 582 |
+
hparams.n_swa,
|
patches/0006-kv-reuse-prefix.patch
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0006] kv-cache reuse for agentic turns β sessionβslot affinity
|
| 3 |
+
|
| 4 |
+
opencoti routes an agentic loop through the bundled llamafile server: each
|
| 5 |
+
turn N+1 re-sends turn N's context prefix (system prompt + tool defs +
|
| 6 |
+
history) verbatim, then appends. The server already reuses KV for a matching
|
| 7 |
+
prefix β cache_prompt defaults to true and get_available_slot() picks the
|
| 8 |
+
idle slot whose cached token-prefix best matches the incoming prompt (above
|
| 9 |
+
--slot-prompt-similarity, then LRU). That covers single-session turn-2 reuse
|
| 10 |
+
at the default --parallel 1.
|
| 11 |
+
|
| 12 |
+
What upstream lacks is *session affinity*. With --parallel N and several
|
| 13 |
+
concurrent opencode sessions sharing slots, the LCP/LRU picker can route a
|
| 14 |
+
session to a slot holding a different session's KV, evicting a prefix that
|
| 15 |
+
would otherwise be reused. This patch adds an optional `session_id` request
|
| 16 |
+
field that keys slot selection:
|
| 17 |
+
|
| 18 |
+
- get_available_slot(): if the request carries a non-empty session_id that
|
| 19 |
+
already maps to an idle slot, return that slot (its resident KV is this
|
| 20 |
+
session's prefix by construction). The existing LCPβLRU path is the
|
| 21 |
+
fallback.
|
| 22 |
+
- launch_slot_with_task(): record session_id -> slot.id when a slot is
|
| 23 |
+
committed to a session, dropping any stale session previously bound to
|
| 24 |
+
that slot.
|
| 25 |
+
|
| 26 |
+
The prefix match itself is still the server's existing token-LCP β affinity
|
| 27 |
+
only ensures the right slot (the one holding the session's KV) is chosen, so
|
| 28 |
+
"(session_id, prefix) reuse" falls out without a separate prefix hash.
|
| 29 |
+
|
| 30 |
+
Regression shield: empty session_id (the default, and what every
|
| 31 |
+
non-opencoti client sends) skips the affinity phase entirely, leaving slot
|
| 32 |
+
selection byte-for-byte identical to upstream. Builds clean under cosmocc
|
| 33 |
+
(std::map / std::string already available via server-task.h).
|
| 34 |
+
|
| 35 |
+
Bench: perf/llamafile/turn-2-latency.bench.ts
|
| 36 |
+
Milestone: F5 M0 β see docs/features/advanced_kv.md
|
| 37 |
+
Design: docs/features/advanced_kv.md (M0)
|
| 38 |
+
Upstreaming: deferred; candidate to propose upstream as a generic session-id
|
| 39 |
+
slot-affinity hint once benched on opencoti workloads.
|
| 40 |
+
---
|
| 41 |
+
diff --git a/llama.cpp/tools/server/server-context.cpp b/llama.cpp/tools/server/server-context.cpp
|
| 42 |
+
--- a/llama.cpp/tools/server/server-context.cpp
|
| 43 |
+
+++ b/llama.cpp/tools/server/server-context.cpp
|
| 44 |
+
@@ -698,6 +698,13 @@ private:
|
| 45 |
+
// Necessary similarity of prompt for slot selection
|
| 46 |
+
float slot_prompt_similarity = 0.0f;
|
| 47 |
+
|
| 48 |
+
+ // opencoti F5 M0 kv-reuse β see docs/features/advanced_kv.md
|
| 49 |
+
+ // session_id -> slot.id affinity, so an agentic session routes back to the
|
| 50 |
+
+ // slot holding its resident KV prefix (turn-to-turn reuse, and no
|
| 51 |
+
+ // cross-session eviction when --parallel > 1). Empty session_id is never
|
| 52 |
+
+ // inserted, so non-opencoti traffic never consults or mutates this map.
|
| 53 |
+
+ std::map<std::string, int> session_to_slot;
|
| 54 |
+
+
|
| 55 |
+
std::string model_name; // name of the loaded model, to be used by API
|
| 56 |
+
std::set<std::string> model_aliases; // additional names for the model
|
| 57 |
+
std::set<std::string> model_tags; // informational tags
|
| 58 |
+
@@ -1249,6 +1256,25 @@ private:
|
| 59 |
+
|
| 60 |
+
bool update_cache = false;
|
| 61 |
+
|
| 62 |
+
+ // opencoti F5 M0 kv-reuse β see docs/features/advanced_kv.md
|
| 63 |
+
+ // Session affinity: if this session already owns an idle slot, reuse
|
| 64 |
+
+ // it. That slot's resident KV is the session's prefix, so the LCP path
|
| 65 |
+
+ // below would pick it anyway when free β but the explicit pin keeps a
|
| 66 |
+
+ // concurrent session from stealing/evicting it under --parallel > 1.
|
| 67 |
+
+ // Skipped entirely for empty session_id, so upstream selection (LCP
|
| 68 |
+
+ // then LRU, below) is unchanged for every non-opencoti request.
|
| 69 |
+
+ if (ret == nullptr && !task.params.session_id.empty()) {
|
| 70 |
+
+ auto it = session_to_slot.find(task.params.session_id);
|
| 71 |
+
+ if (it != session_to_slot.end()) {
|
| 72 |
+
+ for (server_slot & slot : slots) {
|
| 73 |
+
+ if (slot.id == it->second && !slot.is_processing()) {
|
| 74 |
+
+ ret = &slot;
|
| 75 |
+
+ break;
|
| 76 |
+
+ }
|
| 77 |
+
+ }
|
| 78 |
+
+ }
|
| 79 |
+
+ }
|
| 80 |
+
+
|
| 81 |
+
// find the slot that has at least n% prompt similarity
|
| 82 |
+
if (ret == nullptr && slot_prompt_similarity != 0.0f) {
|
| 83 |
+
float sim_best = 0;
|
| 84 |
+
@@ -1498,6 +1524,22 @@ private:
|
| 85 |
+
slot.smpl.reset();
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
+ // opencoti F5 M0 kv-reuse β see docs/features/advanced_kv.md
|
| 89 |
+
+ // Record sessionβslot affinity before `task` is moved into the slot,
|
| 90 |
+
+ // so this session's next turn routes back here and reuses its KV. A
|
| 91 |
+
+ // slot serves one session at a time, so first drop any stale session
|
| 92 |
+
+ // that previously mapped to this slot id.
|
| 93 |
+
+ if (!task.params.session_id.empty()) {
|
| 94 |
+
+ for (auto it = session_to_slot.begin(); it != session_to_slot.end(); ) {
|
| 95 |
+
+ if (it->second == slot.id && it->first != task.params.session_id) {
|
| 96 |
+
+ it = session_to_slot.erase(it);
|
| 97 |
+
+ } else {
|
| 98 |
+
+ ++it;
|
| 99 |
+
+ }
|
| 100 |
+
+ }
|
| 101 |
+
+ session_to_slot[task.params.session_id] = slot.id;
|
| 102 |
+
+ }
|
| 103 |
+
+
|
| 104 |
+
slot.task = std::make_unique<const server_task>(std::move(task));
|
| 105 |
+
|
| 106 |
+
slot.state = slot.task->is_child()
|
| 107 |
+
diff --git a/llama.cpp/tools/server/server-task.cpp b/llama.cpp/tools/server/server-task.cpp
|
| 108 |
+
--- a/llama.cpp/tools/server/server-task.cpp
|
| 109 |
+
+++ b/llama.cpp/tools/server/server-task.cpp
|
| 110 |
+
@@ -272,6 +272,7 @@ task_params server_task::params_from_json_cmpl(
|
| 111 |
+
params.n_discard = std::max(0, params.n_discard);
|
| 112 |
+
params.n_cmpl = json_value(data, "n_cmpl", json_value(data, "n", 1));
|
| 113 |
+
params.n_cache_reuse = json_value(data, "n_cache_reuse", defaults.n_cache_reuse);
|
| 114 |
+
+ params.session_id = json_value(data, "session_id", std::string("")); // opencoti F5 M0 kv-reuse β see docs/features/advanced_kv.md
|
| 115 |
+
//params.t_max_prompt_ms = json_value(data, "t_max_prompt_ms", defaults.t_max_prompt_ms); // TODO: implement
|
| 116 |
+
params.t_max_predict_ms = json_value(data, "t_max_predict_ms", defaults.t_max_predict_ms);
|
| 117 |
+
params.response_fields = json_value(data, "response_fields", std::vector<std::string>());
|
| 118 |
+
diff --git a/llama.cpp/tools/server/server-task.h b/llama.cpp/tools/server/server-task.h
|
| 119 |
+
--- a/llama.cpp/tools/server/server-task.h
|
| 120 |
+
+++ b/llama.cpp/tools/server/server-task.h
|
| 121 |
+
@@ -61,6 +61,14 @@ struct task_params {
|
| 122 |
+
|
| 123 |
+
int32_t n_cache_reuse = 0; // min chunk size to attempt reusing from the cache via KV shifting (0 = disabled)
|
| 124 |
+
|
| 125 |
+
+ // opencoti F5 M0 kv-reuse β see docs/features/advanced_kv.md
|
| 126 |
+
+ // Optional session affinity key. When non-empty, the server routes this
|
| 127 |
+
+ // request back to the slot the same session last used, reusing its
|
| 128 |
+
+ // resident KV prefix across agentic turns. Empty (the default, and what
|
| 129 |
+
+ // every non-opencoti client sends) preserves upstream slot selection
|
| 130 |
+
+ // byte-for-byte.
|
| 131 |
+
+ std::string session_id;
|
| 132 |
+
+
|
| 133 |
+
// number of prompt tokens before the latest user message
|
| 134 |
+
int32_t n_before_user = -1;
|
| 135 |
+
|
patches/0007-build-header-deps.patch
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0007] build infra β compiler-driven header dependency tracking
|
| 3 |
+
|
| 4 |
+
Editing any llama.cpp/src (or common/, tools/server/) header never marked its
|
| 5 |
+
dependent objects out of date, so every header change forced a full
|
| 6 |
+
from-scratch rebuild β the single biggest drag on iterating the F4/F5 vendored
|
| 7 |
+
patches.
|
| 8 |
+
|
| 9 |
+
Root cause: the build tracks header deps via cosmopolitan's `mkdeps`
|
| 10 |
+
(build/deps.mk β o/$(MODE)/depend). mkdeps only resolves cosmopolitan's
|
| 11 |
+
root-relative include style; llama.cpp's sources use `#include "foo.h"`
|
| 12 |
+
resolved through `-iquote` dirs, which mkdeps cannot follow, so it emitted
|
| 13 |
+
ZERO edges for llama.cpp/src headers. Source->object tracking worked (the
|
| 14 |
+
pattern rule), but header->object did not.
|
| 15 |
+
|
| 16 |
+
Fix (compiler-driven deps, the modern standard): add `-MMD` to the COMPILE.c /
|
| 17 |
+
COMPILE.cc commands so cosmocc emits an accurate per-translation-unit
|
| 18 |
+
`<obj>.o.d` (project headers only β system headers excluded), then `-include`
|
| 19 |
+
those `.o.d` files in build/deps.mk. clang resolves the real `-iquote`
|
| 20 |
+
includes, so the header set is exact. cosmocc supports -MMD natively
|
| 21 |
+
(get_dependency_outputs in bin/cosmocc): the x86 `.o.d` lands next to the
|
| 22 |
+
object and an `.aarch64/*.o.d` twin under the arch subdir; only the x86 copy is
|
| 23 |
+
included (header sets are arch-independent).
|
| 24 |
+
|
| 25 |
+
Effect: a header edit now recompiles exactly the affected objects (both fat
|
| 26 |
+
arches, via the existing rule), and nothing else β no more scratch rebuilds.
|
| 27 |
+
The first build after this patch lands populates the .o.d set (a one-time full
|
| 28 |
+
compile); every build thereafter is correctly incremental.
|
| 29 |
+
|
| 30 |
+
Interaction with ccache: ccache wraps cosmocc's FAT driver, so a cache hit
|
| 31 |
+
restores only the x86 .o (never the .aarch64 twin). With accurate header
|
| 32 |
+
tracking, objects are no longer manually deleted to force recompiles, so the
|
| 33 |
+
.aarch64 twin always persists on disk in a warm tree and hits stay safe. The
|
| 34 |
+
remaining hazard β a warm cache restoring x86-only objects into a freshly
|
| 35 |
+
reset (o/-wiped) tree β is handled in the opencoti build pipeline, which runs
|
| 36 |
+
the full `build` with CCACHE_RECACHE=1.
|
| 37 |
+
|
| 38 |
+
Bench: docs/features/llamafile_build.md (incremental-rebuild verification:
|
| 39 |
+
touch an included header -> only its dependents recompile; touch a
|
| 40 |
+
non-dependency header -> no recompile)
|
| 41 |
+
Milestone: build infrastructure (supports F4/F5 β see docs/features/advanced_kv.md)
|
| 42 |
+
Upstreaming: build-only; no behavior change to the produced binary. Not
|
| 43 |
+
intended for upstream (cosmopolitan/llamafile use mkdeps by design); opencoti
|
| 44 |
+
carries it to make iterating the vendored patch series tractable.
|
| 45 |
+
|
| 46 |
+
diff --git a/build/deps.mk b/build/deps.mk
|
| 47 |
+
--- a/build/deps.mk
|
| 48 |
+
+++ b/build/deps.mk
|
| 49 |
+
@@ -23,3 +23,15 @@ $(INCS):
|
| 50 |
+
rm -f o/$(MODE)/depend
|
| 51 |
+
|
| 52 |
+
-include o/$(MODE)/depend
|
| 53 |
+
+
|
| 54 |
+
+# opencoti build-header-deps β see docs/features/llamafile_build.md
|
| 55 |
+
+# Pull in the per-object dependency files cosmocc emits under -MMD (added to
|
| 56 |
+
+# build/rules.mk COMPILE.*). mkdeps' o/$(MODE)/depend above cannot resolve
|
| 57 |
+
+# llama.cpp/src's -iquote includes, so it tracked zero of those headers and a
|
| 58 |
+
+# header edit never recompiled its dependents β every change forced a
|
| 59 |
+
+# from-scratch rebuild. The compiler-emitted `<obj>.o.d` files carry the exact
|
| 60 |
+
+# header set per translation unit. Only the x86 `.o.d` is pulled in; the
|
| 61 |
+
+# `.aarch64/*.o.d` twins list identical headers (arch-independent). The shell
|
| 62 |
+
+# find runs at parse time over prior-build outputs (none on a cold build β a
|
| 63 |
+
+# harmless empty include, everything compiles anyway).
|
| 64 |
+
+-include $(shell [ -d o/$(MODE) ] && find o/$(MODE) -name '*.o.d' -not -path '*/.aarch64/*' 2>/dev/null)
|
| 65 |
+
diff --git a/build/rules.mk b/build/rules.mk
|
| 66 |
+
--- a/build/rules.mk
|
| 67 |
+
+++ b/build/rules.mk
|
| 68 |
+
@@ -6,8 +6,22 @@
|
| 69 |
+
# ==============================================================================
|
| 70 |
+
|
| 71 |
+
LINK.o = $(CXX) $(CCFLAGS) $(LDFLAGS)
|
| 72 |
+
-COMPILE.c = $(CC) $(CCFLAGS) $(CFLAGS) $(CPPFLAGS_) $(CPPFLAGS) $(TARGET_ARCH) -c
|
| 73 |
+
-COMPILE.cc = $(CXX) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS_) $(CPPFLAGS) $(TARGET_ARCH) -c
|
| 74 |
+
+# opencoti build-header-deps β see docs/features/llamafile_build.md
|
| 75 |
+
+# -MMD makes cosmocc emit a per-TU dependency file listing the real (project,
|
| 76 |
+
+# not system) header set; build/deps.mk -includes them so a header edit
|
| 77 |
+
+# recompiles exactly the affected objects. Without this, mkdeps' depend tracked
|
| 78 |
+
+# zero llama.cpp/src headers (it can't resolve -iquote includes), so any header
|
| 79 |
+
+# change forced a from-scratch rebuild.
|
| 80 |
+
+#
|
| 81 |
+
+# `-MF $@.d` is REQUIRED, not cosmetic: cosmocc defaults the dep file to
|
| 82 |
+
+# `<obj>.d` (it appends `.d` β e.g. foo.cpp.o.d), but ccache derives the
|
| 83 |
+
+# expected name from `-o foo.cpp.o` by stripping `.o` β foo.cpp.d. That
|
| 84 |
+
+# mismatch made ccache stat a file cosmocc never wrote ("failed to stat
|
| 85 |
+
+# β¦foo.cpp.d") and abort every compile with "ccache internal error" β nothing
|
| 86 |
+
+# was ever cached. Pinning -MF to cosmocc's actual name (`$@.d` = foo.cpp.o.d)
|
| 87 |
+
+# makes the two agree, so ccache caches and restores normally.
|
| 88 |
+
+COMPILE.c = $(CC) $(CCFLAGS) $(CFLAGS) $(CPPFLAGS_) $(CPPFLAGS) $(TARGET_ARCH) -MMD -MF $@.d -c
|
| 89 |
+
+COMPILE.cc = $(CXX) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS_) $(CPPFLAGS) $(TARGET_ARCH) -MMD -MF $@.d -c
|
| 90 |
+
|
| 91 |
+
# ==============================================================================
|
| 92 |
+
# Standard Compilation Rules
|
patches/0010-rest-kv-eviction.patch
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0010] retention-aware KV eviction β KeyDiff key-similarity, server-window
|
| 3 |
+
|
| 4 |
+
When an agentic context exceeds a slot's n_ctx, the server's context-shift
|
| 5 |
+
discards the *positional middle* [n_keep, n_keep+n_discard) regardless of what
|
| 6 |
+
sits there. This patch makes that choice retention-aware: it discards the
|
| 7 |
+
lowest-value *contiguous window* of the same width instead, keeping the
|
| 8 |
+
distinctive tokens and dropping the redundant ones.
|
| 9 |
+
|
| 10 |
+
Reference choice. The named technique (TRIM-KV, arXiv 2605.09649) needs
|
| 11 |
+
per-model retention gates trained by distillation β not a drop-in runtime
|
| 12 |
+
patch. Attention-score methods (H2O, SnapKV) need the materialized attention
|
| 13 |
+
matrix, which Flash Attention never produces (the vendored pin defaults
|
| 14 |
+
flash_attn_type = AUTO). So this substitutes a score-free, FA-compatible
|
| 15 |
+
criterion β KeyDiff (arXiv 2504.15364): score each cached key by its
|
| 16 |
+
*distinctiveness*, the mean over heads of (1 - cosine(key_head, mean_head)).
|
| 17 |
+
Keys close to the sequence's mean are redundant (evict); keys far from it are
|
| 18 |
+
distinctive (keep).
|
| 19 |
+
|
| 20 |
+
Mechanism (the "server-window" depth β mirror-safe):
|
| 21 |
+
- src/llama-memory.h: a new non-pure virtual seq_key_scores(seq_id,
|
| 22 |
+
layer_hint) with a default empty body, so no backend is forced to implement
|
| 23 |
+
it and the pure-virtual surface is unchanged. (Adding it grows the vtable β
|
| 24 |
+
a consistent recompile of all llama.cpp TUs is required; see the build
|
| 25 |
+
gotcha below.)
|
| 26 |
+
- src/llama-kv-cache.cpp: the real implementation β read each cached key of
|
| 27 |
+
one representative layer to host (amortized: only at the rare context-shift
|
| 28 |
+
trigger, unlike per-decode-interval scoring), group into heads, return a
|
| 29 |
+
per-position distinctiveness vector (+INF for absent positions). Empty when
|
| 30 |
+
unsupported (no cells, or a key type with no float converter) so the caller
|
| 31 |
+
falls back to positional discard.
|
| 32 |
+
- src/llama-kv-cache-iswa.cpp: delegate to the non-SWA base cache.
|
| 33 |
+
- src/llama-context.cpp + include/llama.h: a thin C shim
|
| 34 |
+
llama_memory_seq_key_scores() β a plain virtual call (no RTTI/downcast).
|
| 35 |
+
- tools/server/server-context.cpp: inside the EXISTING context-shift block,
|
| 36 |
+
when rest_kv_eviction is on, slide a width-n_discard window over
|
| 37 |
+
[n_keep, n_tokens - rest_kv_recent) and discard the minimum-retention one.
|
| 38 |
+
It is still ONE contiguous window, so the seq_rm/seq_add + token-mirror
|
| 39 |
+
rewrite stay 1:1 with KV positions β no rework of prefix-reuse/sampler.
|
| 40 |
+
- common/common.h + common/arg.cpp: --rest-kv-eviction (off by default),
|
| 41 |
+
--rest-kv-recent N (protect the recent tail), --rest-kv-layer N (-1 = auto).
|
| 42 |
+
|
| 43 |
+
Regression shield: --rest-kv-eviction off (the default, and every non-opencoti
|
| 44 |
+
client) leaves w0 = n_keep, i.e. context-shift is byte-for-byte upstream. Any
|
| 45 |
+
failure (non-KV memory, quantized keys, no room after the recent tail) also
|
| 46 |
+
falls back to the positional middle.
|
| 47 |
+
|
| 48 |
+
Build gotcha (buglog bug-175): seq_key_scores is a new virtual on
|
| 49 |
+
llama_memory_i, so the vtable layout changes. The llamafile Makefile tracks no
|
| 50 |
+
header deps; force a consistent recompile of all llama.cpp src/common/tools
|
| 51 |
+
objects (delete x86 .o, rebuild) β and because cosmocc is a fat compiler whose
|
| 52 |
+
aarch64 sibling object is NOT restored by a ccache hit, delete only x86 .o that
|
| 53 |
+
lack an aarch64 twin and rebuild with CCACHE_DISABLE=1, or use the full
|
| 54 |
+
reproducible build:llamafile.
|
| 55 |
+
|
| 56 |
+
Bench: perf/llamafile/rest-kv-eviction.bench.ts
|
| 57 |
+
Milestone: F5 M1 β see docs/features/advanced_kv.md
|
| 58 |
+
Design: docs/features/advanced_kv.md (M1)
|
| 59 |
+
Upstreaming: deferred; candidate to propose upstream as a pluggable
|
| 60 |
+
context-shift eviction policy once benched on opencoti workloads.
|
| 61 |
+
---
|
| 62 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 63 |
+
--- a/llama.cpp/common/arg.cpp
|
| 64 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 65 |
+
@@ -1398,6 +1398,29 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 66 |
+
params.ctx_shift = value;
|
| 67 |
+
}
|
| 68 |
+
).set_examples({LLAMA_EXAMPLE_COMPLETION, LLAMA_EXAMPLE_CLI, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_IMATRIX, LLAMA_EXAMPLE_PERPLEXITY}).set_env("LLAMA_ARG_CONTEXT_SHIFT"));
|
| 69 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 70 |
+
+ add_opt(common_arg(
|
| 71 |
+
+ {"--rest-kv-eviction"},
|
| 72 |
+
+ {"--no-rest-kv-eviction"},
|
| 73 |
+
+ string_format("when context shift fires, evict the lowest-retention (KeyDiff key-similarity) contiguous window instead of the positional middle (default: %s)", params.rest_kv_eviction ? "enabled" : "disabled"),
|
| 74 |
+
+ [](common_params & params, bool value) {
|
| 75 |
+
+ params.rest_kv_eviction = value;
|
| 76 |
+
+ }
|
| 77 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_EVICTION"));
|
| 78 |
+
+ add_opt(common_arg(
|
| 79 |
+
+ {"--rest-kv-recent"}, "N",
|
| 80 |
+
+ string_format("rest-kv: protect the most-recent N tokens from eviction (default: %d)", params.rest_kv_recent),
|
| 81 |
+
+ [](common_params & params, int value) {
|
| 82 |
+
+ params.rest_kv_recent = value;
|
| 83 |
+
+ }
|
| 84 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_RECENT"));
|
| 85 |
+
+ add_opt(common_arg(
|
| 86 |
+
+ {"--rest-kv-layer"}, "N",
|
| 87 |
+
+ string_format("rest-kv: model layer whose keys score retention, -1 = auto/middle (default: %d)", params.rest_kv_layer),
|
| 88 |
+
+ [](common_params & params, int value) {
|
| 89 |
+
+ params.rest_kv_layer = value;
|
| 90 |
+
+ }
|
| 91 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_LAYER"));
|
| 92 |
+
add_opt(common_arg(
|
| 93 |
+
{"--chunks"}, "N",
|
| 94 |
+
string_format("max number of chunks to process (default: %d, -1 = all)", params.n_chunks),
|
| 95 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 96 |
+
--- a/llama.cpp/common/common.h
|
| 97 |
+
+++ b/llama.cpp/common/common.h
|
| 98 |
+
@@ -554,6 +554,14 @@ struct common_params {
|
| 99 |
+
bool swa_full = false; // use full-size SWA cache (https://github.com/ggml-org/llama.cpp/pull/13194#issuecomment-2868343055)
|
| 100 |
+
bool kv_unified = false; // enable unified KV cache
|
| 101 |
+
|
| 102 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 103 |
+
+ // When a slot fills n_ctx and ctx_shift fires, discard the lowest-retention
|
| 104 |
+
+ // *contiguous* window (KeyDiff key-similarity) instead of the blind
|
| 105 |
+
+ // positional middle. Off => byte-for-byte upstream context-shift.
|
| 106 |
+
+ bool rest_kv_eviction = false; // master switch (default off)
|
| 107 |
+
+ int32_t rest_kv_recent = 256; // protect the most-recent N tokens from eviction
|
| 108 |
+
+ int32_t rest_kv_layer = -1; // representative layer for scoring; -1 = auto (middle)
|
| 109 |
+
+
|
| 110 |
+
bool input_prefix_bos = false; // prefix BOS to user inputs, preceding input_prefix
|
| 111 |
+
bool use_mmap = true; // enable mmap to use filesystem cache
|
| 112 |
+
bool use_direct_io = false; // read from disk without buffering
|
| 113 |
+
diff --git a/llama.cpp/include/llama.h b/llama.cpp/include/llama.h
|
| 114 |
+
--- a/llama.cpp/include/llama.h
|
| 115 |
+
+++ b/llama.cpp/include/llama.h
|
| 116 |
+
@@ -730,6 +730,19 @@ extern "C" {
|
| 117 |
+
llama_pos p0,
|
| 118 |
+
llama_pos p1);
|
| 119 |
+
|
| 120 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 121 |
+
+ // Fill out[0..n) with per-position KeyDiff retention scores for seq_id
|
| 122 |
+
+ // (higher = keep, +INF = absent position). layer_hint < 0 selects a default
|
| 123 |
+
+ // representative layer. Returns the number of finite scores written; 0 when
|
| 124 |
+
+ // unsupported (non-KV memory, a key type with no float converter, or an
|
| 125 |
+
+ // empty sequence) β the caller should then fall back to positional eviction.
|
| 126 |
+
+ LLAMA_API size_t llama_memory_seq_key_scores(
|
| 127 |
+
+ llama_memory_t mem,
|
| 128 |
+
+ llama_seq_id seq_id,
|
| 129 |
+
+ int32_t layer_hint,
|
| 130 |
+
+ float * out,
|
| 131 |
+
+ size_t n);
|
| 132 |
+
+
|
| 133 |
+
// Copy all tokens that belong to the specified sequence to another sequence
|
| 134 |
+
// p0 < 0 : [0, p1]
|
| 135 |
+
// p1 < 0 : [p0, inf)
|
| 136 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 137 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 138 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 139 |
+
@@ -3732,6 +3732,38 @@ bool llama_memory_seq_rm(
|
| 140 |
+
return mem->seq_rm(seq_id, p0, p1);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
+// opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 144 |
+
+// Thin C shim over the (defaulted) virtual llama_memory_i::seq_key_scores β a
|
| 145 |
+
+// plain virtual dispatch, so no RTTI/downcast and no change to the pure-virtual
|
| 146 |
+
+// surface of the interface. Non-KV memories inherit the empty default and this
|
| 147 |
+
+// returns 0, leaving the server on its positional fallback.
|
| 148 |
+
+size_t llama_memory_seq_key_scores(
|
| 149 |
+
+ llama_memory_t mem,
|
| 150 |
+
+ llama_seq_id seq_id,
|
| 151 |
+
+ int32_t layer_hint,
|
| 152 |
+
+ float * out,
|
| 153 |
+
+ size_t n) {
|
| 154 |
+
+ if (!mem || !out || n == 0) {
|
| 155 |
+
+ return 0;
|
| 156 |
+
+ }
|
| 157 |
+
+
|
| 158 |
+
+ const std::vector<float> scores = mem->seq_key_scores(seq_id, layer_hint);
|
| 159 |
+
+ if (scores.empty()) {
|
| 160 |
+
+ return 0;
|
| 161 |
+
+ }
|
| 162 |
+
+
|
| 163 |
+
+ size_t cnt = 0;
|
| 164 |
+
+ for (size_t p = 0; p < n; ++p) {
|
| 165 |
+
+ const float s = p < scores.size() ? scores[p] : std::numeric_limits<float>::infinity();
|
| 166 |
+
+ out[p] = s;
|
| 167 |
+
+ if (std::isfinite(s)) {
|
| 168 |
+
+ cnt++;
|
| 169 |
+
+ }
|
| 170 |
+
+ }
|
| 171 |
+
+
|
| 172 |
+
+ return cnt;
|
| 173 |
+
+}
|
| 174 |
+
+
|
| 175 |
+
void llama_memory_seq_cp(
|
| 176 |
+
llama_memory_t mem,
|
| 177 |
+
llama_seq_id seq_id_src,
|
| 178 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.cpp b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 179 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 180 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 181 |
+
@@ -117,6 +117,14 @@ llama_pos llama_kv_cache_iswa::seq_pos_max(llama_seq_id seq_id) const {
|
| 182 |
+
return kv_swa->seq_pos_max(seq_id);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
+std::vector<float> llama_kv_cache_iswa::seq_key_scores(llama_seq_id seq_id, int32_t layer_hint) const {
|
| 186 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 187 |
+
+ // Score on the non-SWA base cache: the SWA cache only ever retains a
|
| 188 |
+
+ // sliding window, so its keys are not the right eviction target. The base
|
| 189 |
+
+ // cache is a superset of the SWA cache, so it has every position we score.
|
| 190 |
+
+ return kv_base->seq_key_scores(seq_id, layer_hint);
|
| 191 |
+
+}
|
| 192 |
+
+
|
| 193 |
+
std::map<ggml_backend_buffer_type_t, size_t> llama_kv_cache_iswa::memory_breakdown() const {
|
| 194 |
+
std::map<ggml_backend_buffer_type_t, size_t> mb = kv_base->memory_breakdown();
|
| 195 |
+
for (const auto & buft_size : kv_swa->memory_breakdown()) {
|
| 196 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.h b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 197 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.h
|
| 198 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 199 |
+
@@ -62,6 +62,9 @@ public:
|
| 200 |
+
llama_pos seq_pos_min(llama_seq_id seq_id) const override;
|
| 201 |
+
llama_pos seq_pos_max(llama_seq_id seq_id) const override;
|
| 202 |
+
|
| 203 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 204 |
+
+ std::vector<float> seq_key_scores(llama_seq_id seq_id, int32_t layer_hint) const override;
|
| 205 |
+
+
|
| 206 |
+
std::map<ggml_backend_buffer_type_t, size_t> memory_breakdown() const override;
|
| 207 |
+
|
| 208 |
+
// state write/load
|
| 209 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 210 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 211 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 212 |
+
@@ -711,6 +711,137 @@ bool llama_kv_cache::seq_rm(llama_seq_id seq_id, llama_pos p0, llama_pos p1) {
|
| 213 |
+
return true;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
+std::vector<float> llama_kv_cache::seq_key_scores(llama_seq_id seq_id, int32_t layer_hint) const {
|
| 217 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 218 |
+
+ // KeyDiff-style, score-free (Flash-Attention-compatible) retention signal:
|
| 219 |
+
+ // for one representative layer, read every cached key of `seq_id`, group
|
| 220 |
+
+ // it into attention heads, and score each position by its key's
|
| 221 |
+
+ // *distinctiveness* β the mean over heads of (1 - cosine(key_head,
|
| 222 |
+
+ // mean_head)). High = far from the sequence's mean key = keep; low =
|
| 223 |
+
+ // redundant = safe to evict. The server uses this to choose the
|
| 224 |
+
+ // lowest-retention contiguous window to discard during context-shift,
|
| 225 |
+
+ // instead of the blind positional middle. Returns scores indexed by token
|
| 226 |
+
+ // position, +INF for absent positions (never selected). An empty result
|
| 227 |
+
+ // means "unsupported here" (no cells, or keys in a type with no float
|
| 228 |
+
+ // converter) and the caller falls back to positional discard. Reads happen
|
| 229 |
+
+ // only at the (infrequent) context-shift trigger, so the device->host copy
|
| 230 |
+
+ // is amortized β unlike per-decode-interval scoring.
|
| 231 |
+
+
|
| 232 |
+
+ if (seq_id < 0 || (size_t) seq_id >= seq_to_stream.size() || layers.empty()) {
|
| 233 |
+
+ return {};
|
| 234 |
+
+ }
|
| 235 |
+
+
|
| 236 |
+
+ const uint32_t strm = seq_to_stream[seq_id];
|
| 237 |
+
+ const auto & cells = v_cells[strm];
|
| 238 |
+
+
|
| 239 |
+
+ const llama_pos pmax = cells.seq_pos_max(seq_id);
|
| 240 |
+
+ if (pmax < 0) {
|
| 241 |
+
+ return {};
|
| 242 |
+
+ }
|
| 243 |
+
+
|
| 244 |
+
+ // pick a representative cache layer. A non-negative hint first tries to map
|
| 245 |
+
+ // a *model* layer id into the cache; failing that it is clamped as a direct
|
| 246 |
+
+ // cache-layer index. Default (hint < 0) is the middle layer β empirically
|
| 247 |
+
+ // the most informative for retention and cheap (one layer's keys only).
|
| 248 |
+
+ int32_t cil = (int32_t) layers.size() / 2;
|
| 249 |
+
+ if (layer_hint >= 0) {
|
| 250 |
+
+ const auto it = map_layer_ids.find(layer_hint);
|
| 251 |
+
+ cil = it != map_layer_ids.end()
|
| 252 |
+
+ ? it->second
|
| 253 |
+
+ : std::min<int32_t>(layer_hint, (int32_t) layers.size() - 1);
|
| 254 |
+
+ }
|
| 255 |
+
+
|
| 256 |
+
+ const auto & layer = layers[cil];
|
| 257 |
+
+ const uint32_t mil = layer.il;
|
| 258 |
+
+
|
| 259 |
+
+ const uint32_t n_head_kv = hparams.n_head_kv(mil);
|
| 260 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(mil);
|
| 261 |
+
+ const uint32_t n_embd_k_gqa = n_head_kv * n_embd_head_k;
|
| 262 |
+
+ if (n_head_kv == 0 || n_embd_head_k == 0 || n_embd_k_gqa == 0) {
|
| 263 |
+
+ return {};
|
| 264 |
+
+ }
|
| 265 |
+
+
|
| 266 |
+
+ ggml_tensor * k = layer.k_stream[strm];
|
| 267 |
+
+ if (!k) {
|
| 268 |
+
+ return {};
|
| 269 |
+
+ }
|
| 270 |
+
+
|
| 271 |
+
+ const auto * tt = ggml_get_type_traits(k->type);
|
| 272 |
+
+ if (!tt || !tt->to_float) {
|
| 273 |
+
+ return {}; // e.g. a quantized K cache with no dequantizer -> positional fallback
|
| 274 |
+
+ }
|
| 275 |
+
+
|
| 276 |
+
+ const size_t row_bytes = ggml_row_size(k->type, n_embd_k_gqa);
|
| 277 |
+
+
|
| 278 |
+
+ // collect (pos, cell index) for this sequence, ascending by position
|
| 279 |
+
+ std::vector<std::pair<llama_pos, uint32_t>> pc;
|
| 280 |
+
+ pc.reserve(cells.get_used());
|
| 281 |
+
+ for (uint32_t i = 0; i < cells.size(); ++i) {
|
| 282 |
+
+ if (!cells.is_empty(i) && cells.seq_has(i, seq_id)) {
|
| 283 |
+
+ pc.emplace_back(cells.pos_get(i), i);
|
| 284 |
+
+ }
|
| 285 |
+
+ }
|
| 286 |
+
+ if (pc.empty()) {
|
| 287 |
+
+ return {};
|
| 288 |
+
+ }
|
| 289 |
+
+ std::sort(pc.begin(), pc.end());
|
| 290 |
+
+
|
| 291 |
+
+ const size_t n = pc.size();
|
| 292 |
+
+
|
| 293 |
+
+ // read each key row once (amortized device->host copy), dequantized to f32
|
| 294 |
+
+ std::vector<float> keys((size_t) n * n_embd_k_gqa);
|
| 295 |
+
+ std::vector<uint8_t> rowbuf(row_bytes);
|
| 296 |
+
+ for (size_t j = 0; j < n; ++j) {
|
| 297 |
+
+ const uint32_t i = pc[j].second;
|
| 298 |
+
+ ggml_backend_tensor_get(k, rowbuf.data(), (size_t) i * row_bytes, row_bytes);
|
| 299 |
+
+ tt->to_float(rowbuf.data(), keys.data() + j * n_embd_k_gqa, (int64_t) n_embd_k_gqa);
|
| 300 |
+
+ }
|
| 301 |
+
+
|
| 302 |
+
+ // per-head mean key across the sequence, then per-head mean norm
|
| 303 |
+
+ std::vector<float> mean((size_t) n_embd_k_gqa, 0.0f);
|
| 304 |
+
+ for (size_t j = 0; j < n; ++j) {
|
| 305 |
+
+ const float * kv = keys.data() + j * n_embd_k_gqa;
|
| 306 |
+
+ for (uint32_t e = 0; e < n_embd_k_gqa; ++e) {
|
| 307 |
+
+ mean[e] += kv[e];
|
| 308 |
+
+ }
|
| 309 |
+
+ }
|
| 310 |
+
+ for (uint32_t e = 0; e < n_embd_k_gqa; ++e) {
|
| 311 |
+
+ mean[e] /= (float) n;
|
| 312 |
+
+ }
|
| 313 |
+
+
|
| 314 |
+
+ std::vector<float> mean_norm(n_head_kv, 0.0f);
|
| 315 |
+
+ for (uint32_t h = 0; h < n_head_kv; ++h) {
|
| 316 |
+
+ const float * mh = mean.data() + (size_t) h * n_embd_head_k;
|
| 317 |
+
+ double s = 0.0;
|
| 318 |
+
+ for (uint32_t d = 0; d < n_embd_head_k; ++d) {
|
| 319 |
+
+ s += (double) mh[d] * (double) mh[d];
|
| 320 |
+
+ }
|
| 321 |
+
+ mean_norm[h] = (float) std::sqrt(s);
|
| 322 |
+
+ }
|
| 323 |
+
+
|
| 324 |
+
+ // distinctiveness per position = mean over heads of (1 - cos(key_head, mean_head))
|
| 325 |
+
+ std::vector<float> out((size_t) pmax + 1, std::numeric_limits<float>::infinity());
|
| 326 |
+
+ for (size_t j = 0; j < n; ++j) {
|
| 327 |
+
+ const float * kv = keys.data() + j * n_embd_k_gqa;
|
| 328 |
+
+ double acc = 0.0;
|
| 329 |
+
+ for (uint32_t h = 0; h < n_head_kv; ++h) {
|
| 330 |
+
+ const float * kh = kv + (size_t) h * n_embd_head_k;
|
| 331 |
+
+ const float * mh = mean.data() + (size_t) h * n_embd_head_k;
|
| 332 |
+
+ double dot = 0.0, kn = 0.0;
|
| 333 |
+
+ for (uint32_t d = 0; d < n_embd_head_k; ++d) {
|
| 334 |
+
+ dot += (double) kh[d] * (double) mh[d];
|
| 335 |
+
+ kn += (double) kh[d] * (double) kh[d];
|
| 336 |
+
+ }
|
| 337 |
+
+ const double denom = std::sqrt(kn) * (double) mean_norm[h];
|
| 338 |
+
+ const double cos = denom > 0.0 ? dot / denom : 0.0;
|
| 339 |
+
+ acc += (1.0 - cos);
|
| 340 |
+
+ }
|
| 341 |
+
+ out[(size_t) pc[j].first] = (float) (acc / (double) n_head_kv);
|
| 342 |
+
+ }
|
| 343 |
+
+
|
| 344 |
+
+ return out;
|
| 345 |
+
+}
|
| 346 |
+
+
|
| 347 |
+
void llama_kv_cache::seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) {
|
| 348 |
+
GGML_ASSERT(seq_id_src >= 0 && (size_t) seq_id_src < seq_to_stream.size());
|
| 349 |
+
GGML_ASSERT(seq_id_dst >= 0 && (size_t) seq_id_dst < seq_to_stream.size());
|
| 350 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 351 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 352 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 353 |
+
@@ -146,6 +146,9 @@ public:
|
| 354 |
+
llama_pos seq_pos_min(llama_seq_id seq_id) const override;
|
| 355 |
+
llama_pos seq_pos_max(llama_seq_id seq_id) const override;
|
| 356 |
+
|
| 357 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 358 |
+
+ std::vector<float> seq_key_scores(llama_seq_id seq_id, int32_t layer_hint) const override;
|
| 359 |
+
+
|
| 360 |
+
std::map<ggml_backend_buffer_type_t, size_t> memory_breakdown() const override;
|
| 361 |
+
|
| 362 |
+
// state write/load
|
| 363 |
+
diff --git a/llama.cpp/src/llama-memory.h b/llama.cpp/src/llama-memory.h
|
| 364 |
+
--- a/llama.cpp/src/llama-memory.h
|
| 365 |
+
+++ b/llama.cpp/src/llama-memory.h
|
| 366 |
+
@@ -6,6 +6,7 @@
|
| 367 |
+
#include <map>
|
| 368 |
+
#include <memory>
|
| 369 |
+
#include <functional>
|
| 370 |
+
+#include <vector>
|
| 371 |
+
|
| 372 |
+
struct llama_ubatch;
|
| 373 |
+
|
| 374 |
+
@@ -112,6 +113,22 @@ struct llama_memory_i {
|
| 375 |
+
virtual llama_pos seq_pos_min(llama_seq_id seq_id) const = 0;
|
| 376 |
+
virtual llama_pos seq_pos_max(llama_seq_id seq_id) const = 0;
|
| 377 |
+
|
| 378 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 379 |
+
+ // Per-position KeyDiff "retention" scores for one sequence: higher = more
|
| 380 |
+
+ // distinctive (key far from the sequence's mean key) = keep; lower = more
|
| 381 |
+
+ // redundant = safe to evict. The server uses these to pick WHICH window to
|
| 382 |
+
+ // discard during context-shift, instead of the blind positional middle.
|
| 383 |
+
+ // Returns a vector indexed by token position [0, seq_pos_max]; absent
|
| 384 |
+
+ // positions are left at +INF (never evicted). The default returns empty so
|
| 385 |
+
+ // non-KV memories β and any caller that never opts in β are byte-for-byte
|
| 386 |
+
+ // unaffected (the eviction caller then falls back to positional discard).
|
| 387 |
+
+ // layer_hint < 0 selects a default representative layer.
|
| 388 |
+
+ virtual std::vector<float> seq_key_scores(llama_seq_id seq_id, int32_t layer_hint) const {
|
| 389 |
+
+ (void) seq_id;
|
| 390 |
+
+ (void) layer_hint;
|
| 391 |
+
+ return {};
|
| 392 |
+
+ }
|
| 393 |
+
+
|
| 394 |
+
virtual std::map<ggml_backend_buffer_type_t, size_t> memory_breakdown() const = 0;
|
| 395 |
+
|
| 396 |
+
//
|
| 397 |
+
diff --git a/llama.cpp/tools/server/server-context.cpp b/llama.cpp/tools/server/server-context.cpp
|
| 398 |
+
--- a/llama.cpp/tools/server/server-context.cpp
|
| 399 |
+
+++ b/llama.cpp/tools/server/server-context.cpp
|
| 400 |
+
@@ -2408,14 +2408,63 @@ private:
|
| 401 |
+
const int n_left = slot.prompt.n_tokens() - n_keep;
|
| 402 |
+
const int n_discard = slot.task->params.n_discard ? slot.task->params.n_discard : (n_left / 2);
|
| 403 |
+
|
| 404 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 405 |
+
+ // Upstream discards the positional middle [n_keep, n_keep+n_discard).
|
| 406 |
+
+ // When rest-kv-eviction is on, slide that same-width window over the
|
| 407 |
+
+ // evictable region [n_keep, n_tokens - rest_kv_recent) and discard the
|
| 408 |
+
+ // window with the LOWEST retention sum (KeyDiff key-similarity) β keep the
|
| 409 |
+
+ // distinctive tokens, drop the redundant ones. It is still ONE contiguous
|
| 410 |
+
+ // window, so the seq_rm/seq_add + token-mirror rewrite below stay 1:1 with
|
| 411 |
+
+ // KV positions. Any failure (eviction off, non-KV memory, quantized keys,
|
| 412 |
+
+ // no room after protecting the recent tail) leaves w0 = n_keep, i.e. the
|
| 413 |
+
+ // byte-for-byte upstream positional discard.
|
| 414 |
+
+ // opencoti F5 M6 MTP-composition: scoring reads ctx_tgt (the authoritative
|
| 415 |
+
+ // full-model KV); the SAME window w0 is then applied to BOTH the target and
|
| 416 |
+
+ // the draft KV cache below, preserving target/draft position alignment for
|
| 417 |
+
+ // speculative decode.
|
| 418 |
+
+ int w0 = n_keep;
|
| 419 |
+
+ if (params_base.rest_kv_eviction && n_discard > 0) {
|
| 420 |
+
+ const int n_tokens = slot.prompt.n_tokens();
|
| 421 |
+
+ const int recent = std::max(0, params_base.rest_kv_recent);
|
| 422 |
+
+ const int hi = n_tokens - recent - n_discard; // last valid window start (inclusive)
|
| 423 |
+
+ if (hi >= n_keep) {
|
| 424 |
+
+ std::vector<float> scores(n_tokens, std::numeric_limits<float>::infinity());
|
| 425 |
+
+ const size_t got = llama_memory_seq_key_scores(
|
| 426 |
+
+ llama_get_memory(ctx_tgt), slot.id, params_base.rest_kv_layer, scores.data(), n_tokens);
|
| 427 |
+
+ if (got > 0) {
|
| 428 |
+
+ // prefix sums over [n_keep, n_tokens); an absent position gets a
|
| 429 |
+
+ // large finite penalty so a window covering one is never picked
|
| 430 |
+
+ // (avoids inf/inf arithmetic in the sliding sum).
|
| 431 |
+
+ const int m = n_tokens - n_keep;
|
| 432 |
+
+ std::vector<double> pref(m + 1, 0.0);
|
| 433 |
+
+ for (int i = 0; i < m; ++i) {
|
| 434 |
+
+ const float sv = scores[n_keep + i];
|
| 435 |
+
+ pref[i + 1] = pref[i] + (std::isfinite(sv) ? (double) sv : 1e30);
|
| 436 |
+
+ }
|
| 437 |
+
+ double best = std::numeric_limits<double>::infinity();
|
| 438 |
+
+ int best_w0 = n_keep;
|
| 439 |
+
+ for (int s = n_keep; s <= hi; ++s) {
|
| 440 |
+
+ const double sum = pref[s - n_keep + n_discard] - pref[s - n_keep];
|
| 441 |
+
+ if (sum < best) {
|
| 442 |
+
+ best = sum;
|
| 443 |
+
+ best_w0 = s;
|
| 444 |
+
+ }
|
| 445 |
+
+ }
|
| 446 |
+
+ w0 = best_w0;
|
| 447 |
+
+ SLT_WRN(slot, "rest-kv eviction: discard [%d, %d) retention=%.3f (vs positional [%d, %d))\n",
|
| 448 |
+
+ w0, w0 + n_discard, best, n_keep, n_keep + n_discard);
|
| 449 |
+
+ }
|
| 450 |
+
+ }
|
| 451 |
+
+ }
|
| 452 |
+
+
|
| 453 |
+
SLT_WRN(slot, "slot context shift, n_keep = %d, n_left = %d, n_discard = %d\n", n_keep, n_left, n_discard);
|
| 454 |
+
|
| 455 |
+
- common_context_seq_rm (ctx_tgt, slot.id, n_keep , n_keep + n_discard);
|
| 456 |
+
- common_context_seq_add(ctx_tgt, slot.id, n_keep + n_discard, slot.prompt.n_tokens(), -n_discard);
|
| 457 |
+
+ common_context_seq_rm (ctx_tgt, slot.id, w0 , w0 + n_discard);
|
| 458 |
+
+ common_context_seq_add(ctx_tgt, slot.id, w0 + n_discard, slot.prompt.n_tokens(), -n_discard);
|
| 459 |
+
|
| 460 |
+
if (ctx_dft) {
|
| 461 |
+
- common_context_seq_rm (ctx_dft.get(), slot.id, n_keep , n_keep + n_discard);
|
| 462 |
+
- common_context_seq_add(ctx_dft.get(), slot.id, n_keep + n_discard, slot.prompt.tokens.pos_next(), -n_discard);
|
| 463 |
+
+ common_context_seq_rm (ctx_dft.get(), slot.id, w0 , w0 + n_discard);
|
| 464 |
+
+ common_context_seq_add(ctx_dft.get(), slot.id, w0 + n_discard, slot.prompt.tokens.pos_next(), -n_discard);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
// add generated tokens to cache
|
| 468 |
+
@@ -2424,7 +2473,7 @@ private:
|
| 469 |
+
GGML_ASSERT(!slot.prompt.tokens.has_mtmd);
|
| 470 |
+
|
| 471 |
+
llama_tokens new_tokens = slot.prompt.tokens.get_tokens(); // copy
|
| 472 |
+
- for (size_t i = n_keep + n_discard; i < new_tokens.size(); i++) {
|
| 473 |
+
+ for (size_t i = w0 + n_discard; i < new_tokens.size(); i++) {
|
| 474 |
+
new_tokens[i - n_discard] = new_tokens[i];
|
| 475 |
+
}
|
| 476 |
+
|
patches/0020-headinfer-per-head.patch
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0020] F5 M2 β HeadInfer: head-wise GPU/CPU KV residency split
|
| 3 |
+
|
| 4 |
+
Keep a configurable fraction of KV heads GPU-resident; offload the rest to
|
| 5 |
+
host memory; reassemble the full head set via ggml_concat on the head axis
|
| 6 |
+
at each attention step. Default off (frac = 1.0). Validated 6/6 on solidPC
|
| 7 |
+
RTX 3090 with Qwen2.5-Coder-0.5B-IQ4_XS at -ngl 99: GPU KV halves (48 β 24
|
| 8 |
+
MiB at frac=0.5), CPU half appears (24 MiB), greedy decode byte-identical
|
| 9 |
+
between baseline and split. See docs/features/advanced_kv.md (F5 M2).
|
| 10 |
+
|
| 11 |
+
The CUDA backend's supports_op for GGML_OP_CONCAT was over-broad (claimed
|
| 12 |
+
any non-I32/I16 type) while the kernel ggml_cuda_op_concat only implements
|
| 13 |
+
F32. Tightened to match the kernel β F16 concat now correctly routes to the
|
| 14 |
+
CPU backend (which has a real F16 path at ggml-cpu/ops.cpp:1980). Required
|
| 15 |
+
for the reassembly to run on F16 KV caches.
|
| 16 |
+
|
| 17 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 18 |
+
--- a/llama.cpp/common/arg.cpp
|
| 19 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 20 |
+
@@ -1421,6 +1421,14 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 21 |
+
params.rest_kv_layer = value;
|
| 22 |
+
}
|
| 23 |
+
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_LAYER"));
|
| 24 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 25 |
+
+ add_opt(common_arg(
|
| 26 |
+
+ {"--headinfer-gpu-heads-frac"}, "F",
|
| 27 |
+
+ string_format("headinfer: fraction of KV heads kept GPU-resident; the rest are offloaded to host memory and streamed back per step (default: %.2f, 1.0 = off). Only effective for GPU-offloaded layers with flash-attention.", (double) params.headinfer_gpu_heads_frac),
|
| 28 |
+
+ [](common_params & params, const std::string & value) {
|
| 29 |
+
+ params.headinfer_gpu_heads_frac = std::stof(value);
|
| 30 |
+
+ }
|
| 31 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_HEADINFER_GPU_HEADS_FRAC"));
|
| 32 |
+
add_opt(common_arg(
|
| 33 |
+
{"--chunks"}, "N",
|
| 34 |
+
string_format("max number of chunks to process (default: %d, -1 = all)", params.n_chunks),
|
| 35 |
+
diff --git a/llama.cpp/common/common.cpp b/llama.cpp/common/common.cpp
|
| 36 |
+
--- a/llama.cpp/common/common.cpp
|
| 37 |
+
+++ b/llama.cpp/common/common.cpp
|
| 38 |
+
@@ -1632,6 +1632,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
|
| 39 |
+
cparams.slot_initial_ctx = params.slot_initial_ctx < 0 ? 0u : (uint32_t) params.slot_initial_ctx;
|
| 40 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 41 |
+
cparams.slot_shrink_idle_ms = params.slot_shrink_idle_ms < 0 ? 0u : (uint32_t) params.slot_shrink_idle_ms;
|
| 42 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 43 |
+
+ cparams.headinfer_gpu_heads_frac = params.headinfer_gpu_heads_frac;
|
| 44 |
+
cparams.n_rs_seq = params.speculative.need_n_rs_seq();
|
| 45 |
+
cparams.n_batch = params.n_batch;
|
| 46 |
+
cparams.n_ubatch = params.n_ubatch;
|
| 47 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 48 |
+
--- a/llama.cpp/common/common.h
|
| 49 |
+
+++ b/llama.cpp/common/common.h
|
| 50 |
+
@@ -435,6 +435,11 @@ struct common_params {
|
| 51 |
+
// slot_initial_ctx and the over-cap pages are decommitted via
|
| 52 |
+
// posix_madvise. 0 disables shrink-on-idle (Phase 2 behavior).
|
| 53 |
+
int32_t slot_shrink_idle_ms = 0;
|
| 54 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 55 |
+
+ // Fraction of KV heads kept GPU-resident; the rest are offloaded to host
|
| 56 |
+
+ // memory and streamed back per step. 1.0 = off (no split). Only active for
|
| 57 |
+
+ // GPU-offloaded layers with flash-attention (non-transposed V cache).
|
| 58 |
+
+ float headinfer_gpu_heads_frac = 1.0f;
|
| 59 |
+
int32_t n_batch = 2048; // logical batch size for prompt processing (must be >=32 to use BLAS)
|
| 60 |
+
int32_t n_ubatch = 512; // physical batch size for prompt processing (must be >=32 to use BLAS)
|
| 61 |
+
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
| 62 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu b/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 63 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 64 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 65 |
+
@@ -5308,8 +5308,16 @@ static bool GGML_CALL ggml_backend_cuda_device_supports_op(ggml_backend_dev_t de
|
| 66 |
+
return op->type == GGML_TYPE_F32 && (op->src[0]->ne[2]*op->src[0]->ne[3]) <= (1 << 15);
|
| 67 |
+
case GGML_OP_CONCAT:
|
| 68 |
+
{
|
| 69 |
+
- ggml_type src0_type = op->src[0]->type;
|
| 70 |
+
- return src0_type != GGML_TYPE_I32 && src0_type != GGML_TYPE_I16;
|
| 71 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 72 |
+
+ // Tighten to F32-only to match ggml_cuda_op_concat's three
|
| 73 |
+
+ // hard asserts at ggml-cuda/concat.cu:158-160. The over-broad
|
| 74 |
+
+ // upstream filter ("any type except I32/I16") trusts the
|
| 75 |
+
+ // scheduler into dispatching F16 concats to CUDA, where the
|
| 76 |
+
+ // kernel asserts; with this fix the scheduler correctly routes
|
| 77 |
+
+ // F16 concat to the CPU backend, which has a real F16 path
|
| 78 |
+
+ // (ggml-cpu/ops.cpp:1980 concat_f16). Required for M2 get_k /
|
| 79 |
+
+ // get_v reassembly on F16 KV caches.
|
| 80 |
+
+ return op->src[0]->type == GGML_TYPE_F32;
|
| 81 |
+
} break;
|
| 82 |
+
case GGML_OP_CONV_TRANSPOSE_1D:
|
| 83 |
+
{
|
| 84 |
+
diff --git a/llama.cpp/include/llama.h b/llama.cpp/include/llama.h
|
| 85 |
+
--- a/llama.cpp/include/llama.h
|
| 86 |
+
+++ b/llama.cpp/include/llama.h
|
| 87 |
+
@@ -353,6 +353,11 @@ extern "C" {
|
| 88 |
+
// (Phase 2 behavior β soft cap grows monotonically). Requires
|
| 89 |
+
// slot_initial_ctx > 0 to have any visible effect.
|
| 90 |
+
uint32_t slot_shrink_idle_ms;
|
| 91 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 92 |
+
+ // Fraction of KV heads kept GPU-resident (rest offloaded to host and
|
| 93 |
+
+ // streamed back per step). 1.0 = off. Only effective for offloaded
|
| 94 |
+
+ // layers with flash-attention (non-transposed V cache).
|
| 95 |
+
+ float headinfer_gpu_heads_frac;
|
| 96 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback (0 = no rollback) [EXPERIMENTAL]
|
| 97 |
+
int32_t n_threads; // number of threads to use for generation
|
| 98 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 99 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 100 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 101 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 102 |
+
@@ -62,6 +62,8 @@ llama_context::llama_context(
|
| 103 |
+
cparams.slot_initial_ctx = params.slot_initial_ctx;
|
| 104 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 105 |
+
cparams.slot_shrink_idle_ms = params.slot_shrink_idle_ms;
|
| 106 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 107 |
+
+ cparams.headinfer_gpu_heads_frac = params.headinfer_gpu_heads_frac;
|
| 108 |
+
|
| 109 |
+
cparams.n_threads = params.n_threads;
|
| 110 |
+
cparams.n_threads_batch = params.n_threads_batch;
|
| 111 |
+
@@ -3353,6 +3355,7 @@ llama_context_params llama_context_default_params() {
|
| 112 |
+
/*.n_seq_max =*/ 1,
|
| 113 |
+
/*.slot_initial_ctx =*/ 0,
|
| 114 |
+
/*.slot_shrink_idle_ms =*/ 0,
|
| 115 |
+
+ /*.headinfer_gpu_heads_frac =*/ 1.0f,
|
| 116 |
+
/*.n_rs_seq =*/ 0,
|
| 117 |
+
/*.n_threads =*/ GGML_DEFAULT_N_THREADS, // TODO: better default
|
| 118 |
+
/*.n_threads_batch =*/ GGML_DEFAULT_N_THREADS,
|
| 119 |
+
diff --git a/llama.cpp/src/llama-cparams.h b/llama.cpp/src/llama-cparams.h
|
| 120 |
+
--- a/llama.cpp/src/llama-cparams.h
|
| 121 |
+
+++ b/llama.cpp/src/llama-cparams.h
|
| 122 |
+
@@ -21,6 +21,8 @@ struct llama_cparams {
|
| 123 |
+
// milliseconds: when a grown KV-cache is idle for at least this long, the
|
| 124 |
+
// soft cap is reset to slot_initial_ctx and the pages beyond are decommitted.
|
| 125 |
+
uint32_t slot_shrink_idle_ms;
|
| 126 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 127 |
+
+ float headinfer_gpu_heads_frac;
|
| 128 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback
|
| 129 |
+
int32_t n_threads; // number of threads to use for generation
|
| 130 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 131 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.cpp b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 132 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 133 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 134 |
+
@@ -22,6 +22,7 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 135 |
+
uint32_t kv_size,
|
| 136 |
+
uint32_t kv_size_initial,
|
| 137 |
+
uint32_t slot_shrink_idle_ms,
|
| 138 |
+
+ float headinfer_gpu_heads_frac,
|
| 139 |
+
uint32_t n_seq_max,
|
| 140 |
+
uint32_t n_ubatch,
|
| 141 |
+
uint32_t n_pad,
|
| 142 |
+
@@ -64,6 +65,7 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 143 |
+
kv_base = std::make_unique<llama_kv_cache>(
|
| 144 |
+
model, type_k, type_v,
|
| 145 |
+
v_trans, offload, unified, size_base, kv_size_initial, slot_shrink_idle_ms,
|
| 146 |
+
+ headinfer_gpu_heads_frac,
|
| 147 |
+
n_seq_max, n_pad, 0, LLAMA_SWA_TYPE_NONE, filter_base, reuse);
|
| 148 |
+
|
| 149 |
+
LLAMA_LOG_INFO("%s: creating SWA KV cache, size = %u cells\n", __func__, size_swa);
|
| 150 |
+
@@ -71,6 +73,7 @@ llama_kv_cache_iswa::llama_kv_cache_iswa(
|
| 151 |
+
kv_swa = std::make_unique<llama_kv_cache>(
|
| 152 |
+
model, type_k, type_v,
|
| 153 |
+
v_trans, offload, unified, size_swa, kv_size_initial, slot_shrink_idle_ms,
|
| 154 |
+
+ headinfer_gpu_heads_frac,
|
| 155 |
+
n_seq_max, n_pad, hparams.n_swa, hparams.swa_type, filter_swa, reuse);
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.h b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 159 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.h
|
| 160 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 161 |
+
@@ -28,6 +28,8 @@ public:
|
| 162 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 163 |
+
// Forwarded to both kv_base and kv_swa. 0 = shrink disabled.
|
| 164 |
+
uint32_t slot_shrink_idle_ms,
|
| 165 |
+
+ // opencoti F5 M2 headinfer β forwarded to both kv_base and kv_swa.
|
| 166 |
+
+ float headinfer_gpu_heads_frac,
|
| 167 |
+
uint32_t n_seq_max,
|
| 168 |
+
uint32_t n_ubatch,
|
| 169 |
+
uint32_t n_pad,
|
| 170 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 171 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 172 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 173 |
+
@@ -110,6 +110,7 @@ llama_kv_cache::llama_kv_cache(
|
| 174 |
+
uint32_t kv_size,
|
| 175 |
+
uint32_t kv_size_initial,
|
| 176 |
+
uint32_t slot_shrink_idle_ms,
|
| 177 |
+
+ float headinfer_gpu_heads_frac,
|
| 178 |
+
uint32_t n_seq_max,
|
| 179 |
+
uint32_t n_pad,
|
| 180 |
+
uint32_t n_swa,
|
| 181 |
+
@@ -262,23 +263,67 @@ llama_kv_cache::llama_kv_cache(
|
| 182 |
+
const bool has_k = true;
|
| 183 |
+
const bool has_v = !is_mla;
|
| 184 |
+
|
| 185 |
+
- ggml_tensor * k = has_k ? ggml_new_tensor_3d(ctx, type_k, n_embd_k_gqa, kv_size, n_stream) : nullptr;
|
| 186 |
+
- ggml_tensor * v = has_v ? ggml_new_tensor_3d(ctx, type_v, n_embd_v_gqa, kv_size, n_stream) : nullptr;
|
| 187 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 188 |
+
+ // Head-residency split: keep the first gpu_heads KV heads on this
|
| 189 |
+
+ // (offloaded/device) layer's buffer and put the remaining heads on a
|
| 190 |
+
+ // host buffer. Splitting on KV-head boundaries is inherently GQA-safe.
|
| 191 |
+
+ // Gated to offloaded, non-transposed-V, unified (n_stream==1) layers;
|
| 192 |
+
+ // frac >= 1.0 (or <= 1 head) means no split (byte-identical upstream).
|
| 193 |
+
+ uint32_t gpu_heads = 0;
|
| 194 |
+
+ uint32_t n_embd_k_gpu = n_embd_k_gqa;
|
| 195 |
+
+ uint32_t n_embd_v_gpu = n_embd_v_gqa;
|
| 196 |
+
+ uint32_t n_embd_k_cpu = 0;
|
| 197 |
+
+ uint32_t n_embd_v_cpu = 0;
|
| 198 |
+
+ if (headinfer_gpu_heads_frac < 1.0f && offload && !v_trans && n_stream == 1) {
|
| 199 |
+
+ const uint32_t n_head_kv = hparams.n_head_kv(il);
|
| 200 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 201 |
+
+ const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 202 |
+
+ if (n_head_kv > 1) {
|
| 203 |
+
+ uint32_t g = (uint32_t) std::lround((double) headinfer_gpu_heads_frac * (double) n_head_kv);
|
| 204 |
+
+ g = std::max(1u, std::min(g, n_head_kv - 1));
|
| 205 |
+
+ gpu_heads = g;
|
| 206 |
+
+ n_embd_k_gpu = g * n_embd_head_k;
|
| 207 |
+
+ n_embd_v_gpu = g * n_embd_head_v;
|
| 208 |
+
+ n_embd_k_cpu = (n_head_kv - g) * n_embd_head_k;
|
| 209 |
+
+ n_embd_v_cpu = (n_head_kv - g) * n_embd_head_v;
|
| 210 |
+
+ LLAMA_LOG_INFO("%s: layer %3d: headinfer split heads %u GPU / %u CPU\n",
|
| 211 |
+
+ __func__, il, gpu_heads, n_head_kv - gpu_heads);
|
| 212 |
+
+ }
|
| 213 |
+
+ }
|
| 214 |
+
+
|
| 215 |
+
+ ggml_tensor * k = has_k ? ggml_new_tensor_3d(ctx, type_k, n_embd_k_gpu, kv_size, n_stream) : nullptr;
|
| 216 |
+
+ ggml_tensor * v = has_v ? ggml_new_tensor_3d(ctx, type_v, n_embd_v_gpu, kv_size, n_stream) : nullptr;
|
| 217 |
+
|
| 218 |
+
has_k && ggml_format_name(k, "cache_k_l%d", il);
|
| 219 |
+
has_v && ggml_format_name(v, "cache_v_l%d", il);
|
| 220 |
+
|
| 221 |
+
+ // opencoti F5 M2 headinfer β the host-resident head subset lives in the
|
| 222 |
+
+ // CPU buffer-type context; the buffer-allocation loop below picks it up
|
| 223 |
+
+ // automatically (ctx_for_buft keys contexts by buffer type).
|
| 224 |
+
+ ggml_tensor * k_cpu = nullptr;
|
| 225 |
+
+ ggml_tensor * v_cpu = nullptr;
|
| 226 |
+
+ if (gpu_heads > 0) {
|
| 227 |
+
+ ggml_context * ctx_cpu = ctx_for_buft(ggml_backend_cpu_buffer_type());
|
| 228 |
+
+ if (!ctx_cpu) {
|
| 229 |
+
+ throw std::runtime_error("failed to create CPU ggml context for headinfer kv cache");
|
| 230 |
+
+ }
|
| 231 |
+
+ k_cpu = has_k ? ggml_new_tensor_3d(ctx_cpu, type_k, n_embd_k_cpu, kv_size, n_stream) : nullptr;
|
| 232 |
+
+ v_cpu = has_v ? ggml_new_tensor_3d(ctx_cpu, type_v, n_embd_v_cpu, kv_size, n_stream) : nullptr;
|
| 233 |
+
+ has_k && k_cpu && ggml_format_name(k_cpu, "cache_k_cpu_l%d", il);
|
| 234 |
+
+ has_v && v_cpu && ggml_format_name(v_cpu, "cache_v_cpu_l%d", il);
|
| 235 |
+
+ }
|
| 236 |
+
+
|
| 237 |
+
std::vector<ggml_tensor *> k_stream;
|
| 238 |
+
std::vector<ggml_tensor *> v_stream;
|
| 239 |
+
|
| 240 |
+
for (uint32_t s = 0; s < n_stream; ++s) {
|
| 241 |
+
- k_stream.push_back(has_k ? ggml_view_2d(ctx, k, n_embd_k_gqa, kv_size, k->nb[1], s*k->nb[2]) : nullptr);
|
| 242 |
+
- v_stream.push_back(has_v ? ggml_view_2d(ctx, v, n_embd_v_gqa, kv_size, v->nb[1], s*v->nb[2]) : nullptr);
|
| 243 |
+
+ k_stream.push_back(has_k ? ggml_view_2d(ctx, k, n_embd_k_gpu, kv_size, k->nb[1], s*k->nb[2]) : nullptr);
|
| 244 |
+
+ v_stream.push_back(has_v ? ggml_view_2d(ctx, v, n_embd_v_gpu, kv_size, v->nb[1], s*v->nb[2]) : nullptr);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
map_layer_ids[il] = layers.size();
|
| 248 |
+
|
| 249 |
+
- layers.push_back({ il, k, v, k_stream, v_stream, });
|
| 250 |
+
+ layers.push_back({ il, k, v, k_stream, v_stream, k_cpu, v_cpu, gpu_heads });
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
if (reuse) {
|
| 254 |
+
@@ -440,6 +485,20 @@ void llama_kv_cache::ensure_cleared(uint32_t up_to_cells) {
|
| 255 |
+
const size_t sz = (size_t) (b - a) * (size_t) layer.v->nb[1];
|
| 256 |
+
ggml_backend_tensor_memset(layer.v, 0, off, sz);
|
| 257 |
+
}
|
| 258 |
+
+ // opencoti F5 M2 headinfer β clear the host-resident head subset
|
| 259 |
+
+ // too (its own strides; same [a, b) cell range).
|
| 260 |
+
+ if (layer.k_cpu) {
|
| 261 |
+
+ const size_t off = (size_t) st * (size_t) layer.k_cpu->nb[2]
|
| 262 |
+
+ + (size_t) a * (size_t) layer.k_cpu->nb[1];
|
| 263 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) layer.k_cpu->nb[1];
|
| 264 |
+
+ ggml_backend_tensor_memset(layer.k_cpu, 0, off, sz);
|
| 265 |
+
+ }
|
| 266 |
+
+ if (layer.v_cpu) {
|
| 267 |
+
+ const size_t off = (size_t) st * (size_t) layer.v_cpu->nb[2]
|
| 268 |
+
+ + (size_t) a * (size_t) layer.v_cpu->nb[1];
|
| 269 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) layer.v_cpu->nb[1];
|
| 270 |
+
+ ggml_backend_tensor_memset(layer.v_cpu, 0, off, sz);
|
| 271 |
+
+ }
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
@@ -610,6 +669,11 @@ void llama_kv_cache::shrink_if_idle() {
|
| 276 |
+
for (const auto & layer : layers) {
|
| 277 |
+
opencoti_decommit_layer_range(layer.k, n_stream, from_cells, to_cells);
|
| 278 |
+
opencoti_decommit_layer_range(layer.v, n_stream, from_cells, to_cells);
|
| 279 |
+
+ // opencoti F5 M2 headinfer β the host-resident head subset IS
|
| 280 |
+
+ // host-pageable, so this is where the GPU-mode shrink actually returns
|
| 281 |
+
+ // RSS (the device k/v above are skipped by the is_host() guard).
|
| 282 |
+
+ opencoti_decommit_layer_range(layer.k_cpu, n_stream, from_cells, to_cells);
|
| 283 |
+
+ opencoti_decommit_layer_range(layer.v_cpu, n_stream, from_cells, to_cells);
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
// After decommit, treat cells [from_cells, to_cells) as "uncleared" β
|
| 287 |
+
@@ -1645,15 +1709,41 @@ uint32_t llama_kv_cache::get_n_kv(const slot_info & sinfo) const {
|
| 288 |
+
ggml_tensor * llama_kv_cache::get_k(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 289 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 290 |
+
|
| 291 |
+
- auto * k = layers[ikv].k;
|
| 292 |
+
+ const auto & layer = layers[ikv];
|
| 293 |
+
+ auto * k = layer.k;
|
| 294 |
+
+
|
| 295 |
+
+ const uint64_t kv_size = get_size();
|
| 296 |
+
+ const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 297 |
+
+
|
| 298 |
+
+ // opencoti F5 M2 headinfer β reassemble the full head set from the
|
| 299 |
+
+ // GPU-resident (k) and host-resident (k_cpu) subsets by concat on the head
|
| 300 |
+
+ // dimension. The backend scheduler streams k_cpu to the compute backend.
|
| 301 |
+
+ if (layer.gpu_heads > 0 && layer.k_cpu) {
|
| 302 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 303 |
+
+ const uint32_t n_head_gpu = layer.gpu_heads;
|
| 304 |
+
+ const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 305 |
+
+ const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_k;
|
| 306 |
+
+ const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_k;
|
| 307 |
+
+ auto * kc = layer.k_cpu;
|
| 308 |
+
+ ggml_tensor * kg_v = ggml_view_4d(ctx, k,
|
| 309 |
+
+ n_embd_head_k, n_head_gpu, n_kv, ns,
|
| 310 |
+
+ ggml_row_size(k->type, n_embd_head_k),
|
| 311 |
+
+ ggml_row_size(k->type, n_embd_gpu),
|
| 312 |
+
+ ggml_row_size(k->type, n_embd_gpu*kv_size),
|
| 313 |
+
+ ggml_row_size(k->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 314 |
+
+ ggml_tensor * kc_v = ggml_view_4d(ctx, kc,
|
| 315 |
+
+ n_embd_head_k, n_head_cpu, n_kv, ns,
|
| 316 |
+
+ ggml_row_size(kc->type, n_embd_head_k),
|
| 317 |
+
+ ggml_row_size(kc->type, n_embd_cpu),
|
| 318 |
+
+ ggml_row_size(kc->type, n_embd_cpu*kv_size),
|
| 319 |
+
+ ggml_row_size(kc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 320 |
+
+ return ggml_concat(ctx, kg_v, kc_v, 1);
|
| 321 |
+
+ }
|
| 322 |
+
|
| 323 |
+
- const uint64_t kv_size = get_size();
|
| 324 |
+
const uint64_t n_embd_k_gqa = k->ne[0];
|
| 325 |
+
|
| 326 |
+
assert(n_embd_k_gqa == hparams.n_embd_k_gqa(il));
|
| 327 |
+
|
| 328 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 329 |
+
-
|
| 330 |
+
return ggml_view_4d(ctx, k,
|
| 331 |
+
hparams.n_embd_head_k(il), hparams.n_head_kv(il), n_kv, ns,
|
| 332 |
+
ggml_row_size(k->type, hparams.n_embd_head_k(il)),
|
| 333 |
+
@@ -1665,16 +1755,42 @@ ggml_tensor * llama_kv_cache::get_k(ggml_context * ctx, int32_t il, uint32_t n_k
|
| 334 |
+
ggml_tensor * llama_kv_cache::get_v(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 335 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 336 |
+
|
| 337 |
+
- auto * v = layers[ikv].v;
|
| 338 |
+
+ const auto & layer = layers[ikv];
|
| 339 |
+
+ auto * v = layer.v;
|
| 340 |
+
+
|
| 341 |
+
+ const uint64_t kv_size = get_size();
|
| 342 |
+
+ const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 343 |
+
+
|
| 344 |
+
+ // opencoti F5 M2 headinfer β concat the GPU + host head subsets. The split
|
| 345 |
+
+ // is gated off when v_trans at construction, so this branch is always the
|
| 346 |
+
+ // non-transposed layout (heads in dim 1, same as get_k).
|
| 347 |
+
+ if (layer.gpu_heads > 0 && layer.v_cpu) {
|
| 348 |
+
+ const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 349 |
+
+ const uint32_t n_head_gpu = layer.gpu_heads;
|
| 350 |
+
+ const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 351 |
+
+ const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_v;
|
| 352 |
+
+ const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_v;
|
| 353 |
+
+ auto * vc = layer.v_cpu;
|
| 354 |
+
+ ggml_tensor * vg_v = ggml_view_4d(ctx, v,
|
| 355 |
+
+ n_embd_head_v, n_head_gpu, n_kv, ns,
|
| 356 |
+
+ ggml_row_size(v->type, n_embd_head_v),
|
| 357 |
+
+ ggml_row_size(v->type, n_embd_gpu),
|
| 358 |
+
+ ggml_row_size(v->type, n_embd_gpu*kv_size),
|
| 359 |
+
+ ggml_row_size(v->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 360 |
+
+ ggml_tensor * vc_v = ggml_view_4d(ctx, vc,
|
| 361 |
+
+ n_embd_head_v, n_head_cpu, n_kv, ns,
|
| 362 |
+
+ ggml_row_size(vc->type, n_embd_head_v),
|
| 363 |
+
+ ggml_row_size(vc->type, n_embd_cpu),
|
| 364 |
+
+ ggml_row_size(vc->type, n_embd_cpu*kv_size),
|
| 365 |
+
+ ggml_row_size(vc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 366 |
+
+ return ggml_concat(ctx, vg_v, vc_v, 1);
|
| 367 |
+
+ }
|
| 368 |
+
|
| 369 |
+
- const uint64_t kv_size = get_size();
|
| 370 |
+
const uint64_t n_embd_v_gqa = v->ne[0];
|
| 371 |
+
|
| 372 |
+
// [TAG_V_CACHE_VARIABLE]
|
| 373 |
+
assert(n_embd_v_gqa >= hparams.n_embd_v_gqa(il));
|
| 374 |
+
|
| 375 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 376 |
+
-
|
| 377 |
+
if (!v_trans) {
|
| 378 |
+
// note: v->nb[1] <= v->nb[2]
|
| 379 |
+
return ggml_view_4d(ctx, v,
|
| 380 |
+
@@ -1699,7 +1815,8 @@ ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggm
|
| 381 |
+
|
| 382 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 383 |
+
|
| 384 |
+
- ggml_tensor * k = layers[ikv].k;
|
| 385 |
+
+ const auto & layer = layers[ikv];
|
| 386 |
+
+ ggml_tensor * k = layer.k;
|
| 387 |
+
|
| 388 |
+
const int64_t n_embd_head = k_cur->ne[0];
|
| 389 |
+
const int64_t n_head = k_cur->ne[1];
|
| 390 |
+
@@ -1711,6 +1828,24 @@ ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggm
|
| 391 |
+
// TODO: add ggml helper function for this?
|
| 392 |
+
GGML_ASSERT(ggml_row_size(k_cur->type, n_embd_head) == k_cur->nb[1]);
|
| 393 |
+
|
| 394 |
+
+ // opencoti F5 M2 headinfer β scatter the incoming heads into the GPU and
|
| 395 |
+
+ // host subsets. The first gpu_heads heads (contiguous in k_cur's dim 0..1)
|
| 396 |
+
+ // store into k; the rest store into k_cpu, both at the same cell indices
|
| 397 |
+
+ // (k_idxs). The single returned dep node ties both set_rows into the
|
| 398 |
+
+ // caller's one ggml_build_forward_expand (its value is never read). Split
|
| 399 |
+
+ // only happens for n_stream==1 (gated at construction) β no per-stream
|
| 400 |
+
+ // reshape needed.
|
| 401 |
+
+ if (layer.gpu_heads > 0 && layer.k_cpu) {
|
| 402 |
+
+ const int64_t g = layer.gpu_heads;
|
| 403 |
+
+ const int64_t c = n_head - g;
|
| 404 |
+
+ ggml_tensor * kc = layer.k_cpu;
|
| 405 |
+
+ ggml_tensor * cur_g = ggml_view_2d(ctx, k_cur, n_embd_head*g, n_tokens, k_cur->nb[2], 0);
|
| 406 |
+
+ ggml_tensor * cur_c = ggml_view_2d(ctx, k_cur, n_embd_head*c, n_tokens, k_cur->nb[2], (size_t) (g*k_cur->nb[1]));
|
| 407 |
+
+ ggml_tensor * store_g = ggml_set_rows(ctx, k, cur_g, k_idxs);
|
| 408 |
+
+ ggml_tensor * store_c = ggml_set_rows(ctx, kc, cur_c, k_idxs);
|
| 409 |
+
+ return ggml_add(ctx, ggml_view_1d(ctx, store_g, 1, 0), ggml_view_1d(ctx, store_c, 1, 0));
|
| 410 |
+
+ }
|
| 411 |
+
+
|
| 412 |
+
k_cur = ggml_view_2d(ctx, k_cur, n_embd_gqa, n_tokens, k_cur->nb[2], 0);
|
| 413 |
+
|
| 414 |
+
const int64_t n_stream = k->ne[2];
|
| 415 |
+
@@ -1734,7 +1869,8 @@ ggml_tensor * llama_kv_cache::cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggm
|
| 416 |
+
|
| 417 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 418 |
+
|
| 419 |
+
- auto * v = layers[ikv].v;
|
| 420 |
+
+ const auto & layer = layers[ikv];
|
| 421 |
+
+ auto * v = layer.v;
|
| 422 |
+
|
| 423 |
+
const int64_t n_embd_head = v_cur->ne[0];
|
| 424 |
+
const int64_t n_head = v_cur->ne[1];
|
| 425 |
+
@@ -1745,6 +1881,20 @@ ggml_tensor * llama_kv_cache::cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggm
|
| 426 |
+
// we can merge dims 0 and 1
|
| 427 |
+
GGML_ASSERT(ggml_row_size(v_cur->type, n_embd_head) == v_cur->nb[1]);
|
| 428 |
+
|
| 429 |
+
+ // opencoti F5 M2 headinfer β head-scatter into the GPU + host subsets.
|
| 430 |
+
+ // Reached only when !v_trans and n_stream==1 (the split is gated off
|
| 431 |
+
+ // otherwise at construction), so this mirrors the cpy_k store exactly.
|
| 432 |
+
+ if (layer.gpu_heads > 0 && layer.v_cpu) {
|
| 433 |
+
+ const int64_t g = layer.gpu_heads;
|
| 434 |
+
+ const int64_t c = n_head - g;
|
| 435 |
+
+ ggml_tensor * vc = layer.v_cpu;
|
| 436 |
+
+ ggml_tensor * cur_g = ggml_view_2d(ctx, v_cur, n_embd_head*g, n_tokens, v_cur->nb[2], 0);
|
| 437 |
+
+ ggml_tensor * cur_c = ggml_view_2d(ctx, v_cur, n_embd_head*c, n_tokens, v_cur->nb[2], (size_t) (g*v_cur->nb[1]));
|
| 438 |
+
+ ggml_tensor * store_g = ggml_set_rows(ctx, v, cur_g, v_idxs);
|
| 439 |
+
+ ggml_tensor * store_c = ggml_set_rows(ctx, vc, cur_c, v_idxs);
|
| 440 |
+
+ return ggml_add(ctx, ggml_view_1d(ctx, store_g, 1, 0), ggml_view_1d(ctx, store_c, 1, 0));
|
| 441 |
+
+ }
|
| 442 |
+
+
|
| 443 |
+
const int64_t n_stream = v->ne[2];
|
| 444 |
+
|
| 445 |
+
// take this branch when FA is enabled (the V cache is not transposed)
|
| 446 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 447 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 448 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 449 |
+
@@ -111,6 +111,11 @@ public:
|
| 450 |
+
// back to kv_size_initial and the over-cap pages are
|
| 451 |
+
// decommitted. 0 disables shrink-on-idle.
|
| 452 |
+
uint32_t slot_shrink_idle_ms,
|
| 453 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 454 |
+
+ // Fraction of KV heads kept GPU-resident (rest offloaded
|
| 455 |
+
+ // to host, streamed back per step). 1.0 = off. Effective
|
| 456 |
+
+ // only for offloaded layers with !v_trans (flash-attn).
|
| 457 |
+
+ float headinfer_gpu_heads_frac,
|
| 458 |
+
uint32_t n_seq_max,
|
| 459 |
+
uint32_t n_pad,
|
| 460 |
+
uint32_t n_swa,
|
| 461 |
+
@@ -269,6 +274,14 @@ private:
|
| 462 |
+
|
| 463 |
+
std::vector<ggml_tensor *> k_stream;
|
| 464 |
+
std::vector<ggml_tensor *> v_stream;
|
| 465 |
+
+
|
| 466 |
+
+ // opencoti F5 M2 headinfer β head-residency split. When gpu_heads > 0,
|
| 467 |
+
+ // `k`/`v` hold the first gpu_heads KV heads on the layer's (GPU) buffer
|
| 468 |
+
+ // and `k_cpu`/`v_cpu` hold the remaining (n_head_kv - gpu_heads) heads
|
| 469 |
+
+ // on a host buffer. gpu_heads == 0 means no split (k/v hold all heads).
|
| 470 |
+
+ ggml_tensor * k_cpu = nullptr;
|
| 471 |
+
+ ggml_tensor * v_cpu = nullptr;
|
| 472 |
+
+ uint32_t gpu_heads = 0;
|
| 473 |
+
};
|
| 474 |
+
|
| 475 |
+
bool v_trans = true; // the value tensor is transposed
|
| 476 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid-iswa.cpp b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 477 |
+
--- a/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 478 |
+
+++ b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 479 |
+
@@ -48,6 +48,8 @@ llama_memory_hybrid_iswa::llama_memory_hybrid_iswa(
|
| 480 |
+
// Hybrid-iswa does not yet thread slot_shrink_idle_ms; pass 0 for
|
| 481 |
+
// Phase 2 backward-compat (no shrink-on-idle).
|
| 482 |
+
0,
|
| 483 |
+
+ // opencoti F5 M2 headinfer β hybrid-iswa does not thread headinfer; 1.0 = off.
|
| 484 |
+
+ 1.0f,
|
| 485 |
+
n_seq_max,
|
| 486 |
+
n_ubatch,
|
| 487 |
+
n_pad,
|
| 488 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid.cpp b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 489 |
+
--- a/llama.cpp/src/llama-memory-hybrid.cpp
|
| 490 |
+
+++ b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 491 |
+
@@ -47,6 +47,8 @@ llama_memory_hybrid::llama_memory_hybrid(
|
| 492 |
+
// Hybrid memory does not yet thread slot_shrink_idle_ms; pass 0 for
|
| 493 |
+
// Phase 2 backward-compat (no shrink-on-idle).
|
| 494 |
+
0,
|
| 495 |
+
+ // opencoti F5 M2 headinfer β hybrid memory does not thread headinfer; 1.0 = off.
|
| 496 |
+
+ 1.0f,
|
| 497 |
+
n_seq_max,
|
| 498 |
+
n_pad,
|
| 499 |
+
n_swa,
|
| 500 |
+
diff --git a/llama.cpp/src/llama-model.cpp b/llama.cpp/src/llama-model.cpp
|
| 501 |
+
--- a/llama.cpp/src/llama-model.cpp
|
| 502 |
+
+++ b/llama.cpp/src/llama-model.cpp
|
| 503 |
+
@@ -2077,6 +2077,8 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
| 504 |
+
cparams.slot_initial_ctx,
|
| 505 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 506 |
+
cparams.slot_shrink_idle_ms,
|
| 507 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 508 |
+
+ cparams.headinfer_gpu_heads_frac,
|
| 509 |
+
cparams.n_seq_max,
|
| 510 |
+
cparams.n_ubatch,
|
| 511 |
+
1,
|
| 512 |
+
@@ -2097,6 +2099,8 @@ llama_memory_i * llama_model::create_memory(const llama_memory_params & params,
|
| 513 |
+
cparams.slot_initial_ctx,
|
| 514 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 515 |
+
cparams.slot_shrink_idle_ms,
|
| 516 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 517 |
+
+ cparams.headinfer_gpu_heads_frac,
|
| 518 |
+
cparams.n_seq_max,
|
| 519 |
+
1,
|
| 520 |
+
hparams.n_swa,
|
patches/0030-neo-pipeline.patch
ADDED
|
@@ -0,0 +1,933 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0030] F5 M3 β NEO: asymmetric GPU/CPU attention pipelining
|
| 3 |
+
|
| 4 |
+
Sits on top of 0020 (M2 head-residency split). When --neo-pipeline != off and
|
| 5 |
+
the headinfer split is active for the layer, build_attn_mha builds TWO
|
| 6 |
+
ggml_flash_attn_ext ops on disjoint Q-head ranges instead of M2's
|
| 7 |
+
concat-then-stream-back path: cur_g consumes the GPU half of K/V on the GPU
|
| 8 |
+
backend; cur_c consumes the CPU half of K/V on the CPU backend (auto-routed
|
| 9 |
+
by the scheduler from each FA op's K/V buffer-type). Output halves are
|
| 10 |
+
ggml_concat'd on the head axis β same head-axis concat M2 used, but on
|
| 11 |
+
O(n_tokens) FA outputs instead of O(n_kv) K/V.
|
| 12 |
+
|
| 13 |
+
A small orchestrator (ggml/include/ggml-neo-pipeline.h + ggml/src/
|
| 14 |
+
ggml-neo-pipeline.cpp) lets the graph builder register each (cur_g, cur_c)
|
| 15 |
+
pair; the CUDA backend's graph_compute hook routes registered cur_g to a
|
| 16 |
+
non-default CUDA stream (curr_stream_no=1) so concurrent CPU FA on cur_c can
|
| 17 |
+
overlap with stream-1 GPU FA. A cudaEvent is recorded on stream 1 after the
|
| 18 |
+
GPU FA dispatch; when a downstream split's graph reads cur_g (typically the
|
| 19 |
+
concat join), the hook inserts a cudaStreamWaitEvent on the consuming stream
|
| 20 |
+
so the read sees completed data. CUDA graph capture is disabled for
|
| 21 |
+
NEO-engaged splits; off-path graphs keep the fast cuda_graph path.
|
| 22 |
+
|
| 23 |
+
Default off in every layer: cparams.neo_pipeline_mode == 0 means
|
| 24 |
+
build_attn_mha_neo is never called and the M3-A structural split never
|
| 25 |
+
engages β the M2 concat-then-stream-back path runs unchanged, byte-identical
|
| 26 |
+
to the 0020-shipped binary. Adapter exposes --neo-pipeline off|on|auto via
|
| 27 |
+
common/arg.cpp; @opencoti/llamafile typed config field
|
| 28 |
+
neoPipelineMode + OPENCOTI_LLAMAFILE_NEO_PIPELINE env mirror the M2
|
| 29 |
+
template.
|
| 30 |
+
|
| 31 |
+
Solidpc RTX 3090 + Qwen2.5-Coder-0.5B IQ4_XS bench (perf/llamafile/
|
| 32 |
+
neo-pipeline.bench.ts) at ctx=4096 / predict=128 / frac=0.5:
|
| 33 |
+
|
| 34 |
+
B baseline (no split) 326.69 tps
|
| 35 |
+
M m2-only (split, M2 path) 186.96 tps
|
| 36 |
+
N neo-on (split, M3 path) 187.04 tps (+0.04% vs M) β C2 FAIL
|
| 37 |
+
O off-id (--neo-pipeline off) 151.65 tps
|
| 38 |
+
|
| 39 |
+
C1 PASS N completion === B completion (correctness intact)
|
| 40 |
+
C2 FAIL N tps - M tps < 5% (NEO overlap mechanism is correct but the
|
| 41 |
+
per-step GPU FA on half-heads finishes in much less wall time
|
| 42 |
+
than the CPU FA on half-heads, so even perfect overlap is
|
| 43 |
+
bounded by the slower CPU half β for this model + ctx,
|
| 44 |
+
M3-B's stream-swap has no observable win over M3-A's
|
| 45 |
+
structural split alone)
|
| 46 |
+
C3 PASS N GPU KV (24 MiB) β M GPU KV (24 MiB) β residency preserved
|
| 47 |
+
C4 PASS O completion === M completion (--neo-pipeline off byte-identical)
|
| 48 |
+
|
| 49 |
+
Per the M3-D gate in docs/features/advanced_kv.md (F5 M3): C2 failed but
|
| 50 |
+
the orchestrator is correct and the off-path is byte-identical to M2,
|
| 51 |
+
so the orchestrator file ships as a wired-but-no-observable-win
|
| 52 |
+
mechanism (kept for M4 ScoutAttention reuse) rather than as a
|
| 53 |
+
dormant stub. The M3-A structural win (per-step CPUβGPU stream-back
|
| 54 |
+
eliminated) is the actual deliverable. M3-C EWMA load-aware controller
|
| 55 |
+
is deferred β without an observable M3-B overlap win it would be
|
| 56 |
+
cosmetic.
|
| 57 |
+
|
| 58 |
+
Surgical-hook count stays 18 β all 0030 changes are in vendored-source
|
| 59 |
+
files, captured here via snapshot-diff (NOT git diff HEAD; bug-121).
|
| 60 |
+
|
| 61 |
+
diff --git a/llama.cpp/BUILD.mk b/llama.cpp/BUILD.mk
|
| 62 |
+
--- a/llama.cpp/BUILD.mk
|
| 63 |
+
+++ b/llama.cpp/BUILD.mk
|
| 64 |
+
@@ -27,6 +27,7 @@ GGML_SRCS_CPP := \
|
| 65 |
+
llama.cpp/ggml/src/ggml-backend-meta.cpp \
|
| 66 |
+
llama.cpp/ggml/src/ggml-backend-reg.cpp \
|
| 67 |
+
llama.cpp/ggml/src/ggml-backend.cpp \
|
| 68 |
+
+ llama.cpp/ggml/src/ggml-neo-pipeline.cpp \
|
| 69 |
+
llama.cpp/ggml/src/ggml-opt.cpp \
|
| 70 |
+
llama.cpp/ggml/src/ggml-threading.cpp \
|
| 71 |
+
llama.cpp/ggml/src/ggml.cpp \
|
| 72 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 73 |
+
--- a/llama.cpp/common/arg.cpp
|
| 74 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 75 |
+
@@ -1429,6 +1429,17 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 76 |
+
params.headinfer_gpu_heads_frac = std::stof(value);
|
| 77 |
+
}
|
| 78 |
+
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_HEADINFER_GPU_HEADS_FRAC"));
|
| 79 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 80 |
+
+ add_opt(common_arg(
|
| 81 |
+
+ {"--neo-pipeline"}, "MODE",
|
| 82 |
+
+ "neo (asymmetric GPU/CPU attention pipelining): off (default), on (engage when headinfer split is active for the layer), auto (reserved, same as on). Requires --headinfer-gpu-heads-frac < 1.0 to do anything.",
|
| 83 |
+
+ [](common_params & params, const std::string & value) {
|
| 84 |
+
+ if (value == "off") params.neo_pipeline_mode = 0;
|
| 85 |
+
+ else if (value == "on") params.neo_pipeline_mode = 1;
|
| 86 |
+
+ else if (value == "auto") params.neo_pipeline_mode = 2;
|
| 87 |
+
+ else throw std::invalid_argument("--neo-pipeline must be off|on|auto");
|
| 88 |
+
+ }
|
| 89 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_NEO_PIPELINE"));
|
| 90 |
+
add_opt(common_arg(
|
| 91 |
+
{"--chunks"}, "N",
|
| 92 |
+
string_format("max number of chunks to process (default: %d, -1 = all)", params.n_chunks),
|
| 93 |
+
diff --git a/llama.cpp/common/common.cpp b/llama.cpp/common/common.cpp
|
| 94 |
+
--- a/llama.cpp/common/common.cpp
|
| 95 |
+
+++ b/llama.cpp/common/common.cpp
|
| 96 |
+
@@ -1634,6 +1634,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
|
| 97 |
+
cparams.slot_shrink_idle_ms = params.slot_shrink_idle_ms < 0 ? 0u : (uint32_t) params.slot_shrink_idle_ms;
|
| 98 |
+
// opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 99 |
+
cparams.headinfer_gpu_heads_frac = params.headinfer_gpu_heads_frac;
|
| 100 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 101 |
+
+ cparams.neo_pipeline_mode = params.neo_pipeline_mode;
|
| 102 |
+
cparams.n_rs_seq = params.speculative.need_n_rs_seq();
|
| 103 |
+
cparams.n_batch = params.n_batch;
|
| 104 |
+
cparams.n_ubatch = params.n_ubatch;
|
| 105 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 106 |
+
--- a/llama.cpp/common/common.h
|
| 107 |
+
+++ b/llama.cpp/common/common.h
|
| 108 |
+
@@ -440,6 +440,11 @@ struct common_params {
|
| 109 |
+
// memory and streamed back per step. 1.0 = off (no split). Only active for
|
| 110 |
+
// GPU-offloaded layers with flash-attention (non-transposed V cache).
|
| 111 |
+
float headinfer_gpu_heads_frac = 1.0f;
|
| 112 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 113 |
+
+ // 0 = off (default; M2 concat path runs), 1 = on (engage when split
|
| 114 |
+
+ // active for the layer), 2 = auto (reserved for future load-based
|
| 115 |
+
+ // engagement; same as `on` today).
|
| 116 |
+
+ uint32_t neo_pipeline_mode = 0;
|
| 117 |
+
int32_t n_batch = 2048; // logical batch size for prompt processing (must be >=32 to use BLAS)
|
| 118 |
+
int32_t n_ubatch = 512; // physical batch size for prompt processing (must be >=32 to use BLAS)
|
| 119 |
+
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
| 120 |
+
diff --git a/llama.cpp/ggml/include/ggml-neo-pipeline.h b/llama.cpp/ggml/include/ggml-neo-pipeline.h
|
| 121 |
+
new file mode 100644
|
| 122 |
+
--- /dev/null
|
| 123 |
+
+++ b/llama.cpp/ggml/include/ggml-neo-pipeline.h
|
| 124 |
+
@@ -0,0 +1,78 @@
|
| 125 |
+
+// opencoti F5 M3 NEO β asymmetric GPU/CPU attention pipelining.
|
| 126 |
+
+//
|
| 127 |
+
+// This header declares the orchestrator surface that lets the M3 two-flash-attn
|
| 128 |
+
+// structural split overlap its GPU and CPU halves. The graph builder registers
|
| 129 |
+
+// each (cur_g, cur_c) pair after constructing it; the CUDA backend consults the
|
| 130 |
+
+// registry at compute time to (a) dispatch GPU FA on a non-default CUDA stream
|
| 131 |
+
+// so the scheduler's stream-0 sync no longer blocks the CPU FA's input copy
|
| 132 |
+
+// and (b) wait on the recorded stream-1 event when a downstream split consumes
|
| 133 |
+
+// cur_g (notably the concat that joins both halves), keeping correctness intact.
|
| 134 |
+
+//
|
| 135 |
+
+// Default-off: ggml_neo_pipeline_set_enabled(false) makes register_pair a no-op
|
| 136 |
+
+// and the CUDA hooks fast-skip. The off-path is byte-identical to the M3-A
|
| 137 |
+
+// shipped path; M3-A in turn is byte-identical to M2's concat path when
|
| 138 |
+
+// --neo-pipeline off (cparams.neo_pipeline_mode == 0). See
|
| 139 |
+
+// docs/features/advanced_kv.md (F5 M3 NEO).
|
| 140 |
+
+#pragma once
|
| 141 |
+
+
|
| 142 |
+
+#include "ggml.h"
|
| 143 |
+
+
|
| 144 |
+
+#ifdef __cplusplus
|
| 145 |
+
+extern "C" {
|
| 146 |
+
+#endif
|
| 147 |
+
+
|
| 148 |
+
+// Global on/off switch. Off by default; the graph builder flips this on for
|
| 149 |
+
+// the duration of a graph build when cparams.neo_pipeline_mode != 0 and at
|
| 150 |
+
+// least one layer has an active headinfer split. While off, all functions in
|
| 151 |
+
+// this file are O(1) no-ops.
|
| 152 |
+
+GGML_API void ggml_neo_pipeline_set_enabled(bool enabled);
|
| 153 |
+
+GGML_API bool ggml_neo_pipeline_enabled(void);
|
| 154 |
+
+
|
| 155 |
+
+// Registers a NEO FA pair. Called from build_attn_mha_neo after constructing
|
| 156 |
+
+// the two flash-attention ops on disjoint head ranges. cur_g lives on the GPU
|
| 157 |
+
+// backend (CUDA-allocated K/V), cur_c on the CPU backend (CPU-allocated K/V).
|
| 158 |
+
+// The CUDA backend later consults this registry to route cur_g to a non-default
|
| 159 |
+
+// stream so cur_c can dispatch concurrently. When disabled, this is a no-op.
|
| 160 |
+
+GGML_API void ggml_neo_pipeline_register_pair(const struct ggml_tensor * cur_g,
|
| 161 |
+
+ const struct ggml_tensor * cur_c);
|
| 162 |
+
+
|
| 163 |
+
+// Membership probe. Returns true iff t was the cur_g argument of a prior
|
| 164 |
+
+// register_pair call in this graph. O(1) fast-path; the CUDA backend calls
|
| 165 |
+
+// this once per node when scanning a graph it is about to dispatch.
|
| 166 |
+
+GGML_API bool ggml_neo_pipeline_is_gpu_partner(const struct ggml_tensor * t);
|
| 167 |
+
+
|
| 168 |
+
+// CUDA event slot for cur_g. The CUDA backend creates a cudaEvent (with
|
| 169 |
+
+// cudaEventCreateWithFlags + cudaEventDisableTiming) the first time it
|
| 170 |
+
+// dispatches cur_g on a non-default stream and stores the opaque handle via
|
| 171 |
+
+// set_event. A later split whose graph reads cur_g calls get_event and
|
| 172 |
+
+// inserts a cudaStreamWaitEvent so the consuming stream waits for the
|
| 173 |
+
+// stream-1 work. Both functions are no-ops when NEO is disabled or t isn't
|
| 174 |
+
+// registered (get_event returns NULL). The void* is treated as cudaEvent_t
|
| 175 |
+
+// at the CUDA call site; the orchestrator does not know the underlying type.
|
| 176 |
+
+GGML_API void * ggml_neo_pipeline_get_event(const struct ggml_tensor * cur_g);
|
| 177 |
+
+GGML_API void ggml_neo_pipeline_set_event(const struct ggml_tensor * cur_g, void * event);
|
| 178 |
+
+
|
| 179 |
+
+// Clears all per-graph registrations. Called by the CUDA backend at the end
|
| 180 |
+
+// of each graph dispatch so registrations don't leak across decode steps.
|
| 181 |
+
+// (The graph builder reinstalls them on the next build.) Notably does NOT
|
| 182 |
+
+// destroy stored events β those are destroyed by the CUDA backend, which
|
| 183 |
+
+// owns the cudaEvent lifecycle (call destroy_all_events at shutdown).
|
| 184 |
+
+GGML_API void ggml_neo_pipeline_clear_registrations(void);
|
| 185 |
+
+
|
| 186 |
+
+// Walks every (cur_g, event) entry, invoking destroyer(event) for each
|
| 187 |
+
+// non-null event, then clears the event map. Called at session/backend
|
| 188 |
+
+// shutdown so cudaEvents are properly released. destroyer is typically a
|
| 189 |
+
+// thin lambda wrapping cudaEventDestroy at the CUDA call site.
|
| 190 |
+
+GGML_API void ggml_neo_pipeline_destroy_all_events(void (*destroyer)(void * event));
|
| 191 |
+
+
|
| 192 |
+
+// Counter of NEO pair dispatches observed by the CUDA backend in the current
|
| 193 |
+
+// process. Surfaced for the M3-E bench's residency / engagement checks.
|
| 194 |
+
+GGML_API int ggml_neo_pipeline_dispatch_count(void);
|
| 195 |
+
+
|
| 196 |
+
+// Increments the dispatch counter. Called by the CUDA backend each time it
|
| 197 |
+
+// observes and routes a registered cur_g to a non-default stream.
|
| 198 |
+
+GGML_API void ggml_neo_pipeline_inc_dispatch_count(void);
|
| 199 |
+
+
|
| 200 |
+
+#ifdef __cplusplus
|
| 201 |
+
+}
|
| 202 |
+
+#endif
|
| 203 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu b/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 204 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 205 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 206 |
+
@@ -2,6 +2,10 @@
|
| 207 |
+
#include "ggml-impl.h"
|
| 208 |
+
#include "ggml-backend-impl.h"
|
| 209 |
+
|
| 210 |
+
+// opencoti F5 M3-B NEO orchestrator (registry of cur_g/cur_c pairs for the
|
| 211 |
+
+// asymmetric attention pipelining; consulted in graph_compute below).
|
| 212 |
+
+#include "ggml-neo-pipeline.h"
|
| 213 |
+
+
|
| 214 |
+
#include "ggml-cuda/allreduce.cuh"
|
| 215 |
+
#include "ggml-cuda/common.cuh"
|
| 216 |
+
#include "ggml-cuda/acc.cuh"
|
| 217 |
+
@@ -4485,6 +4489,58 @@ static enum ggml_status GGML_CALL ggml_backend_cuda_graph_compute(ggml_backend_t
|
| 218 |
+
|
| 219 |
+
ggml_cuda_set_device(cuda_ctx->device);
|
| 220 |
+
|
| 221 |
+
+ // opencoti F5 M3-B NEO: scan the graph for registered cur_g flash-attn
|
| 222 |
+
+ // outputs and for downstream consumers of prior cur_g. The orchestrator
|
| 223 |
+
+ // is dormant unless the graph builder called set_enabled(true) (which
|
| 224 |
+
+ // only happens when --neo-pipeline != off AND at least one layer has
|
| 225 |
+
+ // an active headinfer split). When dormant this loop body is one
|
| 226 |
+
+ // atomic-load fast-skip.
|
| 227 |
+
+ //
|
| 228 |
+
+ // When NEO is engaged for this split:
|
| 229 |
+
+ // - If the split computes a cur_g, swap curr_stream_no to 1 so the
|
| 230 |
+
+ // kernels dispatch on the alternate CUDA stream; the scheduler's
|
| 231 |
+
+ // stream-0 sync in compute_splits will NOT block on this work, so
|
| 232 |
+
+ // the subsequent split's q_c host copy starts immediately and the
|
| 233 |
+
+ // CPU FA on cur_c can overlap with our stream-1 work. We record an
|
| 234 |
+
+ // event on stream 1 at exit so downstream consumers know when
|
| 235 |
+
+ // cur_g is done.
|
| 236 |
+
+ // - If the split reads a prior cur_g (typically the concat join),
|
| 237 |
+
+ // stream-wait on the recorded event before kernels read it.
|
| 238 |
+
+ //
|
| 239 |
+
+ // CUDA graph capture is disabled when NEO is active for this split:
|
| 240 |
+
+ // capturing kernel dispatch on a non-default stream interacts with the
|
| 241 |
+
+ // graph_key cache and the existing concurrent_event re-ordering pass
|
| 242 |
+
+ // in ways we haven't validated. Off-path graphs keep the cuda_graph
|
| 243 |
+
+ // fast path; only NEO-engaged splits pay this cost.
|
| 244 |
+
+ bool neo_active = false;
|
| 245 |
+
+ ggml_tensor * neo_cur_g_to_record = nullptr;
|
| 246 |
+
+ if (ggml_neo_pipeline_enabled()) {
|
| 247 |
+
+ for (int i = 0; i < cgraph->n_nodes; ++i) {
|
| 248 |
+
+ ggml_tensor * node = cgraph->nodes[i];
|
| 249 |
+
+ if (ggml_neo_pipeline_is_gpu_partner(node)) {
|
| 250 |
+
+ neo_active = true;
|
| 251 |
+
+ neo_cur_g_to_record = node;
|
| 252 |
+
+ break;
|
| 253 |
+
+ }
|
| 254 |
+
+ for (int s = 0; s < GGML_MAX_SRC; ++s) {
|
| 255 |
+
+ if (node->src[s] != nullptr && ggml_neo_pipeline_is_gpu_partner(node->src[s])) {
|
| 256 |
+
+ void * ev = ggml_neo_pipeline_get_event(node->src[s]);
|
| 257 |
+
+ if (ev != nullptr) {
|
| 258 |
+
+ CUDA_CHECK(cudaStreamWaitEvent(cuda_ctx->stream(), (cudaEvent_t) ev, 0));
|
| 259 |
+
+ }
|
| 260 |
+
+ }
|
| 261 |
+
+ }
|
| 262 |
+
+ }
|
| 263 |
+
+ }
|
| 264 |
+
+ if (neo_active) {
|
| 265 |
+
+ // Materialize stream 1 (creates it on first use) and switch the
|
| 266 |
+
+ // context to dispatch all subsequent kernels there. Both calls are
|
| 267 |
+
+ // cheap: stream() is a vector lookup + lazy create; the curr_stream_no
|
| 268 |
+
+ // write is a single int.
|
| 269 |
+
+ (void) cuda_ctx->stream(cuda_ctx->device, 1);
|
| 270 |
+
+ cuda_ctx->curr_stream_no = 1;
|
| 271 |
+
+ }
|
| 272 |
+
+
|
| 273 |
+
bool use_cuda_graph = false;
|
| 274 |
+
bool cuda_graph_update_required = false;
|
| 275 |
+
const void * graph_key = nullptr;
|
| 276 |
+
@@ -4524,6 +4580,16 @@ static enum ggml_status GGML_CALL ggml_backend_cuda_graph_compute(ggml_backend_t
|
| 277 |
+
}
|
| 278 |
+
#endif // USE_CUDA_GRAPH
|
| 279 |
+
|
| 280 |
+
+ // opencoti F5 M3-B NEO: force-disable cuda_graph capture for NEO-active
|
| 281 |
+
+ // splits. Capturing on a non-default stream is structurally fine in CUDA
|
| 282 |
+
+ // but interacts with the existing graph_key cache + concurrent_event
|
| 283 |
+
+ // re-ordering pass in ways we haven't validated. Off-path graphs keep
|
| 284 |
+
+ // the fast cuda_graph path.
|
| 285 |
+
+ if (neo_active) {
|
| 286 |
+
+ use_cuda_graph = false;
|
| 287 |
+
+ cuda_graph_update_required = false;
|
| 288 |
+
+ }
|
| 289 |
+
+
|
| 290 |
+
if (use_cuda_graph && cuda_graph_update_required) {
|
| 291 |
+
// Start CUDA graph capture
|
| 292 |
+
{
|
| 293 |
+
@@ -4536,6 +4602,23 @@ static enum ggml_status GGML_CALL ggml_backend_cuda_graph_compute(ggml_backend_t
|
| 294 |
+
|
| 295 |
+
ggml_cuda_graph_evaluate_and_capture(cuda_ctx, cgraph, use_cuda_graph, cuda_graph_update_required, graph_key);
|
| 296 |
+
|
| 297 |
+
+ // opencoti F5 M3-B NEO exit: record an event on stream 1 marking cur_g's
|
| 298 |
+
+ // completion so the downstream concat (consumer split) can stream-wait
|
| 299 |
+
+ // on it from stream 0. Restore curr_stream_no=0 so the next graph_compute
|
| 300 |
+
+ // call starts on the default stream as normal.
|
| 301 |
+
+ if (neo_active && neo_cur_g_to_record != nullptr) {
|
| 302 |
+
+ void * ev = ggml_neo_pipeline_get_event(neo_cur_g_to_record);
|
| 303 |
+
+ if (ev == nullptr) {
|
| 304 |
+
+ cudaEvent_t new_ev;
|
| 305 |
+
+ CUDA_CHECK(cudaEventCreateWithFlags(&new_ev, cudaEventDisableTiming));
|
| 306 |
+
+ ev = (void *) new_ev;
|
| 307 |
+
+ ggml_neo_pipeline_set_event(neo_cur_g_to_record, ev);
|
| 308 |
+
+ }
|
| 309 |
+
+ CUDA_CHECK(cudaEventRecord((cudaEvent_t) ev, cuda_ctx->stream(cuda_ctx->device, 1)));
|
| 310 |
+
+ ggml_neo_pipeline_inc_dispatch_count();
|
| 311 |
+
+ cuda_ctx->curr_stream_no = 0;
|
| 312 |
+
+ }
|
| 313 |
+
+
|
| 314 |
+
return GGML_STATUS_SUCCESS;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
diff --git a/llama.cpp/ggml/src/ggml-neo-pipeline.cpp b/llama.cpp/ggml/src/ggml-neo-pipeline.cpp
|
| 318 |
+
new file mode 100644
|
| 319 |
+
--- /dev/null
|
| 320 |
+
+++ b/llama.cpp/ggml/src/ggml-neo-pipeline.cpp
|
| 321 |
+
@@ -0,0 +1,126 @@
|
| 322 |
+
+// opencoti F5 M3 NEO orchestrator implementation.
|
| 323 |
+
+//
|
| 324 |
+
+// Backend-agnostic registry that lets the CUDA backend recognize the M3-built
|
| 325 |
+
+// (cur_g, cur_c) flash-attention pairs and route cur_g to a non-default CUDA
|
| 326 |
+
+// stream so cur_c can dispatch on the CPU backend concurrently.
|
| 327 |
+
+//
|
| 328 |
+
+// The orchestrator stores tensor pointers and opaque cudaEvent handles. It
|
| 329 |
+
+// does not call any CUDA API directly β the CUDA backend creates events with
|
| 330 |
+
+// cudaEventCreateWithFlags and hands them to set_event; on shutdown the CUDA
|
| 331 |
+
+// backend supplies a destroyer callback that wraps cudaEventDestroy.
|
| 332 |
+
+//
|
| 333 |
+
+// All maps are protected by a single mutex. Lookups are O(1) (unordered_map).
|
| 334 |
+
+// While the global enabled flag is false, every public function fast-skips
|
| 335 |
+
+// without touching the maps, so the off-path is one atomic-load + branch.
|
| 336 |
+
+//
|
| 337 |
+
+// See ggml/include/ggml-neo-pipeline.h for the public API contract and
|
| 338 |
+
+// docs/features/advanced_kv.md (F5 M3) for the surrounding design.
|
| 339 |
+
+
|
| 340 |
+
+#include "ggml-neo-pipeline.h"
|
| 341 |
+
+
|
| 342 |
+
+#include <atomic>
|
| 343 |
+
+#include <mutex>
|
| 344 |
+
+#include <unordered_map>
|
| 345 |
+
+#include <unordered_set>
|
| 346 |
+
+
|
| 347 |
+
+namespace {
|
| 348 |
+
+
|
| 349 |
+
+std::atomic<bool> g_enabled{false};
|
| 350 |
+
+std::atomic<int> g_dispatch_count{0};
|
| 351 |
+
+
|
| 352 |
+
+std::mutex g_mu;
|
| 353 |
+
+
|
| 354 |
+
+// Registered cur_g tensors. Membership probe is O(1).
|
| 355 |
+
+std::unordered_set<const ggml_tensor *> g_gpu_partners;
|
| 356 |
+
+
|
| 357 |
+
+// Map from cur_g β cudaEvent_t handle (opaque void *). NULL slot means the
|
| 358 |
+
+// CUDA backend hasn't created an event for this tensor yet β get_event
|
| 359 |
+
+// returns nullptr in that case so the caller knows to create-and-set.
|
| 360 |
+
+std::unordered_map<const ggml_tensor *, void *> g_events;
|
| 361 |
+
+
|
| 362 |
+
+} // namespace
|
| 363 |
+
+
|
| 364 |
+
+void ggml_neo_pipeline_set_enabled(bool enabled) {
|
| 365 |
+
+ g_enabled.store(enabled, std::memory_order_release);
|
| 366 |
+
+}
|
| 367 |
+
+
|
| 368 |
+
+bool ggml_neo_pipeline_enabled(void) {
|
| 369 |
+
+ return g_enabled.load(std::memory_order_acquire);
|
| 370 |
+
+}
|
| 371 |
+
+
|
| 372 |
+
+void ggml_neo_pipeline_register_pair(const ggml_tensor * cur_g, const ggml_tensor * cur_c) {
|
| 373 |
+
+ (void) cur_c; // cpu partner is informational; the orchestrator only acts on cur_g
|
| 374 |
+
+ if (!ggml_neo_pipeline_enabled()) {
|
| 375 |
+
+ return;
|
| 376 |
+
+ }
|
| 377 |
+
+ if (cur_g == nullptr) {
|
| 378 |
+
+ return;
|
| 379 |
+
+ }
|
| 380 |
+
+ std::lock_guard<std::mutex> lock(g_mu);
|
| 381 |
+
+ g_gpu_partners.insert(cur_g);
|
| 382 |
+
+}
|
| 383 |
+
+
|
| 384 |
+
+bool ggml_neo_pipeline_is_gpu_partner(const ggml_tensor * t) {
|
| 385 |
+
+ if (!ggml_neo_pipeline_enabled()) {
|
| 386 |
+
+ return false;
|
| 387 |
+
+ }
|
| 388 |
+
+ if (t == nullptr) {
|
| 389 |
+
+ return false;
|
| 390 |
+
+ }
|
| 391 |
+
+ std::lock_guard<std::mutex> lock(g_mu);
|
| 392 |
+
+ return g_gpu_partners.find(t) != g_gpu_partners.end();
|
| 393 |
+
+}
|
| 394 |
+
+
|
| 395 |
+
+void * ggml_neo_pipeline_get_event(const ggml_tensor * cur_g) {
|
| 396 |
+
+ if (!ggml_neo_pipeline_enabled()) {
|
| 397 |
+
+ return nullptr;
|
| 398 |
+
+ }
|
| 399 |
+
+ if (cur_g == nullptr) {
|
| 400 |
+
+ return nullptr;
|
| 401 |
+
+ }
|
| 402 |
+
+ std::lock_guard<std::mutex> lock(g_mu);
|
| 403 |
+
+ const auto it = g_events.find(cur_g);
|
| 404 |
+
+ if (it == g_events.end()) {
|
| 405 |
+
+ return nullptr;
|
| 406 |
+
+ }
|
| 407 |
+
+ return it->second;
|
| 408 |
+
+}
|
| 409 |
+
+
|
| 410 |
+
+void ggml_neo_pipeline_set_event(const ggml_tensor * cur_g, void * event) {
|
| 411 |
+
+ if (!ggml_neo_pipeline_enabled()) {
|
| 412 |
+
+ return;
|
| 413 |
+
+ }
|
| 414 |
+
+ if (cur_g == nullptr) {
|
| 415 |
+
+ return;
|
| 416 |
+
+ }
|
| 417 |
+
+ std::lock_guard<std::mutex> lock(g_mu);
|
| 418 |
+
+ g_events[cur_g] = event;
|
| 419 |
+
+}
|
| 420 |
+
+
|
| 421 |
+
+void ggml_neo_pipeline_clear_registrations(void) {
|
| 422 |
+
+ std::lock_guard<std::mutex> lock(g_mu);
|
| 423 |
+
+ g_gpu_partners.clear();
|
| 424 |
+
+ // Note: g_events deliberately NOT cleared here. Events are reusable
|
| 425 |
+
+ // across decode steps as long as the underlying tensor pointers stay
|
| 426 |
+
+ // stable. They're cleaned up by destroy_all_events on shutdown.
|
| 427 |
+
+}
|
| 428 |
+
+
|
| 429 |
+
+void ggml_neo_pipeline_destroy_all_events(void (*destroyer)(void * event)) {
|
| 430 |
+
+ std::lock_guard<std::mutex> lock(g_mu);
|
| 431 |
+
+ if (destroyer != nullptr) {
|
| 432 |
+
+ for (auto & kv : g_events) {
|
| 433 |
+
+ if (kv.second != nullptr) {
|
| 434 |
+
+ destroyer(kv.second);
|
| 435 |
+
+ }
|
| 436 |
+
+ }
|
| 437 |
+
+ }
|
| 438 |
+
+ g_events.clear();
|
| 439 |
+
+}
|
| 440 |
+
+
|
| 441 |
+
+int ggml_neo_pipeline_dispatch_count(void) {
|
| 442 |
+
+ return g_dispatch_count.load(std::memory_order_acquire);
|
| 443 |
+
+}
|
| 444 |
+
+
|
| 445 |
+
+void ggml_neo_pipeline_inc_dispatch_count(void) {
|
| 446 |
+
+ g_dispatch_count.fetch_add(1, std::memory_order_relaxed);
|
| 447 |
+
+}
|
| 448 |
+
diff --git a/llama.cpp/include/llama.h b/llama.cpp/include/llama.h
|
| 449 |
+
--- a/llama.cpp/include/llama.h
|
| 450 |
+
+++ b/llama.cpp/include/llama.h
|
| 451 |
+
@@ -358,6 +358,10 @@ extern "C" {
|
| 452 |
+
// streamed back per step). 1.0 = off. Only effective for offloaded
|
| 453 |
+
// layers with flash-attention (non-transposed V cache).
|
| 454 |
+
float headinfer_gpu_heads_frac;
|
| 455 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 456 |
+
+ // 0 = off (default), 1 = on, 2 = auto. Effective only when the
|
| 457 |
+
+ // M2 head split is active for a given layer.
|
| 458 |
+
+ uint32_t neo_pipeline_mode;
|
| 459 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback (0 = no rollback) [EXPERIMENTAL]
|
| 460 |
+
int32_t n_threads; // number of threads to use for generation
|
| 461 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 462 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 463 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 464 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 465 |
+
@@ -64,6 +64,8 @@ llama_context::llama_context(
|
| 466 |
+
cparams.slot_shrink_idle_ms = params.slot_shrink_idle_ms;
|
| 467 |
+
// opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 468 |
+
cparams.headinfer_gpu_heads_frac = params.headinfer_gpu_heads_frac;
|
| 469 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 470 |
+
+ cparams.neo_pipeline_mode = params.neo_pipeline_mode;
|
| 471 |
+
|
| 472 |
+
cparams.n_threads = params.n_threads;
|
| 473 |
+
cparams.n_threads_batch = params.n_threads_batch;
|
| 474 |
+
@@ -3356,6 +3358,7 @@ llama_context_params llama_context_default_params() {
|
| 475 |
+
/*.slot_initial_ctx =*/ 0,
|
| 476 |
+
/*.slot_shrink_idle_ms =*/ 0,
|
| 477 |
+
/*.headinfer_gpu_heads_frac =*/ 1.0f,
|
| 478 |
+
+ /*.neo_pipeline_mode =*/ 0, // opencoti F5 M3 neo β off by default
|
| 479 |
+
/*.n_rs_seq =*/ 0,
|
| 480 |
+
/*.n_threads =*/ GGML_DEFAULT_N_THREADS, // TODO: better default
|
| 481 |
+
/*.n_threads_batch =*/ GGML_DEFAULT_N_THREADS,
|
| 482 |
+
diff --git a/llama.cpp/src/llama-cparams.h b/llama.cpp/src/llama-cparams.h
|
| 483 |
+
--- a/llama.cpp/src/llama-cparams.h
|
| 484 |
+
+++ b/llama.cpp/src/llama-cparams.h
|
| 485 |
+
@@ -23,6 +23,15 @@ struct llama_cparams {
|
| 486 |
+
uint32_t slot_shrink_idle_ms;
|
| 487 |
+
// opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 488 |
+
float headinfer_gpu_heads_frac;
|
| 489 |
+
+ // opencoti F5 M3 neo β asymmetric GPU/CPU pipelining of attention.
|
| 490 |
+
+ // 0 = off (M2 concat path runs; binary equivalent to upstream when
|
| 491 |
+
+ // headinfer_gpu_heads_frac == 1.0).
|
| 492 |
+
+ // 1 = on (engage when headinfer split is active for the layer:
|
| 493 |
+
+ // build two flash_attn_ext ops on disjoint head ranges, run
|
| 494 |
+
+ // them on GPU and CPU backends respectively, concat outputs).
|
| 495 |
+
+ // 2 = auto (same engagement as `on` for now β reserved for future
|
| 496 |
+
+ // load-based engagement heuristics).
|
| 497 |
+
+ uint32_t neo_pipeline_mode;
|
| 498 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback
|
| 499 |
+
int32_t n_threads; // number of threads to use for generation
|
| 500 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 501 |
+
diff --git a/llama.cpp/src/llama-graph.cpp b/llama.cpp/src/llama-graph.cpp
|
| 502 |
+
--- a/llama.cpp/src/llama-graph.cpp
|
| 503 |
+
+++ b/llama.cpp/src/llama-graph.cpp
|
| 504 |
+
@@ -11,6 +11,10 @@
|
| 505 |
+
#include "llama-memory-hybrid-iswa.h"
|
| 506 |
+
#include "llama-memory-recurrent.h"
|
| 507 |
+
|
| 508 |
+
+// opencoti F5 M3 NEO orchestrator (registers cur_g/cur_c pairs so the CUDA
|
| 509 |
+
+// backend can route GPU FA to a non-default stream and overlap with CPU FA).
|
| 510 |
+
+#include "ggml-neo-pipeline.h"
|
| 511 |
+
+
|
| 512 |
+
#include <cassert>
|
| 513 |
+
#include <cmath>
|
| 514 |
+
#include <cstring>
|
| 515 |
+
@@ -2085,6 +2089,140 @@ ggml_tensor * llm_graph_context::build_attn_mha(
|
| 516 |
+
return cur;
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
+// opencoti F5 M3 neo β head-axis-split attention. Builds two flash_attn_ext
|
| 520 |
+
+// ops on disjoint head ranges:
|
| 521 |
+
+// GPU side: q[..., :gpu_heads*gqa, ...] Γ k_g Γ v_g (auto-routes to GPU
|
| 522 |
+
+// via the scheduler because k_g/v_g are views on the layer's
|
| 523 |
+
+// GPU buffer-type tensor β see ggml-backend.cpp:797-852).
|
| 524 |
+
+// CPU side: q[..., gpu_heads*gqa:, ...] Γ k_c Γ v_c (auto-routes to CPU
|
| 525 |
+
+// because k_c/v_c are views on the CPU buffer-type tensor that
|
| 526 |
+
+// M2's headinfer allocates alongside the GPU half β see
|
| 527 |
+
+// llama-kv-cache.cpp:get_k_cpu/get_v_cpu).
|
| 528 |
+
+// Outputs are concatenated on the head axis and fed into the standard
|
| 529 |
+
+// reshape_2d β Wo flow. This eliminates M2's per-step CPUβGPU stream-back
|
| 530 |
+
+// (O(n_kv) bytes per layer) β only the small O(n_tokens) CPU FA output
|
| 531 |
+
+// is bounced to GPU for the concat. Pre-M3-B, the two FA ops run serially
|
| 532 |
+
+// under the scheduler's default dispatch; M3-B's orchestrator will overlap
|
| 533 |
+
+// them concurrently.
|
| 534 |
+
+ggml_tensor * llm_graph_context::build_attn_mha_neo(
|
| 535 |
+
+ ggml_tensor * q,
|
| 536 |
+
+ ggml_tensor * k_g,
|
| 537 |
+
+ ggml_tensor * v_g,
|
| 538 |
+
+ ggml_tensor * k_c,
|
| 539 |
+
+ ggml_tensor * v_c,
|
| 540 |
+
+ uint32_t gpu_heads,
|
| 541 |
+
+ ggml_tensor * kq_b,
|
| 542 |
+
+ ggml_tensor * kq_mask,
|
| 543 |
+
+ ggml_tensor * sinks,
|
| 544 |
+
+ ggml_tensor * v_mla,
|
| 545 |
+
+ float kq_scale,
|
| 546 |
+
+ int il) const {
|
| 547 |
+
+ // Preconditions enforced by the M2 split gate that produced k_c/v_c.
|
| 548 |
+
+ GGML_ASSERT(cparams.flash_attn && "NEO path requires flash-attn");
|
| 549 |
+
+ GGML_ASSERT(kq_b == nullptr && "NEO path does not support KQ bias");
|
| 550 |
+
+ GGML_ASSERT(v_mla == nullptr && "NEO path does not support MLA");
|
| 551 |
+
+ GGML_ASSERT(gpu_heads > 0 && "NEO path requires an active head split");
|
| 552 |
+
+ GGML_ASSERT(k_g && v_g && k_c && v_c);
|
| 553 |
+
+
|
| 554 |
+
+ // K/V layout is identical between the GPU and CPU halves (non-transposed
|
| 555 |
+
+ // V per the M2 gate). Stream axis matches across both β both are views
|
| 556 |
+
+ // on tensors allocated with the same n_stream at construction.
|
| 557 |
+
+ GGML_ASSERT(k_g->ne[3] == k_c->ne[3]);
|
| 558 |
+
+ GGML_ASSERT(v_g->ne[3] == v_c->ne[3]);
|
| 559 |
+
+ GGML_ASSERT(k_g->nb[1] <= k_g->nb[2] && "headinfer split requires non-transposed V");
|
| 560 |
+
+
|
| 561 |
+
+ // The full Q has all n_head_q heads. Heads are split on KV-head-group
|
| 562 |
+
+ // boundaries (M2's GQA clamp ensures gpu_heads is a whole number of
|
| 563 |
+
+ // groups). gqa_ratio = n_head_q / n_head_kv; n_head_kv = gpu_heads +
|
| 564 |
+
+ // cpu_heads. Q-heads serving the same KV head are CONTIGUOUS in Q's
|
| 565 |
+
+ // dim 1 under standard GGUF, so the slice is a clean view (no extra
|
| 566 |
+
+ // permute beyond the one already in build_attn_mha).
|
| 567 |
+
+ const uint32_t n_head_q = (uint32_t) q->ne[1];
|
| 568 |
+
+ const uint32_t n_head_kv = (uint32_t) (k_g->ne[1] + k_c->ne[1]);
|
| 569 |
+
+ GGML_ASSERT(n_head_q % n_head_kv == 0 && "GQA ratio must divide n_head_q");
|
| 570 |
+
+ const uint32_t gqa_ratio = n_head_q / n_head_kv;
|
| 571 |
+
+ const uint32_t gpu_q_heads = gpu_heads * gqa_ratio;
|
| 572 |
+
+ const uint32_t cpu_q_heads = (n_head_kv - gpu_heads) * gqa_ratio;
|
| 573 |
+
+ GGML_ASSERT(gpu_q_heads + cpu_q_heads == n_head_q);
|
| 574 |
+
+
|
| 575 |
+
+ // Mirror build_attn_mha's n_stream reshape + permute on Q, but slice Q
|
| 576 |
+
+ // on the head axis BEFORE the permute so the dim-1 slice stays
|
| 577 |
+
+ // contiguous in the input layout.
|
| 578 |
+
+ const auto n_stream = k_g->ne[3];
|
| 579 |
+
+ ggml_tensor * q_4 = ggml_view_4d(ctx0, q,
|
| 580 |
+
+ q->ne[0], q->ne[1], q->ne[2]/n_stream, n_stream,
|
| 581 |
+
+ q->nb[1], q->nb[2], q->nb[3]/n_stream, 0);
|
| 582 |
+
+
|
| 583 |
+
+ ggml_tensor * q_g = ggml_view_4d(ctx0, q_4,
|
| 584 |
+
+ q_4->ne[0], gpu_q_heads, q_4->ne[2], q_4->ne[3],
|
| 585 |
+
+ q_4->nb[1], q_4->nb[2], q_4->nb[3],
|
| 586 |
+
+ /*offset=*/ 0);
|
| 587 |
+
+ ggml_tensor * q_c = ggml_view_4d(ctx0, q_4,
|
| 588 |
+
+ q_4->ne[0], cpu_q_heads, q_4->ne[2], q_4->ne[3],
|
| 589 |
+
+ q_4->nb[1], q_4->nb[2], q_4->nb[3],
|
| 590 |
+
+ /*offset=*/ (size_t) gpu_q_heads * q_4->nb[1]);
|
| 591 |
+
+
|
| 592 |
+
+ q_g = ggml_permute(ctx0, q_g, 0, 2, 1, 3);
|
| 593 |
+
+ q_c = ggml_permute(ctx0, q_c, 0, 2, 1, 3);
|
| 594 |
+
+ ggml_tensor * k_g_p = ggml_permute(ctx0, k_g, 0, 2, 1, 3);
|
| 595 |
+
+ ggml_tensor * v_g_p = ggml_permute(ctx0, v_g, 0, 2, 1, 3);
|
| 596 |
+
+ ggml_tensor * k_c_p = ggml_permute(ctx0, k_c, 0, 2, 1, 3);
|
| 597 |
+
+ ggml_tensor * v_c_p = ggml_permute(ctx0, v_c, 0, 2, 1, 3);
|
| 598 |
+
+
|
| 599 |
+
+ // F32 K β F16 cast mirrors build_attn_mha lines 1881-1887 (the embedding
|
| 600 |
+
+ // model non-causal path). v_trans is asserted false above, so no extra
|
| 601 |
+
+ // transpose is needed; V already has the same dim-1 head layout as K.
|
| 602 |
+
+ if (k_g_p->type == GGML_TYPE_F32) k_g_p = ggml_cast(ctx0, k_g_p, GGML_TYPE_F16);
|
| 603 |
+
+ if (k_c_p->type == GGML_TYPE_F32) k_c_p = ggml_cast(ctx0, k_c_p, GGML_TYPE_F16);
|
| 604 |
+
+ if (v_g_p->type == GGML_TYPE_F32) v_g_p = ggml_cast(ctx0, v_g_p, GGML_TYPE_F16);
|
| 605 |
+
+ if (v_c_p->type == GGML_TYPE_F32) v_c_p = ggml_cast(ctx0, v_c_p, GGML_TYPE_F16);
|
| 606 |
+
+
|
| 607 |
+
+ // Sinks (if present) carry one F32 entry per Q-head; slice them to
|
| 608 |
+
+ // match each half. The flash_attn_ext_add_sinks op asserts
|
| 609 |
+
+ // sinks->ne[0] == q->src[0]->ne[2] (the q-head axis after permute).
|
| 610 |
+
+ ggml_tensor * sinks_g = nullptr;
|
| 611 |
+
+ ggml_tensor * sinks_c = nullptr;
|
| 612 |
+
+ if (sinks) {
|
| 613 |
+
+ GGML_ASSERT((uint32_t) sinks->ne[0] == n_head_q);
|
| 614 |
+
+ sinks_g = ggml_view_1d(ctx0, sinks, gpu_q_heads, 0);
|
| 615 |
+
+ sinks_c = ggml_view_1d(ctx0, sinks, cpu_q_heads,
|
| 616 |
+
+ (size_t) gpu_q_heads * sinks->nb[0]);
|
| 617 |
+
+ }
|
| 618 |
+
+
|
| 619 |
+
+ const float alibi_bias = hparams.f_max_alibi_bias;
|
| 620 |
+
+ const float logit_cap = hparams.attn_soft_cap ? hparams.f_attn_logit_softcapping : 0.0f;
|
| 621 |
+
+
|
| 622 |
+
+ ggml_tensor * cur_g = ggml_flash_attn_ext(ctx0, q_g, k_g_p, v_g_p, kq_mask, kq_scale, alibi_bias, logit_cap);
|
| 623 |
+
+ cb(cur_g, LLAMA_TENSOR_NAME_FATTN "_g", il);
|
| 624 |
+
+ ggml_flash_attn_ext_add_sinks(cur_g, sinks_g);
|
| 625 |
+
+ ggml_flash_attn_ext_set_prec (cur_g, GGML_PREC_F32);
|
| 626 |
+
+
|
| 627 |
+
+ ggml_tensor * cur_c = ggml_flash_attn_ext(ctx0, q_c, k_c_p, v_c_p, kq_mask, kq_scale, alibi_bias, logit_cap);
|
| 628 |
+
+ cb(cur_c, LLAMA_TENSOR_NAME_FATTN "_c", il);
|
| 629 |
+
+ ggml_flash_attn_ext_add_sinks(cur_c, sinks_c);
|
| 630 |
+
+ ggml_flash_attn_ext_set_prec (cur_c, GGML_PREC_F32);
|
| 631 |
+
+
|
| 632 |
+
+ // opencoti F5 M3-B: enable + register the pair so the CUDA backend swaps
|
| 633 |
+
+ // cur_g to a non-default stream at dispatch time, letting cur_c (on CPU)
|
| 634 |
+
+ // overlap. set_enabled(true) is idempotent and only flipped here β the
|
| 635 |
+
+ // call site already gates on neo_pipeline_mode != 0 && headinfer_split_active,
|
| 636 |
+
+ // so reaching this point means NEO is engaged for this layer.
|
| 637 |
+
+ ggml_neo_pipeline_set_enabled(true);
|
| 638 |
+
+ ggml_neo_pipeline_register_pair(cur_g, cur_c);
|
| 639 |
+
+
|
| 640 |
+
+ // FA output shape is [v_dim, n_q_heads_split, n_tokens, n_stream].
|
| 641 |
+
+ // Concat on dim 1 (q-head axis) to reassemble the full Q-head set.
|
| 642 |
+
+ // The scheduler synchronizes here β cur_c gets lifted to GPU so the
|
| 643 |
+
+ // concat runs on whichever backend hosts cur_g (typically GPU).
|
| 644 |
+
+ ggml_tensor * cur = ggml_concat(ctx0, cur_g, cur_c, 1);
|
| 645 |
+
+ cb(cur, LLAMA_TENSOR_NAME_FATTN, il);
|
| 646 |
+
+
|
| 647 |
+
+ // Match build_attn_mha's flash-attn final reshape (line 1913).
|
| 648 |
+
+ cur = ggml_reshape_2d(ctx0, cur, cur->ne[0]*cur->ne[1], cur->ne[2]*cur->ne[3]);
|
| 649 |
+
+
|
| 650 |
+
+ return cur;
|
| 651 |
+
+}
|
| 652 |
+
+
|
| 653 |
+
llm_graph_input_attn_no_cache * llm_graph_context::build_attn_inp_no_cache() const {
|
| 654 |
+
auto inp = std::make_unique<llm_graph_input_attn_no_cache>(hparams, cparams);
|
| 655 |
+
|
| 656 |
+
@@ -2237,10 +2375,29 @@ ggml_tensor * llm_graph_context::build_attn(
|
| 657 |
+
const auto & kq_mask = inp->get_kq_mask();
|
| 658 |
+
|
| 659 |
+
ggml_tensor * q = q_cur;
|
| 660 |
+
- ggml_tensor * k = mctx_cur->get_k(ctx0, il);
|
| 661 |
+
- ggml_tensor * v = mctx_cur->get_v(ctx0, il);
|
| 662 |
+
|
| 663 |
+
- ggml_tensor * cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, kq_scale, il);
|
| 664 |
+
+ // opencoti F5 M3 neo β head-axis-split attention. When the M2 head
|
| 665 |
+
+ // residency split is active for this layer AND --neo-pipeline is on,
|
| 666 |
+
+ // build two flash_attn_ext ops on disjoint head ranges instead of the
|
| 667 |
+
+ // M2 concat-reassembly path (which streams k_cpu/v_cpu back to GPU each
|
| 668 |
+
+ // step and runs a single full-head FA op). The scheduler routes each
|
| 669 |
+
+ // FA op to the backend matching its K/V buffer-type. v_mla must be
|
| 670 |
+
+ // null (build_attn for KV asserts this above at :2102), kq_b too;
|
| 671 |
+
+ // the M2 gate ensures non-transposed V. See docs/features/advanced_kv.md.
|
| 672 |
+
+ ggml_tensor * cur;
|
| 673 |
+
+ if (cparams.neo_pipeline_mode != 0 && mctx_cur->headinfer_split_active(il)) {
|
| 674 |
+
+ ggml_tensor * k_g = mctx_cur->get_k_gpu(ctx0, il);
|
| 675 |
+
+ ggml_tensor * v_g = mctx_cur->get_v_gpu(ctx0, il);
|
| 676 |
+
+ ggml_tensor * k_c = mctx_cur->get_k_cpu(ctx0, il);
|
| 677 |
+
+ ggml_tensor * v_c = mctx_cur->get_v_cpu(ctx0, il);
|
| 678 |
+
+ cur = build_attn_mha_neo(q, k_g, v_g, k_c, v_c,
|
| 679 |
+
+ mctx_cur->headinfer_gpu_heads(il),
|
| 680 |
+
+ kq_b, kq_mask, sinks, v_mla, kq_scale, il);
|
| 681 |
+
+ } else {
|
| 682 |
+
+ ggml_tensor * k = mctx_cur->get_k(ctx0, il);
|
| 683 |
+
+ ggml_tensor * v = mctx_cur->get_v(ctx0, il);
|
| 684 |
+
+ cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, kq_scale, il);
|
| 685 |
+
+ }
|
| 686 |
+
cb(cur, "kqv_out", il);
|
| 687 |
+
|
| 688 |
+
if (inp->self_v_rot) {
|
| 689 |
+
diff --git a/llama.cpp/src/llama-graph.h b/llama.cpp/src/llama-graph.h
|
| 690 |
+
--- a/llama.cpp/src/llama-graph.h
|
| 691 |
+
+++ b/llama.cpp/src/llama-graph.h
|
| 692 |
+
@@ -908,6 +908,27 @@ struct llm_graph_context {
|
| 693 |
+
float kq_scale,
|
| 694 |
+
int il) const;
|
| 695 |
+
|
| 696 |
+
+ // opencoti F5 M3 neo β head-axis split flash-attn. Builds two
|
| 697 |
+
+ // ggml_flash_attn_ext ops on disjoint head ranges (GPU vs CPU subsets,
|
| 698 |
+
+ // routed automatically by the scheduler via the K/V tensors' buffer
|
| 699 |
+
+ // types) and concatenates their outputs on the head axis before the
|
| 700 |
+
+ // reshape/Wo projection. Flash-attention only (assert kq_b == nullptr,
|
| 701 |
+
+ // v_mla == nullptr, !v_trans for V β the M2 split gate already enforces
|
| 702 |
+
+ // this). Output shape matches build_attn_mha's flash-attn branch.
|
| 703 |
+
+ ggml_tensor * build_attn_mha_neo(
|
| 704 |
+
+ ggml_tensor * q, // [n_embd_head_q, n_head_q, n_tokens]
|
| 705 |
+
+ ggml_tensor * k_g, // GPU-resident K subset (gpu_heads on dim 1)
|
| 706 |
+
+ ggml_tensor * v_g, // GPU-resident V subset (gpu_heads on dim 1)
|
| 707 |
+
+ ggml_tensor * k_c, // CPU-resident K subset (cpu_heads on dim 1)
|
| 708 |
+
+ ggml_tensor * v_c, // CPU-resident V subset (cpu_heads on dim 1)
|
| 709 |
+
+ uint32_t gpu_heads,
|
| 710 |
+
+ ggml_tensor * kq_b, // must be nullptr
|
| 711 |
+
+ ggml_tensor * kq_mask,
|
| 712 |
+
+ ggml_tensor * sinks, // [n_head_q] or nullptr
|
| 713 |
+
+ ggml_tensor * v_mla, // must be nullptr
|
| 714 |
+
+ float kq_scale,
|
| 715 |
+
+ int il) const;
|
| 716 |
+
+
|
| 717 |
+
llm_graph_input_attn_no_cache * build_attn_inp_no_cache() const;
|
| 718 |
+
|
| 719 |
+
ggml_tensor * build_attn(
|
| 720 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 721 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 722 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 723 |
+
@@ -1810,6 +1810,116 @@ ggml_tensor * llama_kv_cache::get_v(ggml_context * ctx, int32_t il, uint32_t n_k
|
| 724 |
+
ggml_row_size(v->type, kv_size*n_embd_v_gqa)*sinfo.s0);
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
+// opencoti F5 M3 neo β head-split-aware accessors. These factor the per-half
|
| 728 |
+
+// view machinery that get_k/get_v perform internally before their concat,
|
| 729 |
+
+// exposing the GPU-only and CPU-only sub-tensors as independent ggml views.
|
| 730 |
+
+// M3 NEO uses them to build two flash_attn_ext ops (one per backend, scheduler
|
| 731 |
+
+// auto-routes by buffer type) and merge their outputs, eliminating M2's
|
| 732 |
+
+// per-step O(n_kv) CPUβGPU stream-back. Dimensions and strides are identical
|
| 733 |
+
+// to the corresponding halves inside M2's get_k/get_v.
|
| 734 |
+
+
|
| 735 |
+
+bool llama_kv_cache::headinfer_split_active(int32_t il) const {
|
| 736 |
+
+ const auto it = map_layer_ids.find(il);
|
| 737 |
+
+ if (it == map_layer_ids.end()) {
|
| 738 |
+
+ return false;
|
| 739 |
+
+ }
|
| 740 |
+
+ const auto & layer = layers[it->second];
|
| 741 |
+
+ return layer.gpu_heads > 0 && layer.k_cpu != nullptr;
|
| 742 |
+
+}
|
| 743 |
+
+
|
| 744 |
+
+uint32_t llama_kv_cache::headinfer_gpu_heads(int32_t il) const {
|
| 745 |
+
+ const auto it = map_layer_ids.find(il);
|
| 746 |
+
+ if (it == map_layer_ids.end()) {
|
| 747 |
+
+ return 0;
|
| 748 |
+
+ }
|
| 749 |
+
+ return layers[it->second].gpu_heads;
|
| 750 |
+
+}
|
| 751 |
+
+
|
| 752 |
+
+ggml_tensor * llama_kv_cache::get_k_gpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 753 |
+
+ const int32_t ikv = map_layer_ids.at(il);
|
| 754 |
+
+ const auto & layer = layers[ikv];
|
| 755 |
+
+ GGML_ASSERT(layer.gpu_heads > 0 && "get_k_gpu called without an active headinfer split");
|
| 756 |
+
+
|
| 757 |
+
+ auto * k = layer.k;
|
| 758 |
+
+ const uint64_t kv_size = get_size();
|
| 759 |
+
+ const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 760 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 761 |
+
+ const uint32_t n_head_gpu = layer.gpu_heads;
|
| 762 |
+
+ const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_k;
|
| 763 |
+
+
|
| 764 |
+
+ return ggml_view_4d(ctx, k,
|
| 765 |
+
+ n_embd_head_k, n_head_gpu, n_kv, ns,
|
| 766 |
+
+ ggml_row_size(k->type, n_embd_head_k),
|
| 767 |
+
+ ggml_row_size(k->type, n_embd_gpu),
|
| 768 |
+
+ ggml_row_size(k->type, n_embd_gpu*kv_size),
|
| 769 |
+
+ ggml_row_size(k->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 770 |
+
+}
|
| 771 |
+
+
|
| 772 |
+
+ggml_tensor * llama_kv_cache::get_k_cpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 773 |
+
+ const int32_t ikv = map_layer_ids.at(il);
|
| 774 |
+
+ const auto & layer = layers[ikv];
|
| 775 |
+
+ GGML_ASSERT(layer.gpu_heads > 0 && layer.k_cpu && "get_k_cpu called without an active headinfer split");
|
| 776 |
+
+
|
| 777 |
+
+ auto * kc = layer.k_cpu;
|
| 778 |
+
+ const uint64_t kv_size = get_size();
|
| 779 |
+
+ const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 780 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 781 |
+
+ const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 782 |
+
+ const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_k;
|
| 783 |
+
+
|
| 784 |
+
+ return ggml_view_4d(ctx, kc,
|
| 785 |
+
+ n_embd_head_k, n_head_cpu, n_kv, ns,
|
| 786 |
+
+ ggml_row_size(kc->type, n_embd_head_k),
|
| 787 |
+
+ ggml_row_size(kc->type, n_embd_cpu),
|
| 788 |
+
+ ggml_row_size(kc->type, n_embd_cpu*kv_size),
|
| 789 |
+
+ ggml_row_size(kc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 790 |
+
+}
|
| 791 |
+
+
|
| 792 |
+
+ggml_tensor * llama_kv_cache::get_v_gpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 793 |
+
+ const int32_t ikv = map_layer_ids.at(il);
|
| 794 |
+
+ const auto & layer = layers[ikv];
|
| 795 |
+
+ GGML_ASSERT(layer.gpu_heads > 0 && "get_v_gpu called without an active headinfer split");
|
| 796 |
+
+ // M2's gate is `!v_trans && n_stream == 1` β when split is active the
|
| 797 |
+
+ // V layout is always non-transposed (matches get_v's non-split branch
|
| 798 |
+
+ // shape modulo head count). Same head-dim-1 stride pattern as K.
|
| 799 |
+
+ GGML_ASSERT(!v_trans && "headinfer split requires non-transposed V");
|
| 800 |
+
+
|
| 801 |
+
+ auto * v = layer.v;
|
| 802 |
+
+ const uint64_t kv_size = get_size();
|
| 803 |
+
+ const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 804 |
+
+ const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 805 |
+
+ const uint32_t n_head_gpu = layer.gpu_heads;
|
| 806 |
+
+ const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_v;
|
| 807 |
+
+
|
| 808 |
+
+ return ggml_view_4d(ctx, v,
|
| 809 |
+
+ n_embd_head_v, n_head_gpu, n_kv, ns,
|
| 810 |
+
+ ggml_row_size(v->type, n_embd_head_v),
|
| 811 |
+
+ ggml_row_size(v->type, n_embd_gpu),
|
| 812 |
+
+ ggml_row_size(v->type, n_embd_gpu*kv_size),
|
| 813 |
+
+ ggml_row_size(v->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 814 |
+
+}
|
| 815 |
+
+
|
| 816 |
+
+ggml_tensor * llama_kv_cache::get_v_cpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 817 |
+
+ const int32_t ikv = map_layer_ids.at(il);
|
| 818 |
+
+ const auto & layer = layers[ikv];
|
| 819 |
+
+ GGML_ASSERT(layer.gpu_heads > 0 && layer.v_cpu && "get_v_cpu called without an active headinfer split");
|
| 820 |
+
+ GGML_ASSERT(!v_trans && "headinfer split requires non-transposed V");
|
| 821 |
+
+
|
| 822 |
+
+ auto * vc = layer.v_cpu;
|
| 823 |
+
+ const uint64_t kv_size = get_size();
|
| 824 |
+
+ const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 825 |
+
+ const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 826 |
+
+ const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 827 |
+
+ const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_v;
|
| 828 |
+
+
|
| 829 |
+
+ return ggml_view_4d(ctx, vc,
|
| 830 |
+
+ n_embd_head_v, n_head_cpu, n_kv, ns,
|
| 831 |
+
+ ggml_row_size(vc->type, n_embd_head_v),
|
| 832 |
+
+ ggml_row_size(vc->type, n_embd_cpu),
|
| 833 |
+
+ ggml_row_size(vc->type, n_embd_cpu*kv_size),
|
| 834 |
+
+ ggml_row_size(vc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 835 |
+
+}
|
| 836 |
+
+
|
| 837 |
+
ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il, const slot_info & sinfo) const {
|
| 838 |
+
GGML_UNUSED(sinfo);
|
| 839 |
+
|
| 840 |
+
@@ -3107,6 +3217,31 @@ ggml_tensor * llama_kv_cache_context::get_v(ggml_context * ctx, int32_t il) cons
|
| 841 |
+
return kv->get_v(ctx, il, n_kv, sinfos[i_cur]);
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
+// opencoti F5 M3 neo β per-batch wrappers around the head-split accessors.
|
| 845 |
+
+ggml_tensor * llama_kv_cache_context::get_k_gpu(ggml_context * ctx, int32_t il) const {
|
| 846 |
+
+ return kv->get_k_gpu(ctx, il, n_kv, sinfos[i_cur]);
|
| 847 |
+
+}
|
| 848 |
+
+
|
| 849 |
+
+ggml_tensor * llama_kv_cache_context::get_v_gpu(ggml_context * ctx, int32_t il) const {
|
| 850 |
+
+ return kv->get_v_gpu(ctx, il, n_kv, sinfos[i_cur]);
|
| 851 |
+
+}
|
| 852 |
+
+
|
| 853 |
+
+ggml_tensor * llama_kv_cache_context::get_k_cpu(ggml_context * ctx, int32_t il) const {
|
| 854 |
+
+ return kv->get_k_cpu(ctx, il, n_kv, sinfos[i_cur]);
|
| 855 |
+
+}
|
| 856 |
+
+
|
| 857 |
+
+ggml_tensor * llama_kv_cache_context::get_v_cpu(ggml_context * ctx, int32_t il) const {
|
| 858 |
+
+ return kv->get_v_cpu(ctx, il, n_kv, sinfos[i_cur]);
|
| 859 |
+
+}
|
| 860 |
+
+
|
| 861 |
+
+bool llama_kv_cache_context::headinfer_split_active(int32_t il) const {
|
| 862 |
+
+ return kv->headinfer_split_active(il);
|
| 863 |
+
+}
|
| 864 |
+
+
|
| 865 |
+
+uint32_t llama_kv_cache_context::headinfer_gpu_heads(int32_t il) const {
|
| 866 |
+
+ return kv->headinfer_gpu_heads(il);
|
| 867 |
+
+}
|
| 868 |
+
+
|
| 869 |
+
ggml_tensor * llama_kv_cache_context::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il) const {
|
| 870 |
+
return kv->cpy_k(ctx, k_cur, k_idxs, il, sinfos[i_cur]);
|
| 871 |
+
}
|
| 872 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 873 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 874 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 875 |
+
@@ -217,6 +217,23 @@ public:
|
| 876 |
+
ggml_tensor * get_k(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
| 877 |
+
ggml_tensor * get_v(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
| 878 |
+
|
| 879 |
+
+ // opencoti F5 M3 neo β head-split-aware K/V accessors. Return separate
|
| 880 |
+
+ // views onto the GPU-resident and host-resident head subsets without the
|
| 881 |
+
+ // concat-reassembly that get_k/get_v perform. Built on the SAME view
|
| 882 |
+
+ // machinery (see llama-kv-cache.cpp get_k for the dimensions); callers
|
| 883 |
+
+ // are expected to gate via headinfer_split_active(il) and to build their
|
| 884 |
+
+ // own combiner (M3 NEO builds two flash_attn_ext ops + a head-axis concat
|
| 885 |
+
+ // on the small attention outputs, eliminating M2's O(n_kv) stream-back).
|
| 886 |
+
+ // For non-split layers these return the equivalent of the un-split view
|
| 887 |
+
+ // for the "_gpu" half and nullptr for "_cpu" β callers must check
|
| 888 |
+
+ // headinfer_split_active first.
|
| 889 |
+
+ ggml_tensor * get_k_gpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
| 890 |
+
+ ggml_tensor * get_v_gpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
| 891 |
+
+ ggml_tensor * get_k_cpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
| 892 |
+
+ ggml_tensor * get_v_cpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
| 893 |
+
+ bool headinfer_split_active(int32_t il) const;
|
| 894 |
+
+ uint32_t headinfer_gpu_heads (int32_t il) const;
|
| 895 |
+
+
|
| 896 |
+
// store k_cur and v_cur in the cache based on the provided head location
|
| 897 |
+
ggml_tensor * cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il, const slot_info & sinfo) const;
|
| 898 |
+
ggml_tensor * cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggml_tensor * v_idxs, int32_t il, const slot_info & sinfo) const;
|
| 899 |
+
@@ -444,6 +461,16 @@ public:
|
| 900 |
+
ggml_tensor * get_k(ggml_context * ctx, int32_t il) const;
|
| 901 |
+
ggml_tensor * get_v(ggml_context * ctx, int32_t il) const;
|
| 902 |
+
|
| 903 |
+
+ // opencoti F5 M3 neo β head-split-aware K/V accessors. Per-batch wrappers
|
| 904 |
+
+ // that delegate to the underlying llama_kv_cache with the current n_kv /
|
| 905 |
+
+ // slot info. See llama-kv-cache.h for semantics.
|
| 906 |
+
+ ggml_tensor * get_k_gpu(ggml_context * ctx, int32_t il) const;
|
| 907 |
+
+ ggml_tensor * get_v_gpu(ggml_context * ctx, int32_t il) const;
|
| 908 |
+
+ ggml_tensor * get_k_cpu(ggml_context * ctx, int32_t il) const;
|
| 909 |
+
+ ggml_tensor * get_v_cpu(ggml_context * ctx, int32_t il) const;
|
| 910 |
+
+ bool headinfer_split_active(int32_t il) const;
|
| 911 |
+
+ uint32_t headinfer_gpu_heads (int32_t il) const;
|
| 912 |
+
+
|
| 913 |
+
// store k_cur and v_cur in the cache based on the provided head location
|
| 914 |
+
// note: the heads in k_cur and v_cur should be laid out contiguously in memory
|
| 915 |
+
// - k_cur [n_embd_head_k, n_head_k, n_tokens]
|
| 916 |
+
diff --git a/llamafile/build-functions.sh b/llamafile/build-functions.sh
|
| 917 |
+
--- a/llamafile/build-functions.sh
|
| 918 |
+
+++ b/llamafile/build-functions.sh
|
| 919 |
+
@@ -218,10 +218,14 @@ compile_ggml_core() {
|
| 920 |
+
local llama_cpp_dir="$1"
|
| 921 |
+
local build_dir="$2"
|
| 922 |
+
|
| 923 |
+
+ # opencoti F5 M3-B: ggml-neo-pipeline.cpp added so the CUDA backend's
|
| 924 |
+
+ # graph_compute hook can call the orchestrator API (registered cur_g
|
| 925 |
+
+ # detection + event slot get/set + dispatch-count increment).
|
| 926 |
+
local ggml_core_sources="\
|
| 927 |
+
$llama_cpp_dir/ggml/src/ggml.c \
|
| 928 |
+
$llama_cpp_dir/ggml/src/ggml-alloc.c \
|
| 929 |
+
$llama_cpp_dir/ggml/src/ggml-backend.cpp \
|
| 930 |
+
+ $llama_cpp_dir/ggml/src/ggml-neo-pipeline.cpp \
|
| 931 |
+
$llama_cpp_dir/ggml/src/ggml-backend-meta.cpp \
|
| 932 |
+
$llama_cpp_dir/ggml/src/ggml-quants.c \
|
| 933 |
+
$llama_cpp_dir/ggml/src/ggml-threading.cpp"
|
patches/0031-per-stream-split.patch
ADDED
|
@@ -0,0 +1,1513 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0031] F4 M3 Phase 4 β per-stream KV tensor split (task #109)
|
| 3 |
+
|
| 4 |
+
Resolves the long-deferred Phase 4 (`docs/decisions/0001-lazy-slot-context.md`)
|
| 5 |
+
of F4 M3 β every layer's K/V (and M2's CPU subset) become OWNING per-stream
|
| 6 |
+
tensor vectors rather than one 3-D tensor with a stream dim, with each
|
| 7 |
+
(stream, buffer-type) pair owning its own backend buffer. Unified mode
|
| 8 |
+
(`n_stream == 1`) collapses to the pre-Phase-4 layout and stays
|
| 9 |
+
byte-identical; non-unified mode (`n_stream > 1`) is functional for the
|
| 10 |
+
first time, unlocking the multi-tenant codepaths (M2 head-split now lifts
|
| 11 |
+
its `n_stream == 1` construction-time gate).
|
| 12 |
+
|
| 13 |
+
Sub-milestone shape (each landed against the M5-shipped tree at HEAD =
|
| 14 |
+
`2373e69` and verified by the M5 stack + M0 turn-2 + M1 eviction + M2
|
| 15 |
+
residency benches before composing):
|
| 16 |
+
|
| 17 |
+
- Phase 4-A β per-stream tensor split. `kv_layer` replaces
|
| 18 |
+
`k`/`v`/`k_cpu`/`v_cpu` direct pointers with
|
| 19 |
+
`std::vector<ggml_tensor *> k_per_stream` / `v_per_stream` /
|
| 20 |
+
`k_cpu_per_stream` / `v_cpu_per_stream`; each per-stream tensor is
|
| 21 |
+
`ggml_new_tensor_3d(ctx_s, type, n_embd, kv_size, 1)`. The trailing
|
| 22 |
+
`dim 3 == 1` keeps stride math identical to the pre-Phase-4
|
| 23 |
+
`[n_embd, kv_size, n_stream]` layout under unified mode. The
|
| 24 |
+
`k_stream` / `v_stream` 2-D view vectors are preserved (offset 0
|
| 25 |
+
within each per-stream tensor) so state I/O and cross-stream copies
|
| 26 |
+
keep operating on the existing view handles.
|
| 27 |
+
|
| 28 |
+
- Phase 4-B β per-stream backend buffers. `ctx_map` becomes a nested
|
| 29 |
+
`std::vector<std::map<ggml_backend_buffer_type_t, ggml_context_ptr,
|
| 30 |
+
ggml_backend_buft_comparator>> ctx_map_per_stream` indexed by stream,
|
| 31 |
+
with a `ctx_for_buft(buft, s)` lambda that returns the get-or-create
|
| 32 |
+
`(stream, buft)` ctx (mem_size budgeted at `4u*n_layer_kv*
|
| 33 |
+
ggml_tensor_overhead()`). Allocation loops nest stream-outer Γ
|
| 34 |
+
buft-inner so each (stream, buft) pair allocates one backend buffer.
|
| 35 |
+
Phase 1 `ensure_cleared`, Phase 3 `opencoti_decommit_layer_range`,
|
| 36 |
+
and the M2 CPU subset construction follow the same per-stream
|
| 37 |
+
iteration shape.
|
| 38 |
+
|
| 39 |
+
- Phase 4-C β reworked write/read graph ops. `cpy_k`/`cpy_v` ship
|
| 40 |
+
option C-2 (N separate `ggml_set_rows`, one per `sinfo.n_stream()`
|
| 41 |
+
stream, tied via `ggml_add` on 1-D views β unified-mode batch_ns==1
|
| 42 |
+
collapses to one `set_rows`, byte-identical). C-1 (concat-then-
|
| 43 |
+
scatter) was rejected at impl time: `ggml_concat` materializes a
|
| 44 |
+
new buffer rather than viewing inputs, so writes routed through the
|
| 45 |
+
concat result would not land in the per-stream tensors. `get_k`/
|
| 46 |
+
`get_v` build per-cache-stream 4-D views and `ggml_concat` them
|
| 47 |
+
along dim 3 (stream axis) β unified mode yields a single view, no
|
| 48 |
+
concat. `set_input_k_idxs`/`set_input_v_idxs` emit LOCAL per-stream
|
| 49 |
+
indices unconditionally (Phase 4-F fix; the previous gating on
|
| 50 |
+
`sinfo.n_stream() > 1` left global indices in --parallel N servers
|
| 51 |
+
serving single-session batches and crashed CUDA decode against the
|
| 52 |
+
per-stream destination). `build_graph_shift` loops streams inside
|
| 53 |
+
the layer loop, slicing `inp->k_shift` per stream.
|
| 54 |
+
|
| 55 |
+
- Phase 4-D β M2 head-split is stream-aware. `get_k_gpu`/`get_v_gpu`/
|
| 56 |
+
`get_k_cpu`/`get_v_cpu` (and the M2 split branch inside `get_k`/
|
| 57 |
+
`get_v`/`cpy_k`/`cpy_v`) build per-stream 4-D views and concat
|
| 58 |
+
them on dim 3 (stream axis); the construction-time gate at
|
| 59 |
+
`headinfer_gpu_heads_frac < 1.0 && offload && !v_trans && n_stream
|
| 60 |
+
== 1` drops the `n_stream == 1` clause so per-stream CPU subsets
|
| 61 |
+
actually allocate for non-unified mode.
|
| 62 |
+
|
| 63 |
+
- Phase 4-E β cross-stream copies in `update()`, `state_write_data`,
|
| 64 |
+
`state_read_data`, and `seq_key_scores` continue to operate on the
|
| 65 |
+
existing `k_stream[s]` / `v_stream[s]` 2-D views (now offset-0 views
|
| 66 |
+
of per-stream tensors). Mechanically distinct from pre-Phase-4 (one
|
| 67 |
+
buffer per stream rather than one shared buffer), semantically the
|
| 68 |
+
same per the buffer transfer mechanism.
|
| 69 |
+
|
| 70 |
+
- Phase 4-F β verification gates. Cosine β₯ 0.999 unified-mode
|
| 71 |
+
equivalence captured indirectly via the M5 stack bench (B/A0/A1/
|
| 72 |
+
A2/A3/S configs all produce byte-identical KV MiB and
|
| 73 |
+
`t2_cached_n=1271` against the M5-shipped baseline) plus M2's
|
| 74 |
+
explicit greedy-decode byte-equality check (`split text ===
|
| 75 |
+
baseline text`, 6/6 PASS). Multi-stream RSS exercise comes via the
|
| 76 |
+
M0 turn-2 scenarioB (`--parallel 2` with two sessions sharing a
|
| 77 |
+
large system prefix β 5/5 PASS, validating per-stream KV reuse).
|
| 78 |
+
M3 neo-pipeline O off-identity config crashes on both pre-Phase-4
|
| 79 |
+
and post-Phase-4 binaries with `concat.cu:165 GGML_ASSERT(src0->
|
| 80 |
+
type == GGML_TYPE_F32)` β pre-existing M3 bug, DEFERRED as task
|
| 81 |
+
#278 and recorded as bug-207. Hook count stable at 20.
|
| 82 |
+
|
| 83 |
+
The HARD gates that survived from earlier sub-milestones (the
|
| 84 |
+
`GGML_ASSERT(n_stream == 1 && "Phase 4-A: get_k temporarily gated...";
|
| 85 |
+
"Phase 4-C lifts")` shape on get_k/get_v/cpy_k/cpy_v/build_graph_shift,
|
| 86 |
+
and the parallel `Phase 4-D lifts` shape on the M2 accessors) all reach
|
| 87 |
+
ship as lifted. Five `GGML_ASSERT(n_stream == 1)` lines deleted; the
|
| 88 |
+
remaining `n_stream == 1` checks are real preconditions (e.g., v_trans +
|
| 89 |
+
multi-stream is asserted out in cpy_v as not-a-real-workload).
|
| 90 |
+
|
| 91 |
+
Unblocks F5 M6 (PolyKV) which needs per-stream lifetimes cleanly
|
| 92 |
+
expressed for the shared compressed KV pool.
|
| 93 |
+
|
| 94 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 95 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 96 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 97 |
+
@@ -160,14 +160,35 @@ llama_kv_cache::llama_kv_cache(
|
| 98 |
+
return strcmp(ggml_backend_buft_name(lhs), ggml_backend_buft_name(rhs)) < 0;
|
| 99 |
+
}
|
| 100 |
+
};
|
| 101 |
+
- std::map<ggml_backend_buffer_type_t, ggml_context_ptr, ggml_backend_buft_comparator> ctx_map;
|
| 102 |
+
|
| 103 |
+
- // create a context for each buffer type
|
| 104 |
+
- auto ctx_for_buft = [&](ggml_backend_buffer_type_t buft) -> ggml_context * {
|
| 105 |
+
+ // opencoti F4 M3 Phase 4-B β per-stream backend buffer ownership.
|
| 106 |
+
+ // Each stream gets its own map of (buft β ctx) so each (stream, buft)
|
| 107 |
+
+ // pair allocates its own backend buffer. Phase 3's
|
| 108 |
+
+ // opencoti_decommit_layer_range then collapses to "decommit this
|
| 109 |
+
+ // stream's buffer range" cleanly, and M6 PolyKV's per-stream
|
| 110 |
+
+ // lifetimes (enroll/disenroll a stream into the pool) become
|
| 111 |
+
+ // mechanically straightforward.
|
| 112 |
+
+ //
|
| 113 |
+
+ // Unified mode (n_stream == 1): one stream, one inner map per
|
| 114 |
+
+ // buft, byte-identical layout to pre-Phase-4 (the buffer-allocation
|
| 115 |
+
+ // loop below ends up with exactly the same `(ctx, buf)` pairs in
|
| 116 |
+
+ // ctxs_bufs as before, in the same order, with the same sizes).
|
| 117 |
+
+ std::vector<std::map<ggml_backend_buffer_type_t, ggml_context_ptr, ggml_backend_buft_comparator>> ctx_map_per_stream;
|
| 118 |
+
+ ctx_map_per_stream.resize(n_stream);
|
| 119 |
+
+
|
| 120 |
+
+ // ctx_for_buft(buft, s): get-or-create the ggml_context for
|
| 121 |
+
+ // (stream s, buft). Each per-stream ctx holds at most 4 tensors
|
| 122 |
+
+ // per layer (K + V owning, K + V views; doubled when M2's CPU
|
| 123 |
+
+ // subset is engaged on a layer). Budget conservatively at
|
| 124 |
+
+ // 4*n_layer_kv slots per stream per buft β address space is free,
|
| 125 |
+
+ // RSS still grows only on first write.
|
| 126 |
+
+ auto ctx_for_buft = [&](ggml_backend_buffer_type_t buft, uint32_t s) -> ggml_context * {
|
| 127 |
+
+ GGML_ASSERT(s < ctx_map_per_stream.size());
|
| 128 |
+
+ auto & ctx_map = ctx_map_per_stream[s];
|
| 129 |
+
auto it = ctx_map.find(buft);
|
| 130 |
+
if (it == ctx_map.end()) {
|
| 131 |
+
ggml_init_params params = {
|
| 132 |
+
- /*.mem_size =*/ size_t(2u*(1 + n_stream)*n_layer_kv*ggml_tensor_overhead()),
|
| 133 |
+
+ /*.mem_size =*/ size_t(4u*n_layer_kv*ggml_tensor_overhead()),
|
| 134 |
+
/*.mem_buffer =*/ NULL,
|
| 135 |
+
/*.no_alloc =*/ true,
|
| 136 |
+
};
|
| 137 |
+
@@ -255,10 +276,10 @@ llama_kv_cache::llama_kv_cache(
|
| 138 |
+
|
| 139 |
+
LLAMA_LOG_DEBUG("%s: layer %3d: dev = %s\n", __func__, il, dev_name);
|
| 140 |
+
|
| 141 |
+
- ggml_context * ctx = ctx_for_buft(buft);
|
| 142 |
+
- if (!ctx) {
|
| 143 |
+
- throw std::runtime_error("failed to create ggml context for kv cache");
|
| 144 |
+
- }
|
| 145 |
+
+ // opencoti F4 M3 Phase 4-B: per-stream ctx selection has moved
|
| 146 |
+
+ // into the per-stream loop below. The layer-level `buft` is the
|
| 147 |
+
+ // device buffer-type for this layer; each stream allocates its
|
| 148 |
+
+ // own ctx for (buft, stream) via ctx_for_buft(buft, s).
|
| 149 |
+
|
| 150 |
+
const bool has_k = true;
|
| 151 |
+
const bool has_v = !is_mla;
|
| 152 |
+
@@ -267,14 +288,21 @@ llama_kv_cache::llama_kv_cache(
|
| 153 |
+
// Head-residency split: keep the first gpu_heads KV heads on this
|
| 154 |
+
// (offloaded/device) layer's buffer and put the remaining heads on a
|
| 155 |
+
// host buffer. Splitting on KV-head boundaries is inherently GQA-safe.
|
| 156 |
+
- // Gated to offloaded, non-transposed-V, unified (n_stream==1) layers;
|
| 157 |
+
- // frac >= 1.0 (or <= 1 head) means no split (byte-identical upstream).
|
| 158 |
+
+ // Gated to offloaded, non-transposed-V layers; frac >= 1.0 (or <= 1
|
| 159 |
+
+ // head) means no split (byte-identical upstream).
|
| 160 |
+
+ //
|
| 161 |
+
+ // opencoti F4 M3 Phase 4-D: lifted the historical n_stream == 1
|
| 162 |
+
+ // gate. With per-stream owning tensors and per-stream backend
|
| 163 |
+
+ // buffers (Phase 4-A/B), each stream's GPU/CPU half lives in its
|
| 164 |
+
+ // own buffer, so the split is well-defined for n_stream > 1.
|
| 165 |
+
+ // Unified mode (n_stream == 1) collapses to the pre-Phase-4
|
| 166 |
+
+ // single-stream layout, byte-identical.
|
| 167 |
+
uint32_t gpu_heads = 0;
|
| 168 |
+
uint32_t n_embd_k_gpu = n_embd_k_gqa;
|
| 169 |
+
uint32_t n_embd_v_gpu = n_embd_v_gqa;
|
| 170 |
+
uint32_t n_embd_k_cpu = 0;
|
| 171 |
+
uint32_t n_embd_v_cpu = 0;
|
| 172 |
+
- if (headinfer_gpu_heads_frac < 1.0f && offload && !v_trans && n_stream == 1) {
|
| 173 |
+
+ if (headinfer_gpu_heads_frac < 1.0f && offload && !v_trans) {
|
| 174 |
+
const uint32_t n_head_kv = hparams.n_head_kv(il);
|
| 175 |
+
const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 176 |
+
const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 177 |
+
@@ -291,39 +319,119 @@ llama_kv_cache::llama_kv_cache(
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
- ggml_tensor * k = has_k ? ggml_new_tensor_3d(ctx, type_k, n_embd_k_gpu, kv_size, n_stream) : nullptr;
|
| 182 |
+
- ggml_tensor * v = has_v ? ggml_new_tensor_3d(ctx, type_v, n_embd_v_gpu, kv_size, n_stream) : nullptr;
|
| 183 |
+
-
|
| 184 |
+
- has_k && ggml_format_name(k, "cache_k_l%d", il);
|
| 185 |
+
- has_v && ggml_format_name(v, "cache_v_l%d", il);
|
| 186 |
+
-
|
| 187 |
+
- // opencoti F5 M2 headinfer β the host-resident head subset lives in the
|
| 188 |
+
- // CPU buffer-type context; the buffer-allocation loop below picks it up
|
| 189 |
+
- // automatically (ctx_for_buft keys contexts by buffer type).
|
| 190 |
+
- ggml_tensor * k_cpu = nullptr;
|
| 191 |
+
- ggml_tensor * v_cpu = nullptr;
|
| 192 |
+
- if (gpu_heads > 0) {
|
| 193 |
+
- ggml_context * ctx_cpu = ctx_for_buft(ggml_backend_cpu_buffer_type());
|
| 194 |
+
- if (!ctx_cpu) {
|
| 195 |
+
- throw std::runtime_error("failed to create CPU ggml context for headinfer kv cache");
|
| 196 |
+
- }
|
| 197 |
+
- k_cpu = has_k ? ggml_new_tensor_3d(ctx_cpu, type_k, n_embd_k_cpu, kv_size, n_stream) : nullptr;
|
| 198 |
+
- v_cpu = has_v ? ggml_new_tensor_3d(ctx_cpu, type_v, n_embd_v_cpu, kv_size, n_stream) : nullptr;
|
| 199 |
+
- has_k && k_cpu && ggml_format_name(k_cpu, "cache_k_cpu_l%d", il);
|
| 200 |
+
- has_v && v_cpu && ggml_format_name(v_cpu, "cache_v_cpu_l%d", il);
|
| 201 |
+
- }
|
| 202 |
+
-
|
| 203 |
+
+ // opencoti F4 M3 Phase 4 β per-stream tensor split.
|
| 204 |
+
+ // Each stream gets its own OWNING 3-D tensor of shape
|
| 205 |
+
+ // [n_embd, kv_size, 1]. Dim 3 == 1 keeps stride math
|
| 206 |
+
+ // identical to the pre-Phase-4 single-tensor layout, so the
|
| 207 |
+
+ // unified-mode (n_stream == 1) buffer layout is byte-identical
|
| 208 |
+
+ // to pre-Phase-4. Phase 4-B: each stream also gets its own
|
| 209 |
+
+ // (buft β ctx) entry, so each (stream, buft) pair allocates
|
| 210 |
+
+ // its own backend buffer in the loop below.
|
| 211 |
+
+ std::vector<ggml_tensor *> k_per_stream;
|
| 212 |
+
+ std::vector<ggml_tensor *> v_per_stream;
|
| 213 |
+
+ std::vector<ggml_tensor *> k_cpu_per_stream;
|
| 214 |
+
+ std::vector<ggml_tensor *> v_cpu_per_stream;
|
| 215 |
+
std::vector<ggml_tensor *> k_stream;
|
| 216 |
+
std::vector<ggml_tensor *> v_stream;
|
| 217 |
+
|
| 218 |
+
for (uint32_t s = 0; s < n_stream; ++s) {
|
| 219 |
+
- k_stream.push_back(has_k ? ggml_view_2d(ctx, k, n_embd_k_gpu, kv_size, k->nb[1], s*k->nb[2]) : nullptr);
|
| 220 |
+
- v_stream.push_back(has_v ? ggml_view_2d(ctx, v, n_embd_v_gpu, kv_size, v->nb[1], s*v->nb[2]) : nullptr);
|
| 221 |
+
+ ggml_context * ctx_s = ctx_for_buft(buft, s);
|
| 222 |
+
+ if (!ctx_s) {
|
| 223 |
+
+ throw std::runtime_error("failed to create ggml context for kv cache");
|
| 224 |
+
+ }
|
| 225 |
+
+
|
| 226 |
+
+ ggml_tensor * k_s = has_k
|
| 227 |
+
+ ? ggml_new_tensor_3d(ctx_s, type_k, n_embd_k_gpu, kv_size, 1)
|
| 228 |
+
+ : nullptr;
|
| 229 |
+
+ ggml_tensor * v_s = has_v
|
| 230 |
+
+ ? ggml_new_tensor_3d(ctx_s, type_v, n_embd_v_gpu, kv_size, 1)
|
| 231 |
+
+ : nullptr;
|
| 232 |
+
+
|
| 233 |
+
+ // Tensor naming preserves the pre-Phase-4 name in unified
|
| 234 |
+
+ // mode (no `_s%u` suffix) so external state-IO callers that
|
| 235 |
+
+ // grep by name find an unchanged layout.
|
| 236 |
+
+ if (k_s) {
|
| 237 |
+
+ if (n_stream == 1) {
|
| 238 |
+
+ ggml_format_name(k_s, "cache_k_l%d", il);
|
| 239 |
+
+ } else {
|
| 240 |
+
+ ggml_format_name(k_s, "cache_k_l%d_s%u", il, s);
|
| 241 |
+
+ }
|
| 242 |
+
+ }
|
| 243 |
+
+ if (v_s) {
|
| 244 |
+
+ if (n_stream == 1) {
|
| 245 |
+
+ ggml_format_name(v_s, "cache_v_l%d", il);
|
| 246 |
+
+ } else {
|
| 247 |
+
+ ggml_format_name(v_s, "cache_v_l%d_s%u", il, s);
|
| 248 |
+
+ }
|
| 249 |
+
+ }
|
| 250 |
+
+
|
| 251 |
+
+ k_per_stream.push_back(k_s);
|
| 252 |
+
+ v_per_stream.push_back(v_s);
|
| 253 |
+
+
|
| 254 |
+
+ // opencoti F5 M2 headinfer β per-stream host-resident head
|
| 255 |
+
+ // subset. Per-stream ctx_cpu so each stream's CPU half lives
|
| 256 |
+
+ // in its own host buffer (Phase 4-B). Vectors stay empty
|
| 257 |
+
+ // when gpu_heads == 0 so headinfer_split_active() can test
|
| 258 |
+
+ // by vector size.
|
| 259 |
+
+ if (gpu_heads > 0) {
|
| 260 |
+
+ ggml_context * ctx_cpu_s = ctx_for_buft(ggml_backend_cpu_buffer_type(), s);
|
| 261 |
+
+ if (!ctx_cpu_s) {
|
| 262 |
+
+ throw std::runtime_error("failed to create CPU ggml context for headinfer kv cache");
|
| 263 |
+
+ }
|
| 264 |
+
+ ggml_tensor * k_cpu_s = has_k
|
| 265 |
+
+ ? ggml_new_tensor_3d(ctx_cpu_s, type_k, n_embd_k_cpu, kv_size, 1)
|
| 266 |
+
+ : nullptr;
|
| 267 |
+
+ ggml_tensor * v_cpu_s = has_v
|
| 268 |
+
+ ? ggml_new_tensor_3d(ctx_cpu_s, type_v, n_embd_v_cpu, kv_size, 1)
|
| 269 |
+
+ : nullptr;
|
| 270 |
+
+ if (k_cpu_s) {
|
| 271 |
+
+ if (n_stream == 1) {
|
| 272 |
+
+ ggml_format_name(k_cpu_s, "cache_k_cpu_l%d", il);
|
| 273 |
+
+ } else {
|
| 274 |
+
+ ggml_format_name(k_cpu_s, "cache_k_cpu_l%d_s%u", il, s);
|
| 275 |
+
+ }
|
| 276 |
+
+ }
|
| 277 |
+
+ if (v_cpu_s) {
|
| 278 |
+
+ if (n_stream == 1) {
|
| 279 |
+
+ ggml_format_name(v_cpu_s, "cache_v_cpu_l%d", il);
|
| 280 |
+
+ } else {
|
| 281 |
+
+ ggml_format_name(v_cpu_s, "cache_v_cpu_l%d_s%u", il, s);
|
| 282 |
+
+ }
|
| 283 |
+
+ }
|
| 284 |
+
+ k_cpu_per_stream.push_back(k_cpu_s);
|
| 285 |
+
+ v_cpu_per_stream.push_back(v_cpu_s);
|
| 286 |
+
+ }
|
| 287 |
+
+
|
| 288 |
+
+ // 2-D views into per-stream 3-D tensor. Offset is 0 within
|
| 289 |
+
+ // the owning tensor β each stream owns the whole 3-D extent.
|
| 290 |
+
+ //
|
| 291 |
+
+ // opencoti F4 M3 Phase 4-E: keeping k_stream/v_stream as the
|
| 292 |
+
+ // existing 2-D view vectors means cross-stream copies in
|
| 293 |
+
+ // update() (ggml_backend_tensor_copy between views), state
|
| 294 |
+
+ // I/O paths (ggml_backend_tensor_set on the per-stream view),
|
| 295 |
+
+ // and seq_key_scores all work without per-stream logic at
|
| 296 |
+
+ // the consumer site β the view routes through to the
|
| 297 |
+
+ // per-stream buffer transparently. Unified-mode operations
|
| 298 |
+
+ // are byte-identical to pre-Phase-4 because the view has
|
| 299 |
+
+ // identical ne/nb and (in unified mode) the same underlying
|
| 300 |
+
+ // buffer layout.
|
| 301 |
+
+ k_stream.push_back(k_s
|
| 302 |
+
+ ? ggml_view_2d(ctx_s, k_s, n_embd_k_gpu, kv_size, k_s->nb[1], 0)
|
| 303 |
+
+ : nullptr);
|
| 304 |
+
+ v_stream.push_back(v_s
|
| 305 |
+
+ ? ggml_view_2d(ctx_s, v_s, n_embd_v_gpu, kv_size, v_s->nb[1], 0)
|
| 306 |
+
+ : nullptr);
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
map_layer_ids[il] = layers.size();
|
| 310 |
+
|
| 311 |
+
- layers.push_back({ il, k, v, k_stream, v_stream, k_cpu, v_cpu, gpu_heads });
|
| 312 |
+
+ layers.push_back({
|
| 313 |
+
+ il,
|
| 314 |
+
+ k_per_stream, v_per_stream,
|
| 315 |
+
+ k_stream, v_stream,
|
| 316 |
+
+ k_cpu_per_stream, v_cpu_per_stream,
|
| 317 |
+
+ gpu_heads,
|
| 318 |
+
+ });
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
if (reuse) {
|
| 322 |
+
@@ -359,31 +467,46 @@ llama_kv_cache::llama_kv_cache(
|
| 323 |
+
// scheduler-visible tensor->buffer wiring intact (sched_reserve runs
|
| 324 |
+
// at construction time and asserts buffer_id >= 0) while moving the
|
| 325 |
+
// RSS commit to the first ensure_cleared() call.
|
| 326 |
+
- for (auto & [buft, ctx] : ctx_map) {
|
| 327 |
+
- ggml_backend_buffer_t buf;
|
| 328 |
+
- if (model.hparams.no_alloc) {
|
| 329 |
+
- buf = ggml_backend_buft_alloc_buffer(buft, /*size =*/ 0); // dummy buffer
|
| 330 |
+
- for (ggml_tensor * t = ggml_get_first_tensor(ctx.get()); t != nullptr; t = ggml_get_next_tensor(ctx.get(), t)) {
|
| 331 |
+
- t->buffer = buf; // set dummy buffer for KV cache so that the backend scheduler won't try to allocate it
|
| 332 |
+
+ //
|
| 333 |
+
+ // opencoti F4 M3 Phase 4-B: per-stream backend buffers. Each
|
| 334 |
+
+ // (stream, buft) pair allocates its own backend buffer here.
|
| 335 |
+
+ // Iteration order is stable: outer loop over streams (s=0,1,...),
|
| 336 |
+
+ // inner loop over bufts (sorted by name via the ctx_map comparator).
|
| 337 |
+
+ // In unified mode (n_stream == 1) this collapses to exactly one
|
| 338 |
+
+ // pass per buft β byte-identical to pre-Phase-4 ctxs_bufs layout.
|
| 339 |
+
+ for (uint32_t s = 0; s < n_stream; ++s) {
|
| 340 |
+
+ for (auto & [buft, ctx] : ctx_map_per_stream[s]) {
|
| 341 |
+
+ ggml_backend_buffer_t buf;
|
| 342 |
+
+ if (model.hparams.no_alloc) {
|
| 343 |
+
+ buf = ggml_backend_buft_alloc_buffer(buft, /*size =*/ 0); // dummy buffer
|
| 344 |
+
+ for (ggml_tensor * t = ggml_get_first_tensor(ctx.get()); t != nullptr; t = ggml_get_next_tensor(ctx.get(), t)) {
|
| 345 |
+
+ t->buffer = buf; // set dummy buffer for KV cache so that the backend scheduler won't try to allocate it
|
| 346 |
+
+ }
|
| 347 |
+
+ } else {
|
| 348 |
+
+ buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft); // real buffer (malloc'd but not zeroed yet)
|
| 349 |
+
+ }
|
| 350 |
+
+ if (!buf) {
|
| 351 |
+
+ throw std::runtime_error("failed to allocate buffer for kv cache");
|
| 352 |
+
+ }
|
| 353 |
+
+
|
| 354 |
+
+ if (n_stream == 1) {
|
| 355 |
+
+ LLAMA_LOG_INFO("%s: %10s KV buffer size = %8.2f MiB\n", __func__,
|
| 356 |
+
+ ggml_backend_buffer_name(buf), ggml_backend_buffer_get_size(buf)/1024.0/1024.0);
|
| 357 |
+
+ } else {
|
| 358 |
+
+ LLAMA_LOG_INFO("%s: %10s KV buffer (stream %u) size = %8.2f MiB\n", __func__,
|
| 359 |
+
+ ggml_backend_buffer_name(buf), s, ggml_backend_buffer_get_size(buf)/1024.0/1024.0);
|
| 360 |
+
}
|
| 361 |
+
- } else {
|
| 362 |
+
- buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx.get(), buft); // real buffer (malloc'd but not zeroed yet)
|
| 363 |
+
- }
|
| 364 |
+
- if (!buf) {
|
| 365 |
+
- throw std::runtime_error("failed to allocate buffer for kv cache");
|
| 366 |
+
- }
|
| 367 |
+
|
| 368 |
+
- LLAMA_LOG_INFO("%s: %10s KV buffer size = %8.2f MiB\n", __func__, ggml_backend_buffer_name(buf), ggml_backend_buffer_get_size(buf)/1024.0/1024.0);
|
| 369 |
+
+ if (model.hparams.no_alloc) {
|
| 370 |
+
+ // Dummy buffer is size 0; clearing is a no-op but keeps the
|
| 371 |
+
+ // upstream invariant that constructed buffers are zeroed.
|
| 372 |
+
+ ggml_backend_buffer_clear(buf, 0);
|
| 373 |
+
+ }
|
| 374 |
+
+ // else: leave the buffer un-cleared. ensure_cleared() will zero it
|
| 375 |
+
+ // on the first inference entry point that touches tensor data.
|
| 376 |
+
|
| 377 |
+
- if (model.hparams.no_alloc) {
|
| 378 |
+
- // Dummy buffer is size 0; clearing is a no-op but keeps the
|
| 379 |
+
- // upstream invariant that constructed buffers are zeroed.
|
| 380 |
+
- ggml_backend_buffer_clear(buf, 0);
|
| 381 |
+
+ ctxs_bufs.emplace_back(std::move(ctx), buf);
|
| 382 |
+
}
|
| 383 |
+
- // else: leave the buffer un-cleared. ensure_cleared() will zero it
|
| 384 |
+
- // on the first inference entry point that touches tensor data.
|
| 385 |
+
-
|
| 386 |
+
- ctxs_bufs.emplace_back(std::move(ctx), buf);
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
if (model.hparams.no_alloc) {
|
| 390 |
+
@@ -471,33 +594,43 @@ void llama_kv_cache::ensure_cleared(uint32_t up_to_cells) {
|
| 391 |
+
// memset on the malloc'd buffer; for offloaded buffers it's a backend
|
| 392 |
+
// op. Either way only bytes for cells [a, b) get touched, so on Linux
|
| 393 |
+
// only those pages get committed to RSS.
|
| 394 |
+
+ //
|
| 395 |
+
+ // opencoti F4 M3 Phase 4: per-stream tensors. Each layer's
|
| 396 |
+
+ // k_per_stream[s] / v_per_stream[s] is now its own owning tensor
|
| 397 |
+
+ // (shape [n_embd, kv_size, 1]) so the per-stream stride math
|
| 398 |
+
+ // collapses to a plain cell-offset memset within each stream's
|
| 399 |
+
+ // tensor. M2's CPU heads follow the same pattern via
|
| 400 |
+
+ // k_cpu_per_stream / v_cpu_per_stream (empty when no split).
|
| 401 |
+
for (const auto & layer : layers) {
|
| 402 |
+
for (uint32_t st = 0; st < n_stream; ++st) {
|
| 403 |
+
- if (layer.k) {
|
| 404 |
+
- const size_t off = (size_t) st * (size_t) layer.k->nb[2]
|
| 405 |
+
- + (size_t) a * (size_t) layer.k->nb[1];
|
| 406 |
+
- const size_t sz = (size_t) (b - a) * (size_t) layer.k->nb[1];
|
| 407 |
+
- ggml_backend_tensor_memset(layer.k, 0, off, sz);
|
| 408 |
+
- }
|
| 409 |
+
- if (layer.v) {
|
| 410 |
+
- const size_t off = (size_t) st * (size_t) layer.v->nb[2]
|
| 411 |
+
- + (size_t) a * (size_t) layer.v->nb[1];
|
| 412 |
+
- const size_t sz = (size_t) (b - a) * (size_t) layer.v->nb[1];
|
| 413 |
+
- ggml_backend_tensor_memset(layer.v, 0, off, sz);
|
| 414 |
+
- }
|
| 415 |
+
- // opencoti F5 M2 headinfer β clear the host-resident head subset
|
| 416 |
+
- // too (its own strides; same [a, b) cell range).
|
| 417 |
+
- if (layer.k_cpu) {
|
| 418 |
+
- const size_t off = (size_t) st * (size_t) layer.k_cpu->nb[2]
|
| 419 |
+
- + (size_t) a * (size_t) layer.k_cpu->nb[1];
|
| 420 |
+
- const size_t sz = (size_t) (b - a) * (size_t) layer.k_cpu->nb[1];
|
| 421 |
+
- ggml_backend_tensor_memset(layer.k_cpu, 0, off, sz);
|
| 422 |
+
+ auto * k = layer.k_per_stream[st];
|
| 423 |
+
+ if (k) {
|
| 424 |
+
+ const size_t off = (size_t) a * (size_t) k->nb[1];
|
| 425 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) k->nb[1];
|
| 426 |
+
+ ggml_backend_tensor_memset(k, 0, off, sz);
|
| 427 |
+
+ }
|
| 428 |
+
+ auto * v = layer.v_per_stream[st];
|
| 429 |
+
+ if (v) {
|
| 430 |
+
+ const size_t off = (size_t) a * (size_t) v->nb[1];
|
| 431 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) v->nb[1];
|
| 432 |
+
+ ggml_backend_tensor_memset(v, 0, off, sz);
|
| 433 |
+
+ }
|
| 434 |
+
+ // M2 headinfer host-resident head subset (same [a, b) cells).
|
| 435 |
+
+ if (st < layer.k_cpu_per_stream.size()) {
|
| 436 |
+
+ auto * k_cpu = layer.k_cpu_per_stream[st];
|
| 437 |
+
+ if (k_cpu) {
|
| 438 |
+
+ const size_t off = (size_t) a * (size_t) k_cpu->nb[1];
|
| 439 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) k_cpu->nb[1];
|
| 440 |
+
+ ggml_backend_tensor_memset(k_cpu, 0, off, sz);
|
| 441 |
+
+ }
|
| 442 |
+
}
|
| 443 |
+
- if (layer.v_cpu) {
|
| 444 |
+
- const size_t off = (size_t) st * (size_t) layer.v_cpu->nb[2]
|
| 445 |
+
- + (size_t) a * (size_t) layer.v_cpu->nb[1];
|
| 446 |
+
- const size_t sz = (size_t) (b - a) * (size_t) layer.v_cpu->nb[1];
|
| 447 |
+
- ggml_backend_tensor_memset(layer.v_cpu, 0, off, sz);
|
| 448 |
+
+ if (st < layer.v_cpu_per_stream.size()) {
|
| 449 |
+
+ auto * v_cpu = layer.v_cpu_per_stream[st];
|
| 450 |
+
+ if (v_cpu) {
|
| 451 |
+
+ const size_t off = (size_t) a * (size_t) v_cpu->nb[1];
|
| 452 |
+
+ const size_t sz = (size_t) (b - a) * (size_t) v_cpu->nb[1];
|
| 453 |
+
+ ggml_backend_tensor_memset(v_cpu, 0, off, sz);
|
| 454 |
+
+ }
|
| 455 |
+
}
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
@@ -542,22 +675,25 @@ bool llama_kv_cache::ensure_capacity(uint32_t cells_needed) {
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 462 |
+
-// Decommit pages of every layer's K/V tensors in the byte-range that
|
| 463 |
+
-// corresponds to cells [from_cells, to_cells). Returns those pages to the OS
|
| 464 |
+
-// β RSS drops by the size of the range, and the next access to those pages
|
| 465 |
+
-// will fault them back in as fresh zero-filled pages.
|
| 466 |
+
+// Decommit pages of one layer's K/V tensor for ONE STREAM in the byte-
|
| 467 |
+
+// range that corresponds to cells [from_cells, to_cells). Returns those
|
| 468 |
+
+// pages to the OS β RSS drops by the size of the range, and the next
|
| 469 |
+
+// access to those pages will fault them back in as fresh zero-filled
|
| 470 |
+
+// pages.
|
| 471 |
+
//
|
| 472 |
+
// Only safe to call after kv_size_alloc has been reset to from_cells (or
|
| 473 |
+
-// lower) and n_cells_cleared has been reset accordingly, so the bookkeeping
|
| 474 |
+
-// stays in sync with what's actually backed by physical memory.
|
| 475 |
+
+// lower) and n_cells_cleared has been reset accordingly, so the
|
| 476 |
+
+// bookkeeping stays in sync with what's actually backed by physical
|
| 477 |
+
+// memory.
|
| 478 |
+
//
|
| 479 |
+
-// The decommit is per-stream because the KV tensors are 3-D
|
| 480 |
+
-// [n_embd, kv_size, n_stream]; cells [a, b) within stream s occupy a
|
| 481 |
+
-// contiguous byte-range whose start address is (base + s*nb[2] + a*nb[1])
|
| 482 |
+
-// and length is (b-a)*nb[1]. posix_madvise is page-grained β bytes that
|
| 483 |
+
-// don't cover a full page are silently kept resident, which is fine for
|
| 484 |
+
-// the cells that straddle page boundaries.
|
| 485 |
+
-static void opencoti_decommit_layer_range(ggml_tensor * t, uint32_t n_stream,
|
| 486 |
+
+// opencoti F4 M3 Phase 4: per-stream tensors. `t` is now ONE stream's
|
| 487 |
+
+// owning 3-D tensor (dim 3 == 1). Cells [a, b) occupy contiguous bytes
|
| 488 |
+
+// at (base + a*nb[1]) for length (b-a)*nb[1]. The stream-stride loop
|
| 489 |
+
+// of the pre-Phase-4 implementation collapses; callers iterate streams
|
| 490 |
+
+// at the call site instead. posix_madvise is page-grained β bytes that
|
| 491 |
+
+// don't cover a full page are silently kept resident, which is fine
|
| 492 |
+
+// for the cells that straddle page boundaries.
|
| 493 |
+
+static void opencoti_decommit_layer_range(ggml_tensor * t,
|
| 494 |
+
uint32_t from_cells, uint32_t to_cells) {
|
| 495 |
+
if (!t || to_cells <= from_cells) {
|
| 496 |
+
return;
|
| 497 |
+
@@ -579,37 +715,32 @@ static void opencoti_decommit_layer_range(ggml_tensor * t, uint32_t n_stream,
|
| 498 |
+
if (!base) {
|
| 499 |
+
return;
|
| 500 |
+
}
|
| 501 |
+
- const size_t stride_stream = (size_t) t->nb[2];
|
| 502 |
+
- const size_t stride_cell = (size_t) t->nb[1];
|
| 503 |
+
- const size_t off_in_buf = (size_t) ((char *) t->data - (char *) base);
|
| 504 |
+
- for (uint32_t s = 0; s < n_stream; ++s) {
|
| 505 |
+
- const size_t off = off_in_buf
|
| 506 |
+
- + (size_t) s * stride_stream
|
| 507 |
+
- + (size_t) from_cells * stride_cell;
|
| 508 |
+
- const size_t sz = (size_t) (to_cells - from_cells) * stride_cell;
|
| 509 |
+
- // posix_madvise wants the address β work in absolute terms.
|
| 510 |
+
- void * addr = (char *) base + off;
|
| 511 |
+
- // Round up to a page boundary on the low side, round down on the
|
| 512 |
+
- // high side, so we only decommit pages we fully own. The cell-aligned
|
| 513 |
+
- // address is unlikely to be page-aligned in general.
|
| 514 |
+
- const size_t page = (size_t) sysconf(_SC_PAGESIZE);
|
| 515 |
+
- if (page == 0 || page == (size_t) -1) {
|
| 516 |
+
- return;
|
| 517 |
+
- }
|
| 518 |
+
- uintptr_t lo = (uintptr_t) addr;
|
| 519 |
+
- uintptr_t hi = lo + sz;
|
| 520 |
+
- uintptr_t lo_aligned = (lo + page - 1) & ~(uintptr_t)(page - 1);
|
| 521 |
+
- uintptr_t hi_aligned = hi & ~(uintptr_t)(page - 1);
|
| 522 |
+
- if (hi_aligned <= lo_aligned) {
|
| 523 |
+
- continue;
|
| 524 |
+
- }
|
| 525 |
+
- // madvise(MADV_DONTNEED) returns 0 on success, -1 on failure (with
|
| 526 |
+
- // errno set). We treat any failure as advisory β the RSS win may
|
| 527 |
+
- // be skipped, but correctness is unaffected (the K/V buffer is
|
| 528 |
+
- // still mapped, just possibly still resident).
|
| 529 |
+
- (void) madvise((void *) lo_aligned, (size_t) (hi_aligned - lo_aligned),
|
| 530 |
+
- MADV_DONTNEED);
|
| 531 |
+
+ const size_t stride_cell = (size_t) t->nb[1];
|
| 532 |
+
+ const size_t off_in_buf = (size_t) ((char *) t->data - (char *) base);
|
| 533 |
+
+ const size_t off = off_in_buf + (size_t) from_cells * stride_cell;
|
| 534 |
+
+ const size_t sz = (size_t) (to_cells - from_cells) * stride_cell;
|
| 535 |
+
+ // posix_madvise wants the address β work in absolute terms.
|
| 536 |
+
+ void * addr = (char *) base + off;
|
| 537 |
+
+ // Round up to a page boundary on the low side, round down on the
|
| 538 |
+
+ // high side, so we only decommit pages we fully own. The cell-aligned
|
| 539 |
+
+ // address is unlikely to be page-aligned in general.
|
| 540 |
+
+ const size_t page = (size_t) sysconf(_SC_PAGESIZE);
|
| 541 |
+
+ if (page == 0 || page == (size_t) -1) {
|
| 542 |
+
+ return;
|
| 543 |
+
}
|
| 544 |
+
+ uintptr_t lo = (uintptr_t) addr;
|
| 545 |
+
+ uintptr_t hi = lo + sz;
|
| 546 |
+
+ uintptr_t lo_aligned = (lo + page - 1) & ~(uintptr_t)(page - 1);
|
| 547 |
+
+ uintptr_t hi_aligned = hi & ~(uintptr_t)(page - 1);
|
| 548 |
+
+ if (hi_aligned <= lo_aligned) {
|
| 549 |
+
+ return;
|
| 550 |
+
+ }
|
| 551 |
+
+ // madvise(MADV_DONTNEED) returns 0 on success, -1 on failure (with
|
| 552 |
+
+ // errno set). We treat any failure as advisory β the RSS win may
|
| 553 |
+
+ // be skipped, but correctness is unaffected (the K/V buffer is
|
| 554 |
+
+ // still mapped, just possibly still resident).
|
| 555 |
+
+ (void) madvise((void *) lo_aligned, (size_t) (hi_aligned - lo_aligned),
|
| 556 |
+
+ MADV_DONTNEED);
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
void llama_kv_cache::shrink_if_idle() {
|
| 560 |
+
@@ -667,13 +798,20 @@ void llama_kv_cache::shrink_if_idle() {
|
| 561 |
+
(unsigned long long) (now - last_active_ms_), evicted);
|
| 562 |
+
|
| 563 |
+
for (const auto & layer : layers) {
|
| 564 |
+
- opencoti_decommit_layer_range(layer.k, n_stream, from_cells, to_cells);
|
| 565 |
+
- opencoti_decommit_layer_range(layer.v, n_stream, from_cells, to_cells);
|
| 566 |
+
- // opencoti F5 M2 headinfer β the host-resident head subset IS
|
| 567 |
+
- // host-pageable, so this is where the GPU-mode shrink actually returns
|
| 568 |
+
- // RSS (the device k/v above are skipped by the is_host() guard).
|
| 569 |
+
- opencoti_decommit_layer_range(layer.k_cpu, n_stream, from_cells, to_cells);
|
| 570 |
+
- opencoti_decommit_layer_range(layer.v_cpu, n_stream, from_cells, to_cells);
|
| 571 |
+
+ for (uint32_t s = 0; s < n_stream; ++s) {
|
| 572 |
+
+ opencoti_decommit_layer_range(layer.k_per_stream[s], from_cells, to_cells);
|
| 573 |
+
+ opencoti_decommit_layer_range(layer.v_per_stream[s], from_cells, to_cells);
|
| 574 |
+
+ // opencoti F5 M2 headinfer β the host-resident head subset
|
| 575 |
+
+ // IS host-pageable, so this is where the GPU-mode shrink
|
| 576 |
+
+ // actually returns RSS (the device k/v above are skipped
|
| 577 |
+
+ // by the is_host() guard in the function).
|
| 578 |
+
+ if (s < layer.k_cpu_per_stream.size()) {
|
| 579 |
+
+ opencoti_decommit_layer_range(layer.k_cpu_per_stream[s], from_cells, to_cells);
|
| 580 |
+
+ }
|
| 581 |
+
+ if (s < layer.v_cpu_per_stream.size()) {
|
| 582 |
+
+ opencoti_decommit_layer_range(layer.v_cpu_per_stream[s], from_cells, to_cells);
|
| 583 |
+
+ }
|
| 584 |
+
+ }
|
| 585 |
+
}
|
| 586 |
+
|
| 587 |
+
// After decommit, treat cells [from_cells, to_cells) as "uncleared" β
|
| 588 |
+
@@ -1683,11 +1821,14 @@ bool llama_kv_cache::get_has_shift() const {
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
ggml_type llama_kv_cache::type_k() const {
|
| 592 |
+
- return layers[0].k->type;
|
| 593 |
+
+ // opencoti F4 M3 Phase 4: all per-stream tensors of a layer carry the
|
| 594 |
+
+ // same type β read it from stream 0. Works in both unified and
|
| 595 |
+
+ // non-unified modes.
|
| 596 |
+
+ return layers[0].k_per_stream[0]->type;
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
ggml_type llama_kv_cache::type_v() const {
|
| 600 |
+
- return layers[0].v->type;
|
| 601 |
+
+ return layers[0].v_per_stream[0]->type;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
uint32_t llama_kv_cache::get_n_kv(const slot_info & sinfo) const {
|
| 605 |
+
@@ -1710,104 +1851,166 @@ ggml_tensor * llama_kv_cache::get_k(ggml_context * ctx, int32_t il, uint32_t n_k
|
| 606 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 607 |
+
|
| 608 |
+
const auto & layer = layers[ikv];
|
| 609 |
+
- auto * k = layer.k;
|
| 610 |
+
|
| 611 |
+
const uint64_t kv_size = get_size();
|
| 612 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 613 |
+
|
| 614 |
+
// opencoti F5 M2 headinfer β reassemble the full head set from the
|
| 615 |
+
- // GPU-resident (k) and host-resident (k_cpu) subsets by concat on the head
|
| 616 |
+
+ // GPU-resident and host-resident subsets by concat on the head
|
| 617 |
+
// dimension. The backend scheduler streams k_cpu to the compute backend.
|
| 618 |
+
- if (layer.gpu_heads > 0 && layer.k_cpu) {
|
| 619 |
+
+ //
|
| 620 |
+
+ // opencoti F4 M3 Phase 4-D: stream-aware. For each cache stream covered
|
| 621 |
+
+ // by sinfo, build a 4-D head-axis-concat view (GPU half + CPU half)
|
| 622 |
+
+ // with that stream's per-stream tensors. Then concat the per-stream
|
| 623 |
+
+ // results along the stream axis (dim 3). Unified mode (sinfo.s1 ==
|
| 624 |
+
+ // sinfo.s0 == 0) collapses to one 4-D head-concat view of stream 0's
|
| 625 |
+
+ // tensors β byte-identical to pre-Phase-4.
|
| 626 |
+
+ if (layer.gpu_heads > 0 && !layer.k_cpu_per_stream.empty() && layer.k_cpu_per_stream[0]) {
|
| 627 |
+
const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 628 |
+
const uint32_t n_head_gpu = layer.gpu_heads;
|
| 629 |
+
const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 630 |
+
const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_k;
|
| 631 |
+
const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_k;
|
| 632 |
+
- auto * kc = layer.k_cpu;
|
| 633 |
+
- ggml_tensor * kg_v = ggml_view_4d(ctx, k,
|
| 634 |
+
- n_embd_head_k, n_head_gpu, n_kv, ns,
|
| 635 |
+
- ggml_row_size(k->type, n_embd_head_k),
|
| 636 |
+
- ggml_row_size(k->type, n_embd_gpu),
|
| 637 |
+
- ggml_row_size(k->type, n_embd_gpu*kv_size),
|
| 638 |
+
- ggml_row_size(k->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 639 |
+
- ggml_tensor * kc_v = ggml_view_4d(ctx, kc,
|
| 640 |
+
- n_embd_head_k, n_head_cpu, n_kv, ns,
|
| 641 |
+
- ggml_row_size(kc->type, n_embd_head_k),
|
| 642 |
+
- ggml_row_size(kc->type, n_embd_cpu),
|
| 643 |
+
- ggml_row_size(kc->type, n_embd_cpu*kv_size),
|
| 644 |
+
- ggml_row_size(kc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 645 |
+
- return ggml_concat(ctx, kg_v, kc_v, 1);
|
| 646 |
+
- }
|
| 647 |
+
-
|
| 648 |
+
- const uint64_t n_embd_k_gqa = k->ne[0];
|
| 649 |
+
|
| 650 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 651 |
+
+ ggml_tensor * k_s = layer.k_per_stream[s_cache];
|
| 652 |
+
+ ggml_tensor * kc_s = layer.k_cpu_per_stream[s_cache];
|
| 653 |
+
+ ggml_tensor * kg_v = ggml_view_4d(ctx, k_s,
|
| 654 |
+
+ n_embd_head_k, n_head_gpu, n_kv, 1,
|
| 655 |
+
+ ggml_row_size(k_s->type, n_embd_head_k),
|
| 656 |
+
+ ggml_row_size(k_s->type, n_embd_gpu),
|
| 657 |
+
+ ggml_row_size(k_s->type, n_embd_gpu*kv_size),
|
| 658 |
+
+ 0);
|
| 659 |
+
+ ggml_tensor * kc_v = ggml_view_4d(ctx, kc_s,
|
| 660 |
+
+ n_embd_head_k, n_head_cpu, n_kv, 1,
|
| 661 |
+
+ ggml_row_size(kc_s->type, n_embd_head_k),
|
| 662 |
+
+ ggml_row_size(kc_s->type, n_embd_cpu),
|
| 663 |
+
+ ggml_row_size(kc_s->type, n_embd_cpu*kv_size),
|
| 664 |
+
+ 0);
|
| 665 |
+
+ return ggml_concat(ctx, kg_v, kc_v, 1);
|
| 666 |
+
+ };
|
| 667 |
+
+
|
| 668 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 669 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 670 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 671 |
+
+ }
|
| 672 |
+
+ return result;
|
| 673 |
+
+ }
|
| 674 |
+
+
|
| 675 |
+
+ // opencoti F4 M3 Phase 4-C: per-stream view + concat along stream axis
|
| 676 |
+
+ // (dim 3). In unified mode (sinfo.s1 == sinfo.s0 == 0) the loop does
|
| 677 |
+
+ // not fire, yielding a single 4-D view of layer.k_per_stream[0] β
|
| 678 |
+
+ // structurally and numerically byte-identical to pre-Phase-4 (same
|
| 679 |
+
+ // ne, same nb, same offset 0). In non-unified mode the result is N
|
| 680 |
+
+ // contiguous concat ops along dim 3 (the stream axis), one per
|
| 681 |
+
+ // covered cache stream. Cost: (ns - 1) concat nodes per layer per turn.
|
| 682 |
+
+ auto * k0 = layer.k_per_stream[sinfo.s0];
|
| 683 |
+
+ const uint64_t n_embd_k_gqa = k0->ne[0];
|
| 684 |
+
assert(n_embd_k_gqa == hparams.n_embd_k_gqa(il));
|
| 685 |
+
|
| 686 |
+
- return ggml_view_4d(ctx, k,
|
| 687 |
+
- hparams.n_embd_head_k(il), hparams.n_head_kv(il), n_kv, ns,
|
| 688 |
+
- ggml_row_size(k->type, hparams.n_embd_head_k(il)),
|
| 689 |
+
- ggml_row_size(k->type, n_embd_k_gqa),
|
| 690 |
+
- ggml_row_size(k->type, n_embd_k_gqa*kv_size),
|
| 691 |
+
- ggml_row_size(k->type, n_embd_k_gqa*kv_size)*sinfo.s0);
|
| 692 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 693 |
+
+ ggml_tensor * k_s = layer.k_per_stream[s_cache];
|
| 694 |
+
+ return ggml_view_4d(ctx, k_s,
|
| 695 |
+
+ hparams.n_embd_head_k(il), hparams.n_head_kv(il), n_kv, 1,
|
| 696 |
+
+ ggml_row_size(k_s->type, hparams.n_embd_head_k(il)),
|
| 697 |
+
+ ggml_row_size(k_s->type, n_embd_k_gqa),
|
| 698 |
+
+ ggml_row_size(k_s->type, n_embd_k_gqa*kv_size),
|
| 699 |
+
+ 0);
|
| 700 |
+
+ };
|
| 701 |
+
+
|
| 702 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 703 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 704 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 705 |
+
+ }
|
| 706 |
+
+ return result;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
ggml_tensor * llama_kv_cache::get_v(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 710 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 711 |
+
|
| 712 |
+
const auto & layer = layers[ikv];
|
| 713 |
+
- auto * v = layer.v;
|
| 714 |
+
|
| 715 |
+
const uint64_t kv_size = get_size();
|
| 716 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 717 |
+
|
| 718 |
+
- // opencoti F5 M2 headinfer β concat the GPU + host head subsets. The split
|
| 719 |
+
- // is gated off when v_trans at construction, so this branch is always the
|
| 720 |
+
- // non-transposed layout (heads in dim 1, same as get_k).
|
| 721 |
+
- if (layer.gpu_heads > 0 && layer.v_cpu) {
|
| 722 |
+
+ // opencoti F5 M2 headinfer β concat the GPU + host head subsets. The
|
| 723 |
+
+ // split is gated off when v_trans at construction, so this branch is
|
| 724 |
+
+ // always the non-transposed layout (heads in dim 1, same as get_k).
|
| 725 |
+
+ //
|
| 726 |
+
+ // opencoti F4 M3 Phase 4-D: stream-aware per-stream concat β same
|
| 727 |
+
+ // pattern as get_k's M2 branch.
|
| 728 |
+
+ if (layer.gpu_heads > 0 && !layer.v_cpu_per_stream.empty() && layer.v_cpu_per_stream[0]) {
|
| 729 |
+
const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 730 |
+
const uint32_t n_head_gpu = layer.gpu_heads;
|
| 731 |
+
const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 732 |
+
const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_v;
|
| 733 |
+
const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_v;
|
| 734 |
+
- auto * vc = layer.v_cpu;
|
| 735 |
+
- ggml_tensor * vg_v = ggml_view_4d(ctx, v,
|
| 736 |
+
- n_embd_head_v, n_head_gpu, n_kv, ns,
|
| 737 |
+
- ggml_row_size(v->type, n_embd_head_v),
|
| 738 |
+
- ggml_row_size(v->type, n_embd_gpu),
|
| 739 |
+
- ggml_row_size(v->type, n_embd_gpu*kv_size),
|
| 740 |
+
- ggml_row_size(v->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 741 |
+
- ggml_tensor * vc_v = ggml_view_4d(ctx, vc,
|
| 742 |
+
- n_embd_head_v, n_head_cpu, n_kv, ns,
|
| 743 |
+
- ggml_row_size(vc->type, n_embd_head_v),
|
| 744 |
+
- ggml_row_size(vc->type, n_embd_cpu),
|
| 745 |
+
- ggml_row_size(vc->type, n_embd_cpu*kv_size),
|
| 746 |
+
- ggml_row_size(vc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 747 |
+
- return ggml_concat(ctx, vg_v, vc_v, 1);
|
| 748 |
+
- }
|
| 749 |
+
-
|
| 750 |
+
- const uint64_t n_embd_v_gqa = v->ne[0];
|
| 751 |
+
+
|
| 752 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 753 |
+
+ ggml_tensor * v_s = layer.v_per_stream[s_cache];
|
| 754 |
+
+ ggml_tensor * vc_s = layer.v_cpu_per_stream[s_cache];
|
| 755 |
+
+ ggml_tensor * vg_v = ggml_view_4d(ctx, v_s,
|
| 756 |
+
+ n_embd_head_v, n_head_gpu, n_kv, 1,
|
| 757 |
+
+ ggml_row_size(v_s->type, n_embd_head_v),
|
| 758 |
+
+ ggml_row_size(v_s->type, n_embd_gpu),
|
| 759 |
+
+ ggml_row_size(v_s->type, n_embd_gpu*kv_size),
|
| 760 |
+
+ 0);
|
| 761 |
+
+ ggml_tensor * vc_v = ggml_view_4d(ctx, vc_s,
|
| 762 |
+
+ n_embd_head_v, n_head_cpu, n_kv, 1,
|
| 763 |
+
+ ggml_row_size(vc_s->type, n_embd_head_v),
|
| 764 |
+
+ ggml_row_size(vc_s->type, n_embd_cpu),
|
| 765 |
+
+ ggml_row_size(vc_s->type, n_embd_cpu*kv_size),
|
| 766 |
+
+ 0);
|
| 767 |
+
+ return ggml_concat(ctx, vg_v, vc_v, 1);
|
| 768 |
+
+ };
|
| 769 |
+
+
|
| 770 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 771 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 772 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 773 |
+
+ }
|
| 774 |
+
+ return result;
|
| 775 |
+
+ }
|
| 776 |
+
+
|
| 777 |
+
+ // opencoti F4 M3 Phase 4-C: per-stream view + concat along stream
|
| 778 |
+
+ // axis. Same shape as get_k, but with v_trans branching. Unified mode
|
| 779 |
+
+ // (sinfo.s1 == sinfo.s0 == 0) keeps a single 4-D view β byte-identical.
|
| 780 |
+
+ auto * v0 = layer.v_per_stream[sinfo.s0];
|
| 781 |
+
+ const uint64_t n_embd_v_gqa = v0->ne[0];
|
| 782 |
+
|
| 783 |
+
// [TAG_V_CACHE_VARIABLE]
|
| 784 |
+
assert(n_embd_v_gqa >= hparams.n_embd_v_gqa(il));
|
| 785 |
+
|
| 786 |
+
if (!v_trans) {
|
| 787 |
+
// note: v->nb[1] <= v->nb[2]
|
| 788 |
+
- return ggml_view_4d(ctx, v,
|
| 789 |
+
- hparams.n_embd_head_v(il), hparams.n_head_kv(il), n_kv, ns,
|
| 790 |
+
- ggml_row_size(v->type, hparams.n_embd_head_v(il)), // v->nb[1]
|
| 791 |
+
- ggml_row_size(v->type, n_embd_v_gqa), // v->nb[2]
|
| 792 |
+
- ggml_row_size(v->type, n_embd_v_gqa*kv_size), // v->nb[3]
|
| 793 |
+
- ggml_row_size(v->type, n_embd_v_gqa*kv_size)*sinfo.s0);
|
| 794 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 795 |
+
+ ggml_tensor * v_s = layer.v_per_stream[s_cache];
|
| 796 |
+
+ return ggml_view_4d(ctx, v_s,
|
| 797 |
+
+ hparams.n_embd_head_v(il), hparams.n_head_kv(il), n_kv, 1,
|
| 798 |
+
+ ggml_row_size(v_s->type, hparams.n_embd_head_v(il)),
|
| 799 |
+
+ ggml_row_size(v_s->type, n_embd_v_gqa),
|
| 800 |
+
+ ggml_row_size(v_s->type, n_embd_v_gqa*kv_size),
|
| 801 |
+
+ 0);
|
| 802 |
+
+ };
|
| 803 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 804 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 805 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 806 |
+
+ }
|
| 807 |
+
+ return result;
|
| 808 |
+
+ }
|
| 809 |
+
+
|
| 810 |
+
+ // v_trans branch: note v->nb[1] > v->nb[2]
|
| 811 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 812 |
+
+ ggml_tensor * v_s = layer.v_per_stream[s_cache];
|
| 813 |
+
+ return ggml_view_4d(ctx, v_s,
|
| 814 |
+
+ n_kv, hparams.n_head_kv(il), hparams.n_embd_head_v(il), 1,
|
| 815 |
+
+ ggml_row_size(v_s->type, kv_size*hparams.n_embd_head_v(il)),
|
| 816 |
+
+ ggml_row_size(v_s->type, kv_size),
|
| 817 |
+
+ ggml_row_size(v_s->type, kv_size*n_embd_v_gqa),
|
| 818 |
+
+ 0);
|
| 819 |
+
+ };
|
| 820 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 821 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 822 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 823 |
+
}
|
| 824 |
+
-
|
| 825 |
+
- // note: v->nb[1] > v->nb[2]
|
| 826 |
+
- return ggml_view_4d(ctx, v,
|
| 827 |
+
- n_kv, hparams.n_head_kv(il), hparams.n_embd_head_v(il), ns,
|
| 828 |
+
- ggml_row_size(v->type, kv_size*hparams.n_embd_head_v(il)), // v->nb[1]
|
| 829 |
+
- ggml_row_size(v->type, kv_size), // v->nb[2]
|
| 830 |
+
- ggml_row_size(v->type, kv_size*n_embd_v_gqa), // v->nb[3]
|
| 831 |
+
- ggml_row_size(v->type, kv_size*n_embd_v_gqa)*sinfo.s0);
|
| 832 |
+
+ return result;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
// opencoti F5 M3 neo β head-split-aware accessors. These factor the per-half
|
| 836 |
+
@@ -1824,7 +2027,13 @@ bool llama_kv_cache::headinfer_split_active(int32_t il) const {
|
| 837 |
+
return false;
|
| 838 |
+
}
|
| 839 |
+
const auto & layer = layers[it->second];
|
| 840 |
+
- return layer.gpu_heads > 0 && layer.k_cpu != nullptr;
|
| 841 |
+
+ // opencoti F4 M3 Phase 4: per-stream k_cpu vector is empty when no split,
|
| 842 |
+
+ // populated when gpu_heads > 0. Stream 0 always exists in unified mode
|
| 843 |
+
+ // (the only mode where split engages today; the M2 construction gate
|
| 844 |
+
+ // requires n_stream == 1).
|
| 845 |
+
+ return layer.gpu_heads > 0
|
| 846 |
+
+ && !layer.k_cpu_per_stream.empty()
|
| 847 |
+
+ && layer.k_cpu_per_stream[0] != nullptr;
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
uint32_t llama_kv_cache::headinfer_gpu_heads(int32_t il) const {
|
| 851 |
+
@@ -1840,93 +2049,131 @@ ggml_tensor * llama_kv_cache::get_k_gpu(ggml_context * ctx, int32_t il, uint32_t
|
| 852 |
+
const auto & layer = layers[ikv];
|
| 853 |
+
GGML_ASSERT(layer.gpu_heads > 0 && "get_k_gpu called without an active headinfer split");
|
| 854 |
+
|
| 855 |
+
- auto * k = layer.k;
|
| 856 |
+
- const uint64_t kv_size = get_size();
|
| 857 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 858 |
+
+ // opencoti F4 M3 Phase 4-D: per-stream M2 GPU subset. In unified
|
| 859 |
+
+ // mode (sinfo.s1 == sinfo.s0 == 0) the loop runs once and yields
|
| 860 |
+
+ // a single 4-D view of layer.k_per_stream[0]'s GPU half β
|
| 861 |
+
+ // byte-identical to pre-Phase-4 (same ne, same nb, offset 0).
|
| 862 |
+
+ const uint64_t kv_size = get_size();
|
| 863 |
+
const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 864 |
+
const uint32_t n_head_gpu = layer.gpu_heads;
|
| 865 |
+
const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_k;
|
| 866 |
+
|
| 867 |
+
- return ggml_view_4d(ctx, k,
|
| 868 |
+
- n_embd_head_k, n_head_gpu, n_kv, ns,
|
| 869 |
+
- ggml_row_size(k->type, n_embd_head_k),
|
| 870 |
+
- ggml_row_size(k->type, n_embd_gpu),
|
| 871 |
+
- ggml_row_size(k->type, n_embd_gpu*kv_size),
|
| 872 |
+
- ggml_row_size(k->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 873 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 874 |
+
+ ggml_tensor * k_s = layer.k_per_stream[s_cache];
|
| 875 |
+
+ return ggml_view_4d(ctx, k_s,
|
| 876 |
+
+ n_embd_head_k, n_head_gpu, n_kv, 1,
|
| 877 |
+
+ ggml_row_size(k_s->type, n_embd_head_k),
|
| 878 |
+
+ ggml_row_size(k_s->type, n_embd_gpu),
|
| 879 |
+
+ ggml_row_size(k_s->type, n_embd_gpu*kv_size),
|
| 880 |
+
+ 0);
|
| 881 |
+
+ };
|
| 882 |
+
+
|
| 883 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 884 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 885 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 886 |
+
+ }
|
| 887 |
+
+ return result;
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
ggml_tensor * llama_kv_cache::get_k_cpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 891 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 892 |
+
const auto & layer = layers[ikv];
|
| 893 |
+
- GGML_ASSERT(layer.gpu_heads > 0 && layer.k_cpu && "get_k_cpu called without an active headinfer split");
|
| 894 |
+
-
|
| 895 |
+
- auto * kc = layer.k_cpu;
|
| 896 |
+
- const uint64_t kv_size = get_size();
|
| 897 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 898 |
+
+ GGML_ASSERT(layer.gpu_heads > 0
|
| 899 |
+
+ && !layer.k_cpu_per_stream.empty()
|
| 900 |
+
+ && layer.k_cpu_per_stream[0]
|
| 901 |
+
+ && "get_k_cpu called without an active headinfer split");
|
| 902 |
+
+
|
| 903 |
+
+ // opencoti F4 M3 Phase 4-D: per-stream M2 CPU subset. Same pattern
|
| 904 |
+
+ // as get_k_gpu; unified mode collapses to a single 4-D view.
|
| 905 |
+
+ const uint64_t kv_size = get_size();
|
| 906 |
+
const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 907 |
+
const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 908 |
+
const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_k;
|
| 909 |
+
|
| 910 |
+
- return ggml_view_4d(ctx, kc,
|
| 911 |
+
- n_embd_head_k, n_head_cpu, n_kv, ns,
|
| 912 |
+
- ggml_row_size(kc->type, n_embd_head_k),
|
| 913 |
+
- ggml_row_size(kc->type, n_embd_cpu),
|
| 914 |
+
- ggml_row_size(kc->type, n_embd_cpu*kv_size),
|
| 915 |
+
- ggml_row_size(kc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 916 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 917 |
+
+ ggml_tensor * kc_s = layer.k_cpu_per_stream[s_cache];
|
| 918 |
+
+ return ggml_view_4d(ctx, kc_s,
|
| 919 |
+
+ n_embd_head_k, n_head_cpu, n_kv, 1,
|
| 920 |
+
+ ggml_row_size(kc_s->type, n_embd_head_k),
|
| 921 |
+
+ ggml_row_size(kc_s->type, n_embd_cpu),
|
| 922 |
+
+ ggml_row_size(kc_s->type, n_embd_cpu*kv_size),
|
| 923 |
+
+ 0);
|
| 924 |
+
+ };
|
| 925 |
+
+
|
| 926 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 927 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 928 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 929 |
+
+ }
|
| 930 |
+
+ return result;
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
ggml_tensor * llama_kv_cache::get_v_gpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 934 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 935 |
+
const auto & layer = layers[ikv];
|
| 936 |
+
GGML_ASSERT(layer.gpu_heads > 0 && "get_v_gpu called without an active headinfer split");
|
| 937 |
+
- // M2's gate is `!v_trans && n_stream == 1` β when split is active the
|
| 938 |
+
- // V layout is always non-transposed (matches get_v's non-split branch
|
| 939 |
+
- // shape modulo head count). Same head-dim-1 stride pattern as K.
|
| 940 |
+
+ // M2 requires non-transposed V (FA on). The split-active branch in
|
| 941 |
+
+ // get_v/cpy_v also requires this. Per-stream view + concat on stream
|
| 942 |
+
+ // axis. Phase 4-D: lifted the n_stream == 1 gate.
|
| 943 |
+
GGML_ASSERT(!v_trans && "headinfer split requires non-transposed V");
|
| 944 |
+
|
| 945 |
+
- auto * v = layer.v;
|
| 946 |
+
- const uint64_t kv_size = get_size();
|
| 947 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 948 |
+
+ const uint64_t kv_size = get_size();
|
| 949 |
+
const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 950 |
+
const uint32_t n_head_gpu = layer.gpu_heads;
|
| 951 |
+
const uint64_t n_embd_gpu = (uint64_t) n_head_gpu * n_embd_head_v;
|
| 952 |
+
|
| 953 |
+
- return ggml_view_4d(ctx, v,
|
| 954 |
+
- n_embd_head_v, n_head_gpu, n_kv, ns,
|
| 955 |
+
- ggml_row_size(v->type, n_embd_head_v),
|
| 956 |
+
- ggml_row_size(v->type, n_embd_gpu),
|
| 957 |
+
- ggml_row_size(v->type, n_embd_gpu*kv_size),
|
| 958 |
+
- ggml_row_size(v->type, n_embd_gpu*kv_size)*sinfo.s0);
|
| 959 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 960 |
+
+ ggml_tensor * v_s = layer.v_per_stream[s_cache];
|
| 961 |
+
+ return ggml_view_4d(ctx, v_s,
|
| 962 |
+
+ n_embd_head_v, n_head_gpu, n_kv, 1,
|
| 963 |
+
+ ggml_row_size(v_s->type, n_embd_head_v),
|
| 964 |
+
+ ggml_row_size(v_s->type, n_embd_gpu),
|
| 965 |
+
+ ggml_row_size(v_s->type, n_embd_gpu*kv_size),
|
| 966 |
+
+ 0);
|
| 967 |
+
+ };
|
| 968 |
+
+
|
| 969 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 970 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 971 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 972 |
+
+ }
|
| 973 |
+
+ return result;
|
| 974 |
+
}
|
| 975 |
+
|
| 976 |
+
ggml_tensor * llama_kv_cache::get_v_cpu(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const {
|
| 977 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 978 |
+
const auto & layer = layers[ikv];
|
| 979 |
+
- GGML_ASSERT(layer.gpu_heads > 0 && layer.v_cpu && "get_v_cpu called without an active headinfer split");
|
| 980 |
+
+ GGML_ASSERT(layer.gpu_heads > 0
|
| 981 |
+
+ && !layer.v_cpu_per_stream.empty()
|
| 982 |
+
+ && layer.v_cpu_per_stream[0]
|
| 983 |
+
+ && "get_v_cpu called without an active headinfer split");
|
| 984 |
+
GGML_ASSERT(!v_trans && "headinfer split requires non-transposed V");
|
| 985 |
+
|
| 986 |
+
- auto * vc = layer.v_cpu;
|
| 987 |
+
- const uint64_t kv_size = get_size();
|
| 988 |
+
- const uint32_t ns = sinfo.s1 - sinfo.s0 + 1;
|
| 989 |
+
+ // opencoti F4 M3 Phase 4-D: per-stream M2 CPU V subset.
|
| 990 |
+
+ const uint64_t kv_size = get_size();
|
| 991 |
+
const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 992 |
+
const uint32_t n_head_cpu = hparams.n_head_kv(il) - layer.gpu_heads;
|
| 993 |
+
const uint64_t n_embd_cpu = (uint64_t) n_head_cpu * n_embd_head_v;
|
| 994 |
+
|
| 995 |
+
- return ggml_view_4d(ctx, vc,
|
| 996 |
+
- n_embd_head_v, n_head_cpu, n_kv, ns,
|
| 997 |
+
- ggml_row_size(vc->type, n_embd_head_v),
|
| 998 |
+
- ggml_row_size(vc->type, n_embd_cpu),
|
| 999 |
+
- ggml_row_size(vc->type, n_embd_cpu*kv_size),
|
| 1000 |
+
- ggml_row_size(vc->type, n_embd_cpu*kv_size)*sinfo.s0);
|
| 1001 |
+
+ auto build_stream_view = [&](uint32_t s_cache) -> ggml_tensor * {
|
| 1002 |
+
+ ggml_tensor * vc_s = layer.v_cpu_per_stream[s_cache];
|
| 1003 |
+
+ return ggml_view_4d(ctx, vc_s,
|
| 1004 |
+
+ n_embd_head_v, n_head_cpu, n_kv, 1,
|
| 1005 |
+
+ ggml_row_size(vc_s->type, n_embd_head_v),
|
| 1006 |
+
+ ggml_row_size(vc_s->type, n_embd_cpu),
|
| 1007 |
+
+ ggml_row_size(vc_s->type, n_embd_cpu*kv_size),
|
| 1008 |
+
+ 0);
|
| 1009 |
+
+ };
|
| 1010 |
+
+
|
| 1011 |
+
+ ggml_tensor * result = build_stream_view(sinfo.s0);
|
| 1012 |
+
+ for (uint32_t s = sinfo.s0 + 1; s <= sinfo.s1; ++s) {
|
| 1013 |
+
+ result = ggml_concat(ctx, result, build_stream_view(s), 3);
|
| 1014 |
+
+ }
|
| 1015 |
+
+ return result;
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il, const slot_info & sinfo) const {
|
| 1019 |
+
- GGML_UNUSED(sinfo);
|
| 1020 |
+
-
|
| 1021 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 1022 |
+
|
| 1023 |
+
const auto & layer = layers[ikv];
|
| 1024 |
+
- ggml_tensor * k = layer.k;
|
| 1025 |
+
|
| 1026 |
+
const int64_t n_embd_head = k_cur->ne[0];
|
| 1027 |
+
const int64_t n_head = k_cur->ne[1];
|
| 1028 |
+
@@ -1941,46 +2188,116 @@ ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggm
|
| 1029 |
+
// opencoti F5 M2 headinfer β scatter the incoming heads into the GPU and
|
| 1030 |
+
// host subsets. The first gpu_heads heads (contiguous in k_cur's dim 0..1)
|
| 1031 |
+
// store into k; the rest store into k_cpu, both at the same cell indices
|
| 1032 |
+
- // (k_idxs). The single returned dep node ties both set_rows into the
|
| 1033 |
+
- // caller's one ggml_build_forward_expand (its value is never read). Split
|
| 1034 |
+
- // only happens for n_stream==1 (gated at construction) β no per-stream
|
| 1035 |
+
- // reshape needed.
|
| 1036 |
+
- if (layer.gpu_heads > 0 && layer.k_cpu) {
|
| 1037 |
+
+ // (k_idxs). The single returned dep node ties all set_rows into the
|
| 1038 |
+
+ // caller's one ggml_build_forward_expand (its value is never read).
|
| 1039 |
+
+ //
|
| 1040 |
+
+ // opencoti F4 M3 Phase 4-D: per-stream M2 scatter β for each batch
|
| 1041 |
+
+ // stream s, slice k_cur and k_idxs by stream, split the head-axis
|
| 1042 |
+
+ // slice into GPU + CPU halves, and emit 2 set_rows per stream against
|
| 1043 |
+
+ // the per-stream tensors. In unified mode (sinfo.n_stream() == 1) this
|
| 1044 |
+
+ // collapses to exactly 2 set_rows on stream 0's tensors β byte-identical
|
| 1045 |
+
+ // to pre-Phase-4.
|
| 1046 |
+
+ if (layer.gpu_heads > 0
|
| 1047 |
+
+ && !layer.k_cpu_per_stream.empty()
|
| 1048 |
+
+ && layer.k_cpu_per_stream[0]) {
|
| 1049 |
+
const int64_t g = layer.gpu_heads;
|
| 1050 |
+
const int64_t c = n_head - g;
|
| 1051 |
+
- ggml_tensor * kc = layer.k_cpu;
|
| 1052 |
+
- ggml_tensor * cur_g = ggml_view_2d(ctx, k_cur, n_embd_head*g, n_tokens, k_cur->nb[2], 0);
|
| 1053 |
+
- ggml_tensor * cur_c = ggml_view_2d(ctx, k_cur, n_embd_head*c, n_tokens, k_cur->nb[2], (size_t) (g*k_cur->nb[1]));
|
| 1054 |
+
- ggml_tensor * store_g = ggml_set_rows(ctx, k, cur_g, k_idxs);
|
| 1055 |
+
- ggml_tensor * store_c = ggml_set_rows(ctx, kc, cur_c, k_idxs);
|
| 1056 |
+
- return ggml_add(ctx, ggml_view_1d(ctx, store_g, 1, 0), ggml_view_1d(ctx, store_c, 1, 0));
|
| 1057 |
+
+ const uint32_t batch_ns = sinfo.n_stream();
|
| 1058 |
+
+ GGML_ASSERT(batch_ns >= 1);
|
| 1059 |
+
+
|
| 1060 |
+
+ const int64_t tokens_per_stream = (batch_ns == 1) ? n_tokens : (n_tokens / (int64_t) batch_ns);
|
| 1061 |
+
+ GGML_ASSERT((int64_t) batch_ns * tokens_per_stream == n_tokens);
|
| 1062 |
+
+
|
| 1063 |
+
+ ggml_tensor * tie = nullptr;
|
| 1064 |
+
+ for (uint32_t s = 0; s < batch_ns; ++s) {
|
| 1065 |
+
+ ggml_tensor * k_s = layer.k_per_stream[sinfo.strm[s]];
|
| 1066 |
+
+ ggml_tensor * kc_s = layer.k_cpu_per_stream[sinfo.strm[s]];
|
| 1067 |
+
+
|
| 1068 |
+
+ // k_cur is [head_dim, n_head, n_tokens]; nb[2] is the stride
|
| 1069 |
+
+ // between tokens. The per-stream slice is the contiguous block
|
| 1070 |
+
+ // of tokens_per_stream tokens at offset s*tokens_per_stream.
|
| 1071 |
+
+ const size_t per_stream_off = (size_t) s * tokens_per_stream * k_cur->nb[2];
|
| 1072 |
+
+ ggml_tensor * cur_g = ggml_view_2d(ctx, k_cur,
|
| 1073 |
+
+ n_embd_head*g, tokens_per_stream,
|
| 1074 |
+
+ k_cur->nb[2], per_stream_off);
|
| 1075 |
+
+ // CPU half: remaining c heads, offset by g heads (g rows in the head-merged dim 0)
|
| 1076 |
+
+ ggml_tensor * cur_c = ggml_view_2d(ctx, k_cur,
|
| 1077 |
+
+ n_embd_head*c, tokens_per_stream,
|
| 1078 |
+
+ k_cur->nb[2], per_stream_off + (size_t)(g*k_cur->nb[1]));
|
| 1079 |
+
+ ggml_tensor * idxs_s = (batch_ns == 1)
|
| 1080 |
+
+ ? k_idxs
|
| 1081 |
+
+ : ggml_view_1d(ctx, k_idxs, tokens_per_stream,
|
| 1082 |
+
+ (size_t) s * tokens_per_stream * k_idxs->nb[0]);
|
| 1083 |
+
+ ggml_tensor * store_g = ggml_set_rows(ctx, k_s, cur_g, idxs_s);
|
| 1084 |
+
+ ggml_tensor * store_c = ggml_set_rows(ctx, kc_s, cur_c, idxs_s);
|
| 1085 |
+
+ ggml_tensor * pair = ggml_add(ctx,
|
| 1086 |
+
+ ggml_view_1d(ctx, store_g, 1, 0),
|
| 1087 |
+
+ ggml_view_1d(ctx, store_c, 1, 0));
|
| 1088 |
+
+ if (!tie) {
|
| 1089 |
+
+ tie = pair;
|
| 1090 |
+
+ } else {
|
| 1091 |
+
+ tie = ggml_add(ctx,
|
| 1092 |
+
+ ggml_view_1d(ctx, tie, 1, 0),
|
| 1093 |
+
+ ggml_view_1d(ctx, pair, 1, 0));
|
| 1094 |
+
+ }
|
| 1095 |
+
+ }
|
| 1096 |
+
+ return tie;
|
| 1097 |
+
}
|
| 1098 |
+
|
| 1099 |
+
k_cur = ggml_view_2d(ctx, k_cur, n_embd_gqa, n_tokens, k_cur->nb[2], 0);
|
| 1100 |
+
|
| 1101 |
+
- const int64_t n_stream = k->ne[2];
|
| 1102 |
+
-
|
| 1103 |
+
- if (n_stream > 1) {
|
| 1104 |
+
- const int64_t kv_size = get_size();
|
| 1105 |
+
-
|
| 1106 |
+
- assert(n_embd_gqa == k->ne[0]);
|
| 1107 |
+
- assert(kv_size == k->ne[1]);
|
| 1108 |
+
-
|
| 1109 |
+
- // merge the buffer across all streams because the idxs are global
|
| 1110 |
+
- k = ggml_reshape_2d(ctx, k, n_embd_gqa, kv_size*n_stream);
|
| 1111 |
+
+ // opencoti F4 M3 Phase 4-C: per-stream scatter via N separate
|
| 1112 |
+
+ // ggml_set_rows ops (option C-2). In unified mode (sinfo.n_stream()
|
| 1113 |
+
+ // == 1) the fast path collapses to one ggml_set_rows against
|
| 1114 |
+
+ // layer.k_per_stream[sinfo.strm[0]] β byte-identical to pre-Phase-4.
|
| 1115 |
+
+ // In non-unified mode (sinfo.n_stream() > 1), set_input_k_idxs has
|
| 1116 |
+
+ // emitted LOCAL indices (no global offset), so each per-stream slice
|
| 1117 |
+
+ // of k_idxs is valid against its own k_per_stream[cache_strm] tensor.
|
| 1118 |
+
+ // The N stores are tied into a single dep node via ggml_add so the
|
| 1119 |
+
+ // caller's one ggml_build_forward_expand picks them all up.
|
| 1120 |
+
+ //
|
| 1121 |
+
+ // C-1 (concat-then-scatter) is not expressible with per-stream owning
|
| 1122 |
+
+ // tensors in ggml: ggml_concat materializes a new buffer rather than
|
| 1123 |
+
+ // creating a view into its inputs, so writes routed through the concat
|
| 1124 |
+
+ // result would not land in the per-stream tensors. C-2 is the natural
|
| 1125 |
+
+ // shape that preserves per-stream buffer ownership.
|
| 1126 |
+
+ const uint32_t batch_ns = sinfo.n_stream();
|
| 1127 |
+
+ GGML_ASSERT(batch_ns >= 1);
|
| 1128 |
+
+
|
| 1129 |
+
+ if (batch_ns == 1) {
|
| 1130 |
+
+ ggml_tensor * k_s = layer.k_per_stream[sinfo.strm[0]];
|
| 1131 |
+
+ return ggml_set_rows(ctx, k_s, k_cur, k_idxs);
|
| 1132 |
+
+ }
|
| 1133 |
+
+
|
| 1134 |
+
+ const int64_t tokens_per_stream = n_tokens / (int64_t) batch_ns;
|
| 1135 |
+
+ GGML_ASSERT((int64_t) batch_ns * tokens_per_stream == n_tokens);
|
| 1136 |
+
+
|
| 1137 |
+
+ ggml_tensor * tie = nullptr;
|
| 1138 |
+
+ for (uint32_t s = 0; s < batch_ns; ++s) {
|
| 1139 |
+
+ ggml_tensor * k_s = layer.k_per_stream[sinfo.strm[s]];
|
| 1140 |
+
+ ggml_tensor * cur_s = ggml_view_2d(ctx, k_cur,
|
| 1141 |
+
+ n_embd_gqa, tokens_per_stream,
|
| 1142 |
+
+ k_cur->nb[1],
|
| 1143 |
+
+ (size_t) s * tokens_per_stream * k_cur->nb[1]);
|
| 1144 |
+
+ ggml_tensor * idxs_s = ggml_view_1d(ctx, k_idxs,
|
| 1145 |
+
+ tokens_per_stream,
|
| 1146 |
+
+ (size_t) s * tokens_per_stream * k_idxs->nb[0]);
|
| 1147 |
+
+ ggml_tensor * store_s = ggml_set_rows(ctx, k_s, cur_s, idxs_s);
|
| 1148 |
+
+ if (!tie) {
|
| 1149 |
+
+ tie = store_s;
|
| 1150 |
+
+ } else {
|
| 1151 |
+
+ tie = ggml_add(ctx,
|
| 1152 |
+
+ ggml_view_1d(ctx, tie, 1, 0),
|
| 1153 |
+
+ ggml_view_1d(ctx, store_s, 1, 0));
|
| 1154 |
+
+ }
|
| 1155 |
+
}
|
| 1156 |
+
-
|
| 1157 |
+
- // store the current K values into the cache
|
| 1158 |
+
- return ggml_set_rows(ctx, k, k_cur, k_idxs);
|
| 1159 |
+
+ return tie;
|
| 1160 |
+
}
|
| 1161 |
+
|
| 1162 |
+
ggml_tensor * llama_kv_cache::cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggml_tensor * v_idxs, int32_t il, const slot_info & sinfo) const {
|
| 1163 |
+
- GGML_UNUSED(sinfo);
|
| 1164 |
+
-
|
| 1165 |
+
const int32_t ikv = map_layer_ids.at(il);
|
| 1166 |
+
|
| 1167 |
+
const auto & layer = layers[ikv];
|
| 1168 |
+
- auto * v = layer.v;
|
| 1169 |
+
|
| 1170 |
+
const int64_t n_embd_head = v_cur->ne[0];
|
| 1171 |
+
const int64_t n_head = v_cur->ne[1];
|
| 1172 |
+
@@ -1992,38 +2309,109 @@ ggml_tensor * llama_kv_cache::cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggm
|
| 1173 |
+
GGML_ASSERT(ggml_row_size(v_cur->type, n_embd_head) == v_cur->nb[1]);
|
| 1174 |
+
|
| 1175 |
+
// opencoti F5 M2 headinfer β head-scatter into the GPU + host subsets.
|
| 1176 |
+
- // Reached only when !v_trans and n_stream==1 (the split is gated off
|
| 1177 |
+
- // otherwise at construction), so this mirrors the cpy_k store exactly.
|
| 1178 |
+
- if (layer.gpu_heads > 0 && layer.v_cpu) {
|
| 1179 |
+
+ // Reached only when !v_trans (the split is gated off at construction
|
| 1180 |
+
+ // otherwise).
|
| 1181 |
+
+ //
|
| 1182 |
+
+ // opencoti F4 M3 Phase 4-D: per-stream M2 V scatter β same shape as
|
| 1183 |
+
+ // cpy_k's M2 branch. Unified mode collapses to 2 set_rows on stream 0,
|
| 1184 |
+
+ // byte-identical to pre-Phase-4.
|
| 1185 |
+
+ if (layer.gpu_heads > 0
|
| 1186 |
+
+ && !layer.v_cpu_per_stream.empty()
|
| 1187 |
+
+ && layer.v_cpu_per_stream[0]) {
|
| 1188 |
+
const int64_t g = layer.gpu_heads;
|
| 1189 |
+
const int64_t c = n_head - g;
|
| 1190 |
+
- ggml_tensor * vc = layer.v_cpu;
|
| 1191 |
+
- ggml_tensor * cur_g = ggml_view_2d(ctx, v_cur, n_embd_head*g, n_tokens, v_cur->nb[2], 0);
|
| 1192 |
+
- ggml_tensor * cur_c = ggml_view_2d(ctx, v_cur, n_embd_head*c, n_tokens, v_cur->nb[2], (size_t) (g*v_cur->nb[1]));
|
| 1193 |
+
- ggml_tensor * store_g = ggml_set_rows(ctx, v, cur_g, v_idxs);
|
| 1194 |
+
- ggml_tensor * store_c = ggml_set_rows(ctx, vc, cur_c, v_idxs);
|
| 1195 |
+
- return ggml_add(ctx, ggml_view_1d(ctx, store_g, 1, 0), ggml_view_1d(ctx, store_c, 1, 0));
|
| 1196 |
+
+ const uint32_t batch_ns = sinfo.n_stream();
|
| 1197 |
+
+ GGML_ASSERT(batch_ns >= 1);
|
| 1198 |
+
+
|
| 1199 |
+
+ const int64_t tokens_per_stream = (batch_ns == 1) ? n_tokens : (n_tokens / (int64_t) batch_ns);
|
| 1200 |
+
+ GGML_ASSERT((int64_t) batch_ns * tokens_per_stream == n_tokens);
|
| 1201 |
+
+
|
| 1202 |
+
+ ggml_tensor * tie = nullptr;
|
| 1203 |
+
+ for (uint32_t s = 0; s < batch_ns; ++s) {
|
| 1204 |
+
+ ggml_tensor * v_s = layer.v_per_stream[sinfo.strm[s]];
|
| 1205 |
+
+ ggml_tensor * vc_s = layer.v_cpu_per_stream[sinfo.strm[s]];
|
| 1206 |
+
+
|
| 1207 |
+
+ const size_t per_stream_off = (size_t) s * tokens_per_stream * v_cur->nb[2];
|
| 1208 |
+
+ ggml_tensor * cur_g = ggml_view_2d(ctx, v_cur,
|
| 1209 |
+
+ n_embd_head*g, tokens_per_stream,
|
| 1210 |
+
+ v_cur->nb[2], per_stream_off);
|
| 1211 |
+
+ ggml_tensor * cur_c = ggml_view_2d(ctx, v_cur,
|
| 1212 |
+
+ n_embd_head*c, tokens_per_stream,
|
| 1213 |
+
+ v_cur->nb[2], per_stream_off + (size_t)(g*v_cur->nb[1]));
|
| 1214 |
+
+ ggml_tensor * idxs_s = (batch_ns == 1)
|
| 1215 |
+
+ ? v_idxs
|
| 1216 |
+
+ : ggml_view_1d(ctx, v_idxs, tokens_per_stream,
|
| 1217 |
+
+ (size_t) s * tokens_per_stream * v_idxs->nb[0]);
|
| 1218 |
+
+ ggml_tensor * store_g = ggml_set_rows(ctx, v_s, cur_g, idxs_s);
|
| 1219 |
+
+ ggml_tensor * store_c = ggml_set_rows(ctx, vc_s, cur_c, idxs_s);
|
| 1220 |
+
+ ggml_tensor * pair = ggml_add(ctx,
|
| 1221 |
+
+ ggml_view_1d(ctx, store_g, 1, 0),
|
| 1222 |
+
+ ggml_view_1d(ctx, store_c, 1, 0));
|
| 1223 |
+
+ if (!tie) {
|
| 1224 |
+
+ tie = pair;
|
| 1225 |
+
+ } else {
|
| 1226 |
+
+ tie = ggml_add(ctx,
|
| 1227 |
+
+ ggml_view_1d(ctx, tie, 1, 0),
|
| 1228 |
+
+ ggml_view_1d(ctx, pair, 1, 0));
|
| 1229 |
+
+ }
|
| 1230 |
+
+ }
|
| 1231 |
+
+ return tie;
|
| 1232 |
+
}
|
| 1233 |
+
|
| 1234 |
+
- const int64_t n_stream = v->ne[2];
|
| 1235 |
+
+ // opencoti F4 M3 Phase 4-C: per-stream V scatter. Two sub-branches
|
| 1236 |
+
+ // depending on v_trans (FA vs non-FA layout).
|
| 1237 |
+
+ //
|
| 1238 |
+
+ // !v_trans (FA on): same shape as cpy_k β N separate ggml_set_rows on
|
| 1239 |
+
+ // the 2-D-merged v_cur, sliced per stream. set_input_v_idxs emits
|
| 1240 |
+
+ // LOCAL per-stream indices in non-unified mode. Unified mode (batch_ns
|
| 1241 |
+
+ // == 1) keeps the simple one-set_rows fast path, byte-identical.
|
| 1242 |
+
+ //
|
| 1243 |
+
+ // v_trans (FA off, V transposed): each row is a single head element.
|
| 1244 |
+
+ // The unified-mode path is preserved; multi-stream + v_trans is not a
|
| 1245 |
+
+ // current workload (the v_trans layout requires no FA, while parallel
|
| 1246 |
+
+ // multi-stream batches are always paired with FA). Asserted out as a
|
| 1247 |
+
+ // safety net; future Phase 4-D/E may lift if a workload appears.
|
| 1248 |
+
+ const uint32_t batch_ns = sinfo.n_stream();
|
| 1249 |
+
+ GGML_ASSERT(batch_ns >= 1);
|
| 1250 |
+
|
| 1251 |
+
- // take this branch when FA is enabled (the V cache is not transposed)
|
| 1252 |
+
if (!v_trans) {
|
| 1253 |
+
v_cur = ggml_view_2d(ctx, v_cur, n_embd_gqa, n_tokens, v_cur->nb[2], 0);
|
| 1254 |
+
|
| 1255 |
+
- if (n_stream > 1) {
|
| 1256 |
+
- const int64_t kv_size = get_size();
|
| 1257 |
+
-
|
| 1258 |
+
- assert(n_embd_gqa == v->ne[0]);
|
| 1259 |
+
- assert(kv_size == v->ne[1]);
|
| 1260 |
+
-
|
| 1261 |
+
- // merge the buffer across all streams because the idxs are global
|
| 1262 |
+
- v = ggml_reshape_2d(ctx, v, n_embd_gqa, kv_size*n_stream);
|
| 1263 |
+
+ if (batch_ns == 1) {
|
| 1264 |
+
+ ggml_tensor * v_s = layer.v_per_stream[sinfo.strm[0]];
|
| 1265 |
+
+ return ggml_set_rows(ctx, v_s, v_cur, v_idxs);
|
| 1266 |
+
+ }
|
| 1267 |
+
+
|
| 1268 |
+
+ const int64_t tokens_per_stream = n_tokens / (int64_t) batch_ns;
|
| 1269 |
+
+ GGML_ASSERT((int64_t) batch_ns * tokens_per_stream == n_tokens);
|
| 1270 |
+
+
|
| 1271 |
+
+ ggml_tensor * tie = nullptr;
|
| 1272 |
+
+ for (uint32_t s = 0; s < batch_ns; ++s) {
|
| 1273 |
+
+ ggml_tensor * v_s = layer.v_per_stream[sinfo.strm[s]];
|
| 1274 |
+
+ ggml_tensor * cur_s = ggml_view_2d(ctx, v_cur,
|
| 1275 |
+
+ n_embd_gqa, tokens_per_stream,
|
| 1276 |
+
+ v_cur->nb[1],
|
| 1277 |
+
+ (size_t) s * tokens_per_stream * v_cur->nb[1]);
|
| 1278 |
+
+ ggml_tensor * idxs_s = ggml_view_1d(ctx, v_idxs,
|
| 1279 |
+
+ tokens_per_stream,
|
| 1280 |
+
+ (size_t) s * tokens_per_stream * v_idxs->nb[0]);
|
| 1281 |
+
+ ggml_tensor * store_s = ggml_set_rows(ctx, v_s, cur_s, idxs_s);
|
| 1282 |
+
+ if (!tie) {
|
| 1283 |
+
+ tie = store_s;
|
| 1284 |
+
+ } else {
|
| 1285 |
+
+ tie = ggml_add(ctx,
|
| 1286 |
+
+ ggml_view_1d(ctx, tie, 1, 0),
|
| 1287 |
+
+ ggml_view_1d(ctx, store_s, 1, 0));
|
| 1288 |
+
+ }
|
| 1289 |
+
}
|
| 1290 |
+
-
|
| 1291 |
+
- return ggml_set_rows(ctx, v, v_cur, v_idxs);
|
| 1292 |
+
+ return tie;
|
| 1293 |
+
}
|
| 1294 |
+
|
| 1295 |
+
+ // v_trans path: not a multi-stream workload; preserve unified-mode
|
| 1296 |
+
+ // behavior exactly and assert multi-stream out.
|
| 1297 |
+
+ GGML_ASSERT(batch_ns == 1 && "v_trans + multi-stream is unsupported; v_trans implies !FA which is mutually exclusive with parallel batches");
|
| 1298 |
+
+ ggml_tensor * v = layer.v_per_stream[sinfo.strm[0]];
|
| 1299 |
+
+
|
| 1300 |
+
if (ggml_row_size(v_cur->type, n_embd_gqa) == v_cur->nb[2]) {
|
| 1301 |
+
// we can merge dims 0, 1 and 2
|
| 1302 |
+
v_cur = ggml_reshape_2d(ctx, v_cur, n_embd_gqa, n_tokens);
|
| 1303 |
+
@@ -2119,11 +2507,23 @@ void llama_kv_cache::set_input_k_idxs(ggml_tensor * dst, const llama_ubatch * ub
|
| 1304 |
+
GGML_ASSERT(ggml_backend_buffer_is_host(dst->buffer));
|
| 1305 |
+
int64_t * data = (int64_t *) dst->data;
|
| 1306 |
+
|
| 1307 |
+
- for (uint32_t s = 0; s < sinfo.n_stream(); ++s) {
|
| 1308 |
+
- const int64_t offs = sinfo.strm[s]*get_size();
|
| 1309 |
+
+ // opencoti F4 M3 Phase 4-C/F: emit LOCAL per-stream indices unconditionally.
|
| 1310 |
+
+ // Phase 4 ALWAYS scatters to per-stream tensors (cpy_k routes to
|
| 1311 |
+
+ // k_per_stream[sinfo.strm[s]] which is sized kv_size, not kv_size*n_stream),
|
| 1312 |
+
+ // so the index must be local to that stream's tensor. In unified mode
|
| 1313 |
+
+ // (n_stream == 1, sinfo.strm[0] == 0) pre-Phase-4 offs was 0 already,
|
| 1314 |
+
+ // so this is byte-identical.
|
| 1315 |
+
+ //
|
| 1316 |
+
+ // Phase 4-F bug fix: the previous version conditioned the offset
|
| 1317 |
+
+ // on sinfo.n_stream() (per-batch streams), which left it global
|
| 1318 |
+
+ // when a --parallel N server served a single-session batch
|
| 1319 |
+
+ // (sinfo.n_stream() == 1 but sinfo.strm[0] != 0). The resulting
|
| 1320 |
+
+ // global indices wrote out-of-bounds against the per-stream
|
| 1321 |
+
+ // tensor and crashed CUDA decode.
|
| 1322 |
+
|
| 1323 |
+
+ for (uint32_t s = 0; s < sinfo.n_stream(); ++s) {
|
| 1324 |
+
for (uint32_t i = 0; i < sinfo.size(); ++i) {
|
| 1325 |
+
- data[s*sinfo.size() + i] = offs + sinfo.idxs[s][i];
|
| 1326 |
+
+ data[s*sinfo.size() + i] = sinfo.idxs[s][i];
|
| 1327 |
+
}
|
| 1328 |
+
}
|
| 1329 |
+
}
|
| 1330 |
+
@@ -2135,12 +2535,15 @@ void llama_kv_cache::set_input_v_idxs(ggml_tensor * dst, const llama_ubatch * ub
|
| 1331 |
+
GGML_ASSERT(ggml_backend_buffer_is_host(dst->buffer));
|
| 1332 |
+
int64_t * data = (int64_t *) dst->data;
|
| 1333 |
+
|
| 1334 |
+
+ // opencoti F4 M3 Phase 4-C/F: same LOCAL-only fix as set_input_k_idxs.
|
| 1335 |
+
+ // The v_trans branch's offset is also dropped β v_trans + multi-stream
|
| 1336 |
+
+ // is asserted out in cpy_v, but the unified-mode v_trans path here is
|
| 1337 |
+
+ // byte-identical regardless (sinfo.strm[0] == 0 makes offs == 0).
|
| 1338 |
+
+
|
| 1339 |
+
if (!v_trans) {
|
| 1340 |
+
for (uint32_t s = 0; s < sinfo.n_stream(); ++s) {
|
| 1341 |
+
- const int64_t offs = sinfo.strm[s]*get_size();
|
| 1342 |
+
-
|
| 1343 |
+
for (uint32_t i = 0; i < sinfo.size(); ++i) {
|
| 1344 |
+
- data[s*sinfo.size() + i] = offs + sinfo.idxs[s][i];
|
| 1345 |
+
+ data[s*sinfo.size() + i] = sinfo.idxs[s][i];
|
| 1346 |
+
}
|
| 1347 |
+
}
|
| 1348 |
+
} else {
|
| 1349 |
+
@@ -2150,11 +2553,9 @@ void llama_kv_cache::set_input_v_idxs(ggml_tensor * dst, const llama_ubatch * ub
|
| 1350 |
+
const int64_t n_embd_v_gqa = hparams.n_embd_v_gqa_max();
|
| 1351 |
+
|
| 1352 |
+
for (uint32_t s = 0; s < sinfo.n_stream(); ++s) {
|
| 1353 |
+
- const int64_t offs = sinfo.strm[s]*kv_size*n_embd_v_gqa;
|
| 1354 |
+
-
|
| 1355 |
+
for (uint32_t i = 0; i < sinfo.size(); ++i) {
|
| 1356 |
+
for (uint32_t j = 0; j < n_embd_v_gqa; ++j) {
|
| 1357 |
+
- data[s*sinfo.size()*n_embd_v_gqa + i*n_embd_v_gqa + j] = offs + j*kv_size + sinfo.idxs[s][i];
|
| 1358 |
+
+ data[s*sinfo.size()*n_embd_v_gqa + i*n_embd_v_gqa + j] = j*kv_size + sinfo.idxs[s][i];
|
| 1359 |
+
}
|
| 1360 |
+
}
|
| 1361 |
+
}
|
| 1362 |
+
@@ -2459,10 +2860,16 @@ size_t llama_kv_cache::total_size() const {
|
| 1363 |
+
}
|
| 1364 |
+
|
| 1365 |
+
size_t llama_kv_cache::size_k_bytes() const {
|
| 1366 |
+
+ // opencoti F4 M3 Phase 4: sum bytes across all per-stream tensors of
|
| 1367 |
+
+ // each layer. In unified mode (n_stream == 1) this is byte-identical
|
| 1368 |
+
+ // to ggml_nbytes(layer.k) of pre-Phase-4. M2's host subset is
|
| 1369 |
+
+ // accounted separately (it lives in a different buffer-type).
|
| 1370 |
+
size_t size_k_bytes = 0;
|
| 1371 |
+
|
| 1372 |
+
for (const auto & layer : layers) {
|
| 1373 |
+
- size_k_bytes += ggml_nbytes(layer.k);
|
| 1374 |
+
+ for (auto * k : layer.k_per_stream) {
|
| 1375 |
+
+ if (k) size_k_bytes += ggml_nbytes(k);
|
| 1376 |
+
+ }
|
| 1377 |
+
}
|
| 1378 |
+
|
| 1379 |
+
return size_k_bytes;
|
| 1380 |
+
@@ -2472,7 +2879,9 @@ size_t llama_kv_cache::size_v_bytes() const {
|
| 1381 |
+
size_t size_v_bytes = 0;
|
| 1382 |
+
|
| 1383 |
+
for (const auto & layer : layers) {
|
| 1384 |
+
- size_v_bytes += layer.v ? ggml_nbytes(layer.v) : 0;
|
| 1385 |
+
+ for (auto * v : layer.v_per_stream) {
|
| 1386 |
+
+ if (v) size_v_bytes += ggml_nbytes(v);
|
| 1387 |
+
+ }
|
| 1388 |
+
}
|
| 1389 |
+
|
| 1390 |
+
return size_v_bytes;
|
| 1391 |
+
@@ -2570,6 +2979,19 @@ ggml_cgraph * llama_kv_cache::build_graph_shift(llm_graph_result * res, llama_co
|
| 1392 |
+
|
| 1393 |
+
const auto & cparams = lctx->get_cparams();
|
| 1394 |
+
|
| 1395 |
+
+ // opencoti F4 M3 Phase 4-C: k-shift loops over per-stream tensors.
|
| 1396 |
+
+ // With per-stream owning tensors, each stream's K is a separate buffer,
|
| 1397 |
+
+ // so we build N rope-shift ops per layer (one per stream). The k_shift
|
| 1398 |
+
+ // input tensor has length get_size()*n_stream, with stream s occupying
|
| 1399 |
+
+ // indices [s*get_size(), (s+1)*get_size()) β set_input_k_shift writes
|
| 1400 |
+
+ // them in that order. Each per-stream rope-shift consumes its
|
| 1401 |
+
+ // contiguous slice via ggml_view_1d on k_shift.
|
| 1402 |
+
+ //
|
| 1403 |
+
+ // In unified mode (n_stream == 1), the loop runs once with stream 0,
|
| 1404 |
+
+ // and the k_shift view degenerates to the full tensor (offset 0,
|
| 1405 |
+
+ // length get_size()). Byte-identical to pre-Phase-4 β same rope-shift
|
| 1406 |
+
+ // op shape and inputs.
|
| 1407 |
+
+
|
| 1408 |
+
for (const auto & layer : layers) {
|
| 1409 |
+
const uint32_t il = layer.il;
|
| 1410 |
+
|
| 1411 |
+
@@ -2585,16 +3007,24 @@ ggml_cgraph * llama_kv_cache::build_graph_shift(llm_graph_result * res, llama_co
|
| 1412 |
+
|
| 1413 |
+
ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
|
| 1414 |
+
|
| 1415 |
+
- ggml_tensor * k =
|
| 1416 |
+
- ggml_view_3d(ctx, layer.k,
|
| 1417 |
+
- n_rot, n_head_kv, get_size()*n_stream,
|
| 1418 |
+
- ggml_row_size(layer.k->type, n_embd_head_k),
|
| 1419 |
+
- ggml_row_size(layer.k->type, n_embd_k_gqa),
|
| 1420 |
+
- ggml_row_size(layer.k->type, n_embd_nope));
|
| 1421 |
+
+ for (uint32_t s = 0; s < n_stream; ++s) {
|
| 1422 |
+
+ ggml_tensor * layer_k = layer.k_per_stream[s];
|
| 1423 |
+
+ ggml_tensor * k =
|
| 1424 |
+
+ ggml_view_3d(ctx, layer_k,
|
| 1425 |
+
+ n_rot, n_head_kv, get_size(),
|
| 1426 |
+
+ ggml_row_size(layer_k->type, n_embd_head_k),
|
| 1427 |
+
+ ggml_row_size(layer_k->type, n_embd_k_gqa),
|
| 1428 |
+
+ ggml_row_size(layer_k->type, n_embd_nope));
|
| 1429 |
+
+
|
| 1430 |
+
+ ggml_tensor * k_shift_s = (n_stream == 1)
|
| 1431 |
+
+ ? inp->k_shift
|
| 1432 |
+
+ : ggml_view_1d(ctx, inp->k_shift, get_size(),
|
| 1433 |
+
+ (size_t) s * get_size() * inp->k_shift->nb[0]);
|
| 1434 |
+
|
| 1435 |
+
- ggml_tensor * cur = build_rope_shift(cparams, ctx, k, inp->k_shift, inp->k_rot, rope_factors, freq_base_l, freq_scale_l, il);
|
| 1436 |
+
+ ggml_tensor * cur = build_rope_shift(cparams, ctx, k, k_shift_s, inp->k_rot, rope_factors, freq_base_l, freq_scale_l, il);
|
| 1437 |
+
|
| 1438 |
+
- ggml_build_forward_expand(gf, cur);
|
| 1439 |
+
+ ggml_build_forward_expand(gf, cur);
|
| 1440 |
+
+ }
|
| 1441 |
+
}
|
| 1442 |
+
|
| 1443 |
+
res->add_input(std::move(inp));
|
| 1444 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 1445 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 1446 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 1447 |
+
@@ -286,19 +286,57 @@ private:
|
| 1448 |
+
// note: can be different from the layer index in the KV cache
|
| 1449 |
+
uint32_t il;
|
| 1450 |
+
|
| 1451 |
+
- ggml_tensor * k;
|
| 1452 |
+
- ggml_tensor * v;
|
| 1453 |
+
-
|
| 1454 |
+
+ // opencoti F4 M3 Phase 4 β per-stream tensor split.
|
| 1455 |
+
+ // Each element is an OWNING 3-D tensor of shape
|
| 1456 |
+
+ // [n_embd_k_gqa, kv_size, 1] (dim 3 == 1 keeps stride math
|
| 1457 |
+
+ // identical to the pre-Phase-4 single-tensor layout). Vector
|
| 1458 |
+
+ // size always equals n_stream.
|
| 1459 |
+
+ //
|
| 1460 |
+
+ // Unified mode (n_stream == 1): k_per_stream[0] is byte-
|
| 1461 |
+
+ // identical to the pre-Phase-4 `k` field (same buft, same
|
| 1462 |
+
+ // ctx, same offsets). Non-unified mode (n_stream > 1): each
|
| 1463 |
+
+ // stream's tensor lives in its own (possibly per-stream)
|
| 1464 |
+
+ // backend buffer (Phase 4-B); cross-stream operations are
|
| 1465 |
+
+ // buffer-to-buffer copies.
|
| 1466 |
+
+ std::vector<ggml_tensor *> k_per_stream;
|
| 1467 |
+
+ std::vector<ggml_tensor *> v_per_stream;
|
| 1468 |
+
+
|
| 1469 |
+
+ // k_stream[s] / v_stream[s] are 2-D views into
|
| 1470 |
+
+ // k_per_stream[s] / v_per_stream[s]. Kept for backward compat
|
| 1471 |
+
+ // with consumers that index by stream; same shape and offset
|
| 1472 |
+
+ // semantics as before. With per-stream tensors the view's
|
| 1473 |
+
+ // offset within its owning tensor is always 0 (each stream
|
| 1474 |
+
+ // owns the whole 3-D extent of its tensor with dim 3 == 1).
|
| 1475 |
+
std::vector<ggml_tensor *> k_stream;
|
| 1476 |
+
std::vector<ggml_tensor *> v_stream;
|
| 1477 |
+
|
| 1478 |
+
- // opencoti F5 M2 headinfer β head-residency split. When gpu_heads > 0,
|
| 1479 |
+
- // `k`/`v` hold the first gpu_heads KV heads on the layer's (GPU) buffer
|
| 1480 |
+
- // and `k_cpu`/`v_cpu` hold the remaining (n_head_kv - gpu_heads) heads
|
| 1481 |
+
- // on a host buffer. gpu_heads == 0 means no split (k/v hold all heads).
|
| 1482 |
+
- ggml_tensor * k_cpu = nullptr;
|
| 1483 |
+
- ggml_tensor * v_cpu = nullptr;
|
| 1484 |
+
+ // opencoti F5 M2 headinfer β per-stream now. When gpu_heads > 0,
|
| 1485 |
+
+ // k_per_stream[s] / v_per_stream[s] hold the first gpu_heads
|
| 1486 |
+
+ // KV heads on the layer's device buffer, and
|
| 1487 |
+
+ // k_cpu_per_stream[s] / v_cpu_per_stream[s] hold the remaining
|
| 1488 |
+
+ // (n_head_kv - gpu_heads) heads on the layer's host buffer.
|
| 1489 |
+
+ // Vectors are empty when no split (gpu_heads == 0).
|
| 1490 |
+
+ std::vector<ggml_tensor *> k_cpu_per_stream;
|
| 1491 |
+
+ std::vector<ggml_tensor *> v_cpu_per_stream;
|
| 1492 |
+
uint32_t gpu_heads = 0;
|
| 1493 |
+
+
|
| 1494 |
+
+ // Convenience accessors for unified-mode (n_stream == 1) call
|
| 1495 |
+
+ // sites. Returns nullptr in non-unified mode β that's the
|
| 1496 |
+
+ // signal that the caller must use per-stream access instead.
|
| 1497 |
+
+ // Phase 4-A uses these at sites that haven't yet been migrated
|
| 1498 |
+
+ // to per-stream awareness; Phase 4-C/D lift them.
|
| 1499 |
+
+ inline ggml_tensor * k_unified() const {
|
| 1500 |
+
+ return k_per_stream.size() == 1 ? k_per_stream[0] : nullptr;
|
| 1501 |
+
+ }
|
| 1502 |
+
+ inline ggml_tensor * v_unified() const {
|
| 1503 |
+
+ return v_per_stream.size() == 1 ? v_per_stream[0] : nullptr;
|
| 1504 |
+
+ }
|
| 1505 |
+
+ inline ggml_tensor * k_cpu_unified() const {
|
| 1506 |
+
+ return k_cpu_per_stream.size() == 1 ? k_cpu_per_stream[0] : nullptr;
|
| 1507 |
+
+ }
|
| 1508 |
+
+ inline ggml_tensor * v_cpu_unified() const {
|
| 1509 |
+
+ return v_cpu_per_stream.size() == 1 ? v_cpu_per_stream[0] : nullptr;
|
| 1510 |
+
+ }
|
| 1511 |
+
};
|
| 1512 |
+
|
| 1513 |
+
bool v_trans = true; // the value tensor is transposed
|
patches/0032-m2-state-io.patch
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: F5 M2 β M2-aware state I/O (bug-222 fix)
|
| 3 |
+
|
| 4 |
+
Bench: perf/llamafile/advanced-kv-stack.bench.ts (Gemma 4 A4B + 31B), bug-222 fix
|
| 5 |
+
Milestone: F5 M2 hardening β see docs/features/advanced_kv.md, .wolf/buglog.json bug-222
|
| 6 |
+
|
| 7 |
+
Pre-existing M2 head split bug surfaced on Gemma 4 iswa: state_write_data /
|
| 8 |
+
state_read_data read/write the K and V cache assuming a single tensor per
|
| 9 |
+
layer with full n_embd_k_gqa / n_embd_v_gqa row size. With M2 head split
|
| 10 |
+
active the layer's tensor (k_stream[s] / v_stream[s]) holds ONLY the GPU
|
| 11 |
+
half (n_embd_k_gpu width), and the CPU half lives in a separate
|
| 12 |
+
k_cpu_per_stream[s] / v_cpu_per_stream[s] tensor. Reading buf_size =
|
| 13 |
+
range_size * row_full from a tensor that has range_size * row_gpu bytes
|
| 14 |
+
crashes with "tensor read out of bounds" in ggml_backend_tensor_get
|
| 15 |
+
(ggml-backend.cpp:314) as soon as the per-range write exceeds the GPU
|
| 16 |
+
half's nbytes β reproducible on Gemma 4 A4B & 31B at frac=0.5 with any
|
| 17 |
+
prompt that crosses a 512-token microbatch boundary and triggers a
|
| 18 |
+
context checkpoint (the cache_prompt server feature snapshots β₯ 634
|
| 19 |
+
cells after the second prompt batch).
|
| 20 |
+
|
| 21 |
+
Fix: detect M2 split per layer (layer.gpu_heads > 0 &&
|
| 22 |
+
layer.k_cpu_per_stream[s]), emit GPU half (row_gpu) then CPU half
|
| 23 |
+
(row_cpu) per range. Total bytes per range stay at range_size * row_full
|
| 24 |
+
(header advertises the full row size unchanged) so io stream accounting
|
| 25 |
+
is preserved; on-disk byte order within a range differs from upstream
|
| 26 |
+
(GPU-first|CPU-first vs interleaved) but writer/reader are symmetric so
|
| 27 |
+
round-trips are consistent. M2 inactive (no head_cpu) path is taken
|
| 28 |
+
byte-identical to upstream β preserves Qwen / unified-mode behavior.
|
| 29 |
+
V branch only fires in !v_trans (M2 is gated off when v_trans).
|
| 30 |
+
|
| 31 |
+
Validated:
|
| 32 |
+
- Gemma 4 A4B-98e-v6-coder-it-Q4_K_M: M5 stack bench 8/9 PASS
|
| 33 |
+
(C5 byte-equality A2/A3 PASS, C9 perf threshold known cost).
|
| 34 |
+
- Gemma 4 31B-it-Q4_K_M: M5 stack bench 8/9 PASS, same shape.
|
| 35 |
+
- Qwen 2.5 1.5B Instruct: regression shield β M5 8/9, M2 6/6, M3 4/4,
|
| 36 |
+
M0 5/5 all PASS unchanged.
|
| 37 |
+
|
| 38 |
+
Bug-222 reproducer: /tmp/gemma-crash-probe.py (cycles reps 10..50 of the
|
| 39 |
+
bench prompt against an A2 server; pre-fix crashes at reps=30, post-fix
|
| 40 |
+
clean through reps=35).
|
| 41 |
+
|
| 42 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 43 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 44 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 45 |
+
@@ -3190,11 +3190,43 @@ void llama_kv_cache::state_write_data(llama_io_write_i & io, const cell_ranges_t
|
| 46 |
+
const uint64_t k_size_row = ggml_row_size(k->type, n_embd_k_gqa);
|
| 47 |
+
io.write(&k_size_row, sizeof(k_size_row));
|
| 48 |
+
|
| 49 |
+
- // Read each range of cells of k_size length and write out
|
| 50 |
+
- for (const auto & range : cr.data) {
|
| 51 |
+
- const size_t range_size = range.second - range.first;
|
| 52 |
+
- const size_t buf_size = range_size * k_size_row;
|
| 53 |
+
- io.write_tensor(k, range.first * k_size_row, buf_size);
|
| 54 |
+
+ // opencoti bug-222 fix β F5 M2 split-aware state write.
|
| 55 |
+
+ // When M2 head split is active, `k` (k_stream[s]) holds only the
|
| 56 |
+
+ // GPU half of the layer's heads; the CPU half lives in
|
| 57 |
+
+ // k_cpu_per_stream[s]. The pre-fix path read `range_size * k_size_row`
|
| 58 |
+
+ // bytes from a tensor that only has `range_size * row_gpu` bytes β
|
| 59 |
+
+ // out-of-bounds crash on any range_size that pushes buf_size past
|
| 60 |
+
+ // the per-stream GPU tensor's nbytes. Fix: detect M2 split, emit
|
| 61 |
+
+ // GPU half then CPU half per range. Total bytes per range equals
|
| 62 |
+
+ // `range_size * k_size_row` (the header advertises this), so on-disk
|
| 63 |
+
+ // accounting matches; the layout differs only in within-range order
|
| 64 |
+
+ // (GPU-first|CPU-first vs interleaved). Reader (state_read_data) is
|
| 65 |
+
+ // symmetric so a save/load round-trip is consistent. When M2 is OFF
|
| 66 |
+
+ // (no head_cpu) the original path is taken byte-identical.
|
| 67 |
+
+ const bool m2_split_active = layer.gpu_heads > 0
|
| 68 |
+
+ && !layer.k_cpu_per_stream.empty()
|
| 69 |
+
+ && layer.k_cpu_per_stream[cr.strm];
|
| 70 |
+
+ if (m2_split_active) {
|
| 71 |
+
+ auto * k_cpu = layer.k_cpu_per_stream[cr.strm];
|
| 72 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 73 |
+
+ const uint32_t n_head_kv = hparams.n_head_kv(il);
|
| 74 |
+
+ const uint32_t n_embd_k_gpu = layer.gpu_heads * n_embd_head_k;
|
| 75 |
+
+ const uint32_t n_embd_k_cpu = (n_head_kv - layer.gpu_heads) * n_embd_head_k;
|
| 76 |
+
+ const uint64_t k_row_gpu = ggml_row_size(k->type, n_embd_k_gpu);
|
| 77 |
+
+ const uint64_t k_row_cpu = ggml_row_size(k_cpu->type, n_embd_k_cpu);
|
| 78 |
+
+ GGML_ASSERT(k_row_gpu + k_row_cpu == k_size_row);
|
| 79 |
+
+ for (const auto & range : cr.data) {
|
| 80 |
+
+ const size_t range_size = range.second - range.first;
|
| 81 |
+
+ io.write_tensor(k, range.first * k_row_gpu, range_size * k_row_gpu);
|
| 82 |
+
+ io.write_tensor(k_cpu, range.first * k_row_cpu, range_size * k_row_cpu);
|
| 83 |
+
+ }
|
| 84 |
+
+ } else {
|
| 85 |
+
+ // Read each range of cells of k_size length and write out
|
| 86 |
+
+ for (const auto & range : cr.data) {
|
| 87 |
+
+ const size_t range_size = range.second - range.first;
|
| 88 |
+
+ const size_t buf_size = range_size * k_size_row;
|
| 89 |
+
+ io.write_tensor(k, range.first * k_size_row, buf_size);
|
| 90 |
+
+ }
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
@@ -3217,11 +3249,33 @@ void llama_kv_cache::state_write_data(llama_io_write_i & io, const cell_ranges_t
|
| 95 |
+
const uint64_t v_size_row = ggml_row_size(v->type, n_embd_v_gqa);
|
| 96 |
+
io.write(&v_size_row, sizeof(v_size_row));
|
| 97 |
+
|
| 98 |
+
- // Read each range of cells of v_size length and write out
|
| 99 |
+
- for (const auto & range : cr.data) {
|
| 100 |
+
- const size_t range_size = range.second - range.first;
|
| 101 |
+
- const size_t buf_size = range_size * v_size_row;
|
| 102 |
+
- io.write_tensor(v, range.first * v_size_row, buf_size);
|
| 103 |
+
+ // opencoti bug-222 fix β F5 M2 split-aware V state write
|
| 104 |
+
+ // (same pattern as K above). M2 is gated off when v_trans, so
|
| 105 |
+
+ // this branch is reached only with the non-transposed V layout.
|
| 106 |
+
+ const bool m2_split_active = layer.gpu_heads > 0
|
| 107 |
+
+ && !layer.v_cpu_per_stream.empty()
|
| 108 |
+
+ && layer.v_cpu_per_stream[cr.strm];
|
| 109 |
+
+ if (m2_split_active) {
|
| 110 |
+
+ auto * v_cpu = layer.v_cpu_per_stream[cr.strm];
|
| 111 |
+
+ const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 112 |
+
+ const uint32_t n_head_kv = hparams.n_head_kv(il);
|
| 113 |
+
+ const uint32_t n_embd_v_gpu = layer.gpu_heads * n_embd_head_v;
|
| 114 |
+
+ const uint32_t n_embd_v_cpu = (n_head_kv - layer.gpu_heads) * n_embd_head_v;
|
| 115 |
+
+ const uint64_t v_row_gpu = ggml_row_size(v->type, n_embd_v_gpu);
|
| 116 |
+
+ const uint64_t v_row_cpu = ggml_row_size(v_cpu->type, n_embd_v_cpu);
|
| 117 |
+
+ GGML_ASSERT(v_row_gpu + v_row_cpu == v_size_row);
|
| 118 |
+
+ for (const auto & range : cr.data) {
|
| 119 |
+
+ const size_t range_size = range.second - range.first;
|
| 120 |
+
+ io.write_tensor(v, range.first * v_row_gpu, range_size * v_row_gpu);
|
| 121 |
+
+ io.write_tensor(v_cpu, range.first * v_row_cpu, range_size * v_row_cpu);
|
| 122 |
+
+ }
|
| 123 |
+
+ } else {
|
| 124 |
+
+ // Read each range of cells of v_size length and write out
|
| 125 |
+
+ for (const auto & range : cr.data) {
|
| 126 |
+
+ const size_t range_size = range.second - range.first;
|
| 127 |
+
+ const size_t buf_size = range_size * v_size_row;
|
| 128 |
+
+ io.write_tensor(v, range.first * v_size_row, buf_size);
|
| 129 |
+
+ }
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
} else {
|
| 133 |
+
@@ -3433,7 +3487,36 @@ bool llama_kv_cache::state_read_data(llama_io_read_i & io, uint32_t strm, uint32
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
if (cell_count) {
|
| 137 |
+
- if (sinfo.is_contiguous()) {
|
| 138 |
+
+ // opencoti bug-222 fix β F5 M2 split-aware state read (K).
|
| 139 |
+
+ // Symmetric to state_write_data: when M2 head split is active,
|
| 140 |
+
+ // read the GPU half then the CPU half and dispatch to the
|
| 141 |
+
+ // respective per-stream tensors. Total bytes equal
|
| 142 |
+
+ // `cell_count * k_size_row` (the header advertises this) so the
|
| 143 |
+
+ // sequential io stream matches what state_write_data wrote.
|
| 144 |
+
+ const bool m2_split_active = layer.gpu_heads > 0
|
| 145 |
+
+ && !layer.k_cpu_per_stream.empty()
|
| 146 |
+
+ && layer.k_cpu_per_stream[strm];
|
| 147 |
+
+ if (m2_split_active) {
|
| 148 |
+
+ auto * k_cpu = layer.k_cpu_per_stream[strm];
|
| 149 |
+
+ const uint32_t n_embd_head_k = hparams.n_embd_head_k(il);
|
| 150 |
+
+ const uint32_t n_head_kv = hparams.n_head_kv(il);
|
| 151 |
+
+ const uint32_t n_embd_k_gpu = layer.gpu_heads * n_embd_head_k;
|
| 152 |
+
+ const uint32_t n_embd_k_cpu = (n_head_kv - layer.gpu_heads) * n_embd_head_k;
|
| 153 |
+
+ const uint64_t k_row_gpu = ggml_row_size(k->type, n_embd_k_gpu);
|
| 154 |
+
+ const uint64_t k_row_cpu = ggml_row_size(k_cpu->type, n_embd_k_cpu);
|
| 155 |
+
+ GGML_ASSERT(k_row_gpu + k_row_cpu == k_size_row);
|
| 156 |
+
+ if (sinfo.is_contiguous()) {
|
| 157 |
+
+ io.read_tensor(k, sinfo.head() * k_row_gpu, cell_count * k_row_gpu);
|
| 158 |
+
+ io.read_tensor(k_cpu, sinfo.head() * k_row_cpu, cell_count * k_row_cpu);
|
| 159 |
+
+ } else {
|
| 160 |
+
+ for (uint32_t i = 0; i < cell_count; ++i) {
|
| 161 |
+
+ io.read_tensor(k, sinfo.idxs[0][i] * k_row_gpu, k_row_gpu);
|
| 162 |
+
+ }
|
| 163 |
+
+ for (uint32_t i = 0; i < cell_count; ++i) {
|
| 164 |
+
+ io.read_tensor(k_cpu, sinfo.idxs[0][i] * k_row_cpu, k_row_cpu);
|
| 165 |
+
+ }
|
| 166 |
+
+ }
|
| 167 |
+
+ } else if (sinfo.is_contiguous()) {
|
| 168 |
+
// Fast path: contiguous cells, single memcpy
|
| 169 |
+
io.read_tensor(k, sinfo.head() * k_size_row, cell_count * k_size_row);
|
| 170 |
+
} else {
|
| 171 |
+
@@ -3476,7 +3559,33 @@ bool llama_kv_cache::state_read_data(llama_io_read_i & io, uint32_t strm, uint32
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
if (cell_count) {
|
| 175 |
+
- if (sinfo.is_contiguous()) {
|
| 176 |
+
+ // opencoti bug-222 fix β F5 M2 split-aware state read (V),
|
| 177 |
+
+ // same pattern as K. M2 is gated off when v_trans, so this
|
| 178 |
+
+ // branch is only reached with the non-transposed V layout.
|
| 179 |
+
+ const bool m2_split_active = layer.gpu_heads > 0
|
| 180 |
+
+ && !layer.v_cpu_per_stream.empty()
|
| 181 |
+
+ && layer.v_cpu_per_stream[strm];
|
| 182 |
+
+ if (m2_split_active) {
|
| 183 |
+
+ auto * v_cpu = layer.v_cpu_per_stream[strm];
|
| 184 |
+
+ const uint32_t n_embd_head_v = hparams.n_embd_head_v(il);
|
| 185 |
+
+ const uint32_t n_head_kv = hparams.n_head_kv(il);
|
| 186 |
+
+ const uint32_t n_embd_v_gpu = layer.gpu_heads * n_embd_head_v;
|
| 187 |
+
+ const uint32_t n_embd_v_cpu = (n_head_kv - layer.gpu_heads) * n_embd_head_v;
|
| 188 |
+
+ const uint64_t v_row_gpu = ggml_row_size(v->type, n_embd_v_gpu);
|
| 189 |
+
+ const uint64_t v_row_cpu = ggml_row_size(v_cpu->type, n_embd_v_cpu);
|
| 190 |
+
+ GGML_ASSERT(v_row_gpu + v_row_cpu == v_size_row);
|
| 191 |
+
+ if (sinfo.is_contiguous()) {
|
| 192 |
+
+ io.read_tensor(v, sinfo.head() * v_row_gpu, cell_count * v_row_gpu);
|
| 193 |
+
+ io.read_tensor(v_cpu, sinfo.head() * v_row_cpu, cell_count * v_row_cpu);
|
| 194 |
+
+ } else {
|
| 195 |
+
+ for (uint32_t i = 0; i < cell_count; ++i) {
|
| 196 |
+
+ io.read_tensor(v, sinfo.idxs[0][i] * v_row_gpu, v_row_gpu);
|
| 197 |
+
+ }
|
| 198 |
+
+ for (uint32_t i = 0; i < cell_count; ++i) {
|
| 199 |
+
+ io.read_tensor(v_cpu, sinfo.idxs[0][i] * v_row_cpu, v_row_cpu);
|
| 200 |
+
+ }
|
| 201 |
+
+ }
|
| 202 |
+
+ } else if (sinfo.is_contiguous()) {
|
| 203 |
+
// Fast path: contiguous cells, single memcpy
|
| 204 |
+
io.read_tensor(v, sinfo.head() * v_size_row, cell_count * v_size_row);
|
| 205 |
+
} else {
|
patches/0033-cpy-tie-blck-align.patch
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti F5 bug-225 β block-align cpy_k/cpy_v dependency-tie views over quantized KV
|
| 2 |
+
|
| 3 |
+
At 256k + q8_0 KV with M2 head split (--headinfer-gpu-heads-frac < 1.0) on Gemma 4
|
| 4 |
+
iswa, server boot crashed during common_init_result's graph_reserve with:
|
| 5 |
+
ggml.c:1301: assert(ne % ggml_blck_size(type) == 0) failed
|
| 6 |
+
|
| 7 |
+
Root cause: the M2 and Phase-4 branches of cpy_k / cpy_v built a graph-topology
|
| 8 |
+
dependency tie via `ggml_view_1d(ctx, store, 1, 0)` over the K/V cache scatter
|
| 9 |
+
result. The tie value is never read by computation β it only forces multiple
|
| 10 |
+
per-stream / per-head writes to be reachable from one ggml_build_forward_expand
|
| 11 |
+
call. At f16 (block size 1), ne=1 passes ggml_row_size silently. At q8_0
|
| 12 |
+
(block size 32), ne=1 fails the assert at graph_reserve before any inference
|
| 13 |
+
runs.
|
| 14 |
+
|
| 15 |
+
Fix at all 4 callsites (cpy_k M2 branch + Phase-4 multi-stream branch; cpy_v
|
| 16 |
+
M2 branch + Phase-4 !v_trans multi-stream branch β 12 view sites total):
|
| 17 |
+
- Replace `ne = 1` with `ggml_blck_size(target->type)` so the view passes
|
| 18 |
+
the quantized-alignment check.
|
| 19 |
+
- Wrap each view in `ggml_cast(..., GGML_TYPE_F32)` so the subsequent
|
| 20 |
+
`ggml_add` survives CUDA bin_bcast's F32/F16-only check (binbcast.cu:376).
|
| 21 |
+
|
| 22 |
+
At f16 KV both wraps are no-ops (blck_size==1, cast on f16-view is byte-identical
|
| 23 |
+
for ne=1 tie semantics never read). At q8_0 the wraps unblock 256k boot.
|
| 24 |
+
|
| 25 |
+
Verification:
|
| 26 |
+
- Boot at 4096 / 32768 / 262144 + q8_0 + M2 frac=0.5 on Gemma 4 A4B Q4_K_M:
|
| 27 |
+
all succeed (no graph_reserve crash).
|
| 28 |
+
- RULER vt@4096 + vt@32768 + niah_single_1@{4096,32768} ours-c1 scored 100/100
|
| 29 |
+
against the post-fix binary on 2026-05-29 β confirms no semantic regression
|
| 30 |
+
at moderate ctx.
|
| 31 |
+
|
| 32 |
+
Related: bug-222 (n_kv/view stride on iswa tail microbatch); bug-224 (concat-F32
|
| 33 |
+
on iswa); bug-226 (256k semantic collapse β different root cause, separate fix).
|
| 34 |
+
|
| 35 |
+
Patch order: applies after 0031-per-stream-split (Phase 4) and 0032-m2-state-io.
|
| 36 |
+
Cosmocc-only edit; CUDA DSO unchanged.
|
| 37 |
+
|
| 38 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 39 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 40 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 41 |
+
@@ -2231,14 +2231,14 @@ ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggm
|
| 42 |
+
ggml_tensor * store_g = ggml_set_rows(ctx, k_s, cur_g, idxs_s);
|
| 43 |
+
ggml_tensor * store_c = ggml_set_rows(ctx, kc_s, cur_c, idxs_s);
|
| 44 |
+
ggml_tensor * pair = ggml_add(ctx,
|
| 45 |
+
- ggml_view_1d(ctx, store_g, 1, 0),
|
| 46 |
+
- ggml_view_1d(ctx, store_c, 1, 0));
|
| 47 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, store_g, ggml_blck_size(store_g->type), 0), GGML_TYPE_F32),
|
| 48 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, store_c, ggml_blck_size(store_c->type), 0), GGML_TYPE_F32));
|
| 49 |
+
if (!tie) {
|
| 50 |
+
tie = pair;
|
| 51 |
+
} else {
|
| 52 |
+
tie = ggml_add(ctx,
|
| 53 |
+
- ggml_view_1d(ctx, tie, 1, 0),
|
| 54 |
+
- ggml_view_1d(ctx, pair, 1, 0));
|
| 55 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, tie, ggml_blck_size(tie->type), 0), GGML_TYPE_F32),
|
| 56 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, pair, ggml_blck_size(pair->type), 0), GGML_TYPE_F32));
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
return tie;
|
| 60 |
+
@@ -2287,8 +2287,8 @@ ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggm
|
| 61 |
+
tie = store_s;
|
| 62 |
+
} else {
|
| 63 |
+
tie = ggml_add(ctx,
|
| 64 |
+
- ggml_view_1d(ctx, tie, 1, 0),
|
| 65 |
+
- ggml_view_1d(ctx, store_s, 1, 0));
|
| 66 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, tie, ggml_blck_size(tie->type), 0), GGML_TYPE_F32),
|
| 67 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, store_s, ggml_blck_size(store_s->type), 0), GGML_TYPE_F32));
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
return tie;
|
| 71 |
+
@@ -2345,14 +2345,14 @@ ggml_tensor * llama_kv_cache::cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggm
|
| 72 |
+
ggml_tensor * store_g = ggml_set_rows(ctx, v_s, cur_g, idxs_s);
|
| 73 |
+
ggml_tensor * store_c = ggml_set_rows(ctx, vc_s, cur_c, idxs_s);
|
| 74 |
+
ggml_tensor * pair = ggml_add(ctx,
|
| 75 |
+
- ggml_view_1d(ctx, store_g, 1, 0),
|
| 76 |
+
- ggml_view_1d(ctx, store_c, 1, 0));
|
| 77 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, store_g, ggml_blck_size(store_g->type), 0), GGML_TYPE_F32),
|
| 78 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, store_c, ggml_blck_size(store_c->type), 0), GGML_TYPE_F32));
|
| 79 |
+
if (!tie) {
|
| 80 |
+
tie = pair;
|
| 81 |
+
} else {
|
| 82 |
+
tie = ggml_add(ctx,
|
| 83 |
+
- ggml_view_1d(ctx, tie, 1, 0),
|
| 84 |
+
- ggml_view_1d(ctx, pair, 1, 0));
|
| 85 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, tie, ggml_blck_size(tie->type), 0), GGML_TYPE_F32),
|
| 86 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, pair, ggml_blck_size(pair->type), 0), GGML_TYPE_F32));
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
return tie;
|
| 90 |
+
@@ -2400,8 +2400,8 @@ ggml_tensor * llama_kv_cache::cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggm
|
| 91 |
+
tie = store_s;
|
| 92 |
+
} else {
|
| 93 |
+
tie = ggml_add(ctx,
|
| 94 |
+
- ggml_view_1d(ctx, tie, 1, 0),
|
| 95 |
+
- ggml_view_1d(ctx, store_s, 1, 0));
|
| 96 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, tie, ggml_blck_size(tie->type), 0), GGML_TYPE_F32),
|
| 97 |
+
+ ggml_cast(ctx, ggml_view_1d(ctx, store_s, ggml_blck_size(store_s->type), 0), GGML_TYPE_F32));
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
return tie;
|
patches/0034-m2-pinned-host.patch
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti F5 M7-A (0034) β pinned host buffer for M2 CPU-half KV
|
| 2 |
+
opencoti note (2026-05-31, bug-250): hunk regenerated via snapshot-diff against the post-0033 bootstrap so it strict git-applies β the original removed two trailing-whitespace lines absent from the current tree. Content unchanged (pinned cpu_buft resolution); verified against the live r2a tree.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
Switches the M2 head-split CPU-half allocation from
|
| 6 |
+
ggml_backend_cpu_buffer_type() (pageable) to ggml_backend_dev_host_buffer_type(dev)
|
| 7 |
+
(CUDA pinned host memory) when the layer is offloaded to a CUDA device. Falls
|
| 8 |
+
back to pageable CPU buft when no CUDA dev / no dev_host_buffer_type.
|
| 9 |
+
|
| 10 |
+
Why: pageable host memory caps PCIe transfers at ~50% of theoretical
|
| 11 |
+
bandwidth because every DMA has to first pin the page or stage through a
|
| 12 |
+
driver-side bounce buffer. Pinned host memory unlocks full PCIe bandwidth
|
| 13 |
+
on every M2 CPUβGPU stream-back AND is the foundation Rolling KV (F5 M7)
|
| 14 |
+
builds on β without pinned residency, the M7 streaming pipeline can't hit
|
| 15 |
+
its compute/copy-overlap budget at PCIe 3.0 x8.
|
| 16 |
+
|
| 17 |
+
Mechanics:
|
| 18 |
+
- Layer scope (~line 270): resolve cpu_buft alongside the existing
|
| 19 |
+
layer GPU buft. Read dev_host_buffer_type(dev); fall back to plain
|
| 20 |
+
cpu_buffer_type if unsupported.
|
| 21 |
+
- Per-stream scope (~line 391): consume the layer-scope cpu_buft
|
| 22 |
+
instead of calling ggml_backend_cpu_buffer_type() inline.
|
| 23 |
+
|
| 24 |
+
f16 and CPU-only paths byte-identical to pre-0034 (dev_host_buffer_type
|
| 25 |
+
returns null β fallback fires β identical alloc). q8_0 + M2 + CUDA paths
|
| 26 |
+
get pinned residency. Foundation for #296 (Rolling KV / M7).
|
| 27 |
+
|
| 28 |
+
Verification:
|
| 29 |
+
- Cosmocc build clean (~80s incremental).
|
| 30 |
+
- Smoke at 32k + f16 + M2 frac=0.5 + M0/M1/M3 flags engaged on Gemma 4
|
| 31 |
+
A4B Q4_K_M: server boots clean, /v1/completions returns coherent
|
| 32 |
+
tokens (no crash, no DSO mismatch).
|
| 33 |
+
- Does NOT fix bug-226 (256k+q8_0 semantic collapse β confirmed
|
| 34 |
+
deterministic via 1-sample canary 2026-05-29 21:51, scored 0/1 with
|
| 35 |
+
same wrong output 'LJXLD' as the original --no-kv-unified failure;
|
| 36 |
+
pinning is orthogonal to the corruption mechanism).
|
| 37 |
+
- Held for bug-226 root cause before shipping as default β pinning a
|
| 38 |
+
broken stack doesn't fix it but doesn't make it worse either.
|
| 39 |
+
|
| 40 |
+
Patch order: applies after 0033-cpy-tie-blck-align. Cosmocc-only edit;
|
| 41 |
+
CUDA DSO unchanged (host-side buffer-type resolution, no kernel changes).
|
| 42 |
+
|
| 43 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 44 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 45 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 46 |
+
@@ -267,11 +267,20 @@ llama_kv_cache::llama_kv_cache(
|
| 47 |
+
|
| 48 |
+
ggml_backend_buffer_type_t buft = ggml_backend_cpu_buffer_type();
|
| 49 |
+
|
| 50 |
+
+ // opencoti F5 M7-A (0034) β resolved at layer scope alongside the
|
| 51 |
+
+ // per-layer GPU buft so the M2 CPU-half allocation below (line ~390)
|
| 52 |
+
+ // can reach pinned host memory when CUDA is offloaded. Falls back to
|
| 53 |
+
+ // pageable CPU buft when no CUDA dev / no dev_host_buffer_type. Pinned
|
| 54 |
+
+ // host buffers DMA at full PCIe bandwidth; pageable hits ~50%.
|
| 55 |
+
+ ggml_backend_buffer_type_t cpu_buft = ggml_backend_cpu_buffer_type();
|
| 56 |
+
if (offload) {
|
| 57 |
+
auto * dev = model.dev_layer(il);
|
| 58 |
+
buft = ggml_backend_dev_buffer_type(dev);
|
| 59 |
+
|
| 60 |
+
dev_name = ggml_backend_dev_name(dev);
|
| 61 |
+
+
|
| 62 |
+
+ ggml_backend_buffer_type_t pinned = ggml_backend_dev_host_buffer_type(dev);
|
| 63 |
+
+ if (pinned) cpu_buft = pinned;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
LLAMA_LOG_DEBUG("%s: layer %3d: dev = %s\n", __func__, il, dev_name);
|
| 67 |
+
@@ -374,7 +383,12 @@ llama_kv_cache::llama_kv_cache(
|
| 68 |
+
// when gpu_heads == 0 so headinfer_split_active() can test
|
| 69 |
+
// by vector size.
|
| 70 |
+
if (gpu_heads > 0) {
|
| 71 |
+
- ggml_context * ctx_cpu_s = ctx_for_buft(ggml_backend_cpu_buffer_type(), s);
|
| 72 |
+
+ // opencoti F5 M7-A (0034) β was ggml_backend_cpu_buffer_type()
|
| 73 |
+
+ // (pageable). Now uses the layer-scope cpu_buft resolved above:
|
| 74 |
+
+ // pinned host buffer when CUDA is the layer dev, plain CPU buft
|
| 75 |
+
+ // otherwise. Pinned unlocks full PCIe DMA for M2's CPU-half
|
| 76 |
+
+ // stream-back AND is the foundation Rolling KV (M7) builds on.
|
| 77 |
+
+ ggml_context * ctx_cpu_s = ctx_for_buft(cpu_buft, s);
|
| 78 |
+
if (!ctx_cpu_s) {
|
| 79 |
+
throw std::runtime_error("failed to create CPU ggml context for headinfer kv cache");
|
| 80 |
+
}
|
patches/0035-concat-q-block-aware.patch
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti F5 bug-226 β block-aware ggml-cpu concat for quantized types
|
| 2 |
+
|
| 3 |
+
At 32k+q8_0 KV with M2 head split (--headinfer-gpu-heads-frac < 1.0)
|
| 4 |
+
without NEO orchestration, the M2 head-axis concat path in get_k/get_v
|
| 5 |
+
(llama-kv-cache.cpp:1881-1910) produced semantically corrupt K/V that
|
| 6 |
+
the downstream flash-attn read as garbage, scoring 0.00% on RULER vt
|
| 7 |
+
with completely wrong output (e.g. predicting 'SHRUU' instead of the
|
| 8 |
+
five expected variables). 5-cell bisection at 32k+q8_0 isolated M2 alone
|
| 9 |
+
(--headinfer-gpu-heads-frac 0.5) as the failing config; M0, M1, M3, and
|
| 10 |
+
vanilla scored 100.
|
| 11 |
+
|
| 12 |
+
Root cause: ggml_concat on block-quantized tensors falls through
|
| 13 |
+
multiple broken paths.
|
| 14 |
+
|
| 15 |
+
The M2 non-NEO get_k/get_v branch returns:
|
| 16 |
+
|
| 17 |
+
ggml_concat(ctx, ggml_view_4d(k_s, head_dim, n_head_gpu, n_kv, 1),
|
| 18 |
+
ggml_view_4d(kc_s, head_dim, n_head_cpu, n_kv, 1), 1)
|
| 19 |
+
|
| 20 |
+
both views are q8_0. The CUDA backend rejects this (concat.cu:165-167
|
| 21 |
+
asserts F32-only β task #253's earlier supports_op tightening was
|
| 22 |
+
correct), so the scheduler dispatches to CPU. CPU concat falls into
|
| 23 |
+
ggml_compute_forward_concat_any (ops.cpp:1894-1935) for any non-
|
| 24 |
+
F16/BF16/I16/I8/F32/I32 type. concat_any iterates i0 over LOGICAL
|
| 25 |
+
elements (0..ne0) with stride nb00, calling memcpy(len = type_size).
|
| 26 |
+
For q8_0, nb00 = type_size = 36 bytes (one block of 32 elements), but
|
| 27 |
+
the code treats it as a per-element stride β reading blocks at offsets
|
| 28 |
+
0, 36, 72, ..., 127Β·36 = 4572 bytes from each row start, well past
|
| 29 |
+
the actual row of (ne0/32)Β·36 = 144 bytes. Result: q8_0 blocks
|
| 30 |
+
scrambled into uninterpretable data β FA reads garbage K β output is
|
| 31 |
+
task-format-correct but value-wrong.
|
| 32 |
+
|
| 33 |
+
At f16 KV (block size 1) this latent bug never triggered because
|
| 34 |
+
concat dispatches to ggml_compute_forward_concat_f16 which correctly
|
| 35 |
+
handles fp16 per-element. The full F5 stack at f16 + 32k scored 100
|
| 36 |
+
in the morning RULER comparison, masking the q8_0 case.
|
| 37 |
+
|
| 38 |
+
Fix: a new ggml_compute_forward_concat_q that copies entire dim-0 rows
|
| 39 |
+
at once for block-quantized types. Dispatched from
|
| 40 |
+
ggml_compute_forward_concat's default branch when ggml_blck_size > 1.
|
| 41 |
+
|
| 42 |
+
For dim != 0 (the M2 head-axis case): each (i1, i2, i3) maps to either
|
| 43 |
+
src0 or src1 (mutually exclusive); one memcpy of (ne0/blck)Β·type_size
|
| 44 |
+
bytes per row. For dim == 0: dst's dim-0 row = src0_row || src1_row,
|
| 45 |
+
asserted block-aligned at the split; two memcpys per row.
|
| 46 |
+
|
| 47 |
+
Non-block-quantized (F32/F16/BF16/I8/I16/I32) paths unchanged. The
|
| 48 |
+
ggml-cuda CUDA path stays F32-only β that's correct since this fix
|
| 49 |
+
lands at the CPU fallback the scheduler already routes to.
|
| 50 |
+
|
| 51 |
+
Lives in ggml-cpu only β no CUDA DSO rebuild needed. Compatible with
|
| 52 |
+
incremental cosmocc rebuild via `OPENCOTI_NO_CCACHE=1 bun run
|
| 53 |
+
build:llamafile:make`.
|
| 54 |
+
|
| 55 |
+
Verification:
|
| 56 |
+
- T2 config smoke (M2-only, no NEO, q8_0, 32k, vt n=1) β bug repro
|
| 57 |
+
pre-fix scored 0.00% with pred='SHRUU'; post-fix expected 100.00%.
|
| 58 |
+
- Full F5 stack (M0+M1+M2+M3+NEO+Phase4) at q8_0 + 32k β unchanged
|
| 59 |
+
from morning baseline (100%).
|
| 60 |
+
- All M0/M1/M2/M3/M5 unified-mode regression-shield benches β unchanged
|
| 61 |
+
(cosine β₯ 0.999); concat_q only fires on block-quantized types, and
|
| 62 |
+
f16/bf16/f32 KV paths route to their own concat_fN as before.
|
| 63 |
+
|
| 64 |
+
apply order: after 0034-m2-pinned-host (this is a ggml-cpu fix; the
|
| 65 |
+
two are independent so order is bookkeeping only).
|
| 66 |
+
|
| 67 |
+
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
| 68 |
+
|
| 69 |
+
diff --git a/llama.cpp/ggml/src/ggml-cpu/ops.cpp b/llama.cpp/ggml/src/ggml-cpu/ops.cpp
|
| 70 |
+
--- a/llama.cpp/ggml/src/ggml-cpu/ops.cpp
|
| 71 |
+
+++ b/llama.cpp/ggml/src/ggml-cpu/ops.cpp
|
| 72 |
+
@@ -2078,6 +2078,119 @@ static void ggml_compute_forward_concat_f32(
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
+// opencoti F5 bug-226 fix (0035) β block-quantized concat.
|
| 77 |
+
+//
|
| 78 |
+
+// concat_any (above) does per-logical-element memcpy(len = type_size),
|
| 79 |
+
+// stepping i0 from 0..ne0 with stride nb00. For block-quantized types
|
| 80 |
+
+// (q8_0, q4_0, β¦) the parent tensor stores ne0 LOGICAL elements packed
|
| 81 |
+
+// in ne0/blck blocks of type_size bytes each, with nb00 = type_size
|
| 82 |
+
+// (one block, not one element). concat_any's loop therefore reads
|
| 83 |
+
+// blocks at offsets 0, type_size, 2Β·type_size, β¦ up to ne0Β·type_size β
|
| 84 |
+
+// well past the actual row of (ne0/blck)Β·type_size bytes. The result
|
| 85 |
+
+// is scrambled block data, which the downstream FA reads as garbage K
|
| 86 |
+
+// (root cause of bug-226 at M2 head-axis q8_0 K/V concat).
|
| 87 |
+
+//
|
| 88 |
+
+// Fix: for block-quantized types, copy entire dim-0 rows at once.
|
| 89 |
+
+// Each dim-0 row of src0/src1/dst is contiguous (nb00 = type_size,
|
| 90 |
+
+// ne0/blck blocks per row, no gaps), so a single memcpy per
|
| 91 |
+
+// (i1, i2, i3) selection on each source captures the right bytes.
|
| 92 |
+
+//
|
| 93 |
+
+// - dim != 0 (M2 head-axis case): per (i1, i2, i3), the whole dim-0
|
| 94 |
+
+// row comes from src0 or src1 (mutually exclusive). One memcpy.
|
| 95 |
+
+// - dim == 0 (split on dim 0): dst's dim-0 row = src0's dim-0 row ||
|
| 96 |
+
+// src1's dim-0 row, concatenated head-to-tail. The split MUST land
|
| 97 |
+
+// on a block boundary (ne00 % blck == 0); when it does, two memcpys.
|
| 98 |
+
+//
|
| 99 |
+
+// The M2 split in llama-kv-cache.cpp:get_k/get_v concats on dim 1
|
| 100 |
+
+// (head axis), so the dim != 0 branch is the hot path. dim 0 is
|
| 101 |
+
+// covered for completeness β any future quantized-feature-axis split
|
| 102 |
+
+// pattern lands correctly without re-discovering this bug.
|
| 103 |
+
+static void ggml_compute_forward_concat_q(
|
| 104 |
+
+ const ggml_compute_params * params,
|
| 105 |
+
+ ggml_tensor * dst) {
|
| 106 |
+
+
|
| 107 |
+
+ const ggml_tensor * src0 = dst->src[0];
|
| 108 |
+
+ const ggml_tensor * src1 = dst->src[1];
|
| 109 |
+
+
|
| 110 |
+
+ const size_t type_size = ggml_type_size(src0->type);
|
| 111 |
+
+ const int64_t blck = ggml_blck_size(src0->type);
|
| 112 |
+
+
|
| 113 |
+
+ GGML_ASSERT(blck > 1 && "concat_q is for block-quantized types");
|
| 114 |
+
+ GGML_ASSERT(ggml_type_size(src1->type) == type_size);
|
| 115 |
+
+ GGML_ASSERT(ggml_blck_size(src1->type) == blck);
|
| 116 |
+
+ GGML_ASSERT(ggml_type_size(dst->type) == type_size);
|
| 117 |
+
+ GGML_ASSERT(ggml_blck_size(dst->type) == blck);
|
| 118 |
+
+
|
| 119 |
+
+ const int ith = params->ith;
|
| 120 |
+
+ const int nth = params->nth;
|
| 121 |
+
+
|
| 122 |
+
+ GGML_TENSOR_BINARY_OP_LOCALS
|
| 123 |
+
+
|
| 124 |
+
+ const int32_t dim = ggml_get_op_params_i32(dst, 0);
|
| 125 |
+
+
|
| 126 |
+
+ GGML_ASSERT(dim >= 0 && dim < 4);
|
| 127 |
+
+
|
| 128 |
+
+ // Both srcs must have block-aligned dim-0 sizes β guaranteed by ggml
|
| 129 |
+
+ // for any well-formed quantized tensor. We assert anyway because if
|
| 130 |
+
+ // a caller violates this, the row math below silently corrupts data.
|
| 131 |
+
+ GGML_ASSERT((ne00 % blck) == 0);
|
| 132 |
+
+ GGML_ASSERT((ne10 % blck) == 0);
|
| 133 |
+
+
|
| 134 |
+
+ // Bytes per dim-0 row of each source / dst.
|
| 135 |
+
+ const size_t row_bytes_src0 = (ne00 / blck) * type_size;
|
| 136 |
+
+ const size_t row_bytes_src1 = (ne10 / blck) * type_size;
|
| 137 |
+
+ const size_t row_bytes_dst = (ne0 / blck) * type_size;
|
| 138 |
+
+
|
| 139 |
+
+ if (dim == 0) {
|
| 140 |
+
+ // dst row = src0 row (head) || src1 row (tail). Both fill at the
|
| 141 |
+
+ // block boundary. For non-concat dims i1/i2/i3 must match between
|
| 142 |
+
+ // src0 and src1 (ggml concat invariant), so no per-dim mux logic.
|
| 143 |
+
+ GGML_ASSERT(row_bytes_src0 + row_bytes_src1 == row_bytes_dst);
|
| 144 |
+
+ for (int i3 = 0; i3 < ne3; i3++) {
|
| 145 |
+
+ for (int i2 = ith; i2 < ne2; i2 += nth) {
|
| 146 |
+
+ for (int i1 = 0; i1 < ne1; i1++) {
|
| 147 |
+
+ const char * x0 = (const char *)src0->data
|
| 148 |
+
+ + i1*nb01 + i2*nb02 + i3*nb03;
|
| 149 |
+
+ const char * x1 = (const char *)src1->data
|
| 150 |
+
+ + i1*nb11 + i2*nb12 + i3*nb13;
|
| 151 |
+
+ char * y = (char *)dst->data
|
| 152 |
+
+ + i1*nb1 + i2*nb2 + i3*nb3;
|
| 153 |
+
+ memcpy(y, x0, row_bytes_src0);
|
| 154 |
+
+ memcpy(y + row_bytes_src0, x1, row_bytes_src1);
|
| 155 |
+
+ }
|
| 156 |
+
+ }
|
| 157 |
+
+ }
|
| 158 |
+
+ return;
|
| 159 |
+
+ }
|
| 160 |
+
+
|
| 161 |
+
+ // dim != 0: each (i1, i2, i3) maps to either src0 or src1, never both.
|
| 162 |
+
+ // row_bytes is shared (src0/src1/dst all have the same dim-0 row).
|
| 163 |
+
+ GGML_ASSERT(row_bytes_src0 == row_bytes_src1);
|
| 164 |
+
+ GGML_ASSERT(row_bytes_src0 == row_bytes_dst);
|
| 165 |
+
+
|
| 166 |
+
+ int64_t o[4] = {0, 0, 0, 0};
|
| 167 |
+
+ o[dim] = src0->ne[dim];
|
| 168 |
+
+
|
| 169 |
+
+ // TODO: smarter multi-threading
|
| 170 |
+
+ for (int i3 = 0; i3 < ne3; i3++) {
|
| 171 |
+
+ for (int i2 = ith; i2 < ne2; i2 += nth) {
|
| 172 |
+
+ for (int i1 = 0; i1 < ne1; i1++) {
|
| 173 |
+
+ const char * x;
|
| 174 |
+
+ if (i1 < ne01 && i2 < ne02 && i3 < ne03) {
|
| 175 |
+
+ x = (const char *)src0->data
|
| 176 |
+
+ + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03;
|
| 177 |
+
+ } else {
|
| 178 |
+
+ x = (const char *)src1->data
|
| 179 |
+
+ + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13;
|
| 180 |
+
+ }
|
| 181 |
+
+ char * y = (char *)dst->data
|
| 182 |
+
+ + i1*nb1 + i2*nb2 + i3*nb3;
|
| 183 |
+
+ memcpy(y, x, row_bytes_dst);
|
| 184 |
+
+ }
|
| 185 |
+
+ }
|
| 186 |
+
+ }
|
| 187 |
+
+}
|
| 188 |
+
+
|
| 189 |
+
void ggml_compute_forward_concat(
|
| 190 |
+
const ggml_compute_params * params,
|
| 191 |
+
ggml_tensor * dst) {
|
| 192 |
+
@@ -2102,7 +2215,16 @@ void ggml_compute_forward_concat(
|
| 193 |
+
} break;
|
| 194 |
+
default:
|
| 195 |
+
{
|
| 196 |
+
- ggml_compute_forward_concat_any(params, dst);
|
| 197 |
+
+ // opencoti F5 bug-226 fix (0035): block-quantized types
|
| 198 |
+
+ // (q8_0, q4_0, q5_0, β¦) route to concat_q which iterates
|
| 199 |
+
+ // over BLOCKS, not logical elements. Without this they
|
| 200 |
+
+ // hit concat_any whose per-element memcpy with type_size
|
| 201 |
+
+ // = block-size scrambles q8_0 K/V at M2 head-axis concat.
|
| 202 |
+
+ if (ggml_blck_size(src0->type) > 1) {
|
| 203 |
+
+ ggml_compute_forward_concat_q(params, dst);
|
| 204 |
+
+ } else {
|
| 205 |
+
+ ggml_compute_forward_concat_any(params, dst);
|
| 206 |
+
+ }
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
}
|
patches/0036-pcie-probe-consume.patch
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti F5-opt W1 (#293) β PCIe/ReBAR profile boot-time reader + autodetect flags
|
| 2 |
+
|
| 3 |
+
WHAT
|
| 4 |
+
Consumes the shipped rebar/PCIe probe (perf/llamafile/rebar-probe.{cu,sh}) at
|
| 5 |
+
server boot: reads the newest probe JSON (or $OPENCOTI_PCIE_PROFILE) for the
|
| 6 |
+
measured pinned H2D bandwidth + PCIe link width/speed, falling back to a
|
| 7 |
+
one-shot nvidia-smi query, then a conservative PCIe3 x8 default. The result
|
| 8 |
+
(effective GB/s + link geometry) is cached in a process-global pcie_profile,
|
| 9 |
+
logged at startup, and consumed later by M7 Rolling KV (#296) for streaming
|
| 10 |
+
tile sizing (tile_bytes_max = compute_ms * eff_bw_GBps * 0.8).
|
| 11 |
+
|
| 12 |
+
Adds:
|
| 13 |
+
- common/pcie-profile.{h,cpp} (NEW β detection + global)
|
| 14 |
+
- common_params.pcie_autodetect/pcie_bw_gbps (common.h)
|
| 15 |
+
- --pcie-autodetect / --pcie-bw-gbps (common/arg.cpp)
|
| 16 |
+
- common/pcie-profile.cpp TU (BUILD.mk COMMON_SRCS_CPP)
|
| 17 |
+
- pcie_profile_init() + boot log (tools/server/server.cpp β hook)
|
| 18 |
+
|
| 19 |
+
PROVENANCE (per opencoti upstream-sync directive)
|
| 20 |
+
opencoti-ORIGINAL β no code copied from upstream llama.cpp. The PCIe-link
|
| 21 |
+
sysfs read + ReBAR-active detection technique derives from lightseek's
|
| 22 |
+
tools/windows/rebar_test.cu (Windows NVML/VirtualAlloc original), Linux-ported
|
| 23 |
+
by opencoti in perf/llamafile/rebar-probe.cu (#293). The server-side reader
|
| 24 |
+
here parses that probe's JSON.
|
| 25 |
+
Upstream base: ggml-org/llama.cpp via Mozilla-Ocho/llamafile submodule
|
| 26 |
+
5e9c63546 (llamafile 0.10.1 pin). Apply order: after 0035 (independent β
|
| 27 |
+
common/ + server additive). Surgical hook: tools/server/server.cpp boot call
|
| 28 |
+
β registered in docs/protocols/UPSTREAM_SYNC.md.
|
| 29 |
+
|
| 30 |
+
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| 31 |
+
|
| 32 |
+
diff -ruN state-pre-0036/llama.cpp/BUILD.mk state-post-0036/llama.cpp/BUILD.mk
|
| 33 |
+
diff --git a/llama.cpp/BUILD.mk b/llama.cpp/BUILD.mk
|
| 34 |
+
--- a/llama.cpp/BUILD.mk
|
| 35 |
+
+++ b/llama.cpp/BUILD.mk
|
| 36 |
+
@@ -242,6 +242,7 @@ COMMON_SRCS_CPP := \
|
| 37 |
+
llama.cpp/common/ngram-cache.cpp \
|
| 38 |
+
llama.cpp/common/ngram-map.cpp \
|
| 39 |
+
llama.cpp/common/ngram-mod.cpp \
|
| 40 |
+
+ llama.cpp/common/pcie-profile.cpp \
|
| 41 |
+
llama.cpp/common/peg-parser.cpp \
|
| 42 |
+
llama.cpp/common/preset.cpp \
|
| 43 |
+
llama.cpp/common/reasoning-budget.cpp \
|
| 44 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 45 |
+
--- a/llama.cpp/common/arg.cpp
|
| 46 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 47 |
+
@@ -1440,6 +1440,23 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 48 |
+
else throw std::invalid_argument("--neo-pipeline must be off|on|auto");
|
| 49 |
+
}
|
| 50 |
+
).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_NEO_PIPELINE"));
|
| 51 |
+
+ // opencoti F5-opt W1 (#293) pcie profile β see docs/features/rolling_kv.md
|
| 52 |
+
+ add_opt(common_arg(
|
| 53 |
+
+ {"--pcie-autodetect"}, "MODE",
|
| 54 |
+
+ "pcie profile: auto-detect host<->device bandwidth + PCIe link at boot (on (default), off). Reads the rebar-probe JSON / nvidia-smi; the result feeds M7 Rolling KV tile sizing.",
|
| 55 |
+
+ [](common_params & params, const std::string & value) {
|
| 56 |
+
+ if (value == "on" || value == "true" || value == "1") params.pcie_autodetect = true;
|
| 57 |
+
+ else if (value == "off" || value == "false" || value == "0") params.pcie_autodetect = false;
|
| 58 |
+
+ else throw std::invalid_argument("--pcie-autodetect must be on|off");
|
| 59 |
+
+ }
|
| 60 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_PCIE_AUTODETECT"));
|
| 61 |
+
+ add_opt(common_arg(
|
| 62 |
+
+ {"--pcie-bw-gbps"}, "F",
|
| 63 |
+
+ string_format("pcie profile: manual override of effective host->device bandwidth in GB/s (default: %.1f = autodetect). > 0 skips detection.", (double) params.pcie_bw_gbps),
|
| 64 |
+
+ [](common_params & params, const std::string & value) {
|
| 65 |
+
+ params.pcie_bw_gbps = std::stof(value);
|
| 66 |
+
+ }
|
| 67 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_PCIE_BW_GBPS"));
|
| 68 |
+
add_opt(common_arg(
|
| 69 |
+
{"--chunks"}, "N",
|
| 70 |
+
string_format("max number of chunks to process (default: %d, -1 = all)", params.n_chunks),
|
| 71 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 72 |
+
--- a/llama.cpp/common/common.h
|
| 73 |
+
+++ b/llama.cpp/common/common.h
|
| 74 |
+
@@ -445,6 +445,12 @@ struct common_params {
|
| 75 |
+
// active for the layer), 2 = auto (reserved for future load-based
|
| 76 |
+
// engagement; same as `on` today).
|
| 77 |
+
uint32_t neo_pipeline_mode = 0;
|
| 78 |
+
+ // opencoti F5-opt W1 (#293) pcie profile β see docs/features/rolling_kv.md
|
| 79 |
+
+ // Boot-time host<->device bandwidth + PCIe link detection, consumed by M7
|
| 80 |
+
+ // Rolling KV (#296) tile sizing. autodetect reads the rebar-probe JSON /
|
| 81 |
+
+ // nvidia-smi; bw_gbps > 0 forces a manual override (0 = autodetect/default).
|
| 82 |
+
+ bool pcie_autodetect = true;
|
| 83 |
+
+ float pcie_bw_gbps = 0.0f;
|
| 84 |
+
int32_t n_batch = 2048; // logical batch size for prompt processing (must be >=32 to use BLAS)
|
| 85 |
+
int32_t n_ubatch = 512; // physical batch size for prompt processing (must be >=32 to use BLAS)
|
| 86 |
+
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
| 87 |
+
diff --git a/llama.cpp/common/pcie-profile.cpp b/llama.cpp/common/pcie-profile.cpp
|
| 88 |
+
new file mode 100644
|
| 89 |
+
--- /dev/null
|
| 90 |
+
+++ b/llama.cpp/common/pcie-profile.cpp
|
| 91 |
+
@@ -0,0 +1,201 @@
|
| 92 |
+
+// opencoti F5-opt W1 (#293) β PCIe link + host<->device bandwidth profile.
|
| 93 |
+
+// See pcie-profile.h for the contract. CPU-side only; never throws.
|
| 94 |
+
+
|
| 95 |
+
+#include "pcie-profile.h"
|
| 96 |
+
+
|
| 97 |
+
+#include <algorithm>
|
| 98 |
+
+#include <cmath>
|
| 99 |
+
+#include <cstdio>
|
| 100 |
+
+#include <cstdlib>
|
| 101 |
+
+#include <cstring>
|
| 102 |
+
+#include <dirent.h>
|
| 103 |
+
+#include <fstream>
|
| 104 |
+
+#include <sstream>
|
| 105 |
+
+#include <string>
|
| 106 |
+
+#include <vector>
|
| 107 |
+
+
|
| 108 |
+
+namespace {
|
| 109 |
+
+
|
| 110 |
+
+// PCIe generation -> per-lane raw transfer rate (GT/s).
|
| 111 |
+
+float gen_to_gtps(int gen) {
|
| 112 |
+
+ switch (gen) {
|
| 113 |
+
+ case 1: return 2.5f;
|
| 114 |
+
+ case 2: return 5.0f;
|
| 115 |
+
+ case 3: return 8.0f;
|
| 116 |
+
+ case 4: return 16.0f;
|
| 117 |
+
+ case 5: return 32.0f;
|
| 118 |
+
+ case 6: return 64.0f;
|
| 119 |
+
+ default: return 0.0f;
|
| 120 |
+
+ }
|
| 121 |
+
+}
|
| 122 |
+
+
|
| 123 |
+
+// Effective usable H2D bandwidth from link geometry. The 0.8 factor folds in
|
| 124 |
+
+// 128b/130b (gen3+) encoding overhead + DMA/protocol inefficiency; matches the
|
| 125 |
+
+// rolling_kv.md auto-adapt formula. width lanes * GT/s / 8 bits-per-byte * 0.8.
|
| 126 |
+
+float geometry_bw_gbps(int width, float gtps) {
|
| 127 |
+
+ if (width <= 0 || gtps <= 0.0f) return 0.0f;
|
| 128 |
+
+ return (float) width * gtps * 0.8f / 8.0f;
|
| 129 |
+
+}
|
| 130 |
+
+
|
| 131 |
+
+// "8.0 GT/s PCIe" / "16.0 GT/s PCIe" -> leading float. Empty/garbage -> 0.
|
| 132 |
+
+float parse_leading_float(const std::string & s) {
|
| 133 |
+
+ return s.empty() ? 0.0f : (float) atof(s.c_str());
|
| 134 |
+
+}
|
| 135 |
+
+
|
| 136 |
+
+// Minimal value-after-key extraction for our OWN well-formed probe JSON.
|
| 137 |
+
+// Returns the substring between key's ':' and the next ',' or '}' / newline.
|
| 138 |
+
+bool json_raw_after(const std::string & body, const std::string & key, std::string & out) {
|
| 139 |
+
+ const size_t k = body.find(key);
|
| 140 |
+
+ if (k == std::string::npos) return false;
|
| 141 |
+
+ size_t c = body.find(':', k + key.size());
|
| 142 |
+
+ if (c == std::string::npos) return false;
|
| 143 |
+
+ ++c;
|
| 144 |
+
+ while (c < body.size() && (body[c] == ' ' || body[c] == '\t')) ++c;
|
| 145 |
+
+ size_t e = c;
|
| 146 |
+
+ while (e < body.size() && body[e] != ',' && body[e] != '\n' && body[e] != '}') ++e;
|
| 147 |
+
+ out = body.substr(c, e - c);
|
| 148 |
+
+ return true;
|
| 149 |
+
+}
|
| 150 |
+
+
|
| 151 |
+
+float json_float_after(const std::string & body, const std::string & key) {
|
| 152 |
+
+ std::string raw;
|
| 153 |
+
+ if (!json_raw_after(body, key, raw)) return 0.0f;
|
| 154 |
+
+ return (float) atof(raw.c_str());
|
| 155 |
+
+}
|
| 156 |
+
+
|
| 157 |
+
+// Quoted-string value: trims surrounding quotes/spaces.
|
| 158 |
+
+std::string json_string_after(const std::string & body, const std::string & key) {
|
| 159 |
+
+ std::string raw;
|
| 160 |
+
+ if (!json_raw_after(body, key, raw)) return {};
|
| 161 |
+
+ size_t a = raw.find('"');
|
| 162 |
+
+ if (a == std::string::npos) return {};
|
| 163 |
+
+ size_t b = raw.find('"', a + 1);
|
| 164 |
+
+ if (b == std::string::npos) return {};
|
| 165 |
+
+ return raw.substr(a + 1, b - a - 1);
|
| 166 |
+
+}
|
| 167 |
+
+
|
| 168 |
+
+std::string read_file(const std::string & path) {
|
| 169 |
+
+ std::ifstream f(path, std::ios::binary);
|
| 170 |
+
+ if (!f.is_open()) return {};
|
| 171 |
+
+ std::ostringstream ss;
|
| 172 |
+
+ ss << f.rdbuf();
|
| 173 |
+
+ return ss.str();
|
| 174 |
+
+}
|
| 175 |
+
+
|
| 176 |
+
+// Newest ./.opencoti/rebar-probe-*.json. Filenames embed a YYYYMMDD-HHMMSS
|
| 177 |
+
+// stamp, so lexicographic max == newest. Empty if none.
|
| 178 |
+
+std::string newest_probe_json() {
|
| 179 |
+
+ const char * env = getenv("OPENCOTI_PCIE_PROFILE");
|
| 180 |
+
+ if (env && env[0]) return std::string(env);
|
| 181 |
+
+
|
| 182 |
+
+ const char * dirs[] = {"./.opencoti", ".opencoti"};
|
| 183 |
+
+ std::string best;
|
| 184 |
+
+ for (const char * d : dirs) {
|
| 185 |
+
+ DIR * dp = opendir(d);
|
| 186 |
+
+ if (!dp) continue;
|
| 187 |
+
+ struct dirent * ent;
|
| 188 |
+
+ while ((ent = readdir(dp)) != nullptr) {
|
| 189 |
+
+ const std::string name = ent->d_name;
|
| 190 |
+
+ if (name.rfind("rebar-probe-", 0) != 0) continue;
|
| 191 |
+
+ if (name.size() < 5 || name.compare(name.size() - 5, 5, ".json") != 0) continue;
|
| 192 |
+
+ const std::string full = std::string(d) + "/" + name;
|
| 193 |
+
+ if (best.empty() || name > best.substr(best.find_last_of('/') + 1)) best = full;
|
| 194 |
+
+ }
|
| 195 |
+
+ closedir(dp);
|
| 196 |
+
+ if (!best.empty()) break;
|
| 197 |
+
+ }
|
| 198 |
+
+ return best;
|
| 199 |
+
+}
|
| 200 |
+
+
|
| 201 |
+
+// Parse a probe JSON into a profile. Prefers the MEASURED pinned H2D bandwidth
|
| 202 |
+
+// over the geometry formula; still records link width/speed for logging.
|
| 203 |
+
+bool profile_from_probe_json(const std::string & path, pcie_profile & out) {
|
| 204 |
+
+ const std::string body = read_file(path);
|
| 205 |
+
+ if (body.empty()) return false;
|
| 206 |
+
+
|
| 207 |
+
+ // measured pinned H2D: locate the "pinned" object, then its "h2d".
|
| 208 |
+
+ float pinned_h2d = 0.0f;
|
| 209 |
+
+ const size_t p = body.find("\"pinned\"");
|
| 210 |
+
+ if (p != std::string::npos) {
|
| 211 |
+
+ pinned_h2d = json_float_after(body.substr(p), "\"h2d\"");
|
| 212 |
+
+ }
|
| 213 |
+
+
|
| 214 |
+
+ const int width = atoi(json_string_after(body, "\"pci_link_width_current\"").c_str());
|
| 215 |
+
+ const float gtps = parse_leading_float(json_string_after(body, "\"pci_link_speed_current\""));
|
| 216 |
+
+
|
| 217 |
+
+ if (pinned_h2d <= 0.0f && width <= 0) return false;
|
| 218 |
+
+
|
| 219 |
+
+ out.link_width = width;
|
| 220 |
+
+ out.link_gtps = gtps;
|
| 221 |
+
+ out.effective_bw_gbps = pinned_h2d > 0.0f ? pinned_h2d : geometry_bw_gbps(width, gtps);
|
| 222 |
+
+ out.source = "probe-json";
|
| 223 |
+
+ out.detail = "from " + path + (pinned_h2d > 0.0f ? " (measured pinned H2D)" : " (link geometry)");
|
| 224 |
+
+ return out.effective_bw_gbps > 0.0f;
|
| 225 |
+
+}
|
| 226 |
+
+
|
| 227 |
+
+// One-shot nvidia-smi query for link gen + width. CPU-side, no CUDA link dep.
|
| 228 |
+
+bool profile_from_nvidia_smi(pcie_profile & out) {
|
| 229 |
+
+ FILE * pp = popen("nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.width.current "
|
| 230 |
+
+ "--format=csv,noheader,nounits -i 0 2>/dev/null", "r");
|
| 231 |
+
+ if (!pp) return false;
|
| 232 |
+
+ char buf[256] = {};
|
| 233 |
+
+ const bool got = fgets(buf, sizeof(buf), pp) != nullptr;
|
| 234 |
+
+ pclose(pp);
|
| 235 |
+
+ if (!got) return false;
|
| 236 |
+
+
|
| 237 |
+
+ int gen = 0, width = 0;
|
| 238 |
+
+ // format: "<gen>, <width>"
|
| 239 |
+
+ if (sscanf(buf, " %d , %d", &gen, &width) < 2) return false;
|
| 240 |
+
+ const float gtps = gen_to_gtps(gen);
|
| 241 |
+
+ const float bw = geometry_bw_gbps(width, gtps);
|
| 242 |
+
+ if (bw <= 0.0f) return false;
|
| 243 |
+
+
|
| 244 |
+
+ out.link_width = width;
|
| 245 |
+
+ out.link_gtps = gtps;
|
| 246 |
+
+ out.effective_bw_gbps = bw;
|
| 247 |
+
+ out.source = "nvidia-smi";
|
| 248 |
+
+ out.detail = "gen" + std::to_string(gen) + " x" + std::to_string(width) + " (link geometry)";
|
| 249 |
+
+ return true;
|
| 250 |
+
+}
|
| 251 |
+
+
|
| 252 |
+
+pcie_profile g_pcie_profile;
|
| 253 |
+
+bool g_pcie_inited = false;
|
| 254 |
+
+
|
| 255 |
+
+} // namespace
|
| 256 |
+
+
|
| 257 |
+
+pcie_profile pcie_profile_detect(bool autodetect, float manual_gbps) {
|
| 258 |
+
+ pcie_profile prof; // conservative default below
|
| 259 |
+
+
|
| 260 |
+
+ if (manual_gbps > 0.0f) {
|
| 261 |
+
+ prof.effective_bw_gbps = manual_gbps;
|
| 262 |
+
+ prof.source = "manual";
|
| 263 |
+
+ prof.detail = "--pcie-bw-gbps override";
|
| 264 |
+
+ return prof;
|
| 265 |
+
+ }
|
| 266 |
+
+
|
| 267 |
+
+ if (autodetect) {
|
| 268 |
+
+ const std::string js = newest_probe_json();
|
| 269 |
+
+ if (!js.empty() && profile_from_probe_json(js, prof)) return prof;
|
| 270 |
+
+ if (profile_from_nvidia_smi(prof)) return prof;
|
| 271 |
+
+ }
|
| 272 |
+
+
|
| 273 |
+
+ // Conservative default: PCIe 3.0 x8 (common on Ryzen 5600G/5700G hosts where
|
| 274 |
+
+ // the iGPU absorbs 8 lanes β see rolling_kv.md). ~6.4 GB/s.
|
| 275 |
+
+ prof.link_width = 8;
|
| 276 |
+
+ prof.link_gtps = 8.0f;
|
| 277 |
+
+ prof.effective_bw_gbps = geometry_bw_gbps(8, 8.0f);
|
| 278 |
+
+ prof.source = "default";
|
| 279 |
+
+ prof.detail = autodetect ? "no probe JSON / nvidia-smi; assuming PCIe3 x8"
|
| 280 |
+
+ : "autodetect disabled; assuming PCIe3 x8";
|
| 281 |
+
+ return prof;
|
| 282 |
+
+}
|
| 283 |
+
+
|
| 284 |
+
+const pcie_profile & pcie_profile_init(bool autodetect, float manual_gbps) {
|
| 285 |
+
+ g_pcie_profile = pcie_profile_detect(autodetect, manual_gbps);
|
| 286 |
+
+ g_pcie_inited = true;
|
| 287 |
+
+ return g_pcie_profile;
|
| 288 |
+
+}
|
| 289 |
+
+
|
| 290 |
+
+const pcie_profile & pcie_profile_get() {
|
| 291 |
+
+ return g_pcie_profile;
|
| 292 |
+
+}
|
| 293 |
+
diff --git a/llama.cpp/common/pcie-profile.h b/llama.cpp/common/pcie-profile.h
|
| 294 |
+
new file mode 100644
|
| 295 |
+
--- /dev/null
|
| 296 |
+
+++ b/llama.cpp/common/pcie-profile.h
|
| 297 |
+
@@ -0,0 +1,38 @@
|
| 298 |
+
+#pragma once
|
| 299 |
+
+
|
| 300 |
+
+// opencoti F5-opt W1 (#293) β PCIe link + host<->device bandwidth profile.
|
| 301 |
+
+//
|
| 302 |
+
+// Boot-time detection of the GPU's effective host-to-device transfer bandwidth
|
| 303 |
+
+// and PCIe link geometry. This is a foundation diagnostic and, more
|
| 304 |
+
+// importantly, the input M7 Rolling KV (#296) uses to size streaming KV tiles:
|
| 305 |
+
+// tile_bytes_max = compute_ms_per_layer * effective_bw_gbps * 0.8
|
| 306 |
+
+// (see docs/features/rolling_kv.md, "Auto-adapt loop", step 1).
|
| 307 |
+
+//
|
| 308 |
+
+// Detection is CPU-side only (no CUDA dependency in common/). Sources, in
|
| 309 |
+
+// preference order:
|
| 310 |
+
+// 1. manual override (--pcie-bw-gbps F, F > 0)
|
| 311 |
+
+// 2. explicit probe JSON via $OPENCOTI_PCIE_PROFILE
|
| 312 |
+
+// 3. newest ./.opencoti/rebar-probe-*.json (measured pinned H2D + link)
|
| 313 |
+
+// 4. one-shot `nvidia-smi` query (pcie.link.gen.current + width.current)
|
| 314 |
+
+// 5. conservative default (PCIe 3.0 x8 ~= 6.4 GB/s)
|
| 315 |
+
+// Never throws; never fails server boot.
|
| 316 |
+
+
|
| 317 |
+
+#include <string>
|
| 318 |
+
+
|
| 319 |
+
+struct pcie_profile {
|
| 320 |
+
+ float effective_bw_gbps = 0.0f; // usable H2D bandwidth estimate (GB/s)
|
| 321 |
+
+ int link_width = 0; // current PCIe lane count (e.g. 8, 16)
|
| 322 |
+
+ float link_gtps = 0.0f; // current per-lane transfer rate (GT/s)
|
| 323 |
+
+ std::string source = "default"; // manual|probe-json|nvidia-smi|default
|
| 324 |
+
+ std::string detail; // human-readable note for logging
|
| 325 |
+
+};
|
| 326 |
+
+
|
| 327 |
+
+// Run detection once. manual_gbps > 0 forces source="manual"; autodetect=false
|
| 328 |
+
+// skips probe/nvidia-smi discovery and returns the conservative default.
|
| 329 |
+
+pcie_profile pcie_profile_detect(bool autodetect, float manual_gbps);
|
| 330 |
+
+
|
| 331 |
+
+// Process-global: pcie_profile_init() runs detection, caches, and returns the
|
| 332 |
+
+// result; pcie_profile_get() returns the cached value (defaults until init
|
| 333 |
+
+// runs). Consumed by M7 Rolling KV (#296) at server boot.
|
| 334 |
+
+const pcie_profile & pcie_profile_init(bool autodetect, float manual_gbps);
|
| 335 |
+
+const pcie_profile & pcie_profile_get();
|
| 336 |
+
diff --git a/llama.cpp/tools/server/server.cpp b/llama.cpp/tools/server/server.cpp
|
| 337 |
+
--- a/llama.cpp/tools/server/server.cpp
|
| 338 |
+
+++ b/llama.cpp/tools/server/server.cpp
|
| 339 |
+
@@ -7,6 +7,7 @@
|
| 340 |
+
#include "arg.h"
|
| 341 |
+
#include "build-info.h"
|
| 342 |
+
#include "common.h"
|
| 343 |
+
+#include "pcie-profile.h" // opencoti F5-opt W1 (#293)
|
| 344 |
+
#include "fit.h"
|
| 345 |
+
#include "llama.h"
|
| 346 |
+
#include "log.h"
|
| 347 |
+
@@ -135,6 +136,17 @@ int server_main(int argc, char ** argv,
|
| 348 |
+
// struct that contains llama context and inference
|
| 349 |
+
server_context ctx_server;
|
| 350 |
+
|
| 351 |
+
+ // opencoti F5-opt W1 (#293): detect host<->device bandwidth + PCIe link
|
| 352 |
+
+ // once at boot; logged here and cached (pcie_profile_get) for M7 Rolling KV
|
| 353 |
+
+ // (#296) streaming-tile sizing. Never fails boot β falls back to a
|
| 354 |
+
+ // conservative PCIe3 x8 default on any detection miss.
|
| 355 |
+
+ {
|
| 356 |
+
+ const pcie_profile & pp = pcie_profile_init(params.pcie_autodetect, params.pcie_bw_gbps);
|
| 357 |
+
+ LOG_INF("%s: pcie profile: %.1f GB/s effective (x%d @ %.1f GT/s, source=%s) β %s\n",
|
| 358 |
+
+ __func__, (double) pp.effective_bw_gbps, pp.link_width, (double) pp.link_gtps,
|
| 359 |
+
+ pp.source.c_str(), pp.detail.c_str());
|
| 360 |
+
+ }
|
| 361 |
+
+
|
| 362 |
+
server_http_context ctx_http;
|
| 363 |
+
if (!ctx_http.init(params)) {
|
| 364 |
+
SRV_ERR("%s", "failed to initialize HTTP server\n");
|
patches/0040-iqk-flash-attn.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0042-fused-moe.patch
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti F5-opt W3 (#292) β op-level fused MoE up+gate+GLU (GGML_OP_MOE_FUSED_UP_GATE)
|
| 2 |
+
|
| 3 |
+
Adds a new ggml op, GGML_OP_MOE_FUSED_UP_GATE, that collapses a MoE FFN's two
|
| 4 |
+
separate per-expert projections (up = up_exps @ cur, gate = gate_exps @ cur)
|
| 5 |
+
plus the GLU activation into ONE op at decode (n_tokens == 1). The CUDA backend
|
| 6 |
+
dispatches it straight to the existing fused mmvq kernel (ggml_cuda_mul_mat_vec_q
|
| 7 |
+
with a fusion arg carrying the gate weight + glu_op), eliminating the gate_up
|
| 8 |
+
intermediate's HBM round-trip and the standalone GLU kernel launch per MoE layer
|
| 9 |
+
per decode token.
|
| 10 |
+
|
| 11 |
+
Off by default. Engaged with --fused-moe-up-gate on (server) / cparams
|
| 12 |
+
.fused_moe_up_gate / adapter fusedMoeUpGate. The build_moe_ffn graph hook only
|
| 13 |
+
emits the op when the model presents SEPARATE up/gate expert weights
|
| 14 |
+
(gate_exps && !gate_up_exps), same type, SILU|GELU activation, at decode β i.e.
|
| 15 |
+
Qwen2-MoE / Qwen3-MoE / OLMoE / Mixtral-style layouts. Models that fuse gate+up
|
| 16 |
+
at the weight level (a single ffn_gate_up_exps β Gemma-4 A4B, Qwen3.5-MoE)
|
| 17 |
+
never match the hook and run unchanged. Flag-OFF is byte-identical to the pre-W3
|
| 18 |
+
binary (op never emitted); flag-ON on a matching model is byte-identical to the
|
| 19 |
+
unfused two-matmul + GLU path. Validated on OLMoE-1B-7B-0924-Instruct-Q4_K_M:
|
| 20 |
+
graph-dump engagement confirmed (34 MOE_FUSED_UP_GATE nodes on CUDA0), greedy
|
| 21 |
+
decode byte-identical OFF vs ON, +2.4% decode tok/s (265.9 -> 272.2).
|
| 22 |
+
|
| 23 |
+
opencoti-hook: f5-opt-fmoe β see docs/features/advanced_kv.md
|
| 24 |
+
|
| 25 |
+
UPSTREAM PROVENANCE (for future re-sync)
|
| 26 |
+
Idea source : ik_llama.cpp op-level MoE fusion
|
| 27 |
+
https://github.com/ikawrakow/ik_llama.cpp
|
| 28 |
+
PR #229 (fused MoE mul_mat_id path), PR #520 (OOAE expert eval).
|
| 29 |
+
Commit context HEAD 8960c5ba5ee9db30ba838304373aa4dbec9f7cbd.
|
| 30 |
+
License MIT, Author Iwan Kawrakow.
|
| 31 |
+
Code vendored: NONE. This op is original opencoti code. ik_llama's
|
| 32 |
+
ggml_cuda_moe_up_gate_unary dispatches a fused mmvq-id kernel;
|
| 33 |
+
our tree already carries the equivalent primitive
|
| 34 |
+
(ggml_cuda_mul_mat_vec_q with ggml_cuda_mm_fusion_args.gate +
|
| 35 |
+
glu_op β ggml-cuda/mmvq.cu:555-582). W3 takes only the
|
| 36 |
+
op-level-fusion IDEA and routes our existing kernel through a
|
| 37 |
+
new ggml op + a build_moe_ffn graph hook. No ik_llama source is
|
| 38 |
+
copied into this patch.
|
| 39 |
+
ABI note : GGML_OP_COUNT 96 -> 97. The cosmocc binary AND the ggml-cuda.so
|
| 40 |
+
DSO must be rebuilt together and paired (asymmetric DSO cache
|
| 41 |
+
hygiene). The CPU backend deliberately aborts the op (CUDA-only;
|
| 42 |
+
CPU MoE fusion is out of scope β only the CPU embedder matters
|
| 43 |
+
for CPU speed, and that is the separate W2 iqk FA engine).
|
| 44 |
+
Op convention (locked from ggml-cuda/mmvq.cu):
|
| 45 |
+
out = up_proj(src0=up_exps @ src1=cur) (.) act(gate_proj=fusion.gate @ cur)
|
| 46 |
+
== ggml_geglu_split(gate, up); the kernel reads the GLU variant from
|
| 47 |
+
fusion.glu_op (NOT dst op_params). New op srcs: src0=up_exps, src1=gate_exps,
|
| 48 |
+
src2=cur, src3=ids; op_params[0]=glu_op. CUDA dispatch passes src0=up_exps,
|
| 49 |
+
src1=cur(=src2), ids=src3, fusion.gate=gate_exps(=src1), glu_op=op_params[0].
|
| 50 |
+
|
| 51 |
+
Files touched (all surgical; no new vendored files):
|
| 52 |
+
ggml/include/ggml.h enum GGML_OP_MOE_FUSED_UP_GATE + ggml_moe_up_gate decl
|
| 53 |
+
ggml/src/ggml.c NAME/SYMBOL entries, COUNT 96->97 asserts, creation fn
|
| 54 |
+
ggml/src/ggml-cpu/ggml-cpu.c forward-dispatch + n_tasks: CUDA-only ABORT stub
|
| 55 |
+
ggml/src/ggml-cuda/ggml-cuda.cu eval dispatch (reuse fused mmvq) + supports_op gate
|
| 56 |
+
src/llama-cparams.h bool fused_moe_up_gate
|
| 57 |
+
include/llama.h bool fused_moe_up_gate (llama_context_params)
|
| 58 |
+
src/llama-context.cpp param copy + default-params initializer
|
| 59 |
+
common/common.h bool fused_moe_up_gate = false
|
| 60 |
+
common/common.cpp cparams copy
|
| 61 |
+
common/arg.cpp --fused-moe-up-gate on|off (LLAMA_ARG_FUSED_MOE_UP_GATE)
|
| 62 |
+
src/llama-graph.cpp build_moe_ffn hook: emit op for separate-up/gate decode
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
opencoti note (2026-05-31, bug-250): hunks regenerated via snapshot-diff against the post-0040 chain so the patch strict git-applies (3 common/* hunks had whitespace context drift). Content unchanged.
|
| 66 |
+
diff --git a/llama.cpp/common/arg.cpp b/llama.cpp/common/arg.cpp
|
| 67 |
+
--- a/llama.cpp/common/arg.cpp
|
| 68 |
+
+++ b/llama.cpp/common/arg.cpp
|
| 69 |
+
@@ -1375,6 +1375,91 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
| 70 |
+
params.slot_initial_ctx = value;
|
| 71 |
+
}
|
| 72 |
+
).set_env("LLAMA_ARG_SLOT_INITIAL_CTX").set_examples({LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_EMBEDDING, LLAMA_EXAMPLE_PARALLEL}));
|
| 73 |
+
+ // opencoti F5 M1 rest-kv-eviction β see docs/features/advanced_kv.md
|
| 74 |
+
+ add_opt(common_arg(
|
| 75 |
+
+ {"--rest-kv-eviction"},
|
| 76 |
+
+ {"--no-rest-kv-eviction"},
|
| 77 |
+
+ string_format("when context shift fires, evict the lowest-retention (KeyDiff key-similarity) contiguous window instead of the positional middle (default: %s)", params.rest_kv_eviction ? "enabled" : "disabled"),
|
| 78 |
+
+ [](common_params & params, bool value) {
|
| 79 |
+
+ params.rest_kv_eviction = value;
|
| 80 |
+
+ }
|
| 81 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_EVICTION"));
|
| 82 |
+
+ add_opt(common_arg(
|
| 83 |
+
+ {"--rest-kv-recent"}, "N",
|
| 84 |
+
+ string_format("rest-kv: protect the most-recent N tokens from eviction (default: %d)", params.rest_kv_recent),
|
| 85 |
+
+ [](common_params & params, int value) {
|
| 86 |
+
+ params.rest_kv_recent = value;
|
| 87 |
+
+ }
|
| 88 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_RECENT"));
|
| 89 |
+
+ add_opt(common_arg(
|
| 90 |
+
+ {"--rest-kv-layer"}, "N",
|
| 91 |
+
+ string_format("rest-kv: model layer whose keys score retention, -1 = auto/middle (default: %d)", params.rest_kv_layer),
|
| 92 |
+
+ [](common_params & params, int value) {
|
| 93 |
+
+ params.rest_kv_layer = value;
|
| 94 |
+
+ }
|
| 95 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_REST_KV_LAYER"));
|
| 96 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 97 |
+
+ add_opt(common_arg(
|
| 98 |
+
+ {"--headinfer-gpu-heads-frac"}, "F",
|
| 99 |
+
+ string_format("headinfer: fraction of KV heads kept GPU-resident; the rest are offloaded to host memory and streamed back per step (default: %.2f, 1.0 = off). Only effective for GPU-offloaded layers with flash-attention.", (double) params.headinfer_gpu_heads_frac),
|
| 100 |
+
+ [](common_params & params, const std::string & value) {
|
| 101 |
+
+ params.headinfer_gpu_heads_frac = std::stof(value);
|
| 102 |
+
+ }
|
| 103 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_HEADINFER_GPU_HEADS_FRAC"));
|
| 104 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 105 |
+
+ add_opt(common_arg(
|
| 106 |
+
+ {"--neo-pipeline"}, "MODE",
|
| 107 |
+
+ "neo (asymmetric GPU/CPU attention pipelining): off (default), on (engage when headinfer split is active for the layer), auto (reserved, same as on). Requires --headinfer-gpu-heads-frac < 1.0 to do anything.",
|
| 108 |
+
+ [](common_params & params, const std::string & value) {
|
| 109 |
+
+ if (value == "off") params.neo_pipeline_mode = 0;
|
| 110 |
+
+ else if (value == "on") params.neo_pipeline_mode = 1;
|
| 111 |
+
+ else if (value == "auto") params.neo_pipeline_mode = 2;
|
| 112 |
+
+ else throw std::invalid_argument("--neo-pipeline must be off|on|auto");
|
| 113 |
+
+ }
|
| 114 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_NEO_PIPELINE"));
|
| 115 |
+
+ // opencoti F5-opt W2 (#290) iqk CPU flash-attention β see docs/features/advanced_kv.md
|
| 116 |
+
+ add_opt(common_arg(
|
| 117 |
+
+ {"--iqk-flash-attn"}, "MODE",
|
| 118 |
+
+ "iqk CPU flash-attention (vendored ik_llama.cpp engine): off (default), on. "
|
| 119 |
+
+ "Speeds up the M2 CPU-half attention on the token-generation path; falls back "
|
| 120 |
+
+ "to the mainline kernel for unsupported (type, shape) cases. x86_64-only.",
|
| 121 |
+
+ [](common_params & params, const std::string & value) {
|
| 122 |
+
+ if (value == "off" || value == "false" || value == "0") params.iqk_flash_attn = false;
|
| 123 |
+
+ else if (value == "on" || value == "true" || value == "1") params.iqk_flash_attn = true;
|
| 124 |
+
+ else throw std::invalid_argument("--iqk-flash-attn must be on|off");
|
| 125 |
+
+ ggml_iqk_flash_attn_set_enabled(params.iqk_flash_attn);
|
| 126 |
+
+ }
|
| 127 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_IQK_FLASH_ATTN"));
|
| 128 |
+
+ // opencoti F5-opt W3 (#292) fused-MoE β see docs/features/advanced_kv.md
|
| 129 |
+
+ add_opt(common_arg(
|
| 130 |
+
+ {"--fused-moe-up-gate"}, "MODE",
|
| 131 |
+
+ "fused MoE up+gate+GLU (CUDA decode): off (default), on. Collapses the "
|
| 132 |
+
+ "separate-up/gate expert matmuls + GLU into one op at decode so the CUDA "
|
| 133 |
+
+ "backend runs the fused mmvq kernel without graph-pattern fusion. Only "
|
| 134 |
+
+ "engages for separate-up/gate MoE models (e.g. Gemma-4 A4B) at n_tokens==1.",
|
| 135 |
+
+ [](common_params & params, const std::string & value) {
|
| 136 |
+
+ if (value == "off" || value == "false" || value == "0") params.fused_moe_up_gate = false;
|
| 137 |
+
+ else if (value == "on" || value == "true" || value == "1") params.fused_moe_up_gate = true;
|
| 138 |
+
+ else throw std::invalid_argument("--fused-moe-up-gate must be on|off");
|
| 139 |
+
+ }
|
| 140 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_FUSED_MOE_UP_GATE"));
|
| 141 |
+
+ // opencoti F5-opt W1 (#293) pcie profile β see docs/features/rolling_kv.md
|
| 142 |
+
+ add_opt(common_arg(
|
| 143 |
+
+ {"--pcie-autodetect"}, "MODE",
|
| 144 |
+
+ "pcie profile: auto-detect host<->device bandwidth + PCIe link at boot (on (default), off). Reads the rebar-probe JSON / nvidia-smi; the result feeds M7 Rolling KV tile sizing.",
|
| 145 |
+
+ [](common_params & params, const std::string & value) {
|
| 146 |
+
+ if (value == "on" || value == "true" || value == "1") params.pcie_autodetect = true;
|
| 147 |
+
+ else if (value == "off" || value == "false" || value == "0") params.pcie_autodetect = false;
|
| 148 |
+
+ else throw std::invalid_argument("--pcie-autodetect must be on|off");
|
| 149 |
+
+ }
|
| 150 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_PCIE_AUTODETECT"));
|
| 151 |
+
+ add_opt(common_arg(
|
| 152 |
+
+ {"--pcie-bw-gbps"}, "F",
|
| 153 |
+
+ string_format("pcie profile: manual override of effective host->device bandwidth in GB/s (default: %.1f = autodetect). > 0 skips detection.", (double) params.pcie_bw_gbps),
|
| 154 |
+
+ [](common_params & params, const std::string & value) {
|
| 155 |
+
+ params.pcie_bw_gbps = std::stof(value);
|
| 156 |
+
+ }
|
| 157 |
+
+ ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_PCIE_BW_GBPS"));
|
| 158 |
+
add_opt(common_arg(
|
| 159 |
+
// opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 160 |
+
{"--slot-shrink-idle-ms"}, "N",
|
| 161 |
+
diff --git a/llama.cpp/common/common.cpp b/llama.cpp/common/common.cpp
|
| 162 |
+
--- a/llama.cpp/common/common.cpp
|
| 163 |
+
+++ b/llama.cpp/common/common.cpp
|
| 164 |
+
@@ -1636,6 +1636,8 @@ struct llama_context_params common_context_params_to_llama(const common_params &
|
| 165 |
+
cparams.headinfer_gpu_heads_frac = params.headinfer_gpu_heads_frac;
|
| 166 |
+
// opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 167 |
+
cparams.neo_pipeline_mode = params.neo_pipeline_mode;
|
| 168 |
+
+ // opencoti F5 W3 #292 fused-MoE β see docs/features/advanced_kv.md
|
| 169 |
+
+ cparams.fused_moe_up_gate = params.fused_moe_up_gate;
|
| 170 |
+
cparams.n_rs_seq = params.speculative.need_n_rs_seq();
|
| 171 |
+
cparams.n_batch = params.n_batch;
|
| 172 |
+
cparams.n_ubatch = params.n_ubatch;
|
| 173 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 174 |
+
--- a/llama.cpp/common/common.h
|
| 175 |
+
+++ b/llama.cpp/common/common.h
|
| 176 |
+
@@ -445,6 +445,41 @@ struct common_params {
|
| 177 |
+
// active for the layer), 2 = auto (reserved for future load-based
|
| 178 |
+
// engagement; same as `on` today).
|
| 179 |
+
uint32_t neo_pipeline_mode = 0;
|
| 180 |
+
+ // opencoti F5 W3 #292 fused-MoE β see docs/features/advanced_kv.md
|
| 181 |
+
+ // false = off (default; unfused 3-node path). true = fuse decode MoE
|
| 182 |
+
+ // up+gate+GLU into one GGML_OP_MOE_FUSED_UP_GATE op for the CUDA backend.
|
| 183 |
+
+ bool fused_moe_up_gate = false;
|
| 184 |
+
+ // opencoti F5-opt W1 (#293) pcie profile β see docs/features/rolling_kv.md
|
| 185 |
+
+ // Boot-time host<->device bandwidth + PCIe link detection, consumed by M7
|
| 186 |
+
+ // Rolling KV (#296) tile sizing. autodetect reads the rebar-probe JSON /
|
| 187 |
+
+ // nvidia-smi; bw_gbps > 0 forces a manual override (0 = autodetect/default).
|
| 188 |
+
+ bool pcie_autodetect = true;
|
| 189 |
+
+ // opencoti F5-opt W2 (#290) iqk CPU flash-attention β see docs/features/advanced_kv.md
|
| 190 |
+
+ // Engage the vendored ik_llama.cpp CPU Flash-Attention engine on the M2
|
| 191 |
+
+ // CPU-half (token-generation path). Off by default; flag-off is byte-identical.
|
| 192 |
+
+ // x86_64-only at runtime (the engine compiles only on the AVX2 x86_64 slice).
|
| 193 |
+
+ bool iqk_flash_attn = false;
|
| 194 |
+
+ float pcie_bw_gbps = 0.0f;
|
| 195 |
+
+ // opencoti F4 M3 Phase 2 β see docs/decisions/0001-lazy-slot-context.md
|
| 196 |
+
+ // Initial KV-cell allocation per sequence. 0 = full n_ctx_seq allocation
|
| 197 |
+
+ // (Phase 1 behavior β only zero-fill is deferred). Positive values cap
|
| 198 |
+
+ // the initial allocation and enable grow-on-demand up to n_ctx_seq.
|
| 199 |
+
+ int32_t slot_initial_ctx = 0;
|
| 200 |
+
+ // opencoti F4 M3 Phase 3 β see docs/decisions/0001-lazy-slot-context.md
|
| 201 |
+
+ // Idle threshold (ms) after which a grown soft cap is shrunk back to
|
| 202 |
+
+ // slot_initial_ctx and the over-cap pages are decommitted via
|
| 203 |
+
+ // posix_madvise. 0 disables shrink-on-idle (Phase 2 behavior).
|
| 204 |
+
+ int32_t slot_shrink_idle_ms = 0;
|
| 205 |
+
+ // opencoti F5 M2 headinfer β see docs/features/advanced_kv.md
|
| 206 |
+
+ // Fraction of KV heads kept GPU-resident; the rest are offloaded to host
|
| 207 |
+
+ // memory and streamed back per step. 1.0 = off (no split). Only active for
|
| 208 |
+
+ // GPU-offloaded layers with flash-attention (non-transposed V cache).
|
| 209 |
+
+ float headinfer_gpu_heads_frac = 1.0f;
|
| 210 |
+
+ // opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 211 |
+
+ // 0 = off (default; M2 concat path runs), 1 = on (engage when split
|
| 212 |
+
+ // active for the layer), 2 = auto (reserved for future load-based
|
| 213 |
+
+ // engagement; same as `on` today).
|
| 214 |
+
+ uint32_t neo_pipeline_mode = 0;
|
| 215 |
+
// opencoti F5-opt W1 (#293) pcie profile β see docs/features/rolling_kv.md
|
| 216 |
+
// Boot-time host<->device bandwidth + PCIe link detection, consumed by M7
|
| 217 |
+
// Rolling KV (#296) tile sizing. autodetect reads the rebar-probe JSON /
|
| 218 |
+
diff --git a/llama.cpp/ggml/include/ggml.h b/llama.cpp/ggml/include/ggml.h
|
| 219 |
+
--- a/llama.cpp/ggml/include/ggml.h
|
| 220 |
+
+++ b/llama.cpp/ggml/include/ggml.h
|
| 221 |
+
@@ -583,6 +583,8 @@ extern "C" {
|
| 222 |
+
|
| 223 |
+
GGML_OP_GLU,
|
| 224 |
+
|
| 225 |
+
+ GGML_OP_MOE_FUSED_UP_GATE, // opencoti-hook: f5-opt-fmoe β W3 #292
|
| 226 |
+
+
|
| 227 |
+
GGML_OP_COUNT,
|
| 228 |
+
};
|
| 229 |
+
|
| 230 |
+
@@ -1437,6 +1439,22 @@ extern "C" {
|
| 231 |
+
struct ggml_tensor * b,
|
| 232 |
+
struct ggml_tensor * ids);
|
| 233 |
+
|
| 234 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292: op-level fused MoE up+gate+GLU.
|
| 235 |
+
+ // build_moe_ffn otherwise emits {mul_mat_id(up), mul_mat_id(gate), glu}
|
| 236 |
+
+ // as 3 nodes that the CUDA backend can only fuse via the fragile
|
| 237 |
+
+ // ggml_can_fuse_subgraph graph-pattern path (which does not engage at
|
| 238 |
+
+ // decode for Gemma-4). This single op lets the backend run the already
|
| 239 |
+
+ // existing fused mmvq kernel directly. Output is GLU(gate@cur) (.) (up@cur),
|
| 240 |
+
+ // matching ggml_geglu_split(gate, up). up_exps/gate_exps: expert weight
|
| 241 |
+
+ // stacks (same type); cur: input activations; ids: expert selection.
|
| 242 |
+
+ GGML_API struct ggml_tensor * ggml_moe_up_gate(
|
| 243 |
+
+ struct ggml_context * ctx,
|
| 244 |
+
+ struct ggml_tensor * up_exps,
|
| 245 |
+
+ struct ggml_tensor * gate_exps,
|
| 246 |
+
+ struct ggml_tensor * cur,
|
| 247 |
+
+ struct ggml_tensor * ids,
|
| 248 |
+
+ enum ggml_glu_op glu_op);
|
| 249 |
+
+
|
| 250 |
+
// A: m columns, n rows,
|
| 251 |
+
// B: p columns, n rows,
|
| 252 |
+
// result is m columns, p rows
|
| 253 |
+
diff --git a/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c b/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c
|
| 254 |
+
--- a/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c
|
| 255 |
+
+++ b/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c
|
| 256 |
+
@@ -2066,6 +2066,13 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm
|
| 257 |
+
{
|
| 258 |
+
ggml_compute_forward_glu(params, tensor);
|
| 259 |
+
} break;
|
| 260 |
+
+ case GGML_OP_MOE_FUSED_UP_GATE:
|
| 261 |
+
+ {
|
| 262 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292. CUDA-only op. The
|
| 263 |
+
+ // build_moe_ffn graph hook emits it only when the CUDA backend
|
| 264 |
+
+ // owns the FFN nodes, so the CPU scheduler never reaches here.
|
| 265 |
+
+ GGML_ABORT("MOE_FUSED_UP_GATE has no CPU implementation (CUDA-only op)");
|
| 266 |
+
+ } break;
|
| 267 |
+
case GGML_OP_GET_REL_POS:
|
| 268 |
+
{
|
| 269 |
+
ggml_compute_forward_get_rel_pos(params, tensor);
|
| 270 |
+
@@ -2333,6 +2340,12 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) {
|
| 271 |
+
GGML_ABORT("fatal error");
|
| 272 |
+
}
|
| 273 |
+
break;
|
| 274 |
+
+ case GGML_OP_MOE_FUSED_UP_GATE:
|
| 275 |
+
+ {
|
| 276 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292. CUDA-only; never
|
| 277 |
+
+ // scheduled on CPU. Single task keeps the planner well-formed.
|
| 278 |
+
+ n_tasks = 1;
|
| 279 |
+
+ } break;
|
| 280 |
+
case GGML_OP_SILU_BACK:
|
| 281 |
+
case GGML_OP_MUL:
|
| 282 |
+
case GGML_OP_DIV:
|
| 283 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu b/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 284 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 285 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/ggml-cuda.cu
|
| 286 |
+
@@ -3005,6 +3005,25 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg
|
| 287 |
+
case GGML_OP_MUL_MAT_ID:
|
| 288 |
+
ggml_cuda_mul_mat_id(ctx, dst);
|
| 289 |
+
break;
|
| 290 |
+
+ case GGML_OP_MOE_FUSED_UP_GATE:
|
| 291 |
+
+ {
|
| 292 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292. Drive the existing fused
|
| 293 |
+
+ // mmvq-id kernel directly: up = src0 @ src1, gate = fusion.gate @
|
| 294 |
+
+ // src1, out = act(gate) (.) up. Byte-identical to the graph-pattern
|
| 295 |
+
+ // fusion path (see the { MUL_MAT_ID, MUL_MAT_ID, GLU } branch in
|
| 296 |
+
+ // the eval fusion loop). Decode-only (ne[2]==1): the build_moe_ffn
|
| 297 |
+
+ // hook only emits this at n_tokens==1 and supports_op enforces it.
|
| 298 |
+
+ const ggml_tensor * up_exps = dst->src[0];
|
| 299 |
+
+ const ggml_tensor * gate_exps = dst->src[1];
|
| 300 |
+
+ const ggml_tensor * cur = dst->src[2];
|
| 301 |
+
+ const ggml_tensor * ids = dst->src[3];
|
| 302 |
+
+ GGML_ASSERT(dst->ne[2] == 1 && "MOE_FUSED_UP_GATE is decode-only (n_tokens==1)");
|
| 303 |
+
+ ggml_cuda_mm_fusion_args_host fusion_data{};
|
| 304 |
+
+ fusion_data.gate = gate_exps;
|
| 305 |
+
+ fusion_data.glu_op = (ggml_glu_op) dst->op_params[0];
|
| 306 |
+
+ ggml_cuda_mul_mat_vec_q(ctx, up_exps, cur, ids, dst, &fusion_data);
|
| 307 |
+
+ }
|
| 308 |
+
+ break;
|
| 309 |
+
case GGML_OP_OUT_PROD:
|
| 310 |
+
ggml_cuda_out_prod(ctx, dst);
|
| 311 |
+
break;
|
| 312 |
+
@@ -5210,6 +5229,44 @@ static bool GGML_CALL ggml_backend_cuda_device_supports_op(ggml_backend_dev_t de
|
| 313 |
+
return false;
|
| 314 |
+
}
|
| 315 |
+
break;
|
| 316 |
+
+ case GGML_OP_MOE_FUSED_UP_GATE:
|
| 317 |
+
+ {
|
| 318 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292. Supported only in the
|
| 319 |
+
+ // exact shape the fused mmvq-id decode kernel handles: quantized
|
| 320 |
+
+ // up/gate expert stacks of identical type, F32 activations, I32
|
| 321 |
+
+ // ids, single-token (decode) output, non-split weights, and a GLU
|
| 322 |
+
+ // op the kernel implements explicitly (GEGLU/SWIGLU/SWIGLU_OAI β
|
| 323 |
+
+ // others silently degrade to a plain product). build_moe_ffn only
|
| 324 |
+
+ // emits the op when all of these hold, so supports_op and the hook
|
| 325 |
+
+ // stay consistent (the scheduler never strands it on the CPU).
|
| 326 |
+
+ const ggml_tensor * up_exps = op->src[0];
|
| 327 |
+
+ const ggml_tensor * gate_exps = op->src[1];
|
| 328 |
+
+ const ggml_tensor * cur = op->src[2];
|
| 329 |
+
+ const ggml_tensor * ids = op->src[3];
|
| 330 |
+
+ if (!up_exps || !gate_exps || !cur || !ids) {
|
| 331 |
+
+ return false;
|
| 332 |
+
+ }
|
| 333 |
+
+ if (up_exps->buffer && ggml_backend_buft_is_cuda_split(up_exps->buffer->buft)) {
|
| 334 |
+
+ return false;
|
| 335 |
+
+ }
|
| 336 |
+
+ if (!ggml_is_quantized(up_exps->type) || up_exps->type != gate_exps->type) {
|
| 337 |
+
+ return false;
|
| 338 |
+
+ }
|
| 339 |
+
+ if (cur->type != GGML_TYPE_F32 || ids->type != GGML_TYPE_I32) {
|
| 340 |
+
+ return false;
|
| 341 |
+
+ }
|
| 342 |
+
+ if (op->ne[2] != 1) {
|
| 343 |
+
+ return false;
|
| 344 |
+
+ }
|
| 345 |
+
+ switch ((ggml_glu_op) op->op_params[0]) {
|
| 346 |
+
+ case GGML_GLU_OP_GEGLU:
|
| 347 |
+
+ case GGML_GLU_OP_SWIGLU:
|
| 348 |
+
+ case GGML_GLU_OP_SWIGLU_OAI:
|
| 349 |
+
+ return true;
|
| 350 |
+
+ default:
|
| 351 |
+
+ return false;
|
| 352 |
+
+ }
|
| 353 |
+
+ }
|
| 354 |
+
case GGML_OP_MUL_MAT:
|
| 355 |
+
case GGML_OP_MUL_MAT_ID:
|
| 356 |
+
{
|
| 357 |
+
diff --git a/llama.cpp/ggml/src/ggml.c b/llama.cpp/ggml/src/ggml.c
|
| 358 |
+
--- a/llama.cpp/ggml/src/ggml.c
|
| 359 |
+
+++ b/llama.cpp/ggml/src/ggml.c
|
| 360 |
+
@@ -1078,9 +1078,10 @@ static const char * GGML_OP_NAME[GGML_OP_COUNT] = {
|
| 361 |
+
"OPT_STEP_SGD",
|
| 362 |
+
|
| 363 |
+
"GLU",
|
| 364 |
+
+ "MOE_FUSED_UP_GATE",
|
| 365 |
+
};
|
| 366 |
+
|
| 367 |
+
-static_assert(GGML_OP_COUNT == 96, "GGML_OP_COUNT != 96");
|
| 368 |
+
+static_assert(GGML_OP_COUNT == 97, "GGML_OP_COUNT != 97");
|
| 369 |
+
|
| 370 |
+
static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = {
|
| 371 |
+
"none",
|
| 372 |
+
@@ -1188,9 +1189,10 @@ static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = {
|
| 373 |
+
"sgd(x)",
|
| 374 |
+
|
| 375 |
+
"glu(x)",
|
| 376 |
+
+ "moe_up_gate(up,gate,x)",
|
| 377 |
+
};
|
| 378 |
+
|
| 379 |
+
-static_assert(GGML_OP_COUNT == 96, "GGML_OP_COUNT != 96");
|
| 380 |
+
+static_assert(GGML_OP_COUNT == 97, "GGML_OP_COUNT != 97");
|
| 381 |
+
|
| 382 |
+
static_assert(GGML_OP_POOL_COUNT == 2, "GGML_OP_POOL_COUNT != 2");
|
| 383 |
+
|
| 384 |
+
@@ -3314,6 +3316,53 @@ struct ggml_tensor * ggml_mul_mat_id(
|
| 385 |
+
return result;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
+// ggml_moe_up_gate
|
| 389 |
+
+// opencoti-hook: f5-opt-fmoe β W3 #292. Op-level fusion of the two parallel
|
| 390 |
+
+// expert matmuls (up = up_exps @ cur, gate = gate_exps @ cur) plus the GLU
|
| 391 |
+
+// activation that build_moe_ffn otherwise emits as 3 separate graph nodes.
|
| 392 |
+
+// Output == ggml_geglu_split(gate, up) == act(gate) (.) up, where the CUDA
|
| 393 |
+
+// backend runs the existing fused mmvq kernel (act on the gate projection,
|
| 394 |
+
+// multiply by the up projection β see ggml-cuda/mmvq.cu). Mirrors
|
| 395 |
+
+// ggml_mul_mat_id's shape contract: up_exps is the primary "as" weight, cur
|
| 396 |
+
+// the "b" activations shared by both projections, ids the expert selection.
|
| 397 |
+
+
|
| 398 |
+
+struct ggml_tensor * ggml_moe_up_gate(
|
| 399 |
+
+ struct ggml_context * ctx,
|
| 400 |
+
+ struct ggml_tensor * up_exps,
|
| 401 |
+
+ struct ggml_tensor * gate_exps,
|
| 402 |
+
+ struct ggml_tensor * cur,
|
| 403 |
+
+ struct ggml_tensor * ids,
|
| 404 |
+
+ enum ggml_glu_op glu_op) {
|
| 405 |
+
+ GGML_ASSERT(!ggml_is_transposed(up_exps));
|
| 406 |
+
+ GGML_ASSERT(!ggml_is_transposed(gate_exps));
|
| 407 |
+
+ GGML_ASSERT(ids->type == GGML_TYPE_I32);
|
| 408 |
+
+
|
| 409 |
+
+ // up and gate are parallel expert stacks: identical shape + type so one
|
| 410 |
+
+ // shared-activation fused mmvq can drive both weight sets.
|
| 411 |
+
+ GGML_ASSERT(ggml_are_same_shape(up_exps, gate_exps));
|
| 412 |
+
+ GGML_ASSERT(up_exps->type == gate_exps->type);
|
| 413 |
+
+
|
| 414 |
+
+ GGML_ASSERT(up_exps->ne[3] == 1); // 3d (one matrix per expert)
|
| 415 |
+
+ GGML_ASSERT(cur->ne[3] == 1); // cur is 3d
|
| 416 |
+
+ GGML_ASSERT(ids->ne[2] == 1 && ids->ne[3] == 1); // ids is 2d
|
| 417 |
+
+ GGML_ASSERT(ids->ne[1] == cur->ne[2]); // one expert list per cur row
|
| 418 |
+
+ GGML_ASSERT(up_exps->ne[0] == cur->ne[0]); // can_mul_mat
|
| 419 |
+
+ GGML_ASSERT(ids->ne[0] % cur->ne[1] == 0); // can broadcast
|
| 420 |
+
+
|
| 421 |
+
+ const int64_t ne[4] = { up_exps->ne[1], ids->ne[0], cur->ne[2], 1 };
|
| 422 |
+
+ struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne);
|
| 423 |
+
+
|
| 424 |
+
+ ggml_set_op_params_i32(result, 0, (int32_t) glu_op);
|
| 425 |
+
+
|
| 426 |
+
+ result->op = GGML_OP_MOE_FUSED_UP_GATE;
|
| 427 |
+
+ result->src[0] = up_exps;
|
| 428 |
+
+ result->src[1] = gate_exps;
|
| 429 |
+
+ result->src[2] = cur;
|
| 430 |
+
+ result->src[3] = ids;
|
| 431 |
+
+
|
| 432 |
+
+ return result;
|
| 433 |
+
+}
|
| 434 |
+
+
|
| 435 |
+
// ggml_out_prod
|
| 436 |
+
|
| 437 |
+
static inline bool ggml_can_out_prod(const struct ggml_tensor * t0, const struct ggml_tensor * t1) {
|
| 438 |
+
diff --git a/llama.cpp/include/llama.h b/llama.cpp/include/llama.h
|
| 439 |
+
--- a/llama.cpp/include/llama.h
|
| 440 |
+
+++ b/llama.cpp/include/llama.h
|
| 441 |
+
@@ -362,6 +362,9 @@ extern "C" {
|
| 442 |
+
// 0 = off (default), 1 = on, 2 = auto. Effective only when the
|
| 443 |
+
// M2 head split is active for a given layer.
|
| 444 |
+
uint32_t neo_pipeline_mode;
|
| 445 |
+
+ // opencoti F5 W3 #292 fused-MoE β see docs/features/advanced_kv.md
|
| 446 |
+
+ // true = fuse decode MoE up+gate+GLU into one op. false = off (default).
|
| 447 |
+
+ bool fused_moe_up_gate;
|
| 448 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback (0 = no rollback) [EXPERIMENTAL]
|
| 449 |
+
int32_t n_threads; // number of threads to use for generation
|
| 450 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 451 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 452 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 453 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 454 |
+
@@ -66,6 +66,8 @@ llama_context::llama_context(
|
| 455 |
+
cparams.headinfer_gpu_heads_frac = params.headinfer_gpu_heads_frac;
|
| 456 |
+
// opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 457 |
+
cparams.neo_pipeline_mode = params.neo_pipeline_mode;
|
| 458 |
+
+ // opencoti F5 W3 #292 fused-MoE β see docs/features/advanced_kv.md
|
| 459 |
+
+ cparams.fused_moe_up_gate = params.fused_moe_up_gate;
|
| 460 |
+
|
| 461 |
+
cparams.n_threads = params.n_threads;
|
| 462 |
+
cparams.n_threads_batch = params.n_threads_batch;
|
| 463 |
+
@@ -3359,6 +3361,7 @@ llama_context_params llama_context_default_params() {
|
| 464 |
+
/*.slot_shrink_idle_ms =*/ 0,
|
| 465 |
+
/*.headinfer_gpu_heads_frac =*/ 1.0f,
|
| 466 |
+
/*.neo_pipeline_mode =*/ 0, // opencoti F5 M3 neo β off by default
|
| 467 |
+
+ /*.fused_moe_up_gate =*/ false, // opencoti F5 W3 #292 β off by default
|
| 468 |
+
/*.n_rs_seq =*/ 0,
|
| 469 |
+
/*.n_threads =*/ GGML_DEFAULT_N_THREADS, // TODO: better default
|
| 470 |
+
/*.n_threads_batch =*/ GGML_DEFAULT_N_THREADS,
|
| 471 |
+
diff --git a/llama.cpp/src/llama-cparams.h b/llama.cpp/src/llama-cparams.h
|
| 472 |
+
--- a/llama.cpp/src/llama-cparams.h
|
| 473 |
+
+++ b/llama.cpp/src/llama-cparams.h
|
| 474 |
+
@@ -32,6 +32,10 @@ struct llama_cparams {
|
| 475 |
+
// 2 = auto (same engagement as `on` for now β reserved for future
|
| 476 |
+
// load-based engagement heuristics).
|
| 477 |
+
uint32_t neo_pipeline_mode;
|
| 478 |
+
+ // opencoti F5 W3 #292 fused-MoE β see docs/features/advanced_kv.md.
|
| 479 |
+
+ // true = build_moe_ffn collapses the up/gate expert matmuls + GLU into one
|
| 480 |
+
+ // GGML_OP_MOE_FUSED_UP_GATE op at decode (n_tokens==1). false = unfused path.
|
| 481 |
+
+ bool fused_moe_up_gate;
|
| 482 |
+
uint32_t n_rs_seq; // number of recurrent-state snapshots per seq for rollback
|
| 483 |
+
int32_t n_threads; // number of threads to use for generation
|
| 484 |
+
int32_t n_threads_batch; // number of threads to use for batch processing
|
| 485 |
+
diff --git a/llama.cpp/src/llama-graph.cpp b/llama.cpp/src/llama-graph.cpp
|
| 486 |
+
--- a/llama.cpp/src/llama-graph.cpp
|
| 487 |
+
+++ b/llama.cpp/src/llama-graph.cpp
|
| 488 |
+
@@ -1534,7 +1534,28 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
| 489 |
+
ggml_tensor * up = nullptr;
|
| 490 |
+
ggml_tensor * experts = nullptr;
|
| 491 |
+
|
| 492 |
+
- if (gate_up_exps) {
|
| 493 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292: collapse the separate-up/gate
|
| 494 |
+
+ // expert matmuls + the GLU activation into ONE GGML_OP_MOE_FUSED_UP_GATE
|
| 495 |
+
+ // op at decode, so the CUDA backend runs the existing fused mmvq kernel
|
| 496 |
+
+ // without relying on graph-pattern fusion (ggml_can_fuse_subgraph, which
|
| 497 |
+
+ // does not engage for Gemma-4 at decode). Guards mirror supports_op:
|
| 498 |
+
+ // decode only (n_tokens==1), separate up/gate of identical type, no
|
| 499 |
+
+ // bias/scale, and GELUβGEGLU or SILUβSWIGLU. Off path is byte-identical.
|
| 500 |
+
+ bool fused_up_gate = false;
|
| 501 |
+
+ if (cparams.fused_moe_up_gate && gate_exps && !gate_up_exps &&
|
| 502 |
+
+ !up_exps_b && !gate_exps_b && !up_exps_s && !gate_exps_s &&
|
| 503 |
+
+ up_exps->type == gate_exps->type && n_tokens == 1 &&
|
| 504 |
+
+ (type_op == LLM_FFN_GELU || type_op == LLM_FFN_SILU)) {
|
| 505 |
+
+ const enum ggml_glu_op glu_op = (type_op == LLM_FFN_GELU)
|
| 506 |
+
+ ? GGML_GLU_OP_GEGLU : GGML_GLU_OP_SWIGLU;
|
| 507 |
+
+ cur = ggml_moe_up_gate(ctx0, up_exps, gate_exps, cur, selected_experts, glu_op);
|
| 508 |
+
+ cb(cur, "ffn_moe_fused_up_gate", il);
|
| 509 |
+
+ fused_up_gate = true;
|
| 510 |
+
+ }
|
| 511 |
+
+
|
| 512 |
+
+ if (fused_up_gate) {
|
| 513 |
+
+ // cur already holds act(gate) (.) up from the fused op
|
| 514 |
+
+ } else if (gate_up_exps) {
|
| 515 |
+
// merged gate_up path: one mul_mat_id, then split into gate and up views
|
| 516 |
+
ggml_tensor * gate_up = build_lora_mm_id(gate_up_exps, cur, selected_experts); // [n_ff*2, n_expert_used, n_tokens]
|
| 517 |
+
cb(gate_up, "ffn_moe_gate_up", il);
|
| 518 |
+
@@ -1601,6 +1622,9 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
| 519 |
+
|
| 520 |
+
const bool has_gate = gate_exps || gate_up_exps;
|
| 521 |
+
|
| 522 |
+
+ // opencoti-hook: f5-opt-fmoe β W3 #292: the fused op already applied the
|
| 523 |
+
+ // GLU activation; skip the separate activation pass.
|
| 524 |
+
+ if (!fused_up_gate)
|
| 525 |
+
switch (type_op) {
|
| 526 |
+
case LLM_FFN_SILU:
|
| 527 |
+
if (gate_exps) {
|
patches/0070-rolling-kv.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0071-state-io-cosmocc.patch
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0071] cosmocc state-IO open fix (bug-269)
|
| 3 |
+
|
| 4 |
+
opencoti F5 M7 (#349, bug-269). Orthogonal to Rolling KV (0070) β captured as
|
| 5 |
+
its own slot. Fixes ALL llama_state_*_save/load_file in the cosmocc/llamafile
|
| 6 |
+
build, which were broken before this: llama.cpp/src/llama-mmap.cpp's COSMOCC
|
| 7 |
+
llama_file constructor routed every open() through llamafile_open_gguf(), which
|
| 8 |
+
pread()s the GGUF magic at offset 0 (EBADF on a write-only "wb" fd) and
|
| 9 |
+
zip-routes any non-GGUF read; and write_raw's fwrite(fp) ran with fp==NULL.
|
| 10 |
+
KV-state files are neither GGUF nor zip and are opened for write, so model
|
| 11 |
+
load was unaffected but state save/load never worked through the binary (the
|
| 12 |
+
M2 bug-222 "state round-trip" was thus never actually exercised end-to-end).
|
| 13 |
+
|
| 14 |
+
FIX (cosmocc/host-only; no CUDA, no kernel):
|
| 15 |
+
* vendors/sources/llamafile/llamafile/llamafile.{c,h} (Mozilla-Ocho llamafile,
|
| 16 |
+
Apache-2.0): add llamafile_open_plain() β a plain fopen-backed handle with
|
| 17 |
+
no GGUF magic probe and no zip routing, for non-model files. It delegates
|
| 18 |
+
to the existing static llamafile_open_file().
|
| 19 |
+
* llama.cpp/src/llama-mmap.cpp: the COSMOCC llama_file ctor falls back to
|
| 20 |
+
llamafile_open_plain() when llamafile_open_gguf() returns NULL (models are
|
| 21 |
+
unaffected β they still open via the gguf path) and sets fp =
|
| 22 |
+
llamafile_fp(lfile) so write_raw has a valid stream.
|
| 23 |
+
|
| 24 |
+
Provenance: opencoti-original. Touches Mozilla-Ocho llamafile.{c,h}
|
| 25 |
+
(Apache-2.0) and upstream llama.cpp llama-mmap.cpp. Applies after 0070
|
| 26 |
+
(independent file set β order immaterial); reverse-applies clean.
|
| 27 |
+
diff --git a/llama.cpp/src/llama-mmap.cpp b/llama.cpp/src/llama-mmap.cpp
|
| 28 |
+
--- a/llama.cpp/src/llama-mmap.cpp
|
| 29 |
+
+++ b/llama.cpp/src/llama-mmap.cpp
|
| 30 |
+
@@ -186,11 +186,25 @@ struct llama_file::impl {
|
| 31 |
+
#else
|
| 32 |
+
impl(const char * fname, const char * mode, [[maybe_unused]] const bool use_direct_io = false) : fname(fname) {
|
| 33 |
+
#ifdef COSMOCC
|
| 34 |
+
- // [llamafile] Use llamafile_open_gguf for all file opening
|
| 35 |
+
+ // [llamafile] Use llamafile_open_gguf for model files (raw .gguf,
|
| 36 |
+
+ // foo.zip@weights, /zip/ paths, and .llamafile archives via zip routing).
|
| 37 |
+
lfile = llamafile_open_gguf(fname, mode);
|
| 38 |
+
+ // opencoti bug-269: llamafile_open_gguf is model-oriented β it pread()s the
|
| 39 |
+
+ // GGUF magic at offset 0 and routes non-GGUF files into a zip parser. KV
|
| 40 |
+
+ // state files (llama_state_*_save_file) are neither GGUF nor zip and are
|
| 41 |
+
+ // opened for write ("wb" β pread EBADF). Both made every state save/restore
|
| 42 |
+
+ // fail in the cosmocc build. When the model opener declines, fall back to a
|
| 43 |
+
+ // plain fopen-backed handle (model loads are unaffected β they succeed above).
|
| 44 |
+
+ if (lfile == NULL) {
|
| 45 |
+
+ lfile = llamafile_open_plain(fname, mode);
|
| 46 |
+
+ }
|
| 47 |
+
if (lfile == NULL) {
|
| 48 |
+
throw std::runtime_error(format("failed to open %s: %s", fname, strerror(errno)));
|
| 49 |
+
}
|
| 50 |
+
+ // write_raw() writes via std::fwrite(fp); the cosmocc open path left fp NULL
|
| 51 |
+
+ // (read-only design). Expose the underlying FILE* so state-save writes land.
|
| 52 |
+
+ // Harmless for reads (read_raw_unsafe uses llamafile_read(lfile)).
|
| 53 |
+
+ fp = llamafile_fp(lfile);
|
| 54 |
+
size = llamafile_size(lfile);
|
| 55 |
+
return;
|
| 56 |
+
#endif
|
| 57 |
+
diff --git a/llamafile/llamafile.c b/llamafile/llamafile.c
|
| 58 |
+
--- a/llamafile/llamafile.c
|
| 59 |
+
+++ b/llamafile/llamafile.c
|
| 60 |
+
@@ -318,6 +318,15 @@ struct llamafile *llamafile_open_gguf(const char *fname, const char *mode) {
|
| 61 |
+
return llamafile_open_zip(fname, 0, mode);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
+// opencoti bug-269: plain fopen-backed handle for non-model files (KV state
|
| 65 |
+
+// save/load). llamafile_open_gguf() is model-oriented β it pread()s the GGUF
|
| 66 |
+
+// magic at offset 0 (which fails EBADF on a write-only "wb" fd) and routes any
|
| 67 |
+
+// non-GGUF file into a zip parser. State files are neither GGUF nor zip and are
|
| 68 |
+
+// opened for write, so they need a path with no magic probe and no zip routing.
|
| 69 |
+
+struct llamafile *llamafile_open_plain(const char *fname, const char *mode) {
|
| 70 |
+
+ return llamafile_open_file(fname, mode);
|
| 71 |
+
+}
|
| 72 |
+
+
|
| 73 |
+
FILE *llamafile_fp(struct llamafile *file) {
|
| 74 |
+
return file->fp;
|
| 75 |
+
}
|
| 76 |
+
diff --git a/llamafile/llamafile.h b/llamafile/llamafile.h
|
| 77 |
+
--- a/llamafile/llamafile.h
|
| 78 |
+
+++ b/llamafile/llamafile.h
|
| 79 |
+
@@ -46,6 +46,9 @@ extern int FLAG_verbose; // Verbose output (chatbot_main.cpp, metal.c, cu
|
| 80 |
+
|
| 81 |
+
struct llamafile;
|
| 82 |
+
struct llamafile *llamafile_open_gguf(const char *, const char *); // UNUSED externally
|
| 83 |
+
+// opencoti bug-269: plain fopen-backed handle (no GGUF magic probe, no zip
|
| 84 |
+
+// routing) for non-model files such as KV state save/load.
|
| 85 |
+
+struct llamafile *llamafile_open_plain(const char *, const char *);
|
| 86 |
+
void llamafile_close(struct llamafile *); // UNUSED externally
|
| 87 |
+
long llamafile_read(struct llamafile *, void *, size_t); // UNUSED externally
|
| 88 |
+
long llamafile_write(struct llamafile *, const void *, size_t); // UNUSED externally
|
patches/0072-poly-kv-pool.patch
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0072] F5 M6 PolyKV S1 β SharedKVPool shared read-only prefix
|
| 3 |
+
|
| 4 |
+
Server-side SharedKVPool (F5 M6 S1, #370): a read-only KV prefix shared across
|
| 5 |
+
sequences/slots so N agents over one base context pay O(1) shared-prefix memory
|
| 6 |
+
instead of O(N). Per-request opt-in via the `shared_pool_slot` (>=0 selects the
|
| 7 |
+
pool donor slot) and `shared_prefix_n_tokens` task params; default-off (slot -1)
|
| 8 |
+
is byte-identical to upstream slot handling. Pure server/task plumbing β no ggml
|
| 9 |
+
or kernel change.
|
| 10 |
+
|
| 11 |
+
Bench: perf/llamafile/advanced-kv-stack.bench.ts (A4/S'); gate .opencoti/m6-s1-shared-pool-gate.sh
|
| 12 |
+
Milestone: F5 M6 PolyKV S1 β see docs/features/poly_kv.md
|
| 13 |
+
Applies after: 0071 (numbered 0072 so lexical apply order lands it after the M7
|
| 14 |
+
rolling-KV 0070/0071 it composes with; the original 0060 slot predates 0070).
|
| 15 |
+
|
| 16 |
+
diff --git a/llama.cpp/tools/server/server-context.cpp b/llama.cpp/tools/server/server-context.cpp
|
| 17 |
+
--- a/llama.cpp/tools/server/server-context.cpp
|
| 18 |
+
+++ b/llama.cpp/tools/server/server-context.cpp
|
| 19 |
+
@@ -2907,6 +2907,48 @@ private:
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
+ // opencoti-hook: poly-kv-pool β see docs/features/poly_kv.md
|
| 24 |
+
+ // F5 M6 PolyKV S1: bit-share a pool slot's read-only prefix into this seq
|
| 25 |
+
+ // (zero-copy cells.seq_add in unified mode) and skip re-processing it.
|
| 26 |
+
+ // Caller contract: this request's prompt MUST begin with exactly the pool
|
| 27 |
+
+ // slot's first shared_prefix_n_tokens tokens, and the pool slot must remain
|
| 28 |
+
+ // resident while sharers reference it. RESIDENCY PREREQUISITE: the server
|
| 29 |
+
+ // must be booted with --no-clear-idle. The upstream idle-slot prompt-cache
|
| 30 |
+
+ // offload (clear_idle, default-on with --kv-unified + --cache-ram; PR #16391)
|
| 31 |
+
+ // serializes an idle slot's KV to a host blob and CLEARS its device cells the
|
| 32 |
+
+ // moment a new task launches β which would evict the pool prefix before any
|
| 33 |
+
+ // sharer's seq_cp runs (seq would be empty β silent miss). We guard against
|
| 34 |
+
+ // that below: if the pool's resident range does not cover [0,P), we skip the
|
| 35 |
+
+ // share and fall through to normal full re-processing (correct, just slower).
|
| 36 |
+
+ // We mirror the copy_state_to() precedent: after seq_cp we also set this
|
| 37 |
+
+ // slot's token bookkeeping to the first P tokens, else keep_first(n_past)
|
| 38 |
+
+ // asserts on the (empty) slot token list (server-common.cpp:397).
|
| 39 |
+
+ if (slot.task->params.shared_pool_slot >= 0 &&
|
| 40 |
+
+ slot.task->params.shared_prefix_n_tokens > 0 &&
|
| 41 |
+
+ slot.task->params.shared_pool_slot != slot.id) {
|
| 42 |
+
+ const int pool = slot.task->params.shared_pool_slot;
|
| 43 |
+
+ int32_t P = std::min<int32_t>(slot.task->params.shared_prefix_n_tokens,
|
| 44 |
+
+ (int32_t) input_tokens.size());
|
| 45 |
+
+ auto * mem = llama_get_memory(ctx_tgt);
|
| 46 |
+
+ // residency guard: the pool seq must actually hold cells covering [0,P)
|
| 47 |
+
+ const llama_pos pool_max = llama_memory_seq_pos_max(mem, pool);
|
| 48 |
+
+ if (pool_max < (llama_pos) (P - 1)) {
|
| 49 |
+
+ SLT_WRN(slot, "poly-kv-pool: pool slot %d not resident to %d (max pos %d) β "
|
| 50 |
+
+ "skipping share, full reprocess (boot with --no-clear-idle)\n",
|
| 51 |
+
+ pool, (int) (P - 1), (int) pool_max);
|
| 52 |
+
+ } else {
|
| 53 |
+
+ // clear any stale KV on this slot, then bit-share the pool's [0,P) prefix
|
| 54 |
+
+ llama_memory_seq_rm(mem, slot.id, -1, -1);
|
| 55 |
+
+ llama_memory_seq_cp(mem, pool, slot.id, 0, P);
|
| 56 |
+
+ // mirror token bookkeeping so keep_first(n_past)/pos_next stay consistent
|
| 57 |
+
+ server_tokens shared = input_tokens.clone();
|
| 58 |
+
+ shared.keep_first(P);
|
| 59 |
+
+ slot.prompt.tokens = std::move(shared);
|
| 60 |
+
+ n_past = P;
|
| 61 |
+
+ SLT_INF(slot, "poly-kv-pool: shared %d-token prefix from slot %d (n_past -> %d)\n", (int) P, pool, n_past);
|
| 62 |
+
+ }
|
| 63 |
+
+ }
|
| 64 |
+
+
|
| 65 |
+
// [TAG_PROMPT_LOGITS]
|
| 66 |
+
if (n_past == slot.task->n_tokens() && n_past > 0) {
|
| 67 |
+
SLT_WRN(slot, "need to evaluate at least 1 token for each active slot (n_past = %d, task.n_tokens() = %d)\n", n_past, slot.task->n_tokens());
|
| 68 |
+
diff --git a/llama.cpp/tools/server/server-task.cpp b/llama.cpp/tools/server/server-task.cpp
|
| 69 |
+
--- a/llama.cpp/tools/server/server-task.cpp
|
| 70 |
+
+++ b/llama.cpp/tools/server/server-task.cpp
|
| 71 |
+
@@ -273,6 +273,8 @@ task_params server_task::params_from_json_cmpl(
|
| 72 |
+
params.n_cmpl = json_value(data, "n_cmpl", json_value(data, "n", 1));
|
| 73 |
+
params.n_cache_reuse = json_value(data, "n_cache_reuse", defaults.n_cache_reuse);
|
| 74 |
+
params.session_id = json_value(data, "session_id", std::string("")); // opencoti F5 M0 kv-reuse β see docs/features/advanced_kv.md
|
| 75 |
+
+ params.shared_pool_slot = json_value(data, "shared_pool_slot", -1); // opencoti-hook: poly-kv-pool β see docs/features/poly_kv.md
|
| 76 |
+
+ params.shared_prefix_n_tokens = json_value(data, "shared_prefix_n_tokens", 0); // opencoti-hook: poly-kv-pool β see docs/features/poly_kv.md
|
| 77 |
+
//params.t_max_prompt_ms = json_value(data, "t_max_prompt_ms", defaults.t_max_prompt_ms); // TODO: implement
|
| 78 |
+
params.t_max_predict_ms = json_value(data, "t_max_predict_ms", defaults.t_max_predict_ms);
|
| 79 |
+
params.response_fields = json_value(data, "response_fields", std::vector<std::string>());
|
| 80 |
+
diff --git a/llama.cpp/tools/server/server-task.h b/llama.cpp/tools/server/server-task.h
|
| 81 |
+
--- a/llama.cpp/tools/server/server-task.h
|
| 82 |
+
+++ b/llama.cpp/tools/server/server-task.h
|
| 83 |
+
@@ -72,6 +72,15 @@ struct task_params {
|
| 84 |
+
// number of prompt tokens before the latest user message
|
| 85 |
+
int32_t n_before_user = -1;
|
| 86 |
+
|
| 87 |
+
+ // opencoti-hook: poly-kv-pool β see docs/features/poly_kv.md
|
| 88 |
+
+ // F5 M6 PolyKV S1 (SharedKVPool). When shared_pool_slot >= 0, the server
|
| 89 |
+
+ // bit-shares this request's [0, shared_prefix_n_tokens) KV with the pool
|
| 90 |
+
+ // slot's already-resident read-only prefix (zero-copy cells.seq_add in
|
| 91 |
+
+ // unified mode), so N agents share ONE physical copy of the prefix and the
|
| 92 |
+
+ // prefix is neither re-stored nor re-processed. <0 (default) is fully inert.
|
| 93 |
+
+ int32_t shared_pool_slot = -1;
|
| 94 |
+
+ int32_t shared_prefix_n_tokens = 0;
|
| 95 |
+
+
|
| 96 |
+
int64_t t_max_prompt_ms = -1; // TODO: implement
|
| 97 |
+
int64_t t_max_predict_ms = -1; // if positive, limit the generation phase to this time limit
|
| 98 |
+
|
patches/0073-turboquant-kv.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0075-d512-turbo-vec.patch
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0075] F5 M6 PolyKV S2 β D=512 fused FA-VEC turbo2/turbo3 (Gemma-4 GLOBAL)
|
| 3 |
+
|
| 4 |
+
#411. The Level-B fused FA-VEC turbo read (0073) covered head_dim {128,256} only;
|
| 5 |
+
the Gemma-4 A4B GLOBAL layers use head_dim 512 (key_length=512) and therefore fell
|
| 6 |
+
back to the Level-A turbo->f16 materialize β a decode tax that scales with the
|
| 7 |
+
global-cache n_kv. This patch adds the head_dim-512 fused FA-VEC instances for
|
| 8 |
+
turbo2/turbo3 so the global cache stays IN-REGISTER:
|
| 9 |
+
|
| 10 |
+
- fattn-vec.cuh: extern DECL_FATTN_VEC_CASE(512, turbo2/3) (the kernel is already
|
| 11 |
+
D-generic: nthreads/nthreads_V derive from D arithmetically, KQ smem ~4-8KB,
|
| 12 |
+
VKQ ~16 regs at ncols=1 β well within budget; verified compile + run clean).
|
| 13 |
+
- fattn.cu: turbo kernel-selection relax (Q->ne[0]==512 routes to VEC) + the two
|
| 14 |
+
D=512 FATTN_VEC_CASE dispatch lines for turbo2/turbo3.
|
| 15 |
+
- template-instances/fattn-vec-instance-turbo{2,3}_0: emit the D=512 case; the
|
| 16 |
+
autogenerator (generate_cu_files.py) emits the same line so re-gen stays exact.
|
| 17 |
+
- llama-graph.cpp: build_attn_mha fuse gate extended (fused_turbo_512) so D=512
|
| 18 |
+
turbo2/turbo3 decode ubatches fuse; turbo4/turbo8 @ 512 keep Level-A.
|
| 19 |
+
|
| 20 |
+
Correctness is Parseval-EXACT: the WHT is block-diagonal over 128-blocks, so D=512
|
| 21 |
+
decomposes into 4 independent Parseval-exact 128-blocks (WHT(Q/s).K_stored ==
|
| 22 |
+
Q.K_true). Acceptance = LOGIT-EQUIVALENCE fused-vs-materialize (#355 harness),
|
| 23 |
+
real_frac=0.0 for BOTH turbo3 and turbo2 (bit-equivalent, not merely close).
|
| 24 |
+
Perf (the point): turbo3 @ ctx 27.7k, fused 41.0 vs materialize 29.7 tok/s = +38%,
|
| 25 |
+
scaling with n_kv. Gate: .opencoti/p411-d512-gate.sh (+ p411-perf.sh).
|
| 26 |
+
|
| 27 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh b/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh
|
| 28 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh
|
| 29 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh
|
| 30 |
+
@@ -618,3 +618,8 @@ EXTERN_DECL_FATTN_VEC_CASES(256, GGML_TYPE_BF16)
|
| 31 |
+
EXTERN_DECL_FATTN_VEC_TURBO(GGML_TYPE_TURBO2_0)
|
| 32 |
+
EXTERN_DECL_FATTN_VEC_TURBO(GGML_TYPE_TURBO3_0)
|
| 33 |
+
EXTERN_DECL_FATTN_VEC_TURBO(GGML_TYPE_TURBO4_0)
|
| 34 |
+
+
|
| 35 |
+
+// opencoti #411 (F5 M6-S2 Level-B): head_dim 512 fused VEC for turbo2/3 ONLY β Gemma-4 GLOBAL
|
| 36 |
+
+// layers (key_length=512). Definitions in the turbo2/turbo3 instance .cu files. turbo4 stays β€256.
|
| 37 |
+
+extern DECL_FATTN_VEC_CASE(512, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0);
|
| 38 |
+
+extern DECL_FATTN_VEC_CASE(512, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0);
|
| 39 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn.cu b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 40 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 41 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 42 |
+
@@ -337,6 +337,12 @@ static void ggml_cuda_flash_attn_ext_vec(ggml_backend_cuda_context & ctx, ggml_t
|
| 43 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0)
|
| 44 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_TURBO4_0, GGML_TYPE_TURBO4_0)
|
| 45 |
+
|
| 46 |
+
+ // opencoti #411 (F5 M6-S2 Level-B): head_dim 512 fused VEC for turbo2/3 ONLY (Gemma-4 GLOBAL
|
| 47 |
+
+ // layers). turbo4/turbo8 keep no D=512 instance (Level-A materialize). Matches the fattn.cu
|
| 48 |
+
+ // kernel-selection relax + the L3 graph gate (both turbo2/3 + D==512 only).
|
| 49 |
+
+ FATTN_VEC_CASE(512, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0)
|
| 50 |
+
+ FATTN_VEC_CASE(512, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0)
|
| 51 |
+
+
|
| 52 |
+
GGML_ABORT("fatal error");
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
@@ -474,7 +480,13 @@ static best_fattn_kernel ggml_cuda_get_best_fattn_kernel(const int device, const
|
| 56 |
+
// read turbo blocks. The graph (build_attn_mha, L3) only emits turbo K/V to FLASH_ATTN_EXT when
|
| 57 |
+
// it intends this path (head_dim β€ 256, decode); turbo8 / head_dim 512 take the materialize lift.
|
| 58 |
+
if (K->type == GGML_TYPE_TURBO2_0 || K->type == GGML_TYPE_TURBO3_0 || K->type == GGML_TYPE_TURBO4_0) {
|
| 59 |
+
- return can_use_vector_kernel ? BEST_FATTN_KERNEL_VEC : BEST_FATTN_KERNEL_NONE;
|
| 60 |
+
+ // opencoti #411 (F5 M6-S2 Level-B): turbo2/3 also ship a D=512 fused VEC instance for Gemma-4
|
| 61 |
+
+ // GLOBAL layers (key_length=512) so the 256k global cache reads in-register instead of the
|
| 62 |
+
+ // Level-A turboβf16 materialize. turbo4/turbo8 have no D=512 instance β keep the β€256 cap
|
| 63 |
+
+ // (Level-A fallback at 512). The L3 graph gate only emits turbo2/3 K/V to FA at 512 to match.
|
| 64 |
+
+ const bool turbo512_vec = Q->ne[0] == 512 && Q->ne[0] % 64 == 0 && K->ne[1] % FATTN_KQ_STRIDE == 0 &&
|
| 65 |
+
+ (K->type == GGML_TYPE_TURBO2_0 || K->type == GGML_TYPE_TURBO3_0);
|
| 66 |
+
+ return (can_use_vector_kernel || turbo512_vec) ? BEST_FATTN_KERNEL_VEC : BEST_FATTN_KERNEL_NONE;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// If Turing tensor cores are available, use them:
|
| 70 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo2_0-turbo2_0.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo2_0-turbo2_0.cu
|
| 71 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo2_0-turbo2_0.cu
|
| 72 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo2_0-turbo2_0.cu
|
| 73 |
+
@@ -6,3 +6,4 @@
|
| 74 |
+
DECL_FATTN_VEC_CASE( 64, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0);
|
| 75 |
+
DECL_FATTN_VEC_CASE(128, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0);
|
| 76 |
+
DECL_FATTN_VEC_CASE(256, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0);
|
| 77 |
+
+DECL_FATTN_VEC_CASE(512, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0); // opencoti #411 (F5 M6-S2 Level-B): Gemma-4 GLOBAL head_dim 512
|
| 78 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo3_0-turbo3_0.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo3_0-turbo3_0.cu
|
| 79 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo3_0-turbo3_0.cu
|
| 80 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-turbo3_0-turbo3_0.cu
|
| 81 |
+
@@ -6,3 +6,4 @@
|
| 82 |
+
DECL_FATTN_VEC_CASE( 64, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0);
|
| 83 |
+
DECL_FATTN_VEC_CASE(128, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0);
|
| 84 |
+
DECL_FATTN_VEC_CASE(256, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0);
|
| 85 |
+
+DECL_FATTN_VEC_CASE(512, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0); // opencoti #411 (F5 M6-S2 Level-B): Gemma-4 GLOBAL head_dim 512
|
| 86 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py b/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py
|
| 87 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py
|
| 88 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py
|
| 89 |
+
@@ -79,6 +79,10 @@ for type_k in TYPES_KV:
|
| 90 |
+
for type_t in ["GGML_TYPE_TURBO2_0", "GGML_TYPE_TURBO3_0", "GGML_TYPE_TURBO4_0"]:
|
| 91 |
+
with open(f"fattn-vec-instance-{get_short_name(type_t)}-{get_short_name(type_t)}.cu", "w") as f:
|
| 92 |
+
f.write(SOURCE_FATTN_VEC.format(type_k=type_t, type_v=type_t))
|
| 93 |
+
+ # opencoti #411 (F5 M6-S2 Level-B): head_dim 512 fused VEC for turbo2/3 ONLY (Gemma-4 GLOBAL
|
| 94 |
+
+ # layers, key_length=512). turbo4 has no D=512 instance (Level-A materialize fallback).
|
| 95 |
+
+ if type_t in ("GGML_TYPE_TURBO2_0", "GGML_TYPE_TURBO3_0"):
|
| 96 |
+
+ f.write(f"DECL_FATTN_VEC_CASE(512, {type_t}, {type_t}); // opencoti #411 (F5 M6-S2 Level-B): Gemma-4 GLOBAL head_dim 512\n")
|
| 97 |
+
|
| 98 |
+
for ncols in [8, 16, 32, 64]:
|
| 99 |
+
for ncols2 in [1, 2, 4, 8, 16, 32]:
|
| 100 |
+
diff --git a/llama.cpp/src/llama-graph.cpp b/llama.cpp/src/llama-graph.cpp
|
| 101 |
+
--- a/llama.cpp/src/llama-graph.cpp
|
| 102 |
+
+++ b/llama.cpp/src/llama-graph.cpp
|
| 103 |
+
@@ -2019,10 +2019,17 @@ ggml_tensor * llm_graph_context::build_attn_mha(
|
| 104 |
+
}();
|
| 105 |
+
const int64_t tbv_n_stream = k->ne[3] > 0 ? k->ne[3] : 1;
|
| 106 |
+
const int64_t tbv_nq_per_strm = q->ne[2] / tbv_n_stream;
|
| 107 |
+
+ // opencoti F5 M6-S2 Level-B #411: head_dim 512 (Gemma-4 GLOBAL layers, key_length=512) fuses
|
| 108 |
+
+ // for turbo2/turbo3 ONLY β the D=512 fused FA-VEC turbo instances added in #411 keep the 256k
|
| 109 |
+
+ // global cache in-register instead of the Level-A turboβf16 materialize (a ~0.55Γ decode tax that
|
| 110 |
+
+ // scales with n_kv). turbo4/turbo8 at 512 still take the materialize fallback (no D=512 instance).
|
| 111 |
+
+ // The WHT is block-diagonal over 128-blocks, so 512 = 4 Parseval-exact 128-blocks β exact.
|
| 112 |
+
+ const bool fused_turbo_512 =
|
| 113 |
+
+ k->ne[0] == 512 && (k->type == GGML_TYPE_TURBO2_0 || k->type == GGML_TYPE_TURBO3_0);
|
| 114 |
+
const bool fused_turbo =
|
| 115 |
+
cparams.flash_attn && kq_b == nullptr &&
|
| 116 |
+
is_turbo_vec(k->type) && k->type == v->type &&
|
| 117 |
+
- (k->ne[0] == 128 || k->ne[0] == 256) &&
|
| 118 |
+
+ (k->ne[0] == 128 || k->ne[0] == 256 || fused_turbo_512) &&
|
| 119 |
+
tbv_nq_per_strm <= tbv_fuse_max_nq;
|
| 120 |
+
|
| 121 |
+
if (fused_turbo) {
|
patches/0078-dca.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0079-gemma-assistant-mtp.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0080-mtp-d512-fa.patch
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0080] Gemma-4 assistant-MTP D512/gqa<=2 flash-attn β real f16 VEC kernel (#491+#493, bug-567)
|
| 3 |
+
|
| 4 |
+
The Gemma-4 E4B assistant-MTP drafter's single GLOBAL (non-SWA) layer is head_count 4 /
|
| 5 |
+
head_count_kv 2 -> gqa_ratio 2 at head_dim 512. On the 0.10.3 base NEITHER the MMA
|
| 6 |
+
(switch_ncols2, fattn.cu) NOR the TILE (switch_ncols2, fattn-tile.cuh) D512 path instantiates
|
| 7 |
+
ncols2 in {1,2} (gated DV<=256 for binary size), so a D512/gqa<=2 flash-attn op aborts at
|
| 8 |
+
runtime (bug-567). Every other Gemma-4 config takes ncols2 in {4,8} and is unaffected
|
| 9 |
+
(A4B/12B/31B and the E4B base are gqa>=4; A4B globals are head16/kv2 -> gqa 8).
|
| 10 |
+
|
| 11 |
+
#491 (host stop-gap): a guard in build_attn_mha (llama-graph.cpp) routed the D512/gqa<=2 layer
|
| 12 |
+
to the EXISTING non-FA explicit-softmax branch. Correct but not a real flash-attn β "fa off" on
|
| 13 |
+
the hot path.
|
| 14 |
+
|
| 15 |
+
#493 (this patch, the real fix): add a REAL D512 f16 flash-attn via the VEC kernel, and narrow
|
| 16 |
+
the #491 guard so f16/f32 K/V take it; the guard now fires ONLY for the residual with no D512
|
| 17 |
+
kernel (quantized / bf16 K/V), which the assistant drafter never hits.
|
| 18 |
+
- template-instances/fattn-vec-instance-f16-f16.cu + generate_cu_files.py:
|
| 19 |
+
DECL_FATTN_VEC_CASE(512, GGML_TYPE_F16, GGML_TYPE_F16) instance (the autogen line + the file).
|
| 20 |
+
- ggml-cuda/fattn-vec.cuh: extern decl for the D512 f16 instance.
|
| 21 |
+
- ggml-cuda/fattn.cu: (a) selector route in ggml_cuda_get_best_fattn_kernel β D512 && gqa_ratio<=2
|
| 22 |
+
&& gqa_opt_applies && K/V f16 -> BEST_FATTN_KERNEL_VEC; (b) the matching VEC dispatch case
|
| 23 |
+
FATTN_VEC_CASE(512, F16, F16) in ggml_cuda_flash_attn_ext_vec, BEFORE the GGML_ABORT.
|
| 24 |
+
NOTE: fattn.cu has TWO independent head-size switches (selector + VEC dispatch); both need the
|
| 25 |
+
D512 case or the selector picks VEC but the dispatch falls through to a decode-time abort.
|
| 26 |
+
- llama.cpp/src/llama-graph.cpp: narrow the #491 guard β fa_no_gpu_kernel fires only when K/V are
|
| 27 |
+
NOT f16/f32 (fa_d512_f16_vec), so f16 D512/gqa<=2 now flash-attns on-GPU.
|
| 28 |
+
|
| 29 |
+
The VEC kernel tiles Q->ne[1] (cols_per_block 1 decode / 2 prefill+verify) so the one f16 D512
|
| 30 |
+
instance is a real flash-attn at ANY n_q (covers decode, prefill, and the spec-verify batch).
|
| 31 |
+
logit_softcap is forced off for D!=128,256 in the VEC kernel; safe because Gemma-4 attn_soft_cap
|
| 32 |
+
is false (softcap 0 -> use_logit_softcap false).
|
| 33 |
+
|
| 34 |
+
Gate (.opencoti/m493-fa-d512-gate.sh): E4B -fa on assistant-MTP decodes 147.86 tps with NO abort;
|
| 35 |
+
the verified-greedy output is BYTE-IDENTICAL to the #491 non-FA soft_max path and draft acceptance
|
| 36 |
+
is unchanged (0.67910 both), so the new VEC FA is numerically correct (exact spec-decode validates
|
| 37 |
+
every token, and a wrong FA would collapse acceptance). A4B (gqa 8, untouched MMA path) regresses
|
| 38 |
+
nowhere. Both DSO paths rebuilt byte-identical (sha256 a8ddcd82).
|
| 39 |
+
|
| 40 |
+
Marker convention: every changed file carries an `opencoti #491`/`opencoti #493` marker; see
|
| 41 |
+
docs/protocols/UPSTREAM_SYNC.md. All hunks are nested (llama.cpp/<path>); no outer-vendor delta.
|
| 42 |
+
|
| 43 |
+
diff --git a/llama.cpp/src/llama-graph.cpp b/llama.cpp/src/llama-graph.cpp
|
| 44 |
+
--- a/llama.cpp/src/llama-graph.cpp
|
| 45 |
+
+++ b/llama.cpp/src/llama-graph.cpp
|
| 46 |
+
@@ -2122,7 +2122,29 @@
|
| 47 |
+
|
| 48 |
+
ggml_tensor * cur;
|
| 49 |
+
|
| 50 |
+
- const bool use_flash_attn = cparams.flash_attn && kq_b == nullptr;
|
| 51 |
+
+ // opencoti F5 M6-S4 mtp (#441/#491): head_dim 512 has NO GPU flash-attn kernel for gqa_ratio β€ 2.
|
| 52 |
+
+ // Both the MMA (ggml_cuda_flash_attn_ext_mma_f16_switch_ncols2, fattn.cu) and TILE
|
| 53 |
+
+ // (launch_fattn_tile_switch_ncols2, fattn-tile.cuh) D512 paths only instantiate ncols2 β {4,8}
|
| 54 |
+
+ // (gqa_ratio > 2); the ncols2 β {1,2} variants are gated out for binary size (DV<=256 only), so a
|
| 55 |
+
+ // D512/gqaβ€2 flash-attn op aborts at runtime (fattn.cu:132 / fattn-tile.cuh:1322). That is exactly
|
| 56 |
+
+ // the Gemma-4 E4B assistant-MTP drafter's single GLOBAL layer: head_count 4 / head_count_kv 2 β
|
| 57 |
+
+ // gqa_ratio 2. Route it through the EXISTING non-FA explicit-softmax branch below (any head_dim/gqa
|
| 58 |
+
+ // β see the "non-flash-attn branch" fallback note at the top of this fn). q/k here are post-permute
|
| 59 |
+
+ // (flash-attn layout), so q->ne[2]/k->ne[2] == the CUDA kernel's gqa_ratio and k->ne[0] == head_dim.
|
| 60 |
+
+ // NARROW: every working D512 model (A4B/E2B/12B/31B + the E4B base, gqa_ratio β₯ 4) keeps flash-attn,
|
| 61 |
+
+ // byte-unchanged; only the gqaβ€2 case is special.
|
| 62 |
+
+ // opencoti #493 (F5 M6-S4): D512/gqaβ€2 with f16 (or f32βf16-cast) K AND V now has a REAL GPU
|
| 63 |
+
+ // flash-attn β the f16 D512 VEC instance, which tiles Q->ne[1] (cols_per_block 1 decode / 2
|
| 64 |
+
+ // prefill+verify) so it computes at ANY n_q (see ggml_cuda_get_best_fattn_kernel, fattn.cu). So
|
| 65 |
+
+ // -fa on COMPUTES on-GPU for the E4B drafter (its KV is f16) instead of diverting to soft_max; no
|
| 66 |
+
+ // global fa-off, no non-FA fallback for the real path. The guard now fires ONLY for the residual
|
| 67 |
+
+ // with no D512/gqaβ€2 kernel: QUANTIZED or bf16 K/V (the VEC D512 instance is f16-only; MMA/TILE
|
| 68 |
+
+ // abort, bug-567) β an exotic config the assistant drafter never hits. f16/f32 K/V β flash-attn.
|
| 69 |
+
+ const bool fa_d512_f16_vec = (k->type == GGML_TYPE_F16 || k->type == GGML_TYPE_F32) &&
|
| 70 |
+
+ (v->type == GGML_TYPE_F16 || v->type == GGML_TYPE_F32);
|
| 71 |
+
+ const bool fa_no_gpu_kernel = k->ne[0] == 512 && k->ne[2] > 0 && (q->ne[2] / k->ne[2]) <= 2 &&
|
| 72 |
+
+ !fa_d512_f16_vec;
|
| 73 |
+
+ const bool use_flash_attn = cparams.flash_attn && kq_b == nullptr && !fa_no_gpu_kernel;
|
| 74 |
+
if (use_flash_attn) {
|
| 75 |
+
GGML_ASSERT(kq_b == nullptr && "Flash attention does not support KQ bias yet");
|
| 76 |
+
|
| 77 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn.cu b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 78 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 79 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 80 |
+
@@ -360,6 +360,13 @@
|
| 81 |
+
FATTN_VEC_CASE(512, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0)
|
| 82 |
+
FATTN_VEC_CASE(512, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0)
|
| 83 |
+
|
| 84 |
+
+ // opencoti #493 (F5 M6-S4): head_dim 512 f16 VEC dispatch β the runtime counterpart to the
|
| 85 |
+
+ // selector's D512/gqa<=2 f16 VEC route (ggml_cuda_get_best_fattn_kernel) + the f16-f16 D512
|
| 86 |
+
+ // instance. Without this case the selector picks VEC but the dispatch falls through to the abort
|
| 87 |
+
+ // below (bug-567 reappeared as a *decode-time* fatal at fattn.cu:363). The macro accepts F16 and
|
| 88 |
+
+ // F32βF16 for both K and V, matching the graph guard. Gemma-4 E4B assistant-MTP drafter global layer.
|
| 89 |
+
+ FATTN_VEC_CASE(512, GGML_TYPE_F16, GGML_TYPE_F16)
|
| 90 |
+
+
|
| 91 |
+
GGML_ABORT("fatal error");
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
@@ -513,6 +520,20 @@
|
| 95 |
+
return (can_use_vector_kernel || turbo512_vec) ? BEST_FATTN_KERNEL_VEC : BEST_FATTN_KERNEL_NONE;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
+ // opencoti #493 (F5 M6-S4): D512 (Gemma-4 GLOBAL layer) at gqa_ratio <= 2 β the E4B assistant-MTP
|
| 99 |
+
+ // drafter (head_count 4 / head_count_kv 2 β gqa 2) β has NO MMA/TILE kernel: switch_ncols2 aborts
|
| 100 |
+
+ // for DKQ>256 && gqa<=2 (the ncols2 β {1,2} D512 instances are if-constexpr'd out, bug-567). The
|
| 101 |
+
+ // f16 D512 VEC instance (#493) tiles Q->ne[1] (cols_per_block 1 for decode, 2 for prefill/verify β
|
| 102 |
+
+ // ggml_cuda_flash_attn_ext_vec_case) so it is a REAL flash-attn at ANY n_q. Route f16/f16 D512 here
|
| 103 |
+
+ // when gqa<=2 so -fa on COMPUTES instead of diverting to the #491 non-FA host guard. D512/gqa>2
|
| 104 |
+
+ // (A4B / 12B / 31B / E4B base) keeps the faster MMA ncols2 β {4,8} path β byte-unchanged. Past the
|
| 105 |
+
+ // case-512 check above, gqa_opt_applies already holds (so gqa_ratio>=2); gqa_ratio<=2 β exactly 2.
|
| 106 |
+
+ if (Q->ne[0] == 512 && gqa_ratio <= 2 && gqa_opt_applies &&
|
| 107 |
+
+ K->type == GGML_TYPE_F16 && V->type == GGML_TYPE_F16 &&
|
| 108 |
+
+ K->ne[1] % FATTN_KQ_STRIDE == 0) {
|
| 109 |
+
+ return BEST_FATTN_KERNEL_VEC;
|
| 110 |
+
+ }
|
| 111 |
+
+
|
| 112 |
+
// If Turing tensor cores are available, use them:
|
| 113 |
+
if (turing_mma_available(cc) && Q->ne[0] != 40 && Q->ne[0] != 72) {
|
| 114 |
+
if (can_use_vector_kernel) {
|
| 115 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh b/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh
|
| 116 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh
|
| 117 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn-vec.cuh
|
| 118 |
+
@@ -631,3 +631,8 @@
|
| 119 |
+
// layers (key_length=512). Definitions in the turbo2/turbo3 instance .cu files. turbo4 stays β€256.
|
| 120 |
+
extern DECL_FATTN_VEC_CASE(512, GGML_TYPE_TURBO2_0, GGML_TYPE_TURBO2_0);
|
| 121 |
+
extern DECL_FATTN_VEC_CASE(512, GGML_TYPE_TURBO3_0, GGML_TYPE_TURBO3_0);
|
| 122 |
+
+
|
| 123 |
+
+// opencoti #493 (F5 M6-S4): head_dim 512 f16 VEC β Gemma-4 GLOBAL layer at gqa_ratio<=2 (the E4B
|
| 124 |
+
+// assistant-MTP drafter), where MMA/TILE abort (DKQ>256 && gqa<=2, bug-567). The VEC kernel tiles
|
| 125 |
+
+// Q->ne[1] so it is a real flash-attn at ANY n_q. Definition in fattn-vec-instance-f16-f16.cu.
|
| 126 |
+
+extern DECL_FATTN_VEC_CASE(512, GGML_TYPE_F16, GGML_TYPE_F16);
|
| 127 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-f16-f16.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-f16-f16.cu
|
| 128 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-f16-f16.cu
|
| 129 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-f16-f16.cu
|
| 130 |
+
@@ -5,3 +5,4 @@
|
| 131 |
+
DECL_FATTN_VEC_CASE( 64, GGML_TYPE_F16, GGML_TYPE_F16);
|
| 132 |
+
DECL_FATTN_VEC_CASE(128, GGML_TYPE_F16, GGML_TYPE_F16);
|
| 133 |
+
DECL_FATTN_VEC_CASE(256, GGML_TYPE_F16, GGML_TYPE_F16);
|
| 134 |
+
+DECL_FATTN_VEC_CASE(512, GGML_TYPE_F16, GGML_TYPE_F16); // opencoti #493: Gemma-4 GLOBAL head_dim 512 f16, gqa<=2
|
| 135 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py b/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py
|
| 136 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py
|
| 137 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/generate_cu_files.py
|
| 138 |
+
@@ -74,6 +74,17 @@
|
| 139 |
+
with open(f"fattn-vec-instance-{get_short_name(type_k)}-{get_short_name(type_v)}.cu", "w") as f:
|
| 140 |
+
f.write(SOURCE_FATTN_VEC.format(type_k=type_k, type_v=type_v))
|
| 141 |
+
|
| 142 |
+
+# opencoti #493 (F5 M6-S4): head_dim 512 f16 VEC for Gemma-4 GLOBAL layers at gqa_ratio<=2 (the E4B
|
| 143 |
+
+# assistant-MTP drafter's single global layer is head_count 4 / head_count_kv 2 β gqa 2). The MMA and
|
| 144 |
+
+# TILE D512 paths only instantiate ncols2 β {4,8} (gqa>2); ncols2 β {1,2} are if-constexpr'd out, so a
|
| 145 |
+
+# D512/gqa<=2 op aborts at runtime (fattn.cu switch_ncols2, bug-567). The VEC kernel tiles Q->ne[1]
|
| 146 |
+
+# (cols_per_block 1 for decode, 2 for prefill/verify β ggml_cuda_flash_attn_ext_vec_case) so it is a
|
| 147 |
+
+# REAL flash-attn at ANY n_q for D512. Mirrors #411's turbo512 but for plain f16 K/V β -fa on COMPUTES
|
| 148 |
+
+# instead of falling back to the #491 non-FA host guard. f16/f16 only (drafter KV is f16); quantized
|
| 149 |
+
+# D512/gqa<=2 keeps the guard (no VEC D512 quant instance).
|
| 150 |
+
+with open("fattn-vec-instance-f16-f16.cu", "a") as f:
|
| 151 |
+
+ f.write("DECL_FATTN_VEC_CASE(512, GGML_TYPE_F16, GGML_TYPE_F16); // opencoti #493: Gemma-4 GLOBAL head_dim 512 f16, gqa<=2\n")
|
| 152 |
+
+
|
| 153 |
+
# opencoti-hook: turboquant perf-lift (Level B) β F5 M6-S2 #392. Fused turbo K/V vec instances
|
| 154 |
+
# (K==V same tier only; the in-register readers live in fattn-turbo.cuh).
|
| 155 |
+
for type_t in ["GGML_TYPE_TURBO2_0", "GGML_TYPE_TURBO3_0", "GGML_TYPE_TURBO4_0"]:
|
patches/0081-tcq-q6-asym-kv.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0082-cuda-build-parallelism.patch
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/llamafile/cuda.sh b/llamafile/cuda.sh
|
| 2 |
+
--- a/llamafile/cuda.sh
|
| 3 |
+
+++ b/llamafile/cuda.sh
|
| 4 |
+
@@ -259,6 +259,22 @@
|
| 5 |
+
COMMON_FLAGS="$COMMON_FLAGS -DGGML_CUDA_FA_ALL_QUANTS"
|
| 6 |
+
fi
|
| 7 |
+
|
| 8 |
+
+# opencoti-hook: cuda-build-parallelism (#531) β nvcc intra-TU arch threading. Each nvcc TU compiles
|
| 9 |
+
+# the gencode arches (sm_75/80/86/89/90/120f); without --threads they run serially, so any TU left
|
| 10 |
+
+# alone at the build tail uses one core while the rest idle. --threads compiles the arches
|
| 11 |
+
+# concurrently. To avoid oversubscribing the bulk (JOBS nvcc already saturate the cores), we divide
|
| 12 |
+
+# the outer job-slot count by NVCC_THREADS so PEAK concurrency stays β JOBS (slots Γ threads). The
|
| 13 |
+
+# Windows cuda.bat already passes --threads 5; the Linux path lacked it. Tunable: NVCC_THREADS=1
|
| 14 |
+
+# disables (restores pre-#531 behavior). See docs/protocols/LLAMAFILE_UPGRADE.md.
|
| 15 |
+
+NVCC_THREADS="${NVCC_THREADS:-2}"
|
| 16 |
+
+JOBS_EFF="$JOBS"
|
| 17 |
+
+if [ "${NVCC_THREADS}" -gt 1 ] 2>/dev/null; then
|
| 18 |
+
+ COMMON_FLAGS="$COMMON_FLAGS --threads ${NVCC_THREADS}"
|
| 19 |
+
+ JOBS_EFF=$(( JOBS / NVCC_THREADS ))
|
| 20 |
+
+ [ "$JOBS_EFF" -lt 1 ] && JOBS_EFF=1
|
| 21 |
+
+fi
|
| 22 |
+
+echo " nvcc --threads ${NVCC_THREADS} β ${JOBS_EFF} job-slots Γ ${NVCC_THREADS} arch-threads (#531)"
|
| 23 |
+
+
|
| 24 |
+
# Collect sources
|
| 25 |
+
collect_gpu_sources "$GGML_CUDA_DIR" "$EXTRA_SOURCES" "$NO_IQ_QUANTS" "$FA_ALL_QUANTS"
|
| 26 |
+
echo " Sources: $NUM_SOURCES .cu files"
|
| 27 |
+
@@ -267,7 +283,7 @@
|
| 28 |
+
START_TIME=$(date +%s)
|
| 29 |
+
|
| 30 |
+
# Compile GPU sources
|
| 31 |
+
-compile_gpu_sources_parallel "$NVCC" "$ARCH_FLAGS" "$COMMON_FLAGS" "$BUILD_DIR" "$JOBS"
|
| 32 |
+
+compile_gpu_sources_parallel "$NVCC" "$ARCH_FLAGS" "$COMMON_FLAGS" "$BUILD_DIR" "$JOBS_EFF"
|
| 33 |
+
|
| 34 |
+
COMPILE_TIME=$(date +%s)
|
| 35 |
+
echo "Compilation took $((COMPILE_TIME - START_TIME)) seconds"
|
| 36 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh b/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh
|
| 37 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh
|
| 38 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh
|
| 39 |
+
@@ -2600,3 +2600,41 @@
|
| 40 |
+
extern DECL_FATTN_MMA_F16_CASE(576, 512, 16, 4);
|
| 41 |
+
extern DECL_FATTN_MMA_F16_CASE(576, 512, 1, 32);
|
| 42 |
+
extern DECL_FATTN_MMA_F16_CASE(576, 512, 2, 32);
|
| 43 |
+
+
|
| 44 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β shard the DCA-fused MMA case into parallel
|
| 45 |
+
+// instance TUs (template-instances/fattn-mma-f16-dca-fused-instance-*.cu), mirroring the stock
|
| 46 |
+
+// DECL_FATTN_MMA_F16_CASE sharding above. Before this, fattn.cu's dca-fused dispatch
|
| 47 |
+
+// (ggml_cuda_flash_attn_ext_mma_f16_dca_fused_switch) instantiated all 48
|
| 48 |
+
+// ggml_cuda_flash_attn_ext_mma_f16_dca_fused_case<DKQ,DV,ncols1,ncols2> INLINE β DKQ==DV β {128,256,512}
|
| 49 |
+
+// Γ ncols2 β {1,2,4,8} Γ ncols1 β {8,16,32,64}/ncols2 β 576 device kernels in ONE TU = a ~52-min
|
| 50 |
+
+// SOLO compile tail (fattn.o was 29 MB, the largest object, finishing ~52 min after every other TU).
|
| 51 |
+
+// These extern decls move the instantiation to the parallel bulk; fattn.cu now just references the
|
| 52 |
+
+// symbols (the regular MMA/TILE/VEC cases were already sharded this way; only our DCA kernel wasn't).
|
| 53 |
+
+// The instance set matches the dispatch 1:1 β every reachable _case<DKQ,DV,ncols1,ncols2> has a
|
| 54 |
+
+// definition, none extra β so the linked DSO is byte-for-byte equivalent (same 576 kernels, relocated).
|
| 55 |
+
+#define DECL_FATTN_MMA_F16_DCA_FUSED_CASE(DKQ, DV, ncols1, ncols2) \
|
| 56 |
+
+ template void ggml_cuda_flash_attn_ext_mma_f16_dca_fused_case \
|
| 57 |
+
+ <DKQ, DV, ncols1, ncols2>(ggml_backend_cuda_context & ctx, ggml_tensor * dst) \
|
| 58 |
+
+
|
| 59 |
+
+// DCA always uses DKQ==DV β {128,256,512}; one extern per (ncols1,ncols2) across all three head dims.
|
| 60 |
+
+#define EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(ncols1, ncols2) \
|
| 61 |
+
+ extern DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, ncols1, ncols2); \
|
| 62 |
+
+ extern DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, ncols1, ncols2); \
|
| 63 |
+
+ extern DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, ncols1, ncols2); \
|
| 64 |
+
+
|
| 65 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 8, 1)
|
| 66 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(16, 1)
|
| 67 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(32, 1)
|
| 68 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(64, 1)
|
| 69 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 4, 2)
|
| 70 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 8, 2)
|
| 71 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(16, 2)
|
| 72 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(32, 2)
|
| 73 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 2, 4)
|
| 74 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 4, 4)
|
| 75 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 8, 4)
|
| 76 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ(16, 4)
|
| 77 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 1, 8)
|
| 78 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 2, 8)
|
| 79 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 4, 8)
|
| 80 |
+
+EXTERN_DECL_FATTN_MMA_F16_DCA_FUSED_ALL_DKQ( 8, 8)
|
| 81 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_1.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_1.cu
|
| 82 |
+
new file mode 100644
|
| 83 |
+
--- /dev/null
|
| 84 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_1.cu
|
| 85 |
+
@@ -0,0 +1,10 @@
|
| 86 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 87 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 88 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 89 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 90 |
+
+
|
| 91 |
+
+#include "../fattn-mma-f16.cuh"
|
| 92 |
+
+
|
| 93 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 16, 1);
|
| 94 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 16, 1);
|
| 95 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 16, 1);
|
| 96 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_2.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_2.cu
|
| 97 |
+
new file mode 100644
|
| 98 |
+
--- /dev/null
|
| 99 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_2.cu
|
| 100 |
+
@@ -0,0 +1,10 @@
|
| 101 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 102 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 103 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 104 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 105 |
+
+
|
| 106 |
+
+#include "../fattn-mma-f16.cuh"
|
| 107 |
+
+
|
| 108 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 16, 2);
|
| 109 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 16, 2);
|
| 110 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 16, 2);
|
| 111 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_4.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_4.cu
|
| 112 |
+
new file mode 100644
|
| 113 |
+
--- /dev/null
|
| 114 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_16-ncols2_4.cu
|
| 115 |
+
@@ -0,0 +1,10 @@
|
| 116 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 117 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 118 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 119 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 120 |
+
+
|
| 121 |
+
+#include "../fattn-mma-f16.cuh"
|
| 122 |
+
+
|
| 123 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 16, 4);
|
| 124 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 16, 4);
|
| 125 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 16, 4);
|
| 126 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_1-ncols2_8.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_1-ncols2_8.cu
|
| 127 |
+
new file mode 100644
|
| 128 |
+
--- /dev/null
|
| 129 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_1-ncols2_8.cu
|
| 130 |
+
@@ -0,0 +1,10 @@
|
| 131 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 132 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 133 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 134 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 135 |
+
+
|
| 136 |
+
+#include "../fattn-mma-f16.cuh"
|
| 137 |
+
+
|
| 138 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 1, 8);
|
| 139 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 1, 8);
|
| 140 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 1, 8);
|
| 141 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_2-ncols2_4.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_2-ncols2_4.cu
|
| 142 |
+
new file mode 100644
|
| 143 |
+
--- /dev/null
|
| 144 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_2-ncols2_4.cu
|
| 145 |
+
@@ -0,0 +1,10 @@
|
| 146 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 147 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 148 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 149 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 150 |
+
+
|
| 151 |
+
+#include "../fattn-mma-f16.cuh"
|
| 152 |
+
+
|
| 153 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 2, 4);
|
| 154 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 2, 4);
|
| 155 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 2, 4);
|
| 156 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_2-ncols2_8.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_2-ncols2_8.cu
|
| 157 |
+
new file mode 100644
|
| 158 |
+
--- /dev/null
|
| 159 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_2-ncols2_8.cu
|
| 160 |
+
@@ -0,0 +1,10 @@
|
| 161 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 162 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 163 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 164 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 165 |
+
+
|
| 166 |
+
+#include "../fattn-mma-f16.cuh"
|
| 167 |
+
+
|
| 168 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 2, 8);
|
| 169 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 2, 8);
|
| 170 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 2, 8);
|
| 171 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_32-ncols2_1.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_32-ncols2_1.cu
|
| 172 |
+
new file mode 100644
|
| 173 |
+
--- /dev/null
|
| 174 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_32-ncols2_1.cu
|
| 175 |
+
@@ -0,0 +1,10 @@
|
| 176 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 177 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 178 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 179 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 180 |
+
+
|
| 181 |
+
+#include "../fattn-mma-f16.cuh"
|
| 182 |
+
+
|
| 183 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 32, 1);
|
| 184 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 32, 1);
|
| 185 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 32, 1);
|
| 186 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_32-ncols2_2.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_32-ncols2_2.cu
|
| 187 |
+
new file mode 100644
|
| 188 |
+
--- /dev/null
|
| 189 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_32-ncols2_2.cu
|
| 190 |
+
@@ -0,0 +1,10 @@
|
| 191 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 192 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 193 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 194 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 195 |
+
+
|
| 196 |
+
+#include "../fattn-mma-f16.cuh"
|
| 197 |
+
+
|
| 198 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 32, 2);
|
| 199 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 32, 2);
|
| 200 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 32, 2);
|
| 201 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_2.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_2.cu
|
| 202 |
+
new file mode 100644
|
| 203 |
+
--- /dev/null
|
| 204 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_2.cu
|
| 205 |
+
@@ -0,0 +1,10 @@
|
| 206 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 207 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 208 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 209 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 210 |
+
+
|
| 211 |
+
+#include "../fattn-mma-f16.cuh"
|
| 212 |
+
+
|
| 213 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 4, 2);
|
| 214 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 4, 2);
|
| 215 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 4, 2);
|
| 216 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_4.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_4.cu
|
| 217 |
+
new file mode 100644
|
| 218 |
+
--- /dev/null
|
| 219 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_4.cu
|
| 220 |
+
@@ -0,0 +1,10 @@
|
| 221 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 222 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 223 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 224 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 225 |
+
+
|
| 226 |
+
+#include "../fattn-mma-f16.cuh"
|
| 227 |
+
+
|
| 228 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 4, 4);
|
| 229 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 4, 4);
|
| 230 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 4, 4);
|
| 231 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_8.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_8.cu
|
| 232 |
+
new file mode 100644
|
| 233 |
+
--- /dev/null
|
| 234 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_4-ncols2_8.cu
|
| 235 |
+
@@ -0,0 +1,10 @@
|
| 236 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 237 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 238 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 239 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 240 |
+
+
|
| 241 |
+
+#include "../fattn-mma-f16.cuh"
|
| 242 |
+
+
|
| 243 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 4, 8);
|
| 244 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 4, 8);
|
| 245 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 4, 8);
|
| 246 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_64-ncols2_1.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_64-ncols2_1.cu
|
| 247 |
+
new file mode 100644
|
| 248 |
+
--- /dev/null
|
| 249 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_64-ncols2_1.cu
|
| 250 |
+
@@ -0,0 +1,10 @@
|
| 251 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 252 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 253 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 254 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 255 |
+
+
|
| 256 |
+
+#include "../fattn-mma-f16.cuh"
|
| 257 |
+
+
|
| 258 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 64, 1);
|
| 259 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 64, 1);
|
| 260 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 64, 1);
|
| 261 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_1.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_1.cu
|
| 262 |
+
new file mode 100644
|
| 263 |
+
--- /dev/null
|
| 264 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_1.cu
|
| 265 |
+
@@ -0,0 +1,10 @@
|
| 266 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 267 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 268 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 269 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 270 |
+
+
|
| 271 |
+
+#include "../fattn-mma-f16.cuh"
|
| 272 |
+
+
|
| 273 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 8, 1);
|
| 274 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 8, 1);
|
| 275 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 8, 1);
|
| 276 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_2.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_2.cu
|
| 277 |
+
new file mode 100644
|
| 278 |
+
--- /dev/null
|
| 279 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_2.cu
|
| 280 |
+
@@ -0,0 +1,10 @@
|
| 281 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 282 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 283 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 284 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 285 |
+
+
|
| 286 |
+
+#include "../fattn-mma-f16.cuh"
|
| 287 |
+
+
|
| 288 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 8, 2);
|
| 289 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 8, 2);
|
| 290 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 8, 2);
|
| 291 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_4.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_4.cu
|
| 292 |
+
new file mode 100644
|
| 293 |
+
--- /dev/null
|
| 294 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_4.cu
|
| 295 |
+
@@ -0,0 +1,10 @@
|
| 296 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 297 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 298 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 299 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 300 |
+
+
|
| 301 |
+
+#include "../fattn-mma-f16.cuh"
|
| 302 |
+
+
|
| 303 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 8, 4);
|
| 304 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 8, 4);
|
| 305 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 8, 4);
|
| 306 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_8.cu b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_8.cu
|
| 307 |
+
new file mode 100644
|
| 308 |
+
--- /dev/null
|
| 309 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-mma-f16-dca-fused-instance-ncols1_8-ncols2_8.cu
|
| 310 |
+
@@ -0,0 +1,10 @@
|
| 311 |
+
+// opencoti-hook: cuda-build-parallelism (#531) β DCA-fused MMA case instance, relocated out of
|
| 312 |
+
+// fattn.cu so it compiles in the parallel bulk instead of inflating fattn.cu's solo tail.
|
| 313 |
+
+// Mirrors template-instances/fattn-mma-f16-instance-*.cu. extern decls live in ../fattn-mma-f16.cuh.
|
| 314 |
+
+// DKQ==DV β {128,256,512} (the only head dims DCA dispatches: Gemma-4 512, qwen35moe 256, qwen* 128).
|
| 315 |
+
+
|
| 316 |
+
+#include "../fattn-mma-f16.cuh"
|
| 317 |
+
+
|
| 318 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(128, 128, 8, 8);
|
| 319 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(256, 256, 8, 8);
|
| 320 |
+
+DECL_FATTN_MMA_F16_DCA_FUSED_CASE(512, 512, 8, 8);
|
patches/0083-tcq-warp-encode.patch
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/set-rows.cu b/llama.cpp/ggml/src/ggml-cuda/set-rows.cu
|
| 2 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/set-rows.cu
|
| 3 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/set-rows.cu
|
| 4 |
+
@@ -748,33 +748,17 @@
|
| 5 |
+
const int64_t s10_i = nb10/sizeof(idx_t), s11_i = nb11/sizeof(idx_t), s12_i = nb12/sizeof(idx_t);
|
| 6 |
+
const int iq_is_k = (strncmp(dst->name, "cache_k_", 8) == 0) ? 1 : 0;
|
| 7 |
+
|
| 8 |
+
- // one-time per-device: prefer the 8 KB/block shared-memory backtrace (opt-in); else global fallback buffer
|
| 9 |
+
- static int tcq3_use_shared_bt[GGML_CUDA_MAX_DEVICES] = {};
|
| 10 |
+
- static bool tcq3_bt_checked [GGML_CUDA_MAX_DEVICES] = {};
|
| 11 |
+
- constexpr int tcq3_bt_shared_bytes = 128 * 64;
|
| 12 |
+
- if (!tcq3_bt_checked[ctx.device]) {
|
| 13 |
+
- tcq3_bt_checked[ctx.device] = true;
|
| 14 |
+
- const char * env = getenv("TURBO_TCQ_SHARED_BT");
|
| 15 |
+
- if (!env || atoi(env) != 0) {
|
| 16 |
+
- int max_shared_optin = 0;
|
| 17 |
+
- CUDA_CHECK(cudaDeviceGetAttribute(&max_shared_optin, cudaDevAttrMaxSharedMemoryPerBlockOptin, ctx.device));
|
| 18 |
+
- if (max_shared_optin >= tcq3_bt_shared_bytes) {
|
| 19 |
+
- if (tcq_fp64_enabled())
|
| 20 |
+
- CUDA_SET_SHARED_MEMORY_LIMIT((k_set_rows_turbo3_tcq<idx_t, double>), tcq3_bt_shared_bytes);
|
| 21 |
+
- else
|
| 22 |
+
- CUDA_SET_SHARED_MEMORY_LIMIT(k_set_rows_turbo3_tcq<idx_t>, tcq3_bt_shared_bytes);
|
| 23 |
+
- tcq3_use_shared_bt[ctx.device] = 1;
|
| 24 |
+
- }
|
| 25 |
+
- }
|
| 26 |
+
- }
|
| 27 |
+
- if (!tcq3_use_shared_bt[ctx.device]) ensure_tcq_bt_buf(ctx.device, ne_total_groups * 128 * 64);
|
| 28 |
+
+ // #533 warp-synchronous encode: bt is always a global per-group slab. With TCQ3_WPB groups/block a shared-bt
|
| 29 |
+
+ // would cost WPB*8KB; and the warp kernel is no longer barrier-bound, so global bt is free here. One 8KB slab
|
| 30 |
+
+ // per group (indexed by `group`, not blockIdx.x). tcq_bt_buf grows monotonically and is reused across launches.
|
| 31 |
+
+ ensure_tcq_bt_buf(ctx.device, ne_total_groups * 128 * 64);
|
| 32 |
+
|
| 33 |
+
const uint3 ne00_fd = init_fastdiv_values((uint32_t) ne00);
|
| 34 |
+
const uint3 ne01_fd = init_fastdiv_values((uint32_t) ne01);
|
| 35 |
+
const uint3 ne02_fd = init_fastdiv_values((uint32_t) ne02);
|
| 36 |
+
const uint3 ne11_fd = init_fastdiv_values((uint32_t) ne11);
|
| 37 |
+
const uint3 ne12_fd = init_fastdiv_values((uint32_t) ne12);
|
| 38 |
+
- const int shared_bytes = tcq3_use_shared_bt[ctx.device] ? tcq3_bt_shared_bytes : 0;
|
| 39 |
+
+ const int tcq3_grid = (int)((ne_total_groups + TCQ3_WPB - 1) / TCQ3_WPB); // #533 warp-packed: TCQ3_WPB groups/block, 32 thr/group
|
| 40 |
+
// opencoti-hook: TCQ activation-dump (F-D, #515) β bind the staging buffer for this launch (no-op unless
|
| 41 |
+
// TURBO_TCQ_DUMP_ACTS is set). tcq_dump_init() is idempotent; dump_dev is nullptr when inactive/cap-reached,
|
| 42 |
+
// in which case d_tcq_dump_buf is left at its nullptr default and the kernel's dump branch is a uniform no-op.
|
| 43 |
+
@@ -782,15 +766,15 @@
|
| 44 |
+
float * dump_dev = tcq_dump_pre_launch((long) ne_total_groups);
|
| 45 |
+
if (dump_dev) CUDA_CHECK(cudaMemcpyToSymbol(d_tcq_dump_buf, &dump_dev, sizeof(float*)));
|
| 46 |
+
if (tcq_fp64_enabled())
|
| 47 |
+
- k_set_rows_turbo3_tcq<idx_t, double><<<(int)ne_total_groups, 512, shared_bytes, stream>>>(
|
| 48 |
+
+ k_set_rows_turbo3_tcq<idx_t, double><<<tcq3_grid, TCQ3_WPB*32, 0, stream>>>(
|
| 49 |
+
src0_d, src1_d, (block_turbo3_tcq *)dst->data,
|
| 50 |
+
- ne_total_groups, tcq_bt_buf[ctx.device], tcq3_use_shared_bt[ctx.device], ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 51 |
+
+ ne_total_groups, tcq_bt_buf[ctx.device], 0, ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 52 |
+
s01_f, s02_f, s03_f, s10_i, s11_i, s12_i, iq_is_k, nb1, nb2, nb3,
|
| 53 |
+
ne00_fd, ne01_fd, ne02_fd, ne11_fd, ne12_fd);
|
| 54 |
+
else
|
| 55 |
+
- k_set_rows_turbo3_tcq<idx_t><<<(int)ne_total_groups, 512, shared_bytes, stream>>>(
|
| 56 |
+
+ k_set_rows_turbo3_tcq<idx_t><<<tcq3_grid, TCQ3_WPB*32, 0, stream>>>(
|
| 57 |
+
src0_d, src1_d, (block_turbo3_tcq *)dst->data,
|
| 58 |
+
- ne_total_groups, tcq_bt_buf[ctx.device], tcq3_use_shared_bt[ctx.device], ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 59 |
+
+ ne_total_groups, tcq_bt_buf[ctx.device], 0, ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 60 |
+
s01_f, s02_f, s03_f, s10_i, s11_i, s12_i, iq_is_k, nb1, nb2, nb3,
|
| 61 |
+
ne00_fd, ne01_fd, ne02_fd, ne11_fd, ne12_fd);
|
| 62 |
+
if (dump_dev) tcq_dump_post_launch((long) ne_total_groups, stream);
|
| 63 |
+
@@ -827,42 +811,26 @@
|
| 64 |
+
const int64_t s10_i = nb10/sizeof(idx_t), s11_i = nb11/sizeof(idx_t), s12_i = nb12/sizeof(idx_t);
|
| 65 |
+
const int iq_is_k = (strncmp(dst->name, "cache_k_", 8) == 0) ? 1 : 0;
|
| 66 |
+
|
| 67 |
+
- static int tcq2_use_shared_bt[GGML_CUDA_MAX_DEVICES] = {};
|
| 68 |
+
- static bool tcq2_bt_checked [GGML_CUDA_MAX_DEVICES] = {};
|
| 69 |
+
- constexpr int tcq2_bt_shared_bytes = 128 * 64;
|
| 70 |
+
- if (!tcq2_bt_checked[ctx.device]) {
|
| 71 |
+
- tcq2_bt_checked[ctx.device] = true;
|
| 72 |
+
- const char * env = getenv("TURBO_TCQ_SHARED_BT");
|
| 73 |
+
- if (!env || atoi(env) != 0) {
|
| 74 |
+
- int max_shared_optin = 0;
|
| 75 |
+
- CUDA_CHECK(cudaDeviceGetAttribute(&max_shared_optin, cudaDevAttrMaxSharedMemoryPerBlockOptin, ctx.device));
|
| 76 |
+
- if (max_shared_optin >= tcq2_bt_shared_bytes) {
|
| 77 |
+
- if (tcq_fp64_enabled())
|
| 78 |
+
- CUDA_SET_SHARED_MEMORY_LIMIT((k_set_rows_turbo2_tcq<idx_t, double>), tcq2_bt_shared_bytes);
|
| 79 |
+
- else
|
| 80 |
+
- CUDA_SET_SHARED_MEMORY_LIMIT(k_set_rows_turbo2_tcq<idx_t>, tcq2_bt_shared_bytes);
|
| 81 |
+
- tcq2_use_shared_bt[ctx.device] = 1;
|
| 82 |
+
- }
|
| 83 |
+
- }
|
| 84 |
+
- }
|
| 85 |
+
- if (!tcq2_use_shared_bt[ctx.device]) ensure_tcq_bt_buf(ctx.device, ne_total_groups * 128 * 64);
|
| 86 |
+
+ // #533 warp-synchronous encode: bt always a global per-group slab (TCQ2_WPB groups/block); warp kernel is
|
| 87 |
+
+ // no longer barrier-bound, so global bt is free. One 8KB slab per group, indexed by `group`.
|
| 88 |
+
+ ensure_tcq_bt_buf(ctx.device, ne_total_groups * 128 * 64);
|
| 89 |
+
|
| 90 |
+
const uint3 ne00_fd = init_fastdiv_values((uint32_t) ne00);
|
| 91 |
+
const uint3 ne01_fd = init_fastdiv_values((uint32_t) ne01);
|
| 92 |
+
const uint3 ne02_fd = init_fastdiv_values((uint32_t) ne02);
|
| 93 |
+
const uint3 ne11_fd = init_fastdiv_values((uint32_t) ne11);
|
| 94 |
+
const uint3 ne12_fd = init_fastdiv_values((uint32_t) ne12);
|
| 95 |
+
- const int shared_bytes = tcq2_use_shared_bt[ctx.device] ? tcq2_bt_shared_bytes : 0;
|
| 96 |
+
+ const int tcq2_grid = (int)((ne_total_groups + TCQ2_WPB - 1) / TCQ2_WPB); // #533 warp-packed: TCQ2_WPB groups/block, 32 thr/group
|
| 97 |
+
if (tcq_fp64_enabled())
|
| 98 |
+
- k_set_rows_turbo2_tcq<idx_t, double><<<(int)ne_total_groups, 256, shared_bytes, stream>>>(
|
| 99 |
+
+ k_set_rows_turbo2_tcq<idx_t, double><<<tcq2_grid, TCQ2_WPB*32, 0, stream>>>(
|
| 100 |
+
src0_d, src1_d, (block_turbo2_tcq *)dst->data,
|
| 101 |
+
- ne_total_groups, tcq_bt_buf[ctx.device], tcq2_use_shared_bt[ctx.device], ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 102 |
+
+ ne_total_groups, tcq_bt_buf[ctx.device], 0, ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 103 |
+
s01_f, s02_f, s03_f, s10_i, s11_i, s12_i, iq_is_k, nb1, nb2, nb3,
|
| 104 |
+
ne00_fd, ne01_fd, ne02_fd, ne11_fd, ne12_fd);
|
| 105 |
+
else
|
| 106 |
+
- k_set_rows_turbo2_tcq<idx_t><<<(int)ne_total_groups, 256, shared_bytes, stream>>>(
|
| 107 |
+
+ k_set_rows_turbo2_tcq<idx_t><<<tcq2_grid, TCQ2_WPB*32, 0, stream>>>(
|
| 108 |
+
src0_d, src1_d, (block_turbo2_tcq *)dst->data,
|
| 109 |
+
- ne_total_groups, tcq_bt_buf[ctx.device], tcq2_use_shared_bt[ctx.device], ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 110 |
+
+ ne_total_groups, tcq_bt_buf[ctx.device], 0, ne00, ne01, ne02, ne10, ne11, ne12, ne13,
|
| 111 |
+
s01_f, s02_f, s03_f, s10_i, s11_i, s12_i, iq_is_k, nb1, nb2, nb3,
|
| 112 |
+
ne00_fd, ne01_fd, ne02_fd, ne11_fd, ne12_fd);
|
| 113 |
+
}
|
| 114 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/turbo-tcq-cuda.cuh b/llama.cpp/ggml/src/ggml-cuda/turbo-tcq-cuda.cuh
|
| 115 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/turbo-tcq-cuda.cuh
|
| 116 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/turbo-tcq-cuda.cuh
|
| 117 |
+
@@ -109,11 +109,16 @@
|
| 118 |
+
static __device__ __forceinline__ float tcq_acc_sqrt(float v) { return sqrtf(v); }
|
| 119 |
+
static __device__ __forceinline__ double tcq_acc_sqrt(double v) { return sqrt(v); }
|
| 120 |
+
|
| 121 |
+
-// TCQ SET_ROWS encode: Viterbi optimal path with right-shift trellis
|
| 122 |
+
-// 512 threads per block (one per trellis state), one block per 128-element group
|
| 123 |
+
-// Double-buffered cost arrays + global memory backtrace (128 syncs/group, was 384)
|
| 124 |
+
+// TCQ SET_ROWS encode: warp-synchronous Viterbi (#533). ncu proved the old 512-thread/__syncthreads design was
|
| 125 |
+
+// LATENCY-bound on the 128-step block-barrier chain (25% SM, 0.1% DRAM, 33% occ); #538's occupancy/shuffle tweak
|
| 126 |
+
+// couldn't help that. This collapses each 128-element group to ONE 32-thread warp (16 states/lane, thread-local
|
| 127 |
+
+// predecessor-min, __syncwarp broadcast, bt in global), packs TCQ3_WPB warps/block, and breaks the warp lockstep
|
| 128 |
+
+// so per-group serial latency is hidden by other warps. Validated byte-equivalent (round-trip decode cosine
|
| 129 |
+
+// 1.00000 vs the old design β .opencoti/tcq-warp-enc-full.cu) + 3.74x on the full encode. Reuses #538's warp-
|
| 130 |
+
+// shuffle min primitive. acc_t fp64 path (#506) preserved; bt is always global (use_shared_bt ignored).
|
| 131 |
+
+#define TCQ3_WPB 8
|
| 132 |
+
template<typename idx_t, typename acc_t = float>
|
| 133 |
+
-static __global__ void __launch_bounds__(512, 1) k_set_rows_turbo3_tcq(
|
| 134 |
+
+static __global__ void __launch_bounds__(TCQ3_WPB*32, 3) k_set_rows_turbo3_tcq(
|
| 135 |
+
const float * __restrict__ src0, const idx_t * __restrict__ src1,
|
| 136 |
+
block_turbo3_tcq * __restrict__ dst, const int64_t ne_total_groups,
|
| 137 |
+
uint8_t * __restrict__ bt_buf,
|
| 138 |
+
@@ -127,12 +132,13 @@
|
| 139 |
+
const uint3 ne00_fd, const uint3 ne01_fd, const uint3 ne02_fd,
|
| 140 |
+
const uint3 ne11_fd, const uint3 ne12_fd) {
|
| 141 |
+
|
| 142 |
+
- const int64_t group = blockIdx.x;
|
| 143 |
+
+ const int warp = threadIdx.x >> 5;
|
| 144 |
+
+ const int lane = threadIdx.x & 31;
|
| 145 |
+
+ const int64_t group = (int64_t)blockIdx.x * TCQ3_WPB + warp;
|
| 146 |
+
if (group >= ne_total_groups) return;
|
| 147 |
+
+ (void) use_shared_bt; (void) ne10; (void) ne13; // bt is always global in the warp design
|
| 148 |
+
|
| 149 |
+
- const int sid = threadIdx.x; // state index 0..511
|
| 150 |
+
-
|
| 151 |
+
- // Compute source and destination pointers (same index math as turbo3)
|
| 152 |
+
+ // Compute source and destination pointers (same index math as the 512-thread design; per-warp group)
|
| 153 |
+
const int64_t i_base = group * QK_TURBO3_TCQ;
|
| 154 |
+
uint32_t tmp = (uint32_t)i_base; uint2 div_mod;
|
| 155 |
+
div_mod = fast_div_modulo(tmp, ne00_fd); const int64_t i00 = div_mod.y; tmp = div_mod.x;
|
| 156 |
+
@@ -145,244 +151,130 @@
|
| 157 |
+
block_turbo3_tcq * dst_blk = (block_turbo3_tcq *)((char *)dst + dst_row*s1 + i02*s2 + i03*s3)
|
| 158 |
+
+ (i00 / QK_TURBO3_TCQ);
|
| 159 |
+
|
| 160 |
+
- // Shared memory layout (~5KB, was ~35KB before global bt optimization):
|
| 161 |
+
- // x[128] : rotated+normalized input (also reused as outputs[] after Viterbi)
|
| 162 |
+
- // cost[512] : path costs buffer A (also reused for reductions)
|
| 163 |
+
- // cost_b[512]: path costs buffer B (double-buffering eliminates 2/3 of syncs)
|
| 164 |
+
- // Backtrace: one predecessor byte for each of the 64 low-state groups per
|
| 165 |
+
- // step. The predecessor is independent of the output bits in sid[8:6], so
|
| 166 |
+
- // storing 128Γ64 bytes is equivalent to the older 128Γ512 layout.
|
| 167 |
+
- extern __shared__ uint8_t bt_shared[];
|
| 168 |
+
- __shared__ acc_t x[128];
|
| 169 |
+
- __shared__ acc_t cost[512];
|
| 170 |
+
- __shared__ acc_t cost_b[512];
|
| 171 |
+
- __shared__ int warp_min_idx[16];
|
| 172 |
+
- __shared__ acc_t warp_min_cost[16];
|
| 173 |
+
- __shared__ acc_t pred_min_cost[64];
|
| 174 |
+
- __shared__ int shared_initial_state;
|
| 175 |
+
-
|
| 176 |
+
- if (sid < 128) x[sid] = grp_src[sid];
|
| 177 |
+
- __syncthreads();
|
| 178 |
+
-
|
| 179 |
+
- __syncthreads();
|
| 180 |
+
-
|
| 181 |
+
- // Norm reduction
|
| 182 |
+
- cost[sid] = (sid < 128) ? x[sid] * x[sid] : 0.0f;
|
| 183 |
+
- __syncthreads();
|
| 184 |
+
- for (int stride = 256; stride >= 32; stride >>= 1) {
|
| 185 |
+
- if (sid < stride) cost[sid] += cost[sid + stride];
|
| 186 |
+
- __syncthreads();
|
| 187 |
+
- }
|
| 188 |
+
- if (sid < 32) {
|
| 189 |
+
- acc_t v = cost[sid];
|
| 190 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 16);
|
| 191 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 8);
|
| 192 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 4);
|
| 193 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 2);
|
| 194 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 1);
|
| 195 |
+
- if (sid == 0) cost[0] = v;
|
| 196 |
+
- }
|
| 197 |
+
- __syncthreads();
|
| 198 |
+
- acc_t grp_norm = tcq_acc_sqrt(cost[0]);
|
| 199 |
+
- acc_t inv_norm = grp_norm > 1e-10f ? 1.0f / grp_norm : 0.0f;
|
| 200 |
+
-
|
| 201 |
+
- if (sid < 128) x[sid] *= inv_norm;
|
| 202 |
+
- __syncthreads();
|
| 203 |
+
-
|
| 204 |
+
- // FWHT. The first five stages are contained within each 32-lane warp, so
|
| 205 |
+
- // use warp shuffles and only synchronize for the two cross-warp stages.
|
| 206 |
+
- if (sid < 128) {
|
| 207 |
+
- acc_t v = x[sid] * TURBO3_WHT_SIGNS1[sid];
|
| 208 |
+
- const int lane = sid & 31;
|
| 209 |
+
-#pragma unroll
|
| 210 |
+
- for (int h = 1; h < 32; h <<= 1) {
|
| 211 |
+
- const acc_t other = __shfl_xor_sync(0xFFFFFFFFULL, v, h);
|
| 212 |
+
- v = (lane & h) ? (other - v) : (v + other);
|
| 213 |
+
- }
|
| 214 |
+
- x[sid] = v;
|
| 215 |
+
- }
|
| 216 |
+
- __syncthreads();
|
| 217 |
+
- if (sid < 64) {
|
| 218 |
+
- const int j = ((sid >> 5) << 6) + (sid & 31);
|
| 219 |
+
- acc_t a = x[j], b = x[j + 32];
|
| 220 |
+
- x[j] = a + b; x[j + 32] = a - b;
|
| 221 |
+
- }
|
| 222 |
+
- __syncthreads();
|
| 223 |
+
- if (sid < 64) {
|
| 224 |
+
- acc_t a = x[sid], b = x[sid + 64];
|
| 225 |
+
- x[sid] = a + b; x[sid + 64] = a - b;
|
| 226 |
+
+ // Per-warp smem: whitened vec x[128] (reused as outputs[] after Viterbi) + pred_min_cost[64] broadcast +
|
| 227 |
+
+ // initial-state scratch. The Viterbi path-cost lives in registers (c[16], 16 states/lane). bt is global.
|
| 228 |
+
+ __shared__ acc_t x_s[TCQ3_WPB][128];
|
| 229 |
+
+ __shared__ acc_t pm_s[TCQ3_WPB][64];
|
| 230 |
+
+ __shared__ int ini_s[TCQ3_WPB];
|
| 231 |
+
+ acc_t * x = x_s[warp];
|
| 232 |
+
+ acc_t * pred_min_cost = pm_s[warp];
|
| 233 |
+
+ uint8_t * bt = bt_buf + group * (int64_t)(128 * 64); // global per-group backtrace slab
|
| 234 |
+
+
|
| 235 |
+
+ // --- load 4 elements/lane (stride-32 layout: lane owns positions L, L+32, L+64, L+96) ---
|
| 236 |
+
+ acc_t e0 = grp_src[lane], e1 = grp_src[lane+32], e2 = grp_src[lane+64], e3 = grp_src[lane+96];
|
| 237 |
+
+ // --- norm (warp-shuffle sum of squares) ---
|
| 238 |
+
+ acc_t ss = e0*e0 + e1*e1 + e2*e2 + e3*e3;
|
| 239 |
+
+#pragma unroll
|
| 240 |
+
+ for (int o = 16; o > 0; o >>= 1) ss += __shfl_xor_sync(0xFFFFFFFFu, ss, o);
|
| 241 |
+
+ acc_t grp_norm = tcq_acc_sqrt(ss);
|
| 242 |
+
+ acc_t inv_norm = grp_norm > 1e-10f ? (acc_t)1.0 / grp_norm : (acc_t)0.0;
|
| 243 |
+
+ e0 *= inv_norm; e1 *= inv_norm; e2 *= inv_norm; e3 *= inv_norm;
|
| 244 |
+
+ // --- FWHT-128 (stride-32 layout: 5 shuffle-butterfly stages + 2 intra-thread cross stages,
|
| 245 |
+
+ // byte-faithful to the 512-thread block FWHT) ---
|
| 246 |
+
+ e0 *= TURBO3_WHT_SIGNS1[lane]; e1 *= TURBO3_WHT_SIGNS1[lane+32];
|
| 247 |
+
+ e2 *= TURBO3_WHT_SIGNS1[lane+64]; e3 *= TURBO3_WHT_SIGNS1[lane+96];
|
| 248 |
+
+#pragma unroll
|
| 249 |
+
+ for (int h = 1; h < 32; h <<= 1) {
|
| 250 |
+
+ acc_t o0 = __shfl_xor_sync(0xFFFFFFFFu, e0, h); e0 = (lane & h) ? o0 - e0 : e0 + o0;
|
| 251 |
+
+ acc_t o1 = __shfl_xor_sync(0xFFFFFFFFu, e1, h); e1 = (lane & h) ? o1 - e1 : e1 + o1;
|
| 252 |
+
+ acc_t o2 = __shfl_xor_sync(0xFFFFFFFFu, e2, h); e2 = (lane & h) ? o2 - e2 : e2 + o2;
|
| 253 |
+
+ acc_t o3 = __shfl_xor_sync(0xFFFFFFFFu, e3, h); e3 = (lane & h) ? o3 - e3 : e3 + o3;
|
| 254 |
+
}
|
| 255 |
+
- __syncthreads();
|
| 256 |
+
+ { acc_t a = e0, b = e1; e0 = a + b; e1 = a - b; acc_t c = e2, d = e3; e2 = c + d; e3 = c - d; } // cross-32: blk0<->1, blk2<->3
|
| 257 |
+
+ { acc_t a = e0, b = e2; e0 = a + b; e2 = a - b; acc_t c = e1, d = e3; e1 = c + d; e3 = c - d; } // cross-64: blk0<->2, blk1<->3
|
| 258 |
+
constexpr float inv_sqrt_128 = 0.08838834764831845f;
|
| 259 |
+
- if (sid < 128) x[sid] *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[sid];
|
| 260 |
+
- __syncthreads();
|
| 261 |
+
-
|
| 262 |
+
- // opencoti-hook: TCQ activation-dump (F-D, #515). x[0..127] is now the post-FWHT unit-norm whitened vec β
|
| 263 |
+
- // exactly the xt the trellis quantizes against d_turbo3_tcq_codebook below, and x stays read-only through the
|
| 264 |
+
- // forward pass. Stage it for the F-E codebook retrain when enabled; nullptr (default) is a uniform no-op.
|
| 265 |
+
- // group == blockIdx.x < ne_total_groups, so [group*128 + sid] is in-bounds of the (ne_total_groups*128) buffer.
|
| 266 |
+
- if (d_tcq_dump_buf != nullptr && sid < 128) {
|
| 267 |
+
- d_tcq_dump_buf[group * 128 + sid] = (float) x[sid];
|
| 268 |
+
- }
|
| 269 |
+
-
|
| 270 |
+
- if (sid == 0) cost[0] = grp_norm;
|
| 271 |
+
- __syncthreads();
|
| 272 |
+
-
|
| 273 |
+
- acc_t saved_norm = cost[0];
|
| 274 |
+
-
|
| 275 |
+
- // Viterbi forward pass: double-buffered cost (1 sync/step, was 3)
|
| 276 |
+
- uint8_t * bt = use_shared_bt ? bt_shared : bt_buf + (int64_t)blockIdx.x * (128 * 64);
|
| 277 |
+
- cost[sid] = 0.0f;
|
| 278 |
+
- __syncthreads();
|
| 279 |
+
-
|
| 280 |
+
+ e0 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane]; e1 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane+32];
|
| 281 |
+
+ e2 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane+64]; e3 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane+96];
|
| 282 |
+
+ x[lane] = e0; x[lane+32] = e1; x[lane+64] = e2; x[lane+96] = e3;
|
| 283 |
+
+ __syncwarp();
|
| 284 |
+
+
|
| 285 |
+
+ // opencoti-hook: TCQ activation-dump (F-D, #515). e0..e3 are the post-FWHT unit-norm whitened vec.
|
| 286 |
+
+ if (d_tcq_dump_buf != nullptr) {
|
| 287 |
+
+ d_tcq_dump_buf[group*128 + lane] = (float) e0;
|
| 288 |
+
+ d_tcq_dump_buf[group*128 + lane+32] = (float) e1;
|
| 289 |
+
+ d_tcq_dump_buf[group*128 + lane+64] = (float) e2;
|
| 290 |
+
+ d_tcq_dump_buf[group*128 + lane+96] = (float) e3;
|
| 291 |
+
+ }
|
| 292 |
+
+ acc_t saved_norm = grp_norm;
|
| 293 |
+
+
|
| 294 |
+
+ // --- Viterbi forward DP: warp-synchronous (16 states/lane, thread-local predecessor-min, __syncwarp). ---
|
| 295 |
+
+ // lane L owns states {16L..16L+15} = trellis-groups 2L (states 16L..16L+7) and 2L+1 (states 16L+8..16L+15);
|
| 296 |
+
+ // each group's 8 predecessors (g*8..g*8+7) fall entirely within lane g/2, so the predecessor-min is
|
| 297 |
+
+ // thread-local (no shuffle). A 64-entry per-warp smem broadcast under __syncwarp replaces the block barrier.
|
| 298 |
+
+ acc_t c[16];
|
| 299 |
+
+#pragma unroll
|
| 300 |
+
+ for (int i = 0; i < 16; i++) c[i] = 0.0f;
|
| 301 |
+
for (int t = 0; t < 128; t++) {
|
| 302 |
+
- // Double-buffer: even steps read cost/write cost_b, odd steps read cost_b/write cost
|
| 303 |
+
- acc_t * cost_rd = (t & 1) ? cost_b : cost;
|
| 304 |
+
- acc_t * cost_wr = (t & 1) ? cost : cost_b;
|
| 305 |
+
-
|
| 306 |
+
+ acc_t pm0 = c[0]; int pp0 = 0;
|
| 307 |
+
+#pragma unroll
|
| 308 |
+
+ for (int j = 1; j < 8; j++) { if (c[j] < pm0) { pm0 = c[j]; pp0 = j; } }
|
| 309 |
+
+ acc_t pm1 = c[8]; int pp1 = 0;
|
| 310 |
+
+#pragma unroll
|
| 311 |
+
+ for (int j = 1; j < 8; j++) { if (c[8+j] < pm1) { pm1 = c[8+j]; pp1 = j; } }
|
| 312 |
+
+ pred_min_cost[2*lane] = pm0; pred_min_cost[2*lane+1] = pm1;
|
| 313 |
+
+ bt[t*64 + 2*lane] = (uint8_t) pp0; bt[t*64 + 2*lane+1] = (uint8_t) pp1;
|
| 314 |
+
+ __syncwarp();
|
| 315 |
+
acc_t xt = x[t];
|
| 316 |
+
-
|
| 317 |
+
- // Right-shift trellis: ns = (prev >> 3) | (out << 6). The best
|
| 318 |
+
- // predecessor depends only on sid's low 6 bits, so compute those 64
|
| 319 |
+
- // minima once instead of repeating the same 8-way scan for each out.
|
| 320 |
+
- if (sid < 64) {
|
| 321 |
+
- const int base_prev = sid << 3;
|
| 322 |
+
- acc_t best = cost_rd[base_prev];
|
| 323 |
+
- int best_p = 0;
|
| 324 |
+
-#pragma unroll
|
| 325 |
+
- for (int p = 1; p < 8; p++) {
|
| 326 |
+
- acc_t c = cost_rd[base_prev | p];
|
| 327 |
+
- if (c < best) {
|
| 328 |
+
- best = c;
|
| 329 |
+
- best_p = p;
|
| 330 |
+
- }
|
| 331 |
+
- }
|
| 332 |
+
- pred_min_cost[sid] = best;
|
| 333 |
+
- bt[t * 64 + sid] = (uint8_t) best_p;
|
| 334 |
+
- }
|
| 335 |
+
- __syncthreads();
|
| 336 |
+
-
|
| 337 |
+
- const int pred_idx = sid & 0x3F;
|
| 338 |
+
- acc_t dist = xt - d_turbo3_tcq_codebook[sid];
|
| 339 |
+
- dist = dist * dist;
|
| 340 |
+
-
|
| 341 |
+
- cost_wr[sid] = pred_min_cost[pred_idx] + dist;
|
| 342 |
+
- __syncthreads();
|
| 343 |
+
- }
|
| 344 |
+
- // After 128 steps (even count): final costs are in cost[] (step 127 writes to cost)
|
| 345 |
+
-
|
| 346 |
+
- // Warp argmin over 512 costs
|
| 347 |
+
- {
|
| 348 |
+
- acc_t my_cost = cost[sid];
|
| 349 |
+
- int my_idx = sid;
|
| 350 |
+
- #pragma unroll
|
| 351 |
+
- for (int offset = 16; offset > 0; offset >>= 1) {
|
| 352 |
+
- acc_t other_cost = __shfl_xor_sync(0xFFFFFFFFULL, my_cost, offset);
|
| 353 |
+
- int other_idx = __shfl_xor_sync(0xFFFFFFFFULL, my_idx, offset);
|
| 354 |
+
- if (other_cost < my_cost) { my_cost = other_cost; my_idx = other_idx; }
|
| 355 |
+
- }
|
| 356 |
+
- if (sid % 32 == 0) {
|
| 357 |
+
- warp_min_cost[sid / 32] = my_cost;
|
| 358 |
+
- warp_min_idx[sid / 32] = my_idx;
|
| 359 |
+
- }
|
| 360 |
+
+#pragma unroll
|
| 361 |
+
+ for (int i = 0; i < 16; i++) { int s = 16*lane + i; acc_t d = xt - d_turbo3_tcq_codebook[s]; c[i] = pred_min_cost[s & 0x3F] + d * d; }
|
| 362 |
+
+ __syncwarp();
|
| 363 |
+
}
|
| 364 |
+
- __syncthreads();
|
| 365 |
+
- if (sid < 32) {
|
| 366 |
+
- acc_t best = (sid < 16) ? warp_min_cost[sid] : 3.4028234663852886e38f;
|
| 367 |
+
- int best_idx = (sid < 16) ? warp_min_idx[sid] : 0;
|
| 368 |
+
-#pragma unroll
|
| 369 |
+
- for (int offset = 16; offset > 0; offset >>= 1) {
|
| 370 |
+
- acc_t other_cost = __shfl_down_sync(0xFFFFFFFFULL, best, offset);
|
| 371 |
+
- int other_idx = __shfl_down_sync(0xFFFFFFFFULL, best_idx, offset);
|
| 372 |
+
- if (other_cost < best) {
|
| 373 |
+
- best = other_cost;
|
| 374 |
+
- best_idx = other_idx;
|
| 375 |
+
- }
|
| 376 |
+
- }
|
| 377 |
+
- if (sid == 0) {
|
| 378 |
+
- shared_initial_state = best_idx; // temporarily: best final state (becomes initial after backtrack)
|
| 379 |
+
- }
|
| 380 |
+
+ // --- argmin over 512 states (per-lane min over 16, then warp-shuffle) ---
|
| 381 |
+
+ acc_t mc = c[0]; int mi = 16*lane;
|
| 382 |
+
+#pragma unroll
|
| 383 |
+
+ for (int i = 1; i < 16; i++) { if (c[i] < mc) { mc = c[i]; mi = 16*lane + i; } }
|
| 384 |
+
+#pragma unroll
|
| 385 |
+
+ for (int o = 16; o > 0; o >>= 1) {
|
| 386 |
+
+ acc_t oc = __shfl_xor_sync(0xFFFFFFFFu, mc, o); int oi = __shfl_xor_sync(0xFFFFFFFFu, mi, o);
|
| 387 |
+
+ if (oc < mc || (oc == mc && oi < mi)) { mc = oc; mi = oi; }
|
| 388 |
+
}
|
| 389 |
+
- __syncthreads();
|
| 390 |
+
-
|
| 391 |
+
- // Save x[] to global buffer before backtrack overwrites it
|
| 392 |
+
-
|
| 393 |
+
- // Backtrack (inherently sequential, reads global bt)
|
| 394 |
+
+ // --- backtrack (lane 0; outputs reuse the x slab β x no longer needed) ---
|
| 395 |
+
uint8_t * outputs = (uint8_t *)x;
|
| 396 |
+
- if (sid == 0) {
|
| 397 |
+
- int state = shared_initial_state;
|
| 398 |
+
+ if (lane == 0) {
|
| 399 |
+
+ int state = mi;
|
| 400 |
+
for (int t = 127; t >= 0; t--) {
|
| 401 |
+
outputs[t] = (uint8_t)(state >> 6);
|
| 402 |
+
- int p = bt[t * 64 + (state & 0x3F)];
|
| 403 |
+
+ int p = bt[t*64 + (state & 0x3F)];
|
| 404 |
+
state = ((state & 0x3F) << 3) | p;
|
| 405 |
+
}
|
| 406 |
+
- shared_initial_state = state;
|
| 407 |
+
+ ini_s[warp] = state;
|
| 408 |
+
}
|
| 409 |
+
- __syncthreads();
|
| 410 |
+
+ __syncwarp();
|
| 411 |
+
+ int initial_state = ini_s[warp];
|
| 412 |
+
|
| 413 |
+
- // Save output symbols to global buffer
|
| 414 |
+
-
|
| 415 |
+
- // Parallel recon norm: t>=2 can compute state directly from 3 outputs (3 shifts of 3 = 9 bits)
|
| 416 |
+
- acc_t my_recon_sq = 0.0f;
|
| 417 |
+
- if (sid < 128) {
|
| 418 |
+
- int cur_state;
|
| 419 |
+
- if (sid < 2) {
|
| 420 |
+
- cur_state = shared_initial_state;
|
| 421 |
+
- for (int t = 0; t <= sid; t++)
|
| 422 |
+
- cur_state = (cur_state >> 3) | (((int)outputs[t]) << 6);
|
| 423 |
+
- } else {
|
| 424 |
+
- cur_state = ((int)outputs[sid - 2] & 0x7)
|
| 425 |
+
- | (((int)outputs[sid - 1] & 0x7) << 3)
|
| 426 |
+
- | (((int)outputs[sid] & 0x7) << 6);
|
| 427 |
+
- }
|
| 428 |
+
- acc_t c = d_turbo3_tcq_codebook[cur_state];
|
| 429 |
+
- my_recon_sq = c * c;
|
| 430 |
+
- }
|
| 431 |
+
- cost[sid] = my_recon_sq;
|
| 432 |
+
- __syncthreads();
|
| 433 |
+
- for (int stride = 256; stride >= 32; stride >>= 1) {
|
| 434 |
+
- if (sid < stride) cost[sid] += cost[sid + stride];
|
| 435 |
+
- __syncthreads();
|
| 436 |
+
- }
|
| 437 |
+
- if (sid < 32) {
|
| 438 |
+
- acc_t v = cost[sid];
|
| 439 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 16);
|
| 440 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 8);
|
| 441 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 4);
|
| 442 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 2);
|
| 443 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 1);
|
| 444 |
+
- if (sid == 0) cost[0] = v;
|
| 445 |
+
+ // --- recon norm (warp-shuffle sum; lane owns recon positions L, L+32, L+64, L+96) ---
|
| 446 |
+
+ acc_t recon_sq = 0.0f;
|
| 447 |
+
+#pragma unroll
|
| 448 |
+
+ for (int b = 0; b < 4; b++) {
|
| 449 |
+
+ int pos = lane + 32*b; int cur_state;
|
| 450 |
+
+ if (pos < 2) { cur_state = initial_state; for (int t = 0; t <= pos; t++) cur_state = (cur_state >> 3) | (((int)outputs[t]) << 6); }
|
| 451 |
+
+ else { cur_state = ((int)outputs[pos-2] & 0x7) | (((int)outputs[pos-1] & 0x7) << 3) | (((int)outputs[pos] & 0x7) << 6); }
|
| 452 |
+
+ acc_t cc = d_turbo3_tcq_codebook[cur_state]; recon_sq += cc * cc;
|
| 453 |
+
}
|
| 454 |
+
- __syncthreads();
|
| 455 |
+
- acc_t recon_norm = tcq_acc_sqrt(cost[0]);
|
| 456 |
+
+#pragma unroll
|
| 457 |
+
+ for (int o = 16; o > 0; o >>= 1) recon_sq += __shfl_xor_sync(0xFFFFFFFFu, recon_sq, o);
|
| 458 |
+
+ acc_t recon_norm = tcq_acc_sqrt(recon_sq);
|
| 459 |
+
acc_t corrected_norm = (recon_norm > 1e-10f) ? saved_norm / recon_norm : saved_norm;
|
| 460 |
+
corrected_norm *= innerq_is_k ? d_tcq_norm_alpha : d_tcq_norm_alpha_v;
|
| 461 |
+
|
| 462 |
+
- // Parallel bitpack: qs stores 6 initial-state bits followed by 128 3-bit
|
| 463 |
+
- // output symbols. Each byte is independent, so avoid the old serial OR loop.
|
| 464 |
+
- if (sid < 49) {
|
| 465 |
+
- const int init_bits = (shared_initial_state >> 3) & 0x3F;
|
| 466 |
+
- uint8_t packed = 0;
|
| 467 |
+
-#pragma unroll
|
| 468 |
+
- for (int bit = 0; bit < 8; bit++) {
|
| 469 |
+
- const int pos = sid * 8 + bit;
|
| 470 |
+
- int v = 0;
|
| 471 |
+
- if (pos < 6) {
|
| 472 |
+
- v = (init_bits >> pos) & 1;
|
| 473 |
+
- } else {
|
| 474 |
+
- const int sym_bit_pos = pos - 6;
|
| 475 |
+
- const int sym_idx = sym_bit_pos / 3;
|
| 476 |
+
- if (sym_idx < 128) {
|
| 477 |
+
- v = (outputs[sym_idx] >> (sym_bit_pos % 3)) & 1;
|
| 478 |
+
- }
|
| 479 |
+
+ // --- bitpack: lane handles bytes {lane, lane+32} that are < 49 ---
|
| 480 |
+
+ const int init_bits = (initial_state >> 3) & 0x3F;
|
| 481 |
+
+#pragma unroll
|
| 482 |
+
+ for (int bb = 0; bb < 2; bb++) {
|
| 483 |
+
+ int byte = lane + 32*bb;
|
| 484 |
+
+ if (byte < 49) {
|
| 485 |
+
+ uint8_t packed = 0;
|
| 486 |
+
+#pragma unroll
|
| 487 |
+
+ for (int bit = 0; bit < 8; bit++) {
|
| 488 |
+
+ const int pos = byte*8 + bit; int v = 0;
|
| 489 |
+
+ if (pos < 6) v = (init_bits >> pos) & 1;
|
| 490 |
+
+ else { const int sym_bit_pos = pos - 6; const int sym_idx = sym_bit_pos / 3; if (sym_idx < 128) v = (outputs[sym_idx] >> (sym_bit_pos % 3)) & 1; }
|
| 491 |
+
+ packed |= (uint8_t)(v << bit);
|
| 492 |
+
}
|
| 493 |
+
- packed |= (uint8_t)(v << bit);
|
| 494 |
+
+ dst_blk->qs[byte] = packed;
|
| 495 |
+
}
|
| 496 |
+
- dst_blk->qs[sid] = packed;
|
| 497 |
+
- }
|
| 498 |
+
- if (sid == 0) {
|
| 499 |
+
- dst_blk->norm = __float2half((float)corrected_norm);
|
| 500 |
+
}
|
| 501 |
+
+ if (lane == 0) dst_blk->norm = __float2half((float)corrected_norm);
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
// TCQ GET_ROWS dequantize (for non-FA paths)
|
| 505 |
+
@@ -455,10 +347,13 @@
|
| 506 |
+
-0.13432936f, -0.05269006f, +0.03536416f, +0.117640756f, -0.022776067f, +0.042032316f, +0.10472976f, +0.18042557f
|
| 507 |
+
};
|
| 508 |
+
|
| 509 |
+
-// 2-bit TCQ SET_ROWS encode: Viterbi optimal path with right-shift trellis (k=2, L=8)
|
| 510 |
+
-// Double-buffered cost arrays + global memory backtrace (128 syncs/group, was 384)
|
| 511 |
+
+// 2-bit TCQ SET_ROWS encode: warp-synchronous Viterbi (#533, k=2, L=8, 256 states). Same warp redesign as
|
| 512 |
+
+// turbo3_tcq: 1 group / 32-thread warp (8 states/lane, thread-local quartet predecessor-min, __syncwarp
|
| 513 |
+
+// broadcast, bt in global), TCQ2_WPB warps/block. Replaces the old 256-thread/__syncthreads block (latency-
|
| 514 |
+
+// bound on the 128-step barrier chain). bt always global (use_shared_bt ignored); acc_t fp64 path preserved.
|
| 515 |
+
+#define TCQ2_WPB 8
|
| 516 |
+
template<typename idx_t, typename acc_t = float>
|
| 517 |
+
-static __global__ void __launch_bounds__(256, 1) k_set_rows_turbo2_tcq(
|
| 518 |
+
+static __global__ void __launch_bounds__(TCQ2_WPB*32, 3) k_set_rows_turbo2_tcq(
|
| 519 |
+
const float * __restrict__ src0, const idx_t * __restrict__ src1,
|
| 520 |
+
block_turbo2_tcq * __restrict__ dst, const int64_t ne_total_groups,
|
| 521 |
+
uint8_t * __restrict__ bt_buf,
|
| 522 |
+
@@ -472,12 +367,14 @@
|
| 523 |
+
const uint3 ne00_fd, const uint3 ne01_fd, const uint3 ne02_fd,
|
| 524 |
+
const uint3 ne11_fd, const uint3 ne12_fd) {
|
| 525 |
+
|
| 526 |
+
- const int grp = blockIdx.x;
|
| 527 |
+
- if (grp >= ne_total_groups) return;
|
| 528 |
+
- const int sid = threadIdx.x; // 0..255 = trellis state
|
| 529 |
+
+ const int warp = threadIdx.x >> 5;
|
| 530 |
+
+ const int lane = threadIdx.x & 31;
|
| 531 |
+
+ const int64_t group = (int64_t)blockIdx.x * TCQ2_WPB + warp;
|
| 532 |
+
+ if (group >= ne_total_groups) return;
|
| 533 |
+
+ (void) use_shared_bt; (void) ne10; (void) ne13; // bt is always global in the warp design
|
| 534 |
+
|
| 535 |
+
- // Compute source and destination pointers (all threads, used by thread 0)
|
| 536 |
+
- const int64_t i_base = int64_t(grp) * QK_TURBO2_TCQ;
|
| 537 |
+
+ // Compute source and destination pointers (same index math as the 256-thread design; per-warp group)
|
| 538 |
+
+ const int64_t i_base = group * QK_TURBO2_TCQ;
|
| 539 |
+
uint32_t tmp = (uint32_t)i_base; uint2 div_mod;
|
| 540 |
+
div_mod = fast_div_modulo(tmp, ne00_fd); const int64_t i00 = div_mod.y; tmp = div_mod.x;
|
| 541 |
+
div_mod = fast_div_modulo(tmp, ne01_fd); const int64_t i01 = div_mod.y; tmp = div_mod.x;
|
| 542 |
+
@@ -489,232 +386,120 @@
|
| 543 |
+
block_turbo2_tcq * dst_blk = (block_turbo2_tcq *)((char *)dst + dst_row*s1 + i02*s2 + i03*s3)
|
| 544 |
+
+ (i00 / QK_TURBO2_TCQ);
|
| 545 |
+
|
| 546 |
+
- // Backtrace: one predecessor byte per 64 low-state groups per step.
|
| 547 |
+
- // The predecessor depends only on sid's low 6 bits (same as turbo3_tcq).
|
| 548 |
+
- extern __shared__ uint8_t bt_shared[];
|
| 549 |
+
- __shared__ acc_t x[128];
|
| 550 |
+
- __shared__ acc_t cost[256];
|
| 551 |
+
- __shared__ acc_t cost_b[256]; // double-buffering for Viterbi
|
| 552 |
+
- __shared__ int warp_min_idx[8];
|
| 553 |
+
- __shared__ acc_t warp_min_cost[8];
|
| 554 |
+
- __shared__ acc_t pred_min_cost[64];
|
| 555 |
+
- __shared__ int shared_initial_state;
|
| 556 |
+
-
|
| 557 |
+
- if (sid < 128) x[sid] = grp_src[sid];
|
| 558 |
+
- __syncthreads();
|
| 559 |
+
-
|
| 560 |
+
- __syncthreads();
|
| 561 |
+
-
|
| 562 |
+
- // Norm reduction
|
| 563 |
+
- cost[sid] = (sid < 128) ? x[sid] * x[sid] : 0.0f;
|
| 564 |
+
- __syncthreads();
|
| 565 |
+
- for (int stride = 128; stride >= 32; stride >>= 1) {
|
| 566 |
+
- if (sid < stride) cost[sid] += cost[sid + stride];
|
| 567 |
+
- __syncthreads();
|
| 568 |
+
- }
|
| 569 |
+
- if (sid < 32) {
|
| 570 |
+
- acc_t v = cost[sid];
|
| 571 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 16);
|
| 572 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 8);
|
| 573 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 4);
|
| 574 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 2);
|
| 575 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 1);
|
| 576 |
+
- if (sid == 0) cost[0] = v;
|
| 577 |
+
- }
|
| 578 |
+
- __syncthreads();
|
| 579 |
+
- acc_t grp_norm = tcq_acc_sqrt(cost[0]);
|
| 580 |
+
- acc_t inv_norm = grp_norm > 1e-10f ? 1.0f / grp_norm : 0.0f;
|
| 581 |
+
-
|
| 582 |
+
- if (sid < 128) x[sid] *= inv_norm;
|
| 583 |
+
- __syncthreads();
|
| 584 |
+
-
|
| 585 |
+
- // FWHT. The first five stages use warp shuffles, the two cross-warp stages
|
| 586 |
+
- // use shared memory (same approach as turbo3_tcq).
|
| 587 |
+
- if (sid < 128) {
|
| 588 |
+
- acc_t v = x[sid] * TURBO3_WHT_SIGNS1[sid];
|
| 589 |
+
- const int lane = sid & 31;
|
| 590 |
+
-#pragma unroll
|
| 591 |
+
- for (int h = 1; h < 32; h <<= 1) {
|
| 592 |
+
- const acc_t other = __shfl_xor_sync(0xFFFFFFFFULL, v, h);
|
| 593 |
+
- v = (lane & h) ? (other - v) : (v + other);
|
| 594 |
+
- }
|
| 595 |
+
- x[sid] = v;
|
| 596 |
+
- }
|
| 597 |
+
- __syncthreads();
|
| 598 |
+
- if (sid < 64) {
|
| 599 |
+
- const int j = ((sid >> 5) << 6) + (sid & 31);
|
| 600 |
+
- acc_t a = x[j], b = x[j + 32];
|
| 601 |
+
- x[j] = a + b; x[j + 32] = a - b;
|
| 602 |
+
- }
|
| 603 |
+
- __syncthreads();
|
| 604 |
+
- if (sid < 64) {
|
| 605 |
+
- acc_t a = x[sid], b = x[sid + 64];
|
| 606 |
+
- x[sid] = a + b; x[sid + 64] = a - b;
|
| 607 |
+
+ // Per-warp smem: whitened vec x[128] (reused as outputs[] after Viterbi) + pred_min_cost[64] broadcast +
|
| 608 |
+
+ // initial-state scratch. Path-cost in registers (c[8], 8 states/lane). bt is global.
|
| 609 |
+
+ __shared__ acc_t x_s[TCQ2_WPB][128];
|
| 610 |
+
+ __shared__ acc_t pm_s[TCQ2_WPB][64];
|
| 611 |
+
+ __shared__ int ini_s[TCQ2_WPB];
|
| 612 |
+
+ acc_t * x = x_s[warp];
|
| 613 |
+
+ acc_t * pred_min_cost = pm_s[warp];
|
| 614 |
+
+ uint8_t * bt = bt_buf + group * (int64_t)(128 * 64); // global per-group backtrace slab
|
| 615 |
+
+
|
| 616 |
+
+ // --- load 4 elements/lane (stride-32 layout: lane owns positions L, L+32, L+64, L+96) ---
|
| 617 |
+
+ acc_t e0 = grp_src[lane], e1 = grp_src[lane+32], e2 = grp_src[lane+64], e3 = grp_src[lane+96];
|
| 618 |
+
+ // --- norm (warp-shuffle sum of squares) ---
|
| 619 |
+
+ acc_t ss = e0*e0 + e1*e1 + e2*e2 + e3*e3;
|
| 620 |
+
+#pragma unroll
|
| 621 |
+
+ for (int o = 16; o > 0; o >>= 1) ss += __shfl_xor_sync(0xFFFFFFFFu, ss, o);
|
| 622 |
+
+ acc_t grp_norm = tcq_acc_sqrt(ss);
|
| 623 |
+
+ acc_t inv_norm = grp_norm > 1e-10f ? (acc_t)1.0 / grp_norm : (acc_t)0.0;
|
| 624 |
+
+ e0 *= inv_norm; e1 *= inv_norm; e2 *= inv_norm; e3 *= inv_norm;
|
| 625 |
+
+ // --- FWHT-128 (stride-32 layout: 5 shuffle-butterfly stages + 2 intra-thread cross stages) ---
|
| 626 |
+
+ e0 *= TURBO3_WHT_SIGNS1[lane]; e1 *= TURBO3_WHT_SIGNS1[lane+32];
|
| 627 |
+
+ e2 *= TURBO3_WHT_SIGNS1[lane+64]; e3 *= TURBO3_WHT_SIGNS1[lane+96];
|
| 628 |
+
+#pragma unroll
|
| 629 |
+
+ for (int h = 1; h < 32; h <<= 1) {
|
| 630 |
+
+ acc_t o0 = __shfl_xor_sync(0xFFFFFFFFu, e0, h); e0 = (lane & h) ? o0 - e0 : e0 + o0;
|
| 631 |
+
+ acc_t o1 = __shfl_xor_sync(0xFFFFFFFFu, e1, h); e1 = (lane & h) ? o1 - e1 : e1 + o1;
|
| 632 |
+
+ acc_t o2 = __shfl_xor_sync(0xFFFFFFFFu, e2, h); e2 = (lane & h) ? o2 - e2 : e2 + o2;
|
| 633 |
+
+ acc_t o3 = __shfl_xor_sync(0xFFFFFFFFu, e3, h); e3 = (lane & h) ? o3 - e3 : e3 + o3;
|
| 634 |
+
}
|
| 635 |
+
- __syncthreads();
|
| 636 |
+
+ { acc_t a = e0, b = e1; e0 = a + b; e1 = a - b; acc_t c = e2, d = e3; e2 = c + d; e3 = c - d; } // cross-32
|
| 637 |
+
+ { acc_t a = e0, b = e2; e0 = a + b; e2 = a - b; acc_t c = e1, d = e3; e1 = c + d; e3 = c - d; } // cross-64
|
| 638 |
+
constexpr float inv_sqrt_128 = 0.08838834764831845f;
|
| 639 |
+
- if (sid < 128) x[sid] *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[sid];
|
| 640 |
+
- __syncthreads();
|
| 641 |
+
-
|
| 642 |
+
- if (sid == 0) cost[0] = grp_norm;
|
| 643 |
+
- __syncthreads();
|
| 644 |
+
-
|
| 645 |
+
- acc_t saved_norm = cost[0];
|
| 646 |
+
-
|
| 647 |
+
- // Viterbi forward pass: double-buffered cost (1 sync/step, was 3)
|
| 648 |
+
- uint8_t * bt = use_shared_bt ? bt_shared : bt_buf + (int64_t)blockIdx.x * (128 * 64);
|
| 649 |
+
- cost[sid] = 0.0f;
|
| 650 |
+
- __syncthreads();
|
| 651 |
+
-
|
| 652 |
+
+ e0 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane]; e1 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane+32];
|
| 653 |
+
+ e2 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane+64]; e3 *= inv_sqrt_128 * TURBO3_WHT_SIGNS2[lane+96];
|
| 654 |
+
+ x[lane] = e0; x[lane+32] = e1; x[lane+64] = e2; x[lane+96] = e3;
|
| 655 |
+
+ __syncwarp();
|
| 656 |
+
+ acc_t saved_norm = grp_norm;
|
| 657 |
+
+
|
| 658 |
+
+ // --- Viterbi forward DP: warp-synchronous (8 states/lane, thread-local quartet pred-min, __syncwarp). ---
|
| 659 |
+
+ // lane L owns states {8L..8L+7} = trellis-groups 2L (states 8L..8L+3) and 2L+1 (states 8L+4..8L+7); each
|
| 660 |
+
+ // group's 4 predecessors (g*4..g*4+3) fall within lane g/2, so predecessor-min is thread-local (no shuffle).
|
| 661 |
+
+ acc_t c[8];
|
| 662 |
+
+#pragma unroll
|
| 663 |
+
+ for (int i = 0; i < 8; i++) c[i] = 0.0f;
|
| 664 |
+
for (int t = 0; t < 128; t++) {
|
| 665 |
+
- acc_t * cost_rd = (t & 1) ? cost_b : cost;
|
| 666 |
+
- acc_t * cost_wr = (t & 1) ? cost : cost_b;
|
| 667 |
+
-
|
| 668 |
+
+ acc_t pm0 = c[0]; int pp0 = 0;
|
| 669 |
+
+#pragma unroll
|
| 670 |
+
+ for (int j = 1; j < 4; j++) { if (c[j] < pm0) { pm0 = c[j]; pp0 = j; } }
|
| 671 |
+
+ acc_t pm1 = c[4]; int pp1 = 0;
|
| 672 |
+
+#pragma unroll
|
| 673 |
+
+ for (int j = 1; j < 4; j++) { if (c[4+j] < pm1) { pm1 = c[4+j]; pp1 = j; } }
|
| 674 |
+
+ pred_min_cost[2*lane] = pm0; pred_min_cost[2*lane+1] = pm1;
|
| 675 |
+
+ bt[t*64 + 2*lane] = (uint8_t) pp0; bt[t*64 + 2*lane+1] = (uint8_t) pp1;
|
| 676 |
+
+ __syncwarp();
|
| 677 |
+
acc_t xt = x[t];
|
| 678 |
+
-
|
| 679 |
+
- // Right-shift trellis (k=2, L=8): ns = (prev >> 2) | (out << 6).
|
| 680 |
+
- // The best predecessor depends only on sid's low 6 bits, so compute
|
| 681 |
+
- // those 64 minima once instead of repeating the same 4-way scan per state.
|
| 682 |
+
- if (sid < 64) {
|
| 683 |
+
- const int base_prev = sid << 2;
|
| 684 |
+
- acc_t best = cost_rd[base_prev];
|
| 685 |
+
- int best_p = 0;
|
| 686 |
+
-#pragma unroll
|
| 687 |
+
- for (int p = 1; p < 4; p++) {
|
| 688 |
+
- acc_t c = cost_rd[base_prev | p];
|
| 689 |
+
- if (c < best) {
|
| 690 |
+
- best = c;
|
| 691 |
+
- best_p = p;
|
| 692 |
+
- }
|
| 693 |
+
- }
|
| 694 |
+
- pred_min_cost[sid] = best;
|
| 695 |
+
- bt[t * 64 + sid] = (uint8_t) best_p;
|
| 696 |
+
- }
|
| 697 |
+
- __syncthreads();
|
| 698 |
+
-
|
| 699 |
+
- const int pred_idx = sid & 0x3F;
|
| 700 |
+
- acc_t dist = xt - d_turbo2_tcq_codebook[sid];
|
| 701 |
+
- dist = dist * dist;
|
| 702 |
+
-
|
| 703 |
+
- cost_wr[sid] = pred_min_cost[pred_idx] + dist;
|
| 704 |
+
- __syncthreads();
|
| 705 |
+
- }
|
| 706 |
+
- // After 128 steps (even count): final costs in cost[]
|
| 707 |
+
-
|
| 708 |
+
- // Warp argmin over 256 costs
|
| 709 |
+
- {
|
| 710 |
+
- acc_t my_cost = cost[sid];
|
| 711 |
+
- int my_idx = sid;
|
| 712 |
+
- #pragma unroll
|
| 713 |
+
- for (int offset = 16; offset > 0; offset >>= 1) {
|
| 714 |
+
- acc_t other_cost = __shfl_xor_sync(0xFFFFFFFFULL, my_cost, offset);
|
| 715 |
+
- int other_idx = __shfl_xor_sync(0xFFFFFFFFULL, my_idx, offset);
|
| 716 |
+
- if (other_cost < my_cost) { my_cost = other_cost; my_idx = other_idx; }
|
| 717 |
+
- }
|
| 718 |
+
- if (sid % 32 == 0) {
|
| 719 |
+
- warp_min_cost[sid / 32] = my_cost;
|
| 720 |
+
- warp_min_idx[sid / 32] = my_idx;
|
| 721 |
+
- }
|
| 722 |
+
+#pragma unroll
|
| 723 |
+
+ for (int i = 0; i < 8; i++) { int s = 8*lane + i; acc_t d = xt - d_turbo2_tcq_codebook[s]; c[i] = pred_min_cost[s & 0x3F] + d * d; }
|
| 724 |
+
+ __syncwarp();
|
| 725 |
+
}
|
| 726 |
+
- __syncthreads();
|
| 727 |
+
- if (sid < 32) {
|
| 728 |
+
- acc_t best = (sid < 8) ? warp_min_cost[sid] : 3.4028234663852886e38f;
|
| 729 |
+
- int best_idx = (sid < 8) ? warp_min_idx[sid] : 0;
|
| 730 |
+
-#pragma unroll
|
| 731 |
+
- for (int offset = 16; offset > 0; offset >>= 1) {
|
| 732 |
+
- acc_t other_cost = __shfl_down_sync(0xFFFFFFFFULL, best, offset);
|
| 733 |
+
- int other_idx = __shfl_down_sync(0xFFFFFFFFULL, best_idx, offset);
|
| 734 |
+
- if (other_cost < best) {
|
| 735 |
+
- best = other_cost;
|
| 736 |
+
- best_idx = other_idx;
|
| 737 |
+
- }
|
| 738 |
+
- }
|
| 739 |
+
- if (sid == 0) {
|
| 740 |
+
- shared_initial_state = best_idx;
|
| 741 |
+
- }
|
| 742 |
+
+ // --- argmin over 256 states (per-lane min over 8, then warp-shuffle) ---
|
| 743 |
+
+ acc_t mc = c[0]; int mi = 8*lane;
|
| 744 |
+
+#pragma unroll
|
| 745 |
+
+ for (int i = 1; i < 8; i++) { if (c[i] < mc) { mc = c[i]; mi = 8*lane + i; } }
|
| 746 |
+
+#pragma unroll
|
| 747 |
+
+ for (int o = 16; o > 0; o >>= 1) {
|
| 748 |
+
+ acc_t oc = __shfl_xor_sync(0xFFFFFFFFu, mc, o); int oi = __shfl_xor_sync(0xFFFFFFFFu, mi, o);
|
| 749 |
+
+ if (oc < mc || (oc == mc && oi < mi)) { mc = oc; mi = oi; }
|
| 750 |
+
}
|
| 751 |
+
- __syncthreads();
|
| 752 |
+
-
|
| 753 |
+
- // Save x[] to global buffer before backtrack overwrites it
|
| 754 |
+
-
|
| 755 |
+
- // Backtrack (inherently sequential, reads compressed bt)
|
| 756 |
+
+ // --- backtrack (lane 0; outputs reuse the x slab) β turbo2: 2-bit output (state>>6), <<2 trellis shift ---
|
| 757 |
+
uint8_t * outputs = (uint8_t *)x;
|
| 758 |
+
- if (sid == 0) {
|
| 759 |
+
- int state = shared_initial_state;
|
| 760 |
+
+ if (lane == 0) {
|
| 761 |
+
+ int state = mi;
|
| 762 |
+
for (int t = 127; t >= 0; t--) {
|
| 763 |
+
outputs[t] = (uint8_t)(state >> 6);
|
| 764 |
+
- int p = bt[t * 64 + (state & 0x3F)];
|
| 765 |
+
+ int p = bt[t*64 + (state & 0x3F)];
|
| 766 |
+
state = ((state & 0x3F) << 2) | p;
|
| 767 |
+
}
|
| 768 |
+
- shared_initial_state = state;
|
| 769 |
+
+ ini_s[warp] = state;
|
| 770 |
+
}
|
| 771 |
+
- __syncthreads();
|
| 772 |
+
-
|
| 773 |
+
- // Save output symbols to global buffer
|
| 774 |
+
+ __syncwarp();
|
| 775 |
+
+ int initial_state = ini_s[warp];
|
| 776 |
+
|
| 777 |
+
- // Parallel recon norm: t>=3 can compute state directly from 4 outputs (4 shifts of 2 = 8 bits)
|
| 778 |
+
- acc_t my_recon_sq = 0.0f;
|
| 779 |
+
- if (sid < 128) {
|
| 780 |
+
- int cur_state;
|
| 781 |
+
- if (sid < 3) {
|
| 782 |
+
- cur_state = shared_initial_state;
|
| 783 |
+
- for (int t = 0; t <= sid; t++)
|
| 784 |
+
- cur_state = (cur_state >> 2) | (((int)outputs[t]) << 6);
|
| 785 |
+
- } else {
|
| 786 |
+
- cur_state = ((int)outputs[sid - 3] & 0x3)
|
| 787 |
+
- | (((int)outputs[sid - 2] & 0x3) << 2)
|
| 788 |
+
- | (((int)outputs[sid - 1] & 0x3) << 4)
|
| 789 |
+
- | (((int)outputs[sid] & 0x3) << 6);
|
| 790 |
+
- }
|
| 791 |
+
- acc_t c = d_turbo2_tcq_codebook[cur_state];
|
| 792 |
+
- my_recon_sq = c * c;
|
| 793 |
+
- }
|
| 794 |
+
- cost[sid] = my_recon_sq;
|
| 795 |
+
- __syncthreads();
|
| 796 |
+
- for (int stride = 128; stride >= 32; stride >>= 1) {
|
| 797 |
+
- if (sid < stride) cost[sid] += cost[sid + stride];
|
| 798 |
+
- __syncthreads();
|
| 799 |
+
- }
|
| 800 |
+
- if (sid < 32) {
|
| 801 |
+
- acc_t v = cost[sid];
|
| 802 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 16);
|
| 803 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 8);
|
| 804 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 4);
|
| 805 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 2);
|
| 806 |
+
- v += __shfl_down_sync(0xFFFFFFFFULL, v, 1);
|
| 807 |
+
- if (sid == 0) cost[0] = v;
|
| 808 |
+
+ // --- recon norm (warp-shuffle sum; turbo2: pos<3 walk, else 4 symbols x 2-bit) ---
|
| 809 |
+
+ acc_t recon_sq = 0.0f;
|
| 810 |
+
+#pragma unroll
|
| 811 |
+
+ for (int b = 0; b < 4; b++) {
|
| 812 |
+
+ int pos = lane + 32*b; int cur_state;
|
| 813 |
+
+ if (pos < 3) { cur_state = initial_state; for (int t = 0; t <= pos; t++) cur_state = (cur_state >> 2) | (((int)outputs[t]) << 6); }
|
| 814 |
+
+ else { cur_state = ((int)outputs[pos-3] & 0x3) | (((int)outputs[pos-2] & 0x3) << 2) | (((int)outputs[pos-1] & 0x3) << 4) | (((int)outputs[pos] & 0x3) << 6); }
|
| 815 |
+
+ acc_t cc = d_turbo2_tcq_codebook[cur_state]; recon_sq += cc * cc;
|
| 816 |
+
}
|
| 817 |
+
- __syncthreads();
|
| 818 |
+
- acc_t recon_norm = tcq_acc_sqrt(cost[0]);
|
| 819 |
+
+#pragma unroll
|
| 820 |
+
+ for (int o = 16; o > 0; o >>= 1) recon_sq += __shfl_xor_sync(0xFFFFFFFFu, recon_sq, o);
|
| 821 |
+
+ acc_t recon_norm = tcq_acc_sqrt(recon_sq);
|
| 822 |
+
acc_t corrected_norm = (recon_norm > 1e-10f) ? saved_norm / recon_norm : saved_norm;
|
| 823 |
+
corrected_norm *= iq_is_k ? d_tcq_norm_alpha : d_tcq_norm_alpha_v;
|
| 824 |
+
|
| 825 |
+
- // Parallel bitpack: qs stores 6 initial-state bits followed by 128 2-bit
|
| 826 |
+
- // output symbols. Each byte is independent (2-bit symbols never cross byte
|
| 827 |
+
- // boundaries after the 6-bit prefix), so avoid the serial OR loop.
|
| 828 |
+
- if (sid < 33) {
|
| 829 |
+
- const int init_bits = (shared_initial_state >> 2) & 0x3F;
|
| 830 |
+
- uint8_t packed = 0;
|
| 831 |
+
-#pragma unroll
|
| 832 |
+
- for (int bit = 0; bit < 8; bit++) {
|
| 833 |
+
- const int pos = sid * 8 + bit;
|
| 834 |
+
- int v = 0;
|
| 835 |
+
- if (pos < 6) {
|
| 836 |
+
- v = (init_bits >> pos) & 1;
|
| 837 |
+
- } else {
|
| 838 |
+
- const int sym_bit_pos = pos - 6;
|
| 839 |
+
- const int sym_idx = sym_bit_pos / 2;
|
| 840 |
+
- if (sym_idx < 128) {
|
| 841 |
+
- v = (outputs[sym_idx] >> (sym_bit_pos % 2)) & 1;
|
| 842 |
+
- }
|
| 843 |
+
+ // --- bitpack: 6 init bits + 128 x 2-bit symbols = 33 bytes; lane handles bytes {lane, lane+32} < 33 ---
|
| 844 |
+
+ const int init_bits = (initial_state >> 2) & 0x3F;
|
| 845 |
+
+#pragma unroll
|
| 846 |
+
+ for (int bb = 0; bb < 2; bb++) {
|
| 847 |
+
+ int byte = lane + 32*bb;
|
| 848 |
+
+ if (byte < 33) {
|
| 849 |
+
+ uint8_t packed = 0;
|
| 850 |
+
+#pragma unroll
|
| 851 |
+
+ for (int bit = 0; bit < 8; bit++) {
|
| 852 |
+
+ const int pos = byte*8 + bit; int v = 0;
|
| 853 |
+
+ if (pos < 6) v = (init_bits >> pos) & 1;
|
| 854 |
+
+ else { const int sym_bit_pos = pos - 6; const int sym_idx = sym_bit_pos / 2; if (sym_idx < 128) v = (outputs[sym_idx] >> (sym_bit_pos % 2)) & 1; }
|
| 855 |
+
+ packed |= (uint8_t)(v << bit);
|
| 856 |
+
}
|
| 857 |
+
- packed |= (uint8_t)(v << bit);
|
| 858 |
+
+ dst_blk->qs[byte] = packed;
|
| 859 |
+
}
|
| 860 |
+
- dst_blk->qs[sid] = packed;
|
| 861 |
+
- }
|
| 862 |
+
- if (sid == 0) {
|
| 863 |
+
- dst_blk->norm = __float2half((float)corrected_norm);
|
| 864 |
+
}
|
| 865 |
+
+ if (lane == 0) dst_blk->norm = __float2half((float)corrected_norm);
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
// 2-bit TCQ GET_ROWS dequantize
|
patches/0084-cuda-graphs.patch
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/llamafile/cuda.sh b/llamafile/cuda.sh
|
| 2 |
+
--- a/llamafile/cuda.sh
|
| 3 |
+
+++ b/llamafile/cuda.sh
|
| 4 |
+
@@ -259,6 +259,19 @@
|
| 5 |
+
COMMON_FLAGS="$COMMON_FLAGS -DGGML_CUDA_FA_ALL_QUANTS"
|
| 6 |
+
fi
|
| 7 |
+
|
| 8 |
+
+# opencoti-hook: cuda-graphs (#533 decode-tps lever L6) β enable CUDA graph capture+replay.
|
| 9 |
+
+# Upstream llama.cpp's CMake defines GGML_CUDA_USE_GRAPHS by default; the llamafile cuda.sh build
|
| 10 |
+
+# omitted it, so the entire `#ifdef USE_CUDA_GRAPH` capture/replay path (ggml-cuda.cu:3318/4478/
|
| 11 |
+
+# 4515/4593/4698; common.cuh:1218) was compiled OUT β every decode step launched hundreds of
|
| 12 |
+
+# kernels individually with full per-launch CPU overhead on a latency-bound (~4.5% SM) decode.
|
| 13 |
+
+# This flag turns it on; the runtime guards (split buffers, unsupported node types, the opencoti
|
| 14 |
+
+# NEO head-split force-disable) fall back to eager capture-by-capture when a graph isn't viable, so
|
| 15 |
+
+# it composes with the opencoti stack. Tunable: OPENCOTI_NO_CUDA_GRAPHS=1 omits it (pre-#533).
|
| 16 |
+
+# See docs/evaluations/decode-levers.md (L6).
|
| 17 |
+
+if [ "${OPENCOTI_NO_CUDA_GRAPHS:-0}" != "1" ]; then
|
| 18 |
+
+ COMMON_FLAGS="$COMMON_FLAGS -DGGML_CUDA_USE_GRAPHS"
|
| 19 |
+
+fi
|
| 20 |
+
+
|
| 21 |
+
# opencoti-hook: cuda-build-parallelism (#531) β nvcc intra-TU arch threading. Each nvcc TU compiles
|
| 22 |
+
# the gencode arches (sm_75/80/86/89/90/120f); without --threads they run serially, so any TU left
|
| 23 |
+
# alone at the build tail uses one core while the rest idle. --threads compiles the arches
|
patches/0085-dca-quant-prefill-inlaunch.patch
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0085] DCA quant-KV prefill β in-launch single-pass dequant (#554)
|
| 3 |
+
|
| 4 |
+
Move scalar/turbo K/V dequant for the multi-chunk DCA fused flash-attn op out of the
|
| 5 |
+
two-stage whole-cache graph lift (dca_lift_to_f16: q->f32->f16 materialized every
|
| 6 |
+
forward, GPU-memory-bound and O(cache) β catastrophic at >=512k) and into a single-pass
|
| 7 |
+
to_fp16_nc pool convert inside the fused launch (mirrors the non-DCA launch_fattn fast
|
| 8 |
+
path). Byte-identical to the prior lift (both compute d*q in f32 then round to f16);
|
| 9 |
+
restores DCA + quantized-KV as a first-class long-context path (~12x at 85k, ~30x at
|
| 10 |
+
512k: 46 -> 1379+ tok/s, the 1M q5_0/q4_0 path). Also moves the regime==3 fused
|
| 11 |
+
early-out above the f16-K asserts in ggml_cuda_flash_attn_ext_lse so quant-K cells reach
|
| 12 |
+
the fused path instead of asserting.
|
| 13 |
+
|
| 14 |
+
dca_is_inkernel_liftable() gates the defer to Q8_0/Q4_0/Q4_1/Q5_0/Q5_1/Q6_0 (+ the
|
| 15 |
+
turbo/TCQ families via the existing to_fp16_nc table); f16/bf16 keep the stock path.
|
| 16 |
+
See .wolf/buglog.json bug-720.
|
| 17 |
+
|
| 18 |
+
diff --git a/llama.cpp/src/dca.cpp b/llama.cpp/src/dca.cpp
|
| 19 |
+
--- a/llama.cpp/src/dca.cpp
|
| 20 |
+
+++ b/llama.cpp/src/dca.cpp
|
| 21 |
+
@@ -204,6 +204,20 @@
|
| 22 |
+
return ggml_cast(ctx0, x, GGML_TYPE_F16);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
+// opencoti F5 dca (DCA all-KV C-perf, bug-720): can the FUSED multi-chunk kernel dequant this cache type
|
| 26 |
+
+// to f16 INSIDE its launch (single-pass to_fp16_nc into a transient pool buffer, mirroring launch_fattn),
|
| 27 |
+
+// instead of dca_lift_to_f16 whole-cache materializing it to f16 in the GRAPH every forward? That graph
|
| 28 |
+
+// lift is the prefill cliff: at 98k/q5_0 the two-stage q->f32->f16 cast ran every ubatch and dragged
|
| 29 |
+
+// DCA-on prefill to ~138 tps vs ~1212 for the same scalar-quant cache DCA-off (which already converts
|
| 30 |
+
+// per-op via launch_fattn). Restrict to the SCALAR quants with a to_fp16_nc converter (convert.cu:843);
|
| 31 |
+
+// turbo/TCQ/bf16 keep dca_lift_to_f16 (already a single-pass DIRECT ->f16 cast, not the slow two-stage),
|
| 32 |
+
+// f16 is a no-op. The in-op convert is byte-IDENTICAL to the lift (both compute d*q in f32 then round to
|
| 33 |
+
+// f16), so this is a pure perf change, gateable by logit-equivalence.
|
| 34 |
+
+static bool dca_is_inkernel_liftable(enum ggml_type t) {
|
| 35 |
+
+ return t == GGML_TYPE_Q8_0 || t == GGML_TYPE_Q4_0 || t == GGML_TYPE_Q4_1 ||
|
| 36 |
+
+ t == GGML_TYPE_Q5_0 || t == GGML_TYPE_Q5_1 || t == GGML_TYPE_Q6_0;
|
| 37 |
+
+}
|
| 38 |
+
+
|
| 39 |
+
ggml_tensor * llm_graph_context::build_attn_dca_core(
|
| 40 |
+
const llama_kv_cache_context * mctx_cur,
|
| 41 |
+
llm_graph_input_dca * inp_dca,
|
| 42 |
+
@@ -226,24 +240,53 @@
|
| 43 |
+
// the kernel). At f16 this is a no-op (byte-identical to the prior assert path). Context-shift
|
| 44 |
+
// stays correct independently: the modular K-shift re-ropes the STORED, still-quantized cache
|
| 45 |
+
// via build_rope_shift's existing dequant->rope->requant quantized branch (llama-kv-cache.cpp).
|
| 46 |
+
- ggml_tensor * k_dca = dca_lift_to_f16(ctx0, mctx_cur->get_k(ctx0, il));
|
| 47 |
+
+ ggml_tensor * k_raw = mctx_cur->get_k(ctx0, il);
|
| 48 |
+
+ ggml_tensor * v_raw = mctx_cur->get_v(ctx0, il);
|
| 49 |
+
+
|
| 50 |
+
+ // opencoti F5 dca (DCA all-KV C-perf, bug-720): on the analytical-band (multi-chunk) path the fused
|
| 51 |
+
+ // kernel dequants scalar-quant K/V to f16 IN ITS LAUNCH (single-pass to_fp16_nc, see
|
| 52 |
+
+ // ggml_cuda_flash_attn_ext_mma_f16_dca_fused_case), so pass the quantized cache THROUGH instead of
|
| 53 |
+
+ // whole-cache materializing it to f16 via dca_lift_to_f16 every forward (the prefill cliff). Gate:
|
| 54 |
+
+ // - !single_chunk: only the fused op carries the in-launch convert; single_chunk uses stock
|
| 55 |
+
+ // ggml_flash_attn_ext (launch_fattn already converts scalar-quant natively) and runs only while
|
| 56 |
+
+ // n_kv <= chunk (a tiny cache) so its lift cost is negligible β keep it on the safe lift path.
|
| 57 |
+
+ // - dca_is_inkernel_liftable: scalar quants the fused launch's to_fp16_nc can read.
|
| 58 |
+
+ // - !v_trans: a transposed quant V view breaks the dim-0 (block) contiguity to_fp16_nc requires; the
|
| 59 |
+
+ // permute(0,2,1,3) below keeps dim-0 intact, but a transpose would not β fall back to the lift.
|
| 60 |
+
+ // The in-op convert is byte-identical to dca_lift_to_f16, so deferred output == lifted output.
|
| 61 |
+
+ const bool v_trans_raw = v_raw->nb[1] > v_raw->nb[2];
|
| 62 |
+
+ const bool defer_quant_lift =
|
| 63 |
+
+ !inp_dca->single_chunk &&
|
| 64 |
+
+ dca_is_inkernel_liftable(k_raw->type) &&
|
| 65 |
+
+ dca_is_inkernel_liftable(v_raw->type) &&
|
| 66 |
+
+ !v_trans_raw;
|
| 67 |
+
|
| 68 |
+
// (2) read V once; permute K + V to the flash-attn layout [n_embd_head, seq, n_head, n_stream].
|
| 69 |
+
// opencoti F5 dca #444 (DCA all-KV C1): lift a block/scalar-quantized V (q8_0/turbo/...) to f16
|
| 70 |
+
// while it is still CONTIGUOUS β a permuted view of a block type is not cleanly cpy-able (the
|
| 71 |
+
// block stride spans dim-0), mirroring build_attn_mha's pre-permute turbo cast. f32 V keeps the
|
| 72 |
+
// post-permute cast below (unchanged); f16 V is untouched (byte-identical).
|
| 73 |
+
- ggml_tensor * v = mctx_cur->get_v(ctx0, il);
|
| 74 |
+
- if (v->type != GGML_TYPE_F16 && v->type != GGML_TYPE_F32) {
|
| 75 |
+
- v = dca_lift_to_f16(ctx0, v);
|
| 76 |
+
- }
|
| 77 |
+
- const bool v_trans = v->nb[1] > v->nb[2];
|
| 78 |
+
- ggml_tensor * vp = ggml_permute(ctx0, v, 0, 2, 1, 3);
|
| 79 |
+
- if (v_trans) {
|
| 80 |
+
- vp = ggml_transpose(ctx0, vp);
|
| 81 |
+
- }
|
| 82 |
+
- if (vp->type == GGML_TYPE_F32) {
|
| 83 |
+
- vp = ggml_cast(ctx0, vp, GGML_TYPE_F16);
|
| 84 |
+
+ ggml_tensor * k_dca;
|
| 85 |
+
+ ggml_tensor * vp;
|
| 86 |
+
+ if (defer_quant_lift) {
|
| 87 |
+
+ // pass scalar-quant K/V straight to the fused op; permute(0,2,1,3) is a clean view (dim-0 = the
|
| 88 |
+
+ // block axis, untouched) that the launch's to_fp16_nc dequants with the permuted strides.
|
| 89 |
+
+ k_dca = k_raw;
|
| 90 |
+
+ vp = ggml_permute(ctx0, v_raw, 0, 2, 1, 3);
|
| 91 |
+
+ } else {
|
| 92 |
+
+ k_dca = dca_lift_to_f16(ctx0, k_raw);
|
| 93 |
+
+ ggml_tensor * v = v_raw;
|
| 94 |
+
+ if (v->type != GGML_TYPE_F16 && v->type != GGML_TYPE_F32) {
|
| 95 |
+
+ v = dca_lift_to_f16(ctx0, v);
|
| 96 |
+
+ }
|
| 97 |
+
+ const bool v_trans = v->nb[1] > v->nb[2];
|
| 98 |
+
+ vp = ggml_permute(ctx0, v, 0, 2, 1, 3);
|
| 99 |
+
+ if (v_trans) {
|
| 100 |
+
+ vp = ggml_transpose(ctx0, vp);
|
| 101 |
+
+ }
|
| 102 |
+
+ if (vp->type == GGML_TYPE_F32) {
|
| 103 |
+
+ vp = ggml_cast(ctx0, vp, GGML_TYPE_F16);
|
| 104 |
+
+ }
|
| 105 |
+
}
|
| 106 |
+
ggml_tensor * kp = ggml_permute(ctx0, k_dca, 0, 2, 1, 3);
|
| 107 |
+
|
| 108 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn.cu b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 109 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 110 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 111 |
+
@@ -788,6 +788,17 @@
|
| 112 |
+
const int64_t n_batch = Q->ne[3];
|
| 113 |
+
const int64_t n_rows = n_head * n_q * n_batch;
|
| 114 |
+
GGML_ASSERT(dst->ne[0] == DV + 1 && "flash_attn_ext_lse dst must pack [O|lse] on ne[0]");
|
| 115 |
+
+
|
| 116 |
+
+ // opencoti F5 dca (DCA all-KV C-perf, bug-720): the FUSED regime (op_params[4]==3) self-contains its
|
| 117 |
+
+ // attention + normalization and now dequant-on-lifts a scalar-quantized K/V INSIDE its own launch
|
| 118 |
+
+ // (ggml_cuda_flash_attn_ext_mma_f16_dca_fused_case). It never runs the streaming_lse recompute below, so
|
| 119 |
+
+ // route it out BEFORE the recompute-only f32-Q / f16-K asserts (the streaming_lse_kernel reads K as f16,
|
| 120 |
+
+ // hence those gates). Without this early-out a quantized DCA cache aborts here at line ~792.
|
| 121 |
+
+ if (ggml_get_op_params_i32(dst, 4) == 3) {
|
| 122 |
+
+ ggml_cuda_flash_attn_ext_mma_f16_dca_fused_switch(ctx, dst);
|
| 123 |
+
+ return;
|
| 124 |
+
+ }
|
| 125 |
+
+
|
| 126 |
+
GGML_ASSERT(Q->type == GGML_TYPE_F32 && "flash_attn_ext_lse expects f32 Q for the lse recompute");
|
| 127 |
+
GGML_ASSERT(K->type == GGML_TYPE_F16 && "flash_attn_ext_lse expects f16 K for the lse recompute");
|
| 128 |
+
|
| 129 |
+
@@ -820,19 +831,11 @@
|
| 130 |
+
const int dca_chunk = ggml_get_op_params_i32(dst, 3);
|
| 131 |
+
const int dca_regime = ggml_get_op_params_i32(dst, 4);
|
| 132 |
+
|
| 133 |
+
- // opencoti F5 dca perf (T87.pD Branch B): FUSED single-pass DCA. The three band passes (INTER/SUCC/
|
| 134 |
+
- // INTRA) are folded into ONE block-per-tile kernel that streams K/V once and swaps the pre-roped Q
|
| 135 |
+
- // variant at the 2 phase boundaries β no per-regime LSE emit, no host combine, no streaming_lse
|
| 136 |
+
- // fallback. The fused case reads its inputs straight off dst (src[0]=Q_intra, src[1]=K, src[2]=V,
|
| 137 |
+
- // src[3]=mask_intra, src[4]=pos_q_real, src[5]=Q_succ, src[6]=Q_inter, op_params[3]=chunk) and writes
|
| 138 |
+
- // normalized O into the leading DV*n_rows of dst's [O|lse] buffer (the trailing lse row is left
|
| 139 |
+
- // untouched β dca.cpp slices it off). Bands partition the causal range [0,i] β one online-softmax
|
| 140 |
+
- // pass is bit-faithful to the 3-op + host LSE combine it replaces.
|
| 141 |
+
- if (dca_regime == 3) {
|
| 142 |
+
- ggml_cuda_flash_attn_ext_mma_f16_dca_fused_switch(ctx, dst);
|
| 143 |
+
- return;
|
| 144 |
+
- }
|
| 145 |
+
-
|
| 146 |
+
+ // opencoti F5 dca perf (T87.pD Branch B): the FUSED single-pass DCA regime (op_params[4]==3) β three
|
| 147 |
+
+ // band passes (INTER/SUCC/INTRA) folded into ONE block-per-tile kernel, no per-regime LSE emit / host
|
| 148 |
+
+ // combine / streaming_lse fallback β is dispatched at the TOP of this function (bug-720), before the
|
| 149 |
+
+ // f32-Q / f16-K recompute asserts, so a quantized DCA cache reaches its in-launch dequant. Only the
|
| 150 |
+
+ // non-fused (regime 0/1/2) lse-recompute path continues below.
|
| 151 |
+
float * lse_out = (float *) dst->data + DV * n_rows;
|
| 152 |
+
opencoti_fattn_dst_lse = lse_out;
|
| 153 |
+
opencoti_fattn_dst_lse_written = false;
|
| 154 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh b/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh
|
| 155 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh
|
| 156 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn-mma-f16.cuh
|
| 157 |
+
@@ -2360,7 +2360,14 @@
|
| 158 |
+
const ggml_tensor * Qi = dst->src[6]; // Q_inter
|
| 159 |
+
const ggml_tensor * Ms = dst->src[7]; // opencoti F5 dca (#617B): dense SUCC mask (NULL β fast path)
|
| 160 |
+
const ggml_tensor * Mi = dst->src[8]; // opencoti F5 dca (#617B): dense INTER mask (NULL β fast path)
|
| 161 |
+
- GGML_ASSERT(K->type == GGML_TYPE_F16 && V->type == GGML_TYPE_F16 && "dca fused: f16 K/V required");
|
| 162 |
+
+ // opencoti F5 dca (DCA all-KV C-perf, bug-720): the fused kernel reads f16 K/V, but a scalar-quantized
|
| 163 |
+
+ // cache is now dequant'd to f16 IN THIS LAUNCH below (single-pass to_fp16_nc into a transient pool
|
| 164 |
+
+ // buffer, mirroring launch_fattn) instead of being whole-cache materialized to f16 in the graph every
|
| 165 |
+
+ // forward (the prefill cliff: ~138 vs ~1212 tps at 98k/q5_0). Accept f16, or any type with a to_fp16_nc
|
| 166 |
+
+ // converter; reject anything else (the kernel would have no f16 source).
|
| 167 |
+
+ GGML_ASSERT((K->type == GGML_TYPE_F16 || ggml_get_to_fp16_nc_cuda(K->type) != nullptr) &&
|
| 168 |
+
+ (V->type == GGML_TYPE_F16 || ggml_get_to_fp16_nc_cuda(V->type) != nullptr) &&
|
| 169 |
+
+ "dca fused: K/V must be f16 or have a to_fp16_nc converter");
|
| 170 |
+
GGML_ASSERT(PQ && Qs && Qi && mask && "dca fused: missing src tensors");
|
| 171 |
+
// opencoti F5 dca (#617B): FRAGMENTED fallback engaged iff the host attached the dense SUCC/INTER
|
| 172 |
+
// masks (only on a non-contiguous, post-context-shift cache). Both present or both absent.
|
| 173 |
+
@@ -2410,6 +2417,44 @@
|
| 174 |
+
ggml_cuda_pool & pool = ctx.pool();
|
| 175 |
+
cudaStream_t main_stream = ctx.stream();
|
| 176 |
+
|
| 177 |
+
+ // opencoti F5 dca (DCA all-KV C-perf, bug-720): dequant-on-lift a scalar-quantized K/V into a transient
|
| 178 |
+
+ // f16 pool buffer here β the SAME single-pass to_fp16_nc convert the stock MMA path uses (launch_fattn,
|
| 179 |
+
+ // fattn-common.cuh) β replacing the old two-stage whole-cache graph lift (dca.cpp dca_lift_to_f16,
|
| 180 |
+
+ // q->f32->f16) that ran every forward and dominated quant-KV DCA-on prefill. K/V are PERMUTED views
|
| 181 |
+
+ // (kp/vp, dim-0 = the contiguous block axis), so use the non-contiguous (strided) converter and pack to
|
| 182 |
+
+ // a contiguous f16 slot; the kernel then reads the slot with contiguous strides. f16 K/V skip the
|
| 183 |
+
+ // convert (k_data/v_data alias the tensor, strides unchanged β byte-identical to the prior f16 path).
|
| 184 |
+
+ ggml_cuda_pool_alloc<half> K_f16(pool);
|
| 185 |
+
+ ggml_cuda_pool_alloc<half> V_f16(pool);
|
| 186 |
+
+ const char * k_data = (const char *) K->data;
|
| 187 |
+
+ size_t nb_k1 = K->nb[1], nb_k2 = K->nb[2], nb_k3 = K->nb[3];
|
| 188 |
+
+ const char * v_data = (const char *) V->data;
|
| 189 |
+
+ size_t nb_v1 = V->nb[1], nb_v2 = V->nb[2], nb_v3 = V->nb[3];
|
| 190 |
+
+ if (K->type != GGML_TYPE_F16) {
|
| 191 |
+
+ const size_t ts = ggml_type_size(K->type);
|
| 192 |
+
+ GGML_ASSERT(K->nb[0] == ts && "dca fused: quant K must have contiguous dim-0 (block axis)");
|
| 193 |
+
+ to_fp16_nc_cuda_t to_fp16 = ggml_get_to_fp16_nc_cuda(K->type);
|
| 194 |
+
+ K_f16.alloc(ggml_nelements(K));
|
| 195 |
+
+ to_fp16(k_data, K_f16.ptr, K->ne[0], K->ne[1], K->ne[2], K->ne[3],
|
| 196 |
+
+ (int64_t) (nb_k1 / ts), (int64_t) (nb_k2 / ts), (int64_t) (nb_k3 / ts), main_stream);
|
| 197 |
+
+ k_data = (const char *) K_f16.ptr;
|
| 198 |
+
+ nb_k1 = K->ne[0] * sizeof(half);
|
| 199 |
+
+ nb_k2 = K->ne[1] * nb_k1;
|
| 200 |
+
+ nb_k3 = K->ne[2] * nb_k2;
|
| 201 |
+
+ }
|
| 202 |
+
+ if (V->type != GGML_TYPE_F16) {
|
| 203 |
+
+ const size_t ts = ggml_type_size(V->type);
|
| 204 |
+
+ GGML_ASSERT(V->nb[0] == ts && "dca fused: quant V must have contiguous dim-0 (block axis)");
|
| 205 |
+
+ to_fp16_nc_cuda_t to_fp16 = ggml_get_to_fp16_nc_cuda(V->type);
|
| 206 |
+
+ V_f16.alloc(ggml_nelements(V));
|
| 207 |
+
+ to_fp16(v_data, V_f16.ptr, V->ne[0], V->ne[1], V->ne[2], V->ne[3],
|
| 208 |
+
+ (int64_t) (nb_v1 / ts), (int64_t) (nb_v2 / ts), (int64_t) (nb_v3 / ts), main_stream);
|
| 209 |
+
+ v_data = (const char *) V_f16.ptr;
|
| 210 |
+
+ nb_v1 = V->ne[0] * sizeof(half);
|
| 211 |
+
+ nb_v2 = V->ne[1] * nb_v1;
|
| 212 |
+
+ nb_v3 = V->ne[2] * nb_v2;
|
| 213 |
+
+ }
|
| 214 |
+
+
|
| 215 |
+
const int ntiles_x = (Q->ne[1] + ncols1 - 1) / ncols1;
|
| 216 |
+
const int gqa_ratio = Q->ne[2] / K->ne[2];
|
| 217 |
+
const int ntiles_z_gqa = (gqa_ratio + ncols2 - 1) / ncols2;
|
| 218 |
+
@@ -2490,7 +2535,7 @@
|
| 219 |
+
|
| 220 |
+
const dim3 grid(nblocks, 1, 1);
|
| 221 |
+
kernel<<<grid, block_dim, nbytes_shared_total, main_stream>>>(
|
| 222 |
+
- (const char *) Q->data, (const char *) K->data, (const char *) V->data,
|
| 223 |
+
+ (const char *) Q->data, k_data, v_data, // opencoti dca bug-720: f16 slot (or aliased f16 tensor)
|
| 224 |
+
(const char *) mask->data,
|
| 225 |
+
Ms ? (const char *) Ms->data : nullptr, // opencoti F5 dca (#617B): dense SUCC mask (null β fast path)
|
| 226 |
+
Mi ? (const char *) Mi->data : nullptr, // opencoti F5 dca (#617B): dense INTER mask (null β fast path)
|
| 227 |
+
@@ -2499,8 +2544,8 @@
|
| 228 |
+
(float *) KQV->data, dst_meta_ptr,
|
| 229 |
+
scale, 0.0f /*max_bias*/, 1.0f /*m0*/, 1.0f /*m1*/, n_head_log2, logit_softcap,
|
| 230 |
+
Q->ne[0], ne01, Q->ne[2], Q->ne[3], Q->nb[1], Q->nb[2], Q->nb[3],
|
| 231 |
+
- K->ne[0], K->ne[1], K->ne[2], K->ne[3], K->nb[1], K->nb[2], K->nb[3],
|
| 232 |
+
- V->nb[1], V->nb[2], V->nb[3],
|
| 233 |
+
+ K->ne[0], K->ne[1], K->ne[2], K->ne[3], nb_k1, nb_k2, nb_k3, // opencoti dca bug-720: slot strides
|
| 234 |
+
+ nb_v1, nb_v2, nb_v3,
|
| 235 |
+
mask->ne[1], mask->ne[2], mask->ne[3], mask->nb[1], mask->nb[2], mask->nb[3]);
|
| 236 |
+
CUDA_CHECK(cudaGetLastError());
|
| 237 |
+
|
patches/0086-f16-moe-mmf-prefill.patch
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0086] F16 MoE prefill β route large-batch f16 mul_mat_id to MMF (#555)
|
| 3 |
+
|
| 4 |
+
ggml_cuda_mul_mat_id had no fast CUDA path for f16/bf16/f32 experts at prefill batch:
|
| 5 |
+
MMQ is quantized-only, and ggml_cuda_should_use_mmf rejected mul_mat_id whenever
|
| 6 |
+
src1_ncols (= n_tokens) exceeded 512 (or 128 when the expert dim > 1024). At prefill
|
| 7 |
+
(~32k tokens) that gate always failed, so f16 MoE fell into the host-side sorted
|
| 8 |
+
fallback in ggml_cuda_mul_mat_id: a per-call cudaStreamSynchronize (which also disables
|
| 9 |
+
CUDA graphs) + an O(n_expert*n_tokens*n_used) host sort + a per-expert cuBLAS GEMM loop.
|
| 10 |
+
Quantized MoE dodges all of it via the single fused MMQ-id kernel. Net: f16 128e prefill
|
| 11 |
+
ran ~12x slower than Q6_K (690 vs 8857 tok/s on Gemma-4-A4B 128e).
|
| 12 |
+
|
| 13 |
+
The MMF kernel's `ncols_dst > 16` branch (ggml_cuda_mul_mat_f, this file) already performs
|
| 14 |
+
GPU-side expert compaction (ggml_cuda_launch_mm_ids_helper, mmid.cuh) entirely on-stream
|
| 15 |
+
and handles arbitrary n_tokens -- the gate was a conservative tuning heuristic, not a
|
| 16 |
+
capability limit. This compiles out that mul_mat_id gate so large-batch f16/bf16/f32 MoE
|
| 17 |
+
rides the fast GPU-compacted MMF path. Measured on a real 128e-F16 (bs2 RTX PRO 6000):
|
| 18 |
+
prefill 690 -> 3081 tok/s (4.47x); RULER-vt@32k retrieval 100.0 (== Q6_K, correctness-clean).
|
| 19 |
+
The residual gap to Q6_K is just the legitimate f16-vs-q6 weight-bandwidth ratio (~2.2x).
|
| 20 |
+
Quantized MoE (MMQ path) and dense f16 mul_mat are untouched -- both are rejected/handled
|
| 21 |
+
before this gate. The upstream gate is preserved, compiled out, behind
|
| 22 |
+
-DOPENCOTI_MMF_LEGACY_GATE.
|
| 23 |
+
|
| 24 |
+
diff --git a/llama.cpp/ggml/src/ggml-cuda/mmf.cu b/llama.cpp/ggml/src/ggml-cuda/mmf.cu
|
| 25 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/mmf.cu
|
| 26 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/mmf.cu
|
| 27 |
+
@@ -160,11 +160,22 @@
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
if (mul_mat_id) {
|
| 31 |
+
+ // opencoti-hook: f16-moe-prefill (#555) β the upstream heuristic below rejected
|
| 32 |
+
+ // large-batch (prefill) f16/bf16/f32 mul_mat_id, sending it to the host-sync sorted
|
| 33 |
+
+ // fallback in ggml_cuda_mul_mat_id (per-call cudaStreamSynchronize + per-expert cuBLAS
|
| 34 |
+
+ // loop + disabled CUDA graphs => measured 12x slower than the quantized MMQ-id path:
|
| 35 |
+
+ // 690 vs 8857 tok/s prefill on Gemma-4-A4B 128e). The MMF kernel's `ncols_dst > 16`
|
| 36 |
+
+ // branch already performs GPU-side expert compaction (ggml_cuda_launch_mm_ids_helper,
|
| 37 |
+
+ // mmid.cuh) entirely on-stream and handles arbitrary n_tokens, so keep f16 MoE on the
|
| 38 |
+
+ // fast fused MMF path for any batch size. Upstream gate preserved (disabled) for ref;
|
| 39 |
+
+ // re-enable with -DOPENCOTI_MMF_LEGACY_GATE.
|
| 40 |
+
+#ifdef OPENCOTI_MMF_LEGACY_GATE
|
| 41 |
+
if (src0_ne[1] <= 1024 && src1_ncols > 512) {
|
| 42 |
+
return false;
|
| 43 |
+
} else if(src0_ne[1] > 1024 && src1_ncols > 128) {
|
| 44 |
+
return false;
|
| 45 |
+
}
|
| 46 |
+
+#endif
|
| 47 |
+
} else {
|
| 48 |
+
if (GGML_CUDA_CC_IS_RDNA3_0(cc) && src1_ncols > 8) {
|
| 49 |
+
return false;
|
patches/0087-dca-yarn-factor.patch
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0087] DCA: wire --dca-yarn-factor into build_dca_rope mscale (bug-740 / #550)
|
| 3 |
+
|
| 4 |
+
--dca-yarn-factor was fully plumbed (arg.cpp -> params -> cparams.dca_yarn_factor,
|
| 5 |
+
public C API, TS adapter) but never READ by the rope path, so the flag was inert
|
| 6 |
+
(bug-740). build_dca_rope now composes it into the attention scaling:
|
| 7 |
+
dca_attn_factor = attn_factor * cparams.dca_yarn_factor
|
| 8 |
+
and passes that to both ggml_rope_multi (MROPE) and ggml_rope_ext (NEOX) in place of
|
| 9 |
+
the bare attn_factor. This is the YaRN mscale piece of the DCA+YaRN composition: DCA
|
| 10 |
+
already keeps q-k distances in-distribution via the per-regime chunk remap, so only the
|
| 11 |
+
softmax temperature (mscale) needs stretching for ctx past the chunk grid -- freq_scale
|
| 12 |
+
/ freq_base stay the model's baked values. Host-only (graph construction); the existing
|
| 13 |
+
CUDA rope kernel reads attn_factor from op_params unchanged, so no DSO rebuild.
|
| 14 |
+
|
| 15 |
+
Default 1.0 => dca_attn_factor == attn_factor exactly (x1.0f is bit-identity for finite
|
| 16 |
+
floats), so the flag-unset path is byte-identical to upstream -- the fix only makes a
|
| 17 |
+
previously-inert flag live; it cannot regress default behavior.
|
| 18 |
+
|
| 19 |
+
Empirical characterization (Qwen3-4B, native 40960, DCA chunk 16384, RULER 5-needle
|
| 20 |
+
substring under ignore_eos; NOT greedy-first-token):
|
| 21 |
+
f16 KV @ 2.4x native: yarn 1.0->5/5 1.25->4/5 1.5->1/5 2.0->0/5 (monotonic: flag live)
|
| 22 |
+
q8_0 KV ladder yarn=1.0: 2.4x->4 4.0x->5 5.0x->3 6.0x->4 (plain-DCA holds deep)
|
| 23 |
+
q8_0 KV @ 6.0x native: yarn 1.0->4 1.1->4 1.25->4 (flat; 1.18 = principled mscale, no win)
|
| 24 |
+
Verdict: the mscale lever has NO beneficial regime for DCA retrieval -- it only
|
| 25 |
+
over-sharpens already-concentrated attention. DEFAULT 1.0 IS CORRECT. Ships as a
|
| 26 |
+
power-user knob (raising it degrades retrieval); documented in docs/evaluations/context.md.
|
| 27 |
+
|
| 28 |
+
diff --git a/llama.cpp/src/dca.cpp b/llama.cpp/src/dca.cpp
|
| 29 |
+
--- a/llama.cpp/src/dca.cpp
|
| 30 |
+
+++ b/llama.cpp/src/dca.cpp
|
| 31 |
+
@@ -164,15 +164,23 @@
|
| 32 |
+
// Rope the DCA per-regime Q (or insert-K) faithfully for the arch. `pos` must be the width
|
| 33 |
+
// build_attn_inp_dca allocated: 4*n_tokens for MROPE (section split below), n_tokens for NEOX.
|
| 34 |
+
ggml_tensor * llm_graph_context::build_dca_rope(ggml_tensor * x, ggml_tensor * pos, const dca_rope & rp) const {
|
| 35 |
+
+ // opencoti-hook: dca-yarn-factor (bug-740 / #550) β compose a YaRN mscale stretch with DCA.
|
| 36 |
+
+ // cparams.dca_yarn_factor (1.0 = none; set by --dca-yarn-factor) multiplies the attention scaling
|
| 37 |
+
+ // (mscale / attn_factor) so the extending-regime softmax is re-tempered for ctx past the chunk grid
|
| 38 |
+
+ // (the final 2-4x of the DCA+YaRN sub-native extension recipe; see docs/features/gemma4_dca.md). DCA
|
| 39 |
+
+ // already keeps q-k distances in-distribution via the per-regime chunk remap, so only the mscale needs
|
| 40 |
+
+ // stretching β freq_scale/freq_base stay the model's baked values. Default 1.0 => this rope call is
|
| 41 |
+
+ // byte-identical to upstream (inert unless the flag is set).
|
| 42 |
+
+ const float dca_attn_factor = attn_factor * cparams.dca_yarn_factor;
|
| 43 |
+
if (dca_is_mrope(rope_type)) {
|
| 44 |
+
int sections[GGML_MROPE_SECTIONS] = { rp.sections[0], rp.sections[1], rp.sections[2], rp.sections[3] };
|
| 45 |
+
return ggml_rope_multi(ctx0, x, pos, rp.freq_factors,
|
| 46 |
+
rp.n_rot, sections, rope_type, n_ctx_orig, rp.freq_base, rp.freq_scale,
|
| 47 |
+
- ext_factor, attn_factor, beta_fast, beta_slow);
|
| 48 |
+
+ ext_factor, dca_attn_factor, beta_fast, beta_slow);
|
| 49 |
+
}
|
| 50 |
+
return ggml_rope_ext(ctx0, x, pos, rp.freq_factors,
|
| 51 |
+
rp.n_rot, rope_type, n_ctx_orig, rp.freq_base, rp.freq_scale,
|
| 52 |
+
- ext_factor, attn_factor, beta_fast, beta_slow);
|
| 53 |
+
+ ext_factor, dca_attn_factor, beta_fast, beta_slow);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// opencoti F5 dca #444 (DCA all-KV C1): dequant-on-lift a cached K/V tensor to f16 for the f16-only
|
patches/0088-sparsev-autopolicy.patch
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0088] sparse-v auto-policy: self-configure quantized-V skip from model arch (#565)
|
| 3 |
+
|
| 4 |
+
The FA-VEC quantized-V skip (sparse-V, #546) is a MEASURED lossless decode win on
|
| 5 |
+
interleaved-SWA models (Gemma-4: the small, concentrated global V-cache lets a
|
| 6 |
+
peak-relative static tau=0.05 harvest the sub-mean V tail β RULER niah 100 at
|
| 7 |
+
64k/128k/256k on A4B-62e AND official Gemma-4-31B), but a no-win on full-attention
|
| 8 |
+
models (Qwen: diffuse weights cluster near the mean, nothing safely skippable). The
|
| 9 |
+
kernel reads its threshold from the TURBO_SPARSE_V_TAU env (ggml-cuda is a runtime
|
| 10 |
+
DSO, so env is the cross-DSO channel). Until now a user had to know to set that env.
|
| 11 |
+
|
| 12 |
+
This puts the intelligence in the BINARY so a STANDALONE `llamafile --server` self-
|
| 13 |
+
configures: at llama_context construction, when the user has NOT set TURBO_SPARSE_V_TAU
|
| 14 |
+
and the model is iSWA (hparams.swa_type != NONE) with a quantized V cache, default the
|
| 15 |
+
env to 0.05 (overwrite=0 β an explicit env still wins). Full-attention models leave it
|
| 16 |
+
unset -> kernel tau=0 -> dense, byte-identical. f16/bf16 V is inert via the kernel's own
|
| 17 |
+
`if constexpr type_V != F16/BF16` guard. Host-only (no DSO change); the existing kernel
|
| 18 |
+
getenv reads the pre-populated value. Announced once at WARN (the llamafile server
|
| 19 |
+
suppresses the model-load INFO block, and a binary that auto-changes inference should
|
| 20 |
+
say so + tell the user how to override).
|
| 21 |
+
|
| 22 |
+
Verified (3090): decision table PASS (policy fires iff iSWA+quant-V+no-env: A4B q8_0
|
| 23 |
+
auto=engaged, override/f16/Qwen-full-attn=off) + A/B at 24k lossless + non-slower.
|
| 24 |
+
---
|
| 25 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 26 |
+
index 722bd7f..2d87414 100644
|
| 27 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 28 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 29 |
+
@@ -54,6 +54,34 @@ llama_context::llama_context(
|
| 30 |
+
|
| 31 |
+
const auto & hparams = model.hparams;
|
| 32 |
+
|
| 33 |
+
+ // opencoti-hook: sparse-v auto-policy (#565) β see docs/features/sparse_attn.md
|
| 34 |
+
+ // Self-configure the FA-VEC quantized-V skip (#546) from the model's OWN architecture, so a
|
| 35 |
+
+ // STANDALONE `llamafile --server` gets the win with no flags (the intelligence lives in the binary,
|
| 36 |
+
+ // not the opencoti TS adapter). The skip is a MEASURED lossless decode win on interleaved-SWA models
|
| 37 |
+
+ // (Gemma-4: the tiny, concentrated global V-cache lets a peak-relative static Ο=0.05 harvest the
|
| 38 |
+
+ // sub-mean V tail β niah 100 at 64k/128k/256k on A4B-62e AND official Gemma-4-31B), but a no-win on
|
| 39 |
+
+ // full-attention models (Qwen: diffuse weights cluster near the mean, nothing safely skippable). It
|
| 40 |
+
+ // only engages for a QUANTIZED V cache (the kernel's `if constexpr type_V != F16/BF16` guard; -ctv
|
| 41 |
+
+ // f16 is inert). ggml-cuda is a runtime-loaded DSO, so the kernel reads its threshold from the
|
| 42 |
+
+ // TURBO_SPARSE_V_TAU env (the existing cross-DSO channel) β we just pre-populate a smart default when
|
| 43 |
+
+ // the user has not, with overwrite=0 so an explicit TURBO_SPARSE_V_TAU (or eps/sink/recent) still
|
| 44 |
+
+ // wins. Set as early as possible (before any graph_reserve / decode) so the kernel's lazy getenv
|
| 45 |
+
+ // caches the policy value. Full-attention models leave the env unset β kernel Ο=0 β dense, byte-identical.
|
| 46 |
+
+ if (!hparams.vocab_only && getenv("TURBO_SPARSE_V_TAU") == nullptr) {
|
| 47 |
+
+ const bool is_iswa = hparams.swa_type != LLAMA_SWA_TYPE_NONE;
|
| 48 |
+
+ // Mirror the kernel's engage guard exactly: any non-float V cache (q8_0/q4_0/turbo*/tcq) is eligible.
|
| 49 |
+
+ const bool quant_v = params.type_v != GGML_TYPE_F16 && params.type_v != GGML_TYPE_BF16 &&
|
| 50 |
+
+ params.type_v != GGML_TYPE_F32;
|
| 51 |
+
+ if (is_iswa && quant_v) {
|
| 52 |
+
+ setenv("TURBO_SPARSE_V_TAU", "0.05", /*overwrite=*/0);
|
| 53 |
+
+ // WARN level (not INFO): the llamafile server suppresses the model-load INFO block, and a binary
|
| 54 |
+
+ // that auto-changes inference behavior should announce it audibly + tell the user how to override.
|
| 55 |
+
+ LLAMA_LOG_WARN("%s: sparse-v auto-policy: iSWA + quantized V (%s) -> TURBO_SPARSE_V_TAU=0.05 "
|
| 56 |
+
+ "(lossless decode win; export TURBO_SPARSE_V_TAU to override)\n",
|
| 57 |
+
+ __func__, ggml_type_name(params.type_v));
|
| 58 |
+
+ }
|
| 59 |
+
+ }
|
| 60 |
+
+
|
| 61 |
+
cparams.n_seq_max = std::max(1u, params.n_seq_max);
|
| 62 |
+
if (cparams.n_seq_max > LLAMA_MAX_SEQ) {
|
| 63 |
+
throw std::runtime_error("n_seq_max must be <= " + std::to_string(LLAMA_MAX_SEQ));
|
patches/0089-sparse-attn-vslash-foundation.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0090-rolling-kv-compute-reserve.patch
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0089] rolling-KV two-pass compute-buffer reserve (bug-1342)
|
| 3 |
+
|
| 4 |
+
The POSITION_WINDOW resident-window sizer (opencoti_compute_resident_window_cells)
|
| 5 |
+
held back a FIXED OPENCOTI_HEADINFER_AUTO_RESERVE_BYTES = 1536 MiB for the compute
|
| 6 |
+
scratch that graph_reserve allocates AFTER the KV cache. But that compute buffer
|
| 7 |
+
SCALES with context β measured 3.63 GiB (3 800 045 568 B) at 262 144-524 288 ctx on
|
| 8 |
+
an RTX PRO 6000 β so the sizer kept ~2.1 GiB too much KV resident and graph_reserve
|
| 9 |
+
OOM'd at boot:
|
| 10 |
+
|
| 11 |
+
ggml_gallocr_reserve_n_impl: failed to allocate CUDA0 buffer of size 3800045568
|
| 12 |
+
graph_reserve: failed to allocate compute buffers
|
| 13 |
+
|
| 14 |
+
Fix: measure the real compute buffer instead of guessing it. Two-pass context init,
|
| 15 |
+
gated to the GPU flash-attn residency-managed path so the common resident case stays
|
| 16 |
+
single-pass / byte-identical:
|
| 17 |
+
|
| 18 |
+
Pass 1 (measure): create_memory with kv_window_measure_pass=true -> the window sizer
|
| 19 |
+
returns a MINIMAL resident window (tiny device KV, boots for ANY compute size).
|
| 20 |
+
sched_reserve() runs the pp worst-case reserve; sum the GPU backends' compute
|
| 21 |
+
buffer via backend_buf_exp_size.
|
| 22 |
+
Pass 2 (real): re-create_memory with kv_compute_reserve_mib = measured + 256 MiB
|
| 23 |
+
safety; the sizer now holds back the true compute buffer and sizes the window
|
| 24 |
+
against (budget - model - measured). sched_need_reserve=true; sched_reserve() again.
|
| 25 |
+
|
| 26 |
+
The compute buffer is independent of the window/tail split (it depends on
|
| 27 |
+
n_ubatch x n_kv x n_embd, and n_kv = full ctx regardless of the resident split), so
|
| 28 |
+
one measurement suffices. kv_compute_reserve_mib / kv_window_measure_pass are INTERNAL
|
| 29 |
+
cparams (set by the context two-pass, not from llama_context_params); hybrid-memory
|
| 30 |
+
paths pass 0/false (two-pass disabled). Host-only change β see
|
| 31 |
+
docs/features/rolling_kv_compute_reserve.md.
|
| 32 |
+
|
| 33 |
+
diff --git a/llama.cpp/src/llama-cparams.h b/llama.cpp/src/llama-cparams.h
|
| 34 |
+
--- a/llama.cpp/src/llama-cparams.h
|
| 35 |
+
+++ b/llama.cpp/src/llama-cparams.h
|
| 36 |
+
@@ -35,6 +35,16 @@
|
| 37 |
+
// opencoti F5 M7 Rolling KV β Stage 4 (#338) residency-tactic knob.
|
| 38 |
+
// 0 = auto (POSITION_WINDOW when eligible, else M2 head split), 1 = head, 2 = window.
|
| 39 |
+
uint32_t kv_residency_mode;
|
| 40 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve (bug-1342) β
|
| 41 |
+
+ // see docs/features/rolling_kv_compute_reserve.md. INTERNAL (not user params):
|
| 42 |
+
+ // set by the llama_context two-pass measurement, NOT from llama_context_params.
|
| 43 |
+
+ // kv_compute_reserve_mib = MEASURED GPU compute-buffer size to hold back from the
|
| 44 |
+
+ // resident-window budget (0 = use the fixed OPENCOTI_HEADINFER_AUTO_RESERVE_BYTES
|
| 45 |
+
+ // default, i.e. pass 1 / two-pass disabled). kv_window_measure_pass forces the
|
| 46 |
+
+ // window sizer to a minimal resident window (tiny device KV, boots for ANY compute
|
| 47 |
+
+ // size) so pass 1 can measure the real compute buffer without OOM.
|
| 48 |
+
+ uint32_t kv_compute_reserve_mib;
|
| 49 |
+
+ bool kv_window_measure_pass;
|
| 50 |
+
// opencoti F5 M3 neo β asymmetric GPU/CPU pipelining of attention.
|
| 51 |
+
// 0 = off (M2 concat path runs; binary equivalent to upstream when
|
| 52 |
+
// headinfer_gpu_heads_frac == 1.0).
|
| 53 |
+
diff --git a/llama.cpp/src/llama-context.cpp b/llama.cpp/src/llama-context.cpp
|
| 54 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 55 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 56 |
+
@@ -106,6 +106,12 @@
|
| 57 |
+
cparams.pcie_bw_gbps = params.pcie_bw_gbps;
|
| 58 |
+
// opencoti F5 M7 Rolling KV β Stage 4 (#338) residency knob β see docs/features/rolling_kv.md
|
| 59 |
+
cparams.kv_residency_mode = params.kv_residency_mode;
|
| 60 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve (bug-1342). INTERNAL:
|
| 61 |
+
+ // the memory init below runs a measurement pass, then re-creates with the measured
|
| 62 |
+
+ // reserve; these start at the "pass 1 / default reserve" values. See
|
| 63 |
+
+ // docs/features/rolling_kv_compute_reserve.md.
|
| 64 |
+
+ cparams.kv_compute_reserve_mib = 0;
|
| 65 |
+
+ cparams.kv_window_measure_pass = false;
|
| 66 |
+
// opencoti F5 M3 neo β see docs/features/advanced_kv.md
|
| 67 |
+
cparams.neo_pipeline_mode = params.neo_pipeline_mode;
|
| 68 |
+
// opencoti F5 W3 #292 fused-MoE β see docs/features/advanced_kv.md
|
| 69 |
+
@@ -373,6 +379,18 @@
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve (bug-1342) β
|
| 74 |
+
+ // see docs/features/rolling_kv_compute_reserve.md. The resident-window sizer must
|
| 75 |
+
+ // hold back the compute buffer (allocated AFTER the KV by sched_reserve), but its
|
| 76 |
+
+ // size scales with context and is only known post-reserve. So: PASS 1 creates a
|
| 77 |
+
+ // MINIMAL resident window (tiny device KV, boots for any compute size), sched_reserve
|
| 78 |
+
+ // measures the real GPU compute buffer, then PASS 2 re-creates the KV sized against
|
| 79 |
+
+ // (free - model - measured). Scoped to the GPU flash-attn residency-managed path so
|
| 80 |
+
+ // the common resident case is unaffected (the pass-2 result is identical there).
|
| 81 |
+
+ const bool opencoti_two_pass = !hparams.vocab_only
|
| 82 |
+
+ && cparams.flash_attn && cparams.offload_kqv
|
| 83 |
+
+ && !model.devices.empty() && cparams.kv_residency_mode != 1;
|
| 84 |
+
+
|
| 85 |
+
// init the memory module
|
| 86 |
+
if (!hparams.vocab_only) {
|
| 87 |
+
llama_memory_params params_mem = {
|
| 88 |
+
@@ -382,6 +400,8 @@
|
| 89 |
+
/*.ctx_type= */ cparams.ctx_type,
|
| 90 |
+
};
|
| 91 |
+
|
| 92 |
+
+ // opencoti bug-1342: pass 1 forces a minimal resident window (measure mode).
|
| 93 |
+
+ cparams.kv_window_measure_pass = opencoti_two_pass;
|
| 94 |
+
memory.reset(model.create_memory(params_mem, cparams));
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
@@ -450,6 +470,41 @@
|
| 98 |
+
|
| 99 |
+
sched_reserve();
|
| 100 |
+
|
| 101 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve (bug-1342). Pass 1
|
| 102 |
+
+ // (above) sized a minimal resident window so sched_reserve could not OOM; the real
|
| 103 |
+
+ // GPU compute buffer is now in backend_buf_exp_size. Sum it over the GPU backends,
|
| 104 |
+
+ // re-create the KV with that measured reserve, and re-reserve. The compute buffer is
|
| 105 |
+
+ // independent of the window/tail split, so the measurement is exact for pass 2. See
|
| 106 |
+
+ // docs/features/rolling_kv_compute_reserve.md.
|
| 107 |
+
+ if (opencoti_two_pass && !model.hparams.no_alloc) {
|
| 108 |
+
+ size_t gpu_compute = 0;
|
| 109 |
+
+ for (size_t i = 0; i < backend_ptrs.size(); ++i) {
|
| 110 |
+
+ if (ggml_backend_dev_type(ggml_backend_get_device(backend_ptrs[i])) == GGML_BACKEND_DEVICE_TYPE_GPU) {
|
| 111 |
+
+ gpu_compute += backend_buf_exp_size[i];
|
| 112 |
+
+ }
|
| 113 |
+
+ }
|
| 114 |
+
+ // + 256 MiB safety margin over the measured worst-case pp compute buffer.
|
| 115 |
+
+ const uint32_t measured_mib = (uint32_t) (gpu_compute / (1024 * 1024)) + 256;
|
| 116 |
+
+ LLAMA_LOG_INFO("%s: rolling-kv two-pass β measured GPU compute buffer = %u MiB; "
|
| 117 |
+
+ "re-sizing resident window against it\n", __func__, measured_mib);
|
| 118 |
+
+ cparams.kv_compute_reserve_mib = measured_mib;
|
| 119 |
+
+ cparams.kv_window_measure_pass = false;
|
| 120 |
+
+
|
| 121 |
+
+ // free the pass-1 KV (incl. its host tail) BEFORE creating pass 2 to avoid a
|
| 122 |
+
+ // transient double host-tail allocation.
|
| 123 |
+
+ memory.reset();
|
| 124 |
+
+ llama_memory_params params_mem2 = {
|
| 125 |
+
+ /*.type_k =*/ params.type_k,
|
| 126 |
+
+ /*.type_v =*/ params.type_v,
|
| 127 |
+
+ /*.swa_full =*/ params.swa_full,
|
| 128 |
+
+ /*.ctx_type= */ cparams.ctx_type,
|
| 129 |
+
+ };
|
| 130 |
+
+ memory.reset(model.create_memory(params_mem2, cparams));
|
| 131 |
+
+
|
| 132 |
+
+ sched_need_reserve = true;
|
| 133 |
+
+ sched_reserve();
|
| 134 |
+
+ }
|
| 135 |
+
+
|
| 136 |
+
if (!cparams.flash_attn) {
|
| 137 |
+
if (ggml_is_quantized(params.type_v)) {
|
| 138 |
+
throw std::runtime_error("quantized V cache was requested, but this requires Flash Attention");
|
| 139 |
+
diff --git a/llama.cpp/src/llama-kv-cache.h b/llama.cpp/src/llama-kv-cache.h
|
| 140 |
+
--- a/llama.cpp/src/llama-kv-cache.h
|
| 141 |
+
+++ b/llama.cpp/src/llama-kv-cache.h
|
| 142 |
+
@@ -131,6 +131,15 @@
|
| 143 |
+
// 0 = auto (POSITION_WINDOW when eligible, else M2 head split),
|
| 144 |
+
// 1 = head (force M2 split), 2 = window (force POSITION_WINDOW).
|
| 145 |
+
uint32_t kv_residency_mode,
|
| 146 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve
|
| 147 |
+
+ // (bug-1342) β see docs/features/rolling_kv_compute_reserve.md.
|
| 148 |
+
+ // compute_reserve_mib: MEASURED GPU compute-buffer size to hold
|
| 149 |
+
+ // back from the resident-window budget (0 = fixed default).
|
| 150 |
+
+ // window_measure_pass: force a MINIMAL resident window (tiny
|
| 151 |
+
+ // device KV) so the context two-pass can measure the real
|
| 152 |
+
+ // compute buffer without OOM.
|
| 153 |
+
+ uint32_t compute_reserve_mib,
|
| 154 |
+
+ bool window_measure_pass,
|
| 155 |
+
uint32_t n_seq_max,
|
| 156 |
+
uint32_t n_pad,
|
| 157 |
+
uint32_t n_swa,
|
| 158 |
+
diff --git a/llama.cpp/src/llama-kv-cache.cpp b/llama.cpp/src/llama-kv-cache.cpp
|
| 159 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 160 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 161 |
+
@@ -138,6 +138,7 @@
|
| 162 |
+
uint32_t kv_size,
|
| 163 |
+
uint32_t n_stream,
|
| 164 |
+
uint32_t vram_target_mib,
|
| 165 |
+
+ size_t compute_reserve_bytes, // opencoti bug-1342: measured (or default) compute hold-back
|
| 166 |
+
const std::function<bool(int32_t)> & filter) {
|
| 167 |
+
// No GPU offload β nothing is "GPU-resident" and nothing to spill; the M2
|
| 168 |
+
// split is only meaningful for offloaded layers. Stay at vanilla.
|
| 169 |
+
@@ -187,9 +188,10 @@
|
| 170 |
+
if (vram_target_mib > 0) {
|
| 171 |
+
budget = std::min(budget, (size_t) vram_target_mib * 1024 * 1024);
|
| 172 |
+
}
|
| 173 |
+
- budget = budget > OPENCOTI_HEADINFER_AUTO_RESERVE_BYTES
|
| 174 |
+
- ? budget - OPENCOTI_HEADINFER_AUTO_RESERVE_BYTES
|
| 175 |
+
- : 0;
|
| 176 |
+
+ // opencoti bug-1342: hold back the MEASURED compute buffer (two-pass) instead of the
|
| 177 |
+
+ // fixed 1536 MiB guess when the context supplied it; 0 β fall back to the default.
|
| 178 |
+
+ const size_t reserve = compute_reserve_bytes ? compute_reserve_bytes : OPENCOTI_HEADINFER_AUTO_RESERVE_BYTES;
|
| 179 |
+
+ budget = budget > reserve ? budget - reserve : 0;
|
| 180 |
+
|
| 181 |
+
if (kv_demand <= budget) {
|
| 182 |
+
LLAMA_LOG_INFO("%s: auto residency = GPU_RESIDENT (KV %.0f MiB fits budget "
|
| 183 |
+
@@ -235,10 +237,21 @@
|
| 184 |
+
uint32_t n_stream,
|
| 185 |
+
uint32_t vram_target_mib,
|
| 186 |
+
uint32_t n_pad,
|
| 187 |
+
+ size_t compute_reserve_bytes, // opencoti bug-1342: measured (or default) compute hold-back
|
| 188 |
+
+ bool measure_pass, // opencoti bug-1342: pass 1 β minimal window (tiny device KV)
|
| 189 |
+
const std::function<bool(int32_t)> & filter) {
|
| 190 |
+
if (!offload || kv_size == 0) {
|
| 191 |
+
return kv_size; // no position-axis spill without GPU offload
|
| 192 |
+
}
|
| 193 |
+
+ // opencoti bug-1342 two-pass: the MEASUREMENT pass forces a MINIMAL resident window
|
| 194 |
+
+ // (one FA stride, ~0.5 MiB device KV) so pass 1 boots for ANY compute-buffer size.
|
| 195 |
+
+ // The context then measures the real compute buffer and re-sizes in pass 2. Aligned
|
| 196 |
+
+ // to lcm(n_pad, FATTN_KQ_STRIDE=256) so the tiny window is a valid FA tiling.
|
| 197 |
+
+ if (measure_pass) {
|
| 198 |
+
+ const uint32_t pad0 = n_pad ? n_pad : 32;
|
| 199 |
+
+ const uint32_t walign0 = pad0 >= 256 ? pad0 : 256;
|
| 200 |
+
+ return kv_size > walign0 ? walign0 : kv_size;
|
| 201 |
+
+ }
|
| 202 |
+
|
| 203 |
+
size_t per_cell = 0; // bytes for ONE position cell across all this cache's KV layers
|
| 204 |
+
ggml_backend_dev_t dev = nullptr;
|
| 205 |
+
@@ -327,6 +340,8 @@
|
| 206 |
+
uint32_t vram_target_mib,
|
| 207 |
+
float pcie_bw_gbps,
|
| 208 |
+
uint32_t kv_residency_mode,
|
| 209 |
+
+ uint32_t compute_reserve_mib,
|
| 210 |
+
+ bool window_measure_pass,
|
| 211 |
+
uint32_t n_seq_max,
|
| 212 |
+
uint32_t n_pad,
|
| 213 |
+
uint32_t n_swa,
|
| 214 |
+
@@ -383,9 +398,14 @@
|
| 215 |
+
// A non-negative frac is the manual escape hatch / upstream default, used
|
| 216 |
+
// verbatim. Every split decision below reads eff_gpu_heads_frac, not the raw
|
| 217 |
+
// ctor argument, so the sentinel never leaks into the allocation math.
|
| 218 |
+
+ // opencoti bug-1342: the compute-buffer reserve to hold back from the resident budget.
|
| 219 |
+
+ // compute_reserve_mib > 0 = the MEASURED GPU compute buffer (context two-pass, pass 2);
|
| 220 |
+
+ // 0 = pass 1 / two-pass disabled β the sizers fall back to the fixed default.
|
| 221 |
+
+ const size_t opencoti_compute_reserve_bytes = (size_t) compute_reserve_mib * 1024 * 1024;
|
| 222 |
+
const float eff_gpu_heads_frac = headinfer_gpu_heads_frac < 0.0f
|
| 223 |
+
? opencoti_compute_auto_gpu_heads_frac(model, hparams, type_k, type_v,
|
| 224 |
+
- v_trans, offload, kv_size, n_stream, vram_target_mib, filter)
|
| 225 |
+
+ v_trans, offload, kv_size, n_stream, vram_target_mib,
|
| 226 |
+
+ opencoti_compute_reserve_bytes, filter)
|
| 227 |
+
: headinfer_gpu_heads_frac;
|
| 228 |
+
|
| 229 |
+
// opencoti F5 M7 Stage 3c β position-axis window sizing. Computed + logged now so the
|
| 230 |
+
@@ -395,7 +415,7 @@
|
| 231 |
+
// value is intentionally unused downstream at this sub-stage. See docs/features/advanced_kv.md.
|
| 232 |
+
const uint32_t window_cells = opencoti_compute_resident_window_cells(
|
| 233 |
+
model, hparams, type_k, type_v, v_trans, offload, kv_size, n_stream,
|
| 234 |
+
- vram_target_mib, n_pad, filter);
|
| 235 |
+
+ vram_target_mib, n_pad, opencoti_compute_reserve_bytes, window_measure_pass, filter);
|
| 236 |
+
// opencoti F5 M7 Stage 4 (#338) β position-window mode is driven by the
|
| 237 |
+
// --kv-residency-mode knob (cparams.kv_residency_mode): 0=auto, 1=head, 2=window.
|
| 238 |
+
// The ELIGIBILITY predicate is the validated POSITION_WINDOW class: the sizer found a
|
| 239 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.h b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 240 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.h
|
| 241 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.h
|
| 242 |
+
@@ -41,6 +41,12 @@
|
| 243 |
+
// opencoti F5 M7 Rolling KV β Stage 4 (#338) residency-tactic knob;
|
| 244 |
+
// forwarded to both kv_base and kv_swa. 0 = auto, 1 = head, 2 = window.
|
| 245 |
+
uint32_t kv_residency_mode,
|
| 246 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve
|
| 247 |
+
+ // (bug-1342); forwarded to both kv_base and kv_swa (only the
|
| 248 |
+
+ // overflowing base cache windows). See
|
| 249 |
+
+ // docs/features/rolling_kv_compute_reserve.md.
|
| 250 |
+
+ uint32_t compute_reserve_mib,
|
| 251 |
+
+ bool window_measure_pass,
|
| 252 |
+
uint32_t n_seq_max,
|
| 253 |
+
uint32_t n_ubatch,
|
| 254 |
+
uint32_t n_pad,
|
| 255 |
+
diff --git a/llama.cpp/src/llama-kv-cache-iswa.cpp b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 256 |
+
--- a/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 257 |
+
+++ b/llama.cpp/src/llama-kv-cache-iswa.cpp
|
| 258 |
+
@@ -26,6 +26,8 @@
|
| 259 |
+
uint32_t vram_target_mib,
|
| 260 |
+
float pcie_bw_gbps,
|
| 261 |
+
uint32_t kv_residency_mode,
|
| 262 |
+
+ uint32_t compute_reserve_mib,
|
| 263 |
+
+ bool window_measure_pass,
|
| 264 |
+
uint32_t n_seq_max,
|
| 265 |
+
uint32_t n_ubatch,
|
| 266 |
+
uint32_t n_pad,
|
| 267 |
+
@@ -70,6 +72,7 @@
|
| 268 |
+
model, type_k, type_v,
|
| 269 |
+
v_trans, offload, unified, size_base, kv_size_initial, slot_shrink_idle_ms,
|
| 270 |
+
headinfer_gpu_heads_frac, vram_target_mib, pcie_bw_gbps, kv_residency_mode,
|
| 271 |
+
+ compute_reserve_mib, window_measure_pass,
|
| 272 |
+
n_seq_max, n_pad, 0, LLAMA_SWA_TYPE_NONE, filter_base, reuse, sparse_attn_block_size);
|
| 273 |
+
|
| 274 |
+
LLAMA_LOG_INFO("%s: creating SWA KV cache, size = %u cells\n", __func__, size_swa);
|
| 275 |
+
@@ -78,6 +81,7 @@
|
| 276 |
+
model, type_k, type_v,
|
| 277 |
+
v_trans, offload, unified, size_swa, kv_size_initial, slot_shrink_idle_ms,
|
| 278 |
+
headinfer_gpu_heads_frac, vram_target_mib, pcie_bw_gbps, kv_residency_mode,
|
| 279 |
+
+ compute_reserve_mib, window_measure_pass,
|
| 280 |
+
n_seq_max, n_pad, hparams.n_swa, hparams.swa_type, filter_swa, reuse, sparse_attn_block_size);
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
diff --git a/llama.cpp/src/llama-model.cpp b/llama.cpp/src/llama-model.cpp
|
| 284 |
+
--- a/llama.cpp/src/llama-model.cpp
|
| 285 |
+
+++ b/llama.cpp/src/llama-model.cpp
|
| 286 |
+
@@ -2100,6 +2100,10 @@
|
| 287 |
+
cparams.pcie_bw_gbps,
|
| 288 |
+
// opencoti F5 M7 Rolling KV β Stage 4 (#338) residency knob
|
| 289 |
+
cparams.kv_residency_mode,
|
| 290 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve
|
| 291 |
+
+ // (bug-1342) β see docs/features/rolling_kv_compute_reserve.md
|
| 292 |
+
+ cparams.kv_compute_reserve_mib,
|
| 293 |
+
+ cparams.kv_window_measure_pass,
|
| 294 |
+
cparams.n_seq_max,
|
| 295 |
+
cparams.n_ubatch,
|
| 296 |
+
1,
|
| 297 |
+
@@ -2130,6 +2134,10 @@
|
| 298 |
+
cparams.pcie_bw_gbps,
|
| 299 |
+
// opencoti F5 M7 Rolling KV β Stage 4 (#338) residency knob
|
| 300 |
+
cparams.kv_residency_mode,
|
| 301 |
+
+ // opencoti F5 M7 Rolling KV β two-pass compute-buffer reserve
|
| 302 |
+
+ // (bug-1342) β see docs/features/rolling_kv_compute_reserve.md
|
| 303 |
+
+ cparams.kv_compute_reserve_mib,
|
| 304 |
+
+ cparams.kv_window_measure_pass,
|
| 305 |
+
cparams.n_seq_max,
|
| 306 |
+
1,
|
| 307 |
+
hparams.n_swa,
|
| 308 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid.cpp b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 309 |
+
--- a/llama.cpp/src/llama-memory-hybrid.cpp
|
| 310 |
+
+++ b/llama.cpp/src/llama-memory-hybrid.cpp
|
| 311 |
+
@@ -55,6 +55,9 @@
|
| 312 |
+
0.0f,
|
| 313 |
+
// opencoti F5 M7 Stage 4 β hybrid memory does not thread the residency-mode knob; 0 = auto.
|
| 314 |
+
0,
|
| 315 |
+
+ // opencoti F5 M7 bug-1342 β hybrid memory does not use the window two-pass; 0 / false.
|
| 316 |
+
+ 0,
|
| 317 |
+
+ false,
|
| 318 |
+
n_seq_max,
|
| 319 |
+
n_pad,
|
| 320 |
+
n_swa,
|
| 321 |
+
diff --git a/llama.cpp/src/llama-memory-hybrid-iswa.cpp b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 322 |
+
--- a/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 323 |
+
+++ b/llama.cpp/src/llama-memory-hybrid-iswa.cpp
|
| 324 |
+
@@ -56,6 +56,9 @@
|
| 325 |
+
0.0f,
|
| 326 |
+
// opencoti F5 M7 Stage 4 β hybrid-iswa does not thread the residency-mode knob; 0 = auto.
|
| 327 |
+
0,
|
| 328 |
+
+ // opencoti F5 M7 bug-1342 β hybrid-iswa does not use the window two-pass; 0 / false.
|
| 329 |
+
+ 0,
|
| 330 |
+
+ false,
|
| 331 |
+
n_seq_max,
|
| 332 |
+
n_ubatch,
|
| 333 |
+
n_pad,
|
patches/0091-multi-session-graph-nodes.patch
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0090] multi-session graph-node budget for --parallel >=2 (bug-2094)
|
| 3 |
+
|
| 4 |
+
graph_max_nodes() sized the compute-graph meta pool as a single-stream budget
|
| 5 |
+
(8*n_tensors for dense archs; max(n_tokens*40, 32*n_tensors) for the qwen3next/
|
| 6 |
+
qwen35/kimi linear-attn branch), INDEPENDENT of the stream count. But with a
|
| 7 |
+
non-unified KV cache (the default; --parallel N => kv_unified=false) the graph
|
| 8 |
+
builds per-stream KV views/masks/copies (the `for s < n_stream` loops throughout
|
| 9 |
+
llama-kv-cache.cpp), so the actual node count scales with the number of streams.
|
| 10 |
+
On a 48-layer model (Qwen2.5-14B-Instruct-1M) --parallel 2 therefore overflowed
|
| 11 |
+
the pool at graph build:
|
| 12 |
+
|
| 13 |
+
llama.cpp/ggml/src/ggml.c:1790: not enough space in the context's memory pool
|
| 14 |
+
|
| 15 |
+
(server aborts at boot; --parallel 1 was fine; independent of --kv-residency-mode
|
| 16 |
+
and of ctx). Fix: scale the node budget by n_stream = (kv_unified ? 1 : n_seq_max),
|
| 17 |
+
applied to both branches. Tied to the exact per-stream-KV-graph mechanism; a
|
| 18 |
+
UNIFIED cache keeps n_stream=1 -> byte-identical to upstream, so the factor
|
| 19 |
+
self-disables when there is no per-stream graph.
|
| 20 |
+
|
| 21 |
+
Measured actual n_nodes (RTX 3090, ctx 8192): dense Qwen3-8B 1267/2131/3571 at
|
| 22 |
+
--parallel 1/2/4 (~776 nodes/stream); iSWA Gemma-4-A4B 2647/3367/4567 (~660/stream);
|
| 23 |
+
qwen35 Qwen3.5-9B 1856/2048/2368 (~170/stream). All boot with 2.5-4x headroom.
|
| 24 |
+
Verified on the original repro (bs2 RTX PRO 6000, 14B-1M-Q8_0 --parallel 2): CRASH
|
| 25 |
+
-> BOOT. Host-only (build:llamafile:make); no CUDA DSO, no restamp.
|
| 26 |
+
|
| 27 |
+
--- a/llama.cpp/src/llama-context.cpp
|
| 28 |
+
+++ b/llama.cpp/src/llama-context.cpp
|
| 29 |
+
@@ -2585,10 +2585,19 @@
|
| 30 |
+
//
|
| 31 |
+
|
| 32 |
+
uint32_t llama_context::graph_max_nodes(uint32_t n_tokens) const {
|
| 33 |
+
+ // opencoti-hook: bug-2094 multi-session graph-node budget β see docs/features/rolling_kv_compute_reserve.md
|
| 34 |
+
+ // With a non-unified KV cache (--parallel N β kv_unified=false) the graph builds
|
| 35 |
+
+ // per-stream KV views/masks/copies (the `for s < n_stream` loops throughout
|
| 36 |
+
+ // llama-kv-cache.cpp), so the node count scales with the stream count. The upstream
|
| 37 |
+
+ // budgets below are single-stream; without this factor --parallel β₯2 overflows the
|
| 38 |
+
+ // graph-meta pool (ggml.c:1790 "not enough space in the context's memory pool"). A
|
| 39 |
+
+ // UNIFIED cache keeps n_stream=1 β byte-identical to upstream, so the factor
|
| 40 |
+
+ // self-disables exactly when there is no per-stream graph.
|
| 41 |
+
+ const uint32_t n_stream = cparams.kv_unified ? 1u : std::max<uint32_t>(1u, cparams.n_seq_max);
|
| 42 |
+
if (model.arch == LLM_ARCH_QWEN3NEXT || model.arch == LLM_ARCH_KIMI_LINEAR || model.arch == LLM_ARCH_QWEN35 || model.arch == LLM_ARCH_QWEN35MOE) {
|
| 43 |
+
- return std::max<uint32_t>(n_tokens * 40, 32u * model.n_tensors());
|
| 44 |
+
+ return std::max<uint32_t>(n_tokens * 40, 32u * model.n_tensors() * n_stream);
|
| 45 |
+
}
|
| 46 |
+
- uint32_t res = std::max<uint32_t>(1024u, 8u*model.n_tensors());
|
| 47 |
+
+ uint32_t res = std::max<uint32_t>(1024u, 8u*model.n_tensors()) * n_stream;
|
| 48 |
+
for (const auto & lora : model.loras) {
|
| 49 |
+
res += lora->get_n_nodes();
|
| 50 |
+
}
|
patches/0092-cuda-build-ccache.patch
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/llamafile/build-functions.sh b/llamafile/build-functions.sh
|
| 2 |
+
index 4781516..5b1c26c 100644
|
| 3 |
+
--- a/llamafile/build-functions.sh
|
| 4 |
+
+++ b/llamafile/build-functions.sh
|
| 5 |
+
@@ -238,8 +238,15 @@ compile_gpu_sources_parallel() {
|
| 6 |
+
obj="$build_dir/${base}.o"
|
| 7 |
+
fi
|
| 8 |
+
|
| 9 |
+
- # Skip if object file is newer than source
|
| 10 |
+
- if [ -f "$obj" ] && [ "$obj" -nt "$src" ]; then
|
| 11 |
+
+ # opencoti-hook: cuda-build-ccache (bug-858) β HEADER-AWARE freshness. The CUDA kernels
|
| 12 |
+
+ # live in .cuh HEADERS; template-instance .cu stubs never change, so comparing the object
|
| 13 |
+
+ # only to its .cu left STALE objects on header edits β stale DSO. Also require the object
|
| 14 |
+
+ # to be newer than the newest ggml-tree header ($OPENCOTI_CUDA_DEPS_NEWEST, exported by
|
| 15 |
+
+ # cuda.sh). Compilation runs through ccache (see cuda.sh), so a header edit then recompiles
|
| 16 |
+
+ # only the TUs whose expanded includes actually changed. The `-z` guard preserves the old
|
| 17 |
+
+ # .cu-only behaviour for any caller that doesn't export the var.
|
| 18 |
+
+ if [ -f "$obj" ] && [ "$obj" -nt "$src" ] \
|
| 19 |
+
+ && { [ -z "$OPENCOTI_CUDA_DEPS_NEWEST" ] || [ "$obj" -nt "$OPENCOTI_CUDA_DEPS_NEWEST" ]; }; then
|
| 20 |
+
echo "[$count/$total] Skipping: $base.cu (up to date)"
|
| 21 |
+
continue
|
| 22 |
+
fi
|
| 23 |
+
@@ -298,8 +305,11 @@ compile_ggml_core() {
|
| 24 |
+
local name="${base%.*}"
|
| 25 |
+
local obj="$build_dir/ggml-core-${name}.o"
|
| 26 |
+
|
| 27 |
+
- # Skip if object file is newer than source
|
| 28 |
+
- if [ -f "$obj" ] && [ "$obj" -nt "$src" ]; then
|
| 29 |
+
+ # opencoti-hook: cuda-build-ccache (bug-858) β HEADER-AWARE freshness (see cuda.sh). Skip
|
| 30 |
+
+ # only when the object is newer than the source AND every ggml-tree header, so a header
|
| 31 |
+
+ # edit correctly forces a recompile. `-z` guard keeps the old behaviour if the var is unset.
|
| 32 |
+
+ if [ -f "$obj" ] && [ "$obj" -nt "$src" ] \
|
| 33 |
+
+ && { [ -z "$OPENCOTI_CUDA_DEPS_NEWEST" ] || [ "$obj" -nt "$OPENCOTI_CUDA_DEPS_NEWEST" ]; }; then
|
| 34 |
+
echo " Skipping: $base (up to date)"
|
| 35 |
+
continue
|
| 36 |
+
fi
|
| 37 |
+
diff --git a/llamafile/cuda.sh b/llamafile/cuda.sh
|
| 38 |
+
index 18d4357..8e38209 100755
|
| 39 |
+
--- a/llamafile/cuda.sh
|
| 40 |
+
+++ b/llamafile/cuda.sh
|
| 41 |
+
@@ -293,10 +293,29 @@ collect_gpu_sources "$GGML_CUDA_DIR" "$EXTRA_SOURCES" "$NO_IQ_QUANTS" "$FA_ALL_Q
|
| 42 |
+
echo " Sources: $NUM_SOURCES .cu files"
|
| 43 |
+
echo ""
|
| 44 |
+
|
| 45 |
+
+# opencoti-hook: cuda-build-ccache (bug-858) β see docs/protocols/LLAMAFILE_UPGRADE.md.
|
| 46 |
+
+# The CUDA kernels live in .cuh HEADERS (fattn-vec.cuh, fattn-mma-f16.cuh, ...); the
|
| 47 |
+
+# template-instance .cu stubs that #include them essentially never change. The per-TU
|
| 48 |
+
+# incremental skip in build-functions.sh compared each object ONLY to its .cu file, so editing a
|
| 49 |
+
+# .cuh recompiled NOTHING β a silently STALE DSO (bug-858; same class as the host bug-167 -MMD
|
| 50 |
+
+# fix, which was never applied to this hand-rolled CUDA loop). Fix: (1) route every TU compile
|
| 51 |
+
+# through ccache, whose include-manifest invalidates the cached object when the .cu OR any
|
| 52 |
+
+# included header changes; (2) export the newest header mtime in the ggml tree so the no-ccache
|
| 53 |
+
+# fallback skip is header-aware. Linking (link_shared_library) stays on raw $NVCC β ccache caches
|
| 54 |
+
+# compiles only.
|
| 55 |
+
+CUDA_CC="$NVCC"
|
| 56 |
+
+if command -v ccache >/dev/null 2>&1; then
|
| 57 |
+
+ CUDA_CC="ccache $NVCC"
|
| 58 |
+
+ echo " ccache: ON for CUDA compilation (header-aware via include manifest)"
|
| 59 |
+
+else
|
| 60 |
+
+ echo " ccache: NOT FOUND β install ccache for fast + correct incremental CUDA rebuilds"
|
| 61 |
+
+fi
|
| 62 |
+
+export OPENCOTI_CUDA_DEPS_NEWEST=$(find "$GGML_CUDA_DIR/../.." \( -name '*.cuh' -o -name '*.h' -o -name '*.hpp' \) -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
|
| 63 |
+
+
|
| 64 |
+
START_TIME=$(date +%s)
|
| 65 |
+
|
| 66 |
+
# Compile GPU sources
|
| 67 |
+
-compile_gpu_sources_parallel "$NVCC" "$ARCH_FLAGS" "$COMMON_FLAGS" "$BUILD_DIR" "$JOBS_EFF"
|
| 68 |
+
+compile_gpu_sources_parallel "$CUDA_CC" "$ARCH_FLAGS" "$COMMON_FLAGS" "$BUILD_DIR" "$JOBS_EFF"
|
| 69 |
+
|
| 70 |
+
COMPILE_TIME=$(date +%s)
|
| 71 |
+
echo "Compilation took $((COMPILE_TIME - START_TIME)) seconds"
|
patches/0093-mtp-dualctx-fused-nextn.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0094-tinyblas-small-gemm.patch
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0094] tinyblas small-GEMM second config (bug-2104, #614)
|
| 3 |
+
|
| 4 |
+
opencoti-hook: tinyblas-small-gemm β skinny f32 GEMMs (Gemma MoE router
|
| 5 |
+
ffn_gate_inp [2816x128], qwen35 DeltaNet chunk GEMMs) hit a single 128x64-tile
|
| 6 |
+
config = 8 blocks on a 142-SM RTX 6000 (5% occupancy, 274us). Add a second
|
| 7 |
+
<32,32,64,...> config selected when the big config yields <24 blocks (90.7us;
|
| 8 |
+
prefill +25-35% on MoE models).
|
| 9 |
+
|
| 10 |
+
diff --git a/llamafile/tinyblas.cu b/llamafile/tinyblas.cu
|
| 11 |
+
index 758f68a..76ba099 100644
|
| 12 |
+
--- a/llamafile/tinyblas.cu
|
| 13 |
+
+++ b/llamafile/tinyblas.cu
|
| 14 |
+
@@ -539,6 +539,17 @@ tinyblasStatus_t tinyblasGE_launch(tinyblasHandle_t handle, tinyblasOperation_t
|
| 15 |
+
int ldc) {
|
| 16 |
+
if (can_use_matvec(aT, bT, m, n, k, alpha, beta))
|
| 17 |
+
return matvec_launch<WORD>(handle, m, k, A, lda, B, C);
|
| 18 |
+
+ // opencoti-hook: tinyblas-small-gemm (#614/bug-2104) β the single 128x64-tile config
|
| 19 |
+
+ // launches only ceil(m/64)*ceil(n/128) blocks after the row-major swap below; a skinny
|
| 20 |
+
+ // GEMM like the Gemma MoE router ([2816x128] x [2816x512] per layer per ubatch) gets 8
|
| 21 |
+
+ // blocks on a 142-SM GPU (~6% occupancy, ~274 us vs cuBLAS ~8 us) and was 35% of A4B
|
| 22 |
+
+ // prefill GPU time. When the big config cannot fill the GPU, use a 32x32-tile config
|
| 23 |
+
+ // (same deterministic FFMA warp2d kernel, 4x1 thread tiles, 2 warps) so the block
|
| 24 |
+
+ // count grows 8x. Threshold 24 keeps every launch that already saturates on the big
|
| 25 |
+
+ // tiles untouched.
|
| 26 |
+
+ if ((long long)CEIL_DIV(m, 64) * CEIL_DIV(n, 128) < 24)
|
| 27 |
+
+ return tinyblasGE_launcher<32, 32, 64, 8, 32, 16, 1, 4, 4, 64>(
|
| 28 |
+
+ handle, bT, aT, n, m, k, alpha, B, ldb, A, lda, beta, C, ldc);
|
| 29 |
+
constexpr int TT = 256;
|
| 30 |
+
constexpr int BM = 128;
|
| 31 |
+
constexpr int BN = 64;
|
patches/0095-prefill-checkpoint-align.patch
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From: opencoti
|
| 2 |
+
Subject: [PATCH 0095] prefill checkpoint alignment: min-step 8192 + state-buffer recycle pool (bug-2105/2107, #614)
|
| 3 |
+
|
| 4 |
+
- checkpoint_min_step 256 -> 8192 (align with b9859; stale port default caused
|
| 5 |
+
several SWA/recurrent state checkpoints per request instead of <=1).
|
| 6 |
+
- opencoti-hook: ckpt-buf-noinit β common_state_buf type + server-side recycle
|
| 7 |
+
pool (ckpt_buf_pool, cap 4) so steady-state requests reuse warm pages: no
|
| 8 |
+
GB-class value-init memset and no page-fault storm inside the state D2H copy.
|
| 9 |
+
(Measured dead end: a non-zeroing allocator alone regressed all models 8-13%.)
|
| 10 |
+
|
| 11 |
+
diff --git a/llama.cpp/common/common.h b/llama.cpp/common/common.h
|
| 12 |
+
index b688492..3bb6aa2 100644
|
| 13 |
+
--- a/llama.cpp/common/common.h
|
| 14 |
+
+++ b/llama.cpp/common/common.h
|
| 15 |
+
@@ -670,7 +673,7 @@ struct common_params {
|
| 16 |
+
bool cache_prompt = true; // whether to enable prompt caching
|
| 17 |
+
bool cache_idle_slots = true; // save and clear idle slots upon starting a new task
|
| 18 |
+
int32_t n_ctx_checkpoints = 32; // max number of context checkpoints per slot
|
| 19 |
+
- int32_t checkpoint_min_step = 256; // minimum spacing between context checkpoints
|
| 20 |
+
+ int32_t checkpoint_min_step = 8192; // minimum spacing between context checkpoints
|
| 21 |
+
int32_t cache_ram_mib = 8192; // -1 = no limit, 0 - disable, 1 = 1 MiB, etc.
|
| 22 |
+
|
| 23 |
+
std::string hostname = "127.0.0.1";
|
| 24 |
+
@@ -1124,14 +1127,21 @@ enum ggml_opt_optimizer_type common_opt_get_optimizer(const char *);
|
| 25 |
+
// prompt utils
|
| 26 |
+
//
|
| 27 |
+
|
| 28 |
+
+// opencoti-hook: ckpt-buf-noinit (#614/bug-2107) β named buffer type for state snapshots so
|
| 29 |
+
+// the server can recycle them across requests (warm pages: no memset, no page-fault storm).
|
| 30 |
+
+// Measured dead end kept for the record: a non-zeroing allocator here made the COLD path
|
| 31 |
+
+// slower (page faults land inside the state D2H copy loop instead of a fast sequential
|
| 32 |
+
+// memset prefault) β plain vector cold + recycled warm is the optimum.
|
| 33 |
+
+using common_state_buf = std::vector<uint8_t>;
|
| 34 |
+
+
|
| 35 |
+
struct common_prompt_checkpoint {
|
| 36 |
+
int64_t n_tokens;
|
| 37 |
+
|
| 38 |
+
llama_pos pos_min;
|
| 39 |
+
llama_pos pos_max;
|
| 40 |
+
|
| 41 |
+
- std::vector<uint8_t> data_tgt;
|
| 42 |
+
- std::vector<uint8_t> data_dft;
|
| 43 |
+
+ common_state_buf data_tgt;
|
| 44 |
+
+ common_state_buf data_dft;
|
| 45 |
+
|
| 46 |
+
size_t size() const;
|
| 47 |
+
|
| 48 |
+
diff --git a/llama.cpp/tools/server/server-context.cpp b/llama.cpp/tools/server/server-context.cpp
|
| 49 |
+
index 7bad046..34b50b1 100644
|
| 50 |
+
--- a/llama.cpp/tools/server/server-context.cpp
|
| 51 |
+
+++ b/llama.cpp/tools/server/server-context.cpp
|
| 52 |
+
@@ -2119,20 +2158,50 @@ private:
|
| 53 |
+
return true;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
+ // opencoti-hook: ckpt-buf-noinit (#614/bug-2107) β recycle retired checkpoint state
|
| 57 |
+
+ // buffers so the pages stay mapped and warm; a fresh GB-class allocation per request
|
| 58 |
+
+ // pays a page-fault storm inside the state D2H copy (worse than the memset it replaced)
|
| 59 |
+
+ std::vector<common_state_buf> ckpt_buf_pool;
|
| 60 |
+
+
|
| 61 |
+
+ void ckpt_buf_recycle(common_prompt_checkpoint & ckpt) {
|
| 62 |
+
+ if (ckpt_buf_pool.size() < 4 && ckpt.data_tgt.capacity() > 0) {
|
| 63 |
+
+ ckpt_buf_pool.push_back(std::move(ckpt.data_tgt));
|
| 64 |
+
+ }
|
| 65 |
+
+ if (ckpt_buf_pool.size() < 4 && ckpt.data_dft.capacity() > 0) {
|
| 66 |
+
+ ckpt_buf_pool.push_back(std::move(ckpt.data_dft));
|
| 67 |
+
+ }
|
| 68 |
+
+ }
|
| 69 |
+
+
|
| 70 |
+
+ common_state_buf ckpt_buf_acquire() {
|
| 71 |
+
+ if (ckpt_buf_pool.empty()) {
|
| 72 |
+
+ return {};
|
| 73 |
+
+ }
|
| 74 |
+
+ common_state_buf buf = std::move(ckpt_buf_pool.back());
|
| 75 |
+
+ ckpt_buf_pool.pop_back();
|
| 76 |
+
+ return buf;
|
| 77 |
+
+ }
|
| 78 |
+
+
|
| 79 |
+
// n_tokens_cur: the number of tokens added to the batch for the current slot
|
| 80 |
+
void create_checkpoint(server_slot & slot, const int64_t n_tokens_cur, llama_pos pos_min, llama_pos pos_max) {
|
| 81 |
+
while (slot.prompt.checkpoints.size() >= (size_t) params_base.n_ctx_checkpoints) {
|
| 82 |
+
// make room for the new checkpoint, if needed
|
| 83 |
+
- const auto & cur = slot.prompt.checkpoints.front();
|
| 84 |
+
+ auto & cur = slot.prompt.checkpoints.front();
|
| 85 |
+
|
| 86 |
+
SLT_WRN(slot, "erasing old context checkpoint (pos_min = %d, pos_max = %d, n_tokens = %" PRId64 ", size = %.3f MiB)\n",
|
| 87 |
+
cur.pos_min, cur.pos_max, cur.n_tokens, (float) cur.size() / 1024 / 1024);
|
| 88 |
+
|
| 89 |
+
+ ckpt_buf_recycle(cur); // opencoti-hook: ckpt-buf-noinit (#614/bug-2107)
|
| 90 |
+
slot.prompt.checkpoints.erase(slot.prompt.checkpoints.begin());
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
auto & cur = slot.prompt.checkpoints.emplace_back();
|
| 94 |
+
|
| 95 |
+
+ // opencoti-hook: ckpt-buf-noinit (#614/bug-2107) β seed with warm recycled buffers
|
| 96 |
+
+ cur.data_tgt = ckpt_buf_acquire();
|
| 97 |
+
+ if (ctx_dft) {
|
| 98 |
+
+ cur.data_dft = ckpt_buf_acquire();
|
| 99 |
+
+ }
|
| 100 |
+
+
|
| 101 |
+
cur.update_pos(slot.prompt.n_tokens() - n_tokens_cur, pos_min, pos_max);
|
| 102 |
+
|
| 103 |
+
cur.update_tgt(ctx_tgt, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
| 104 |
+
@@ -2980,9 +3042,10 @@ private:
|
| 105 |
+
{
|
| 106 |
+
// erase any checkpoints with pos_max > pos_next
|
| 107 |
+
for (auto it = slot.prompt.checkpoints.begin(); it != slot.prompt.checkpoints.end();) {
|
| 108 |
+
- const auto & cur = *it;
|
| 109 |
+
+ auto & cur = *it;
|
| 110 |
+
if (cur.pos_max > pos_next) {
|
| 111 |
+
SLT_WRN(slot, "erased invalidated context checkpoint (pos_min = %d, pos_max = %d, n_tokens = %" PRId64 ", n_swa = %d, pos_next = %d, size = %.3f MiB)\n", cur.pos_min, cur.pos_max, cur.n_tokens, n_swa, pos_next, (float) cur.size() / 1024 / 1024);
|
| 112 |
+
+ ckpt_buf_recycle(cur); // opencoti-hook: ckpt-buf-noinit (#614/bug-2107)
|
| 113 |
+
it = slot.prompt.checkpoints.erase(it);
|
| 114 |
+
} else {
|
| 115 |
+
++it;
|
| 116 |
+
diff --git a/llama.cpp/tools/server/server-task.cpp b/llama.cpp/tools/server/server-task.cpp
|
| 117 |
+
index 803e31f..70ca8dc 100644
|
| 118 |
+
--- a/llama.cpp/tools/server/server-task.cpp
|
| 119 |
+
+++ b/llama.cpp/tools/server/server-task.cpp
|
| 120 |
+
@@ -2025,8 +2025,8 @@ server_prompt * server_prompt_cache::alloc(const server_prompt & prompt, size_t
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
- std::vector<uint8_t> state_data_tgt;
|
| 125 |
+
- std::vector<uint8_t> state_data_dft;
|
| 126 |
+
+ common_state_buf state_data_tgt; // opencoti-hook: ckpt-buf-noinit (#614/bug-2107)
|
| 127 |
+
+ common_state_buf state_data_dft;
|
| 128 |
+
|
| 129 |
+
// check if we can allocate enough memory for the new state
|
| 130 |
+
try {
|
| 131 |
+
diff --git a/llama.cpp/tools/server/server-task.h b/llama.cpp/tools/server/server-task.h
|
| 132 |
+
index 933bd99..380dc27 100644
|
| 133 |
+
--- a/llama.cpp/tools/server/server-task.h
|
| 134 |
+
+++ b/llama.cpp/tools/server/server-task.h
|
| 135 |
+
@@ -582,8 +582,8 @@ struct server_task_result_apply_lora : server_task_result {
|
| 136 |
+
};
|
| 137 |
+
|
| 138 |
+
struct server_prompt_data {
|
| 139 |
+
- std::vector<uint8_t> main;
|
| 140 |
+
- std::vector<uint8_t> drft;
|
| 141 |
+
+ common_state_buf main; // opencoti-hook: ckpt-buf-noinit (#614/bug-2107)
|
| 142 |
+
+ common_state_buf drft;
|
| 143 |
+
|
| 144 |
+
size_t size() const {
|
| 145 |
+
return main.size() + drft.size();
|
patches/0096-remove-single-ctx-gemma-mtp.patch
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
patches/0097-native-elf-cmake.patch
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0097 β native-ELF CMake lane (#619, secondary artifact of #613)
|
| 2 |
+
|
| 3 |
+
Makes the vendored tree buildable as a standard Linux ELF llama-server via
|
| 4 |
+
plain cmake + g++/nvcc (rpath libggml-cuda.so; clean gdb/nsys), alongside the
|
| 5 |
+
cosmocc Makefile lane which is UNTOUCHED (BUILD.mk lists sources explicitly
|
| 6 |
+
and never reads CMakeLists; the new stub TU is CMake-lane-only).
|
| 7 |
+
|
| 8 |
+
- 5 CMakeLists surgical hooks: add opencoti TUs absent from upstream source
|
| 9 |
+
lists (ggml-turbo-quant.c, ggml-neo-pipeline.cpp, ggml-iqk-flash-attn.cpp
|
| 10 |
+
-> ggml-base; dca.cpp -> llama; pcie-profile.cpp -> common; ELF stubs ->
|
| 11 |
+
ggml-cpu) + compile the Mozilla-overlay server.cpp with -Dmain=llama_server
|
| 12 |
+
(upstream defines llama_server directly; the cosmo lane renames at build).
|
| 13 |
+
- NEW ggml/src/ggml-cpu/opencoti-elf-stubs.cpp: return-false stubs for the
|
| 14 |
+
cosmocc-bound llamafile/sgemm.cpp CPU fastpath dispatchers (mixmul,
|
| 15 |
+
mixmul_needs, mixmul_iqk, fa_* helpers) β every call site falls back to the
|
| 16 |
+
upstream generic path (same contract as fa_helpers_unsupported.cpp), so the
|
| 17 |
+
ELF build loses only CPU fastpaths, never correctness.
|
| 18 |
+
|
| 19 |
+
Build (bs2-proven 2026-07-05): cmake -S llama.cpp -B build-elf -DGGML_CUDA=ON
|
| 20 |
+
-DGGML_CUDA_FA_ALL_QUANTS=ON (REQUIRED β turbo/TCQ FA-VEC instances are only
|
| 21 |
+
globbed under this flag; cosmo lane always sets it) -DCMAKE_CUDA_ARCHITECTURES=120
|
| 22 |
+
-DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF -DGGML_NATIVE=OFF.
|
| 23 |
+
Gate: A4B -ngl 99 boot, dual-ctx assistant-MTP accept 0.764, 310.2 tps decode.
|
| 24 |
+
|
| 25 |
+
--- a/llama.cpp/ggml/src/CMakeLists.txt
|
| 26 |
+
+++ b/llama.cpp/ggml/src/CMakeLists.txt
|
| 27 |
+
@@ -205,6 +205,9 @@
|
| 28 |
+
ggml-threading.cpp
|
| 29 |
+
ggml-threading.h
|
| 30 |
+
ggml-quants.c
|
| 31 |
+
+ ggml-turbo-quant.c # opencoti-hook: native-elf-cmake (#619) β turbo/TCQ CPU quant, in cosmo BUILD.mk but absent here
|
| 32 |
+
+ ggml-neo-pipeline.cpp # opencoti-hook: native-elf-cmake (#619) β NEO pipeline control (pure std)
|
| 33 |
+
+ ggml-iqk-flash-attn.cpp # opencoti-hook: native-elf-cmake (#619) β iqk FA enable flag (pure ggml)
|
| 34 |
+
ggml-quants.h
|
| 35 |
+
gguf.cpp)
|
| 36 |
+
|
| 37 |
+
--- a/llama.cpp/src/CMakeLists.txt
|
| 38 |
+
+++ b/llama.cpp/src/CMakeLists.txt
|
| 39 |
+
@@ -18,6 +18,7 @@
|
| 40 |
+
llama-context.cpp
|
| 41 |
+
llama-cparams.cpp
|
| 42 |
+
llama-grammar.cpp
|
| 43 |
+
+ dca.cpp # opencoti-hook: native-elf-cmake (#619) β DCA (matches neither llama-*.cpp list nor models/ glob)
|
| 44 |
+
llama-graph.cpp
|
| 45 |
+
llama-hparams.cpp
|
| 46 |
+
llama-impl.cpp
|
| 47 |
+
--- a/llama.cpp/common/CMakeLists.txt
|
| 48 |
+
+++ b/llama.cpp/common/CMakeLists.txt
|
| 49 |
+
@@ -66,6 +66,7 @@
|
| 50 |
+
chat.cpp
|
| 51 |
+
chat.h
|
| 52 |
+
common.cpp
|
| 53 |
+
+ pcie-profile.cpp # opencoti-hook: native-elf-cmake (#619) β PCIe bandwidth profile (rolling-KV)
|
| 54 |
+
common.h
|
| 55 |
+
console.cpp
|
| 56 |
+
console.h
|
| 57 |
+
--- a/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt
|
| 58 |
+
+++ b/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt
|
| 59 |
+
@@ -52,6 +52,7 @@
|
| 60 |
+
ggml-cpu/vec.cpp
|
| 61 |
+
ggml-cpu/ops.h
|
| 62 |
+
ggml-cpu/ops.cpp
|
| 63 |
+
+ ggml-cpu/opencoti-elf-stubs.cpp # opencoti-hook: native-elf-cmake (#619) β sgemm.cpp dispatcher stubs (cosmo lane uses real sgemm.cpp)
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
target_compile_features(${GGML_CPU_NAME} PRIVATE c_std_11 cxx_std_17)
|
| 67 |
+
--- a/llama.cpp/tools/server/CMakeLists.txt
|
| 68 |
+
+++ b/llama.cpp/tools/server/CMakeLists.txt
|
| 69 |
+
@@ -31,6 +31,11 @@
|
| 70 |
+
|
| 71 |
+
set(TARGET llama-server-impl)
|
| 72 |
+
|
| 73 |
+
+# opencoti-hook: native-elf-cmake (#619) β the Mozilla overlay's server.cpp defines
|
| 74 |
+
+# `int main` (the cosmocc Makefile lane renames it at compile time); upstream
|
| 75 |
+
+# defines `llama_server` directly. Rename here so main.cpp's wrapper links.
|
| 76 |
+
+set_source_files_properties(server.cpp PROPERTIES COMPILE_DEFINITIONS "main=llama_server")
|
| 77 |
+
+
|
| 78 |
+
add_library(${TARGET}
|
| 79 |
+
server.cpp
|
| 80 |
+
server-http.cpp
|
| 81 |
+
--- /dev/null
|
| 82 |
+
+++ b/llama.cpp/ggml/src/ggml-cpu/opencoti-elf-stubs.cpp
|
| 83 |
+
@@ -0,0 +1,57 @@
|
| 84 |
+
+// opencoti-hook: native-elf-cmake (#619) β see docs/features/llamafile_build.md
|
| 85 |
+
+//
|
| 86 |
+
+// CMake(native-ELF)-lane-ONLY stubs for the llamafile/sgemm.cpp CPU fastpath
|
| 87 |
+
+// dispatchers. sgemm.cpp includes <cosmo.h> (cosmocc runtime CPUID/dispatch)
|
| 88 |
+
+// and cannot compile under plain g++; every call site treats a false return
|
| 89 |
+
+// as "not handled, use the upstream ggml helper" (ggml-cpu.c:1723 mixmul_iqk,
|
| 90 |
+
+// ops.cpp:8519/8560 flash-attn helpers β same contract as Mozilla's own
|
| 91 |
+
+// fa_helpers_unsupported.cpp), so these stubs are behavior-safe: the native
|
| 92 |
+
+// ELF build loses only the iqk/tinyBLAS *CPU* fastpaths, never correctness.
|
| 93 |
+
+// The cosmocc Makefile lane never compiles this file (BUILD.mk lists sources
|
| 94 |
+
+// explicitly) and keeps the real sgemm.cpp implementations.
|
| 95 |
+
+
|
| 96 |
+
+#include <cstddef>
|
| 97 |
+
+#include <cstdint>
|
| 98 |
+
+
|
| 99 |
+
+struct ggml_compute_params;
|
| 100 |
+
+struct ggml_tensor;
|
| 101 |
+
+
|
| 102 |
+
+extern "C" {
|
| 103 |
+
+
|
| 104 |
+
+// Top-level tinyBLAS MoE mixmul (ggml-cpu.c:1592, GGML_USE_LLAMAFILE):
|
| 105 |
+
+// false -> generic mul_mat_id path. _needs sizes its work buffer
|
| 106 |
+
+// (ggml-cpu.c:3002): 0 is consistent with mixmul never running.
|
| 107 |
+
+bool llamafile_mixmul(const struct ggml_compute_params *,
|
| 108 |
+
+ const struct ggml_tensor *,
|
| 109 |
+
+ const struct ggml_tensor *,
|
| 110 |
+
+ const struct ggml_tensor *,
|
| 111 |
+
+ struct ggml_tensor *) {
|
| 112 |
+
+ return false;
|
| 113 |
+
+}
|
| 114 |
+
+
|
| 115 |
+
+size_t llamafile_mixmul_needs(const struct ggml_tensor *,
|
| 116 |
+
+ const struct ggml_tensor *,
|
| 117 |
+
+ const struct ggml_tensor *) {
|
| 118 |
+
+ return 0;
|
| 119 |
+
+}
|
| 120 |
+
+
|
| 121 |
+
+bool llamafile_mixmul_iqk(long, long, long, int, int,
|
| 122 |
+
+ const void *, const void *, float *,
|
| 123 |
+
+ long, long, const void *, int, int) {
|
| 124 |
+
+ return false;
|
| 125 |
+
+}
|
| 126 |
+
+
|
| 127 |
+
+bool llamafile_fa_vec_dot_f16(int, float *, const void *, const void *) {
|
| 128 |
+
+ return false;
|
| 129 |
+
+}
|
| 130 |
+
+
|
| 131 |
+
+bool llamafile_fa_fp16_to_fp32_row(const void *, float *, int64_t) {
|
| 132 |
+
+ return false;
|
| 133 |
+
+}
|
| 134 |
+
+
|
| 135 |
+
+bool llamafile_fa_simd_gemm(float *, const float *, const float *,
|
| 136 |
+
+ int, int, int) {
|
| 137 |
+
+ return false;
|
| 138 |
+
+}
|
| 139 |
+
+
|
| 140 |
+
+} // extern "C"
|
patches/0098-rolling-kv-lse-decode.patch
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0098 β rolling-KV: dst_lse emission for head_dim<=256 decode (bug-1843, task #623)
|
| 2 |
+
|
| 3 |
+
Extends the Stage-3a LSE channel (opencoti_fattn_dst_lse, drained by
|
| 4 |
+
launch_fattn's flash_attn_combine_results / stream-k fixup) to head_dim<=256
|
| 5 |
+
n_q==1 decode at all three streaming-FA call sites (S3b overlap, S3a
|
| 6 |
+
single-stream, S2 resident-view). Previously only head_dim>256 armed the
|
| 7 |
+
channel; Qwen-class D128 decode fell through to the per-tile
|
| 8 |
+
streaming_lse_kernel recompute β a <<<n_rows,WARP_SIZE>>> full key-rescan per
|
| 9 |
+
tile that was MEASURED as ~80% of spill-decode wall (bug-1843 A/B on the 3090:
|
| 10 |
+
1.14 tps base vs 6.05 tps OPENCOTI_LSE_NOOP, Qwen3-8B-Q8_0 ctx40960 vt14000).
|
| 11 |
+
|
| 12 |
+
Mechanism: after the per-tile FA call, `lse_emitted = decode_lse &&
|
| 13 |
+
opencoti_fattn_dst_lse_written` β the written flag reports whether a finalize
|
| 14 |
+
(stream-k fixup or parallel-blocks combine) actually ran; the recompute now
|
| 15 |
+
fires only when the dispatch resolved to a no-finalize single-block path. The
|
| 16 |
+
combine emits the identical per-row value (kqmax + logf(denominator), row
|
| 17 |
+
order h + n_head*(qi + n_q*b)) the recompute produced. The `this_kv >
|
| 18 |
+
2*FATTN_KQ_STRIDE` guard is retained everywhere (bug-263 conservatism).
|
| 19 |
+
Prefill and D>256 decode are behavior-identical.
|
| 20 |
+
|
| 21 |
+
Gates (2026-07-05, 3090, DSO df46dc36):
|
| 22 |
+
- perf: spill_base 1.14 -> 6.04 tps (5.3x, at the NOOP ceiling), needle
|
| 23 |
+
PRESENT; resident 42.5 tps unchanged; OPENCOTI_LSE_NOOP now speed-inert
|
| 24 |
+
(6.02) proving the recompute is structurally off the path.
|
| 25 |
+
- correctness: teacher-forced logit-equiv (Qwen3-8B D128, ours-resident REF vs
|
| 26 |
+
POSITION_WINDOW 256/16384 cells resident): real_frac=0.0, divergences=0,
|
| 27 |
+
frac_full_agree=1.0, mean_tv 0.0068 -> PASS.
|
| 28 |
+
|
| 29 |
+
DSO-only (fattn.cu; no header change, no host rebuild needed).
|
| 30 |
+
|
| 31 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 32 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 33 |
+
@@ -1596,11 +1596,20 @@
|
| 34 |
+
// ntiles_KV=16 at 512, not 2). `>` is kept only because it is no worse
|
| 35 |
+
// than `>=`; the real mechanism is unisolated. Real Stage-3c tail tiles
|
| 36 |
+
// can be β€512 at decode β MUST resolve before relying on this path.
|
| 37 |
+
- const bool decode_lse = (n_q == 1 && head_dim > 256 && this_kv > 2*FATTN_KQ_STRIDE);
|
| 38 |
+
+ // opencoti-hook: f5-rolling-kv bug-1843 β head_dim<=256 (Qwen-class VEC
|
| 39 |
+
+ // decode) now ALSO arms the channel: the parallel-blocks combine emits
|
| 40 |
+
+ // the per-row lse (Stage-3a formula in flash_attn_combine_results), and
|
| 41 |
+
+ // opencoti_fattn_dst_lse_written reports whether a finalize actually ran
|
| 42 |
+
+ // (T87.pD-opt O2 pattern) so the streaming_lse recompute below fires only
|
| 43 |
+
+ // when the dispatch resolved to a no-finalize single-block path. The
|
| 44 |
+
+ // recompute was measured at ~80% of spill-decode cost (1.14 vs 6.05 tps
|
| 45 |
+
+ // A/B via OPENCOTI_LSE_NOOP).
|
| 46 |
+
+ const bool decode_lse = (n_q == 1 && this_kv > 2*FATTN_KQ_STRIDE);
|
| 47 |
+
if (decode_lse) opencoti_fattn_dst_lse = lse_t;
|
| 48 |
+
ggml_cuda_flash_attn_ext(ctx, &dst_t);
|
| 49 |
+
+ const bool lse_emitted = decode_lse && opencoti_fattn_dst_lse_written;
|
| 50 |
+
|
| 51 |
+
- if (!decode_lse) {
|
| 52 |
+
+ if (!lse_emitted) {
|
| 53 |
+
// opencoti F5 bug-1840 fix (b) DIAGNOSTIC β OPENCOTI_LSE_NOOP skips
|
| 54 |
+
// the per-tile streaming_lse recompute (a low-parallelism
|
| 55 |
+
// <<<n_rows,32>>> rescan run once per tile because Qwen/head_dimβ€256
|
| 56 |
+
@@ -1727,11 +1736,14 @@
|
| 57 |
+
// (softcap-aware). The `> 2*FATTN_KQ_STRIDE` boundary is NOT a fix for
|
| 58 |
+
// bug-263 (UNRESOLVED: decode tile_kv_full==512 corrupts on either lse
|
| 59 |
+
// path) β kept only as no-worse-than-`>=`. See .wolf/buglog.json.
|
| 60 |
+
- const bool decode_lse = (n_q == 1 && head_dim > 256 && this_kv > 2*FATTN_KQ_STRIDE);
|
| 61 |
+
+ // bug-1843: head_dim<=256 also arms the channel; recompute only when no
|
| 62 |
+
+ // finalize ran (opencoti_fattn_dst_lse_written) β see the S3b note above.
|
| 63 |
+
+ const bool decode_lse = (n_q == 1 && this_kv > 2*FATTN_KQ_STRIDE);
|
| 64 |
+
if (decode_lse) opencoti_fattn_dst_lse = lse_t;
|
| 65 |
+
ggml_cuda_flash_attn_ext(ctx, &dst_t);
|
| 66 |
+
+ const bool lse_emitted = decode_lse && opencoti_fattn_dst_lse_written;
|
| 67 |
+
|
| 68 |
+
- if (!decode_lse) {
|
| 69 |
+
+ if (!lse_emitted) {
|
| 70 |
+
streaming_lse_kernel<<<n_rows, WARP_SIZE, 0, stream>>>(
|
| 71 |
+
(const char *)Q->data, sK,
|
| 72 |
+
mask ? (const char *)mask->data + (size_t)kv0*mask->nb[0] : nullptr,
|
| 73 |
+
@@ -1764,7 +1776,10 @@
|
| 74 |
+
// boundary is NOT a fix for bug-263 (UNRESOLVED: decode tile_kv_full==512
|
| 75 |
+
// corrupts on either lse path) β kept only as no-worse-than-`>=`. See the
|
| 76 |
+
// S3b overlap path's full note + .wolf/buglog.json.
|
| 77 |
+
- const bool decode_lse = (n_q == 1 && head_dim > 256 && this_kv > 2*FATTN_KQ_STRIDE);
|
| 78 |
+
+ // bug-1843: head_dim<=256 also arms the channel; recompute only when no
|
| 79 |
+
+ // finalize ran (opencoti_fattn_dst_lse_written) β see the S3b note above.
|
| 80 |
+
+ const bool decode_lse = (n_q == 1 && this_kv > 2*FATTN_KQ_STRIDE);
|
| 81 |
+
+ bool lse_emitted = false;
|
| 82 |
+
if (this_kv > 0) {
|
| 83 |
+
// FA over the tile's key-slice β O_t (stock kernel, untouched).
|
| 84 |
+
ggml_tensor Kt = *K; Kt.ne[1] = this_kv; Kt.data = (char *)K->data + (size_t)kv0*K->nb[1];
|
| 85 |
+
@@ -1784,10 +1799,11 @@
|
| 86 |
+
dst_t.data = O_tiles.ptr + (size_t)t * nel;
|
| 87 |
+
if (decode_lse) opencoti_fattn_dst_lse = lse_t;
|
| 88 |
+
ggml_cuda_flash_attn_ext(ctx, &dst_t);
|
| 89 |
+
+ lse_emitted = decode_lse && opencoti_fattn_dst_lse_written;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// lse_t (this_kv==0 β empty tile: kernel writes -inf, combine ignores it).
|
| 93 |
+
- if (!decode_lse) {
|
| 94 |
+
+ if (!lse_emitted) {
|
| 95 |
+
streaming_lse_kernel<<<n_rows, WARP_SIZE, 0, stream>>>(
|
| 96 |
+
(const char *)Q->data,
|
| 97 |
+
(const char *)K->data + (size_t)kv0*K->nb[1],
|
patches/0099-dca-gemma4-wiring.patch
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0099 β DCA: wire Gemma-4 builders (bug-2118, task #626 unblock)
|
| 2 |
+
|
| 3 |
+
`--dca on` was a SILENT NO-OP on all Gemma-4 archs: 0078-dca.patch wired only
|
| 4 |
+
the Qwen builders; dca.cpp's iswa build_attn_dca overload sat uncalled by
|
| 5 |
+
gemma4.cpp / gemma4-assistant.cpp. Consequence: every historical Gemma x DCA
|
| 6 |
+
eval measured DCA-off (contamination catalog: docs/evaluations bug-2118 notes).
|
| 7 |
+
|
| 8 |
+
Wiring: build_attn_inp_dca on the iswa base cache; per-layer dca_l gates
|
| 9 |
+
GLOBAL (non-SWA) layers only; Q/K rope moves inside build_attn_dca via the
|
| 10 |
+
dca_rope bundle (chunked-regime rope must happen per-band); the assistant's
|
| 11 |
+
Q-only shared-KV path passes (Qcur, nullptr, nullptr). SWA layers and DCA-off
|
| 12 |
+
are byte-identical (else-branch keeps the original build_attn call).
|
| 13 |
+
|
| 14 |
+
Gate (bs2, A4B + assistant, 32k, 24k prompt): INTRA regime (chunk==ctx) text
|
| 15 |
+
byte-identical to DCA-off with identical accept (f16 0.677 both; q8q4 text
|
| 16 |
+
identical); cross-chunk f16 accept 0.939, needle PRESENT.
|
| 17 |
+
|
| 18 |
+
--- a/llama.cpp/src/models/gemma4.cpp
|
| 19 |
+
+++ b/llama.cpp/src/models/gemma4.cpp
|
| 20 |
+
@@ -1,4 +1,6 @@
|
| 21 |
+
#include "models.h"
|
| 22 |
+
+#include "dca.h" // opencoti F5 dca bug-2118
|
| 23 |
+
+#include "llama-kv-cache-iswa.h" // bug-2118: get_base() on the iswa mctx
|
| 24 |
+
|
| 25 |
+
void llama_model_gemma4::load_arch_hparams(llama_model_loader & ml) {
|
| 26 |
+
hparams.swa_type = LLAMA_SWA_TYPE_STANDARD;
|
| 27 |
+
@@ -161,6 +163,14 @@
|
| 28 |
+
// TODO: is causal == true correct? might need some changes
|
| 29 |
+
auto * inp_attn = build_attn_inp_kv_iswa();
|
| 30 |
+
|
| 31 |
+
+ // opencoti F5 dca bug-2118 β Gemma-4 DCA wiring. 0078 wired only the Qwen builders;
|
| 32 |
+
+ // the iswa build_attn_dca overload (dca.cpp) existed uncalled, so '--dca on' was a
|
| 33 |
+
+ // silent no-op on Gemma. DCA applies to the GLOBAL (non-SWA) layers only (the SWA
|
| 34 |
+
+ // window is <= chunk already); the overload stores PRE-rope K + handles has_kv==false
|
| 35 |
+
+ // shared-KV globals (null k/v -> read the earlier layer's cache).
|
| 36 |
+
+ const bool use_dca = cparams.dca_enabled;
|
| 37 |
+
+ llm_graph_input_dca * inp_dca = use_dca ? build_attn_inp_dca(inp_attn->mctx->get_base()) : nullptr;
|
| 38 |
+
+
|
| 39 |
+
ggml_tensor * inp_out_ids = build_inp_out_ids();
|
| 40 |
+
|
| 41 |
+
// opencoti-hook: gemma4-mtp-hidden (bug-858 dual-context MTP) β when a context extracts the
|
| 42 |
+
@@ -202,6 +212,10 @@
|
| 43 |
+
freq_factors = model.layers[il].rope_freqs;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
+ // opencoti F5 dca bug-2118 β global layers route through build_attn_dca (which
|
| 47 |
+
+ // ropes Q per regime and pre-ropes K at insert), so skip the standard pre-rope.
|
| 48 |
+
+ const bool dca_l = use_dca && !hparams.is_swa(il);
|
| 49 |
+
+
|
| 50 |
+
// Q projection (shared for both non-KV and KV layers)
|
| 51 |
+
// this is to mirror Gemma4Attention in pytorch code
|
| 52 |
+
ggml_tensor * Qcur;
|
| 53 |
+
@@ -214,9 +228,11 @@
|
| 54 |
+
Qcur = build_norm(Qcur, model.layers[il].attn_q_norm, nullptr, LLM_NORM_RMS, il);
|
| 55 |
+
cb(Qcur, "Qcur_normed", il);
|
| 56 |
+
|
| 57 |
+
+ if (!dca_l) { // bug-2118: DCA ropes Q per regime in build_attn_dca
|
| 58 |
+
Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, freq_factors, n_rot_l, rope_type, n_ctx_orig, freq_base_l, freq_scale_l,
|
| 59 |
+
ext_factor, attn_factor, beta_fast, beta_slow);
|
| 60 |
+
cb(Qcur, "Qcur_pos", il);
|
| 61 |
+
+ }
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// self-attention
|
| 65 |
+
@@ -238,14 +254,28 @@
|
| 66 |
+
cb(Kcur, "Kcur_normed", il);
|
| 67 |
+
cb(Vcur, "Vcur_normed", il);
|
| 68 |
+
|
| 69 |
+
+ if (!dca_l) { // bug-2118: DCA pre-ropes K at insert in build_attn_dca
|
| 70 |
+
Kcur = ggml_rope_ext(ctx0, Kcur, inp_pos, freq_factors, n_rot_l, rope_type, n_ctx_orig, freq_base_l, freq_scale_l,
|
| 71 |
+
ext_factor, attn_factor, beta_fast, beta_slow);
|
| 72 |
+
|
| 73 |
+
cb(Kcur, "Kcur_pos", il);
|
| 74 |
+
+ }
|
| 75 |
+
|
| 76 |
+
+ if (dca_l) { // bug-2118 (wo_s dropped like the qwen3 DCA path β null on these GGUFs)
|
| 77 |
+
+ const dca_rope rp = { freq_base_l, freq_scale_l, n_rot_l, freq_factors };
|
| 78 |
+
+ cur = build_attn_dca(inp_attn, inp_dca, model.layers[il].wo, nullptr,
|
| 79 |
+
+ Qcur, Kcur, Vcur, rp, hparams.f_attention_scale, il);
|
| 80 |
+
+ } else {
|
| 81 |
+
cur = build_attn(inp_attn, model.layers[il].wo,
|
| 82 |
+
nullptr, model.layers[il].wo_s, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr,
|
| 83 |
+
hparams.f_attention_scale, il);
|
| 84 |
+
+ }
|
| 85 |
+
+ } else if (dca_l) {
|
| 86 |
+
+ // bug-2118: shared-KV global layer under DCA β Q-only read of the earlier
|
| 87 |
+
+ // layer's DCA-formatted cache (null k/v -> no store).
|
| 88 |
+
+ const dca_rope rp = { freq_base_l, freq_scale_l, n_rot_l, freq_factors };
|
| 89 |
+
+ cur = build_attn_dca(inp_attn, inp_dca, model.layers[il].wo, nullptr,
|
| 90 |
+
+ Qcur, nullptr, nullptr, rp, hparams.f_attention_scale, il);
|
| 91 |
+
} else {
|
| 92 |
+
// reuse KV cache of earlier layers
|
| 93 |
+
cur = build_attn(inp_attn,
|
| 94 |
+
--- a/llama.cpp/src/models/gemma4-assistant.cpp
|
| 95 |
+
+++ b/llama.cpp/src/models/gemma4-assistant.cpp
|
| 96 |
+
@@ -1,5 +1,7 @@
|
| 97 |
+
// opencoti F5 M6-S4 mtp
|
| 98 |
+
#include "models.h"
|
| 99 |
+
+#include "dca.h" // opencoti F5 dca bug-2118
|
| 100 |
+
+#include "llama-kv-cache-iswa.h" // bug-2118: get_base() on the iswa mctx
|
| 101 |
+
|
| 102 |
+
#include <cmath>
|
| 103 |
+
#include <vector>
|
| 104 |
+
@@ -190,6 +192,13 @@
|
| 105 |
+
ggml_tensor * inp_pos = build_inp_pos();
|
| 106 |
+
ggml_tensor * inp_out_ids = build_inp_out_ids();
|
| 107 |
+
|
| 108 |
+
+ // opencoti F5 dca bug-2118 β when the TARGET runs DCA, the drafter's global layers
|
| 109 |
+
+ // read the target's DCA-formatted (pre-roped, position-remapped) shared K/V, so its
|
| 110 |
+
+ // Q must be roped per regime through the same build_attn_dca path. Without this the
|
| 111 |
+
+ // drafter reads remapped keys with raw-extrapolated Q beyond native ctx -> accept 0.
|
| 112 |
+
+ const bool use_dca = cparams.dca_enabled;
|
| 113 |
+
+ llm_graph_input_dca * inp_dca = use_dca ? build_attn_inp_dca(inp_attn->mctx->get_base()) : nullptr;
|
| 114 |
+
+
|
| 115 |
+
ggml_tensor * inpL = cur;
|
| 116 |
+
|
| 117 |
+
for (int il = 0; il < n_layer; ++il) {
|
| 118 |
+
@@ -212,13 +221,22 @@
|
| 119 |
+
cb(Qcur, "Qcur_normed", il);
|
| 120 |
+
|
| 121 |
+
ggml_tensor * freq_factors = is_swa ? nullptr : model.layers[il].rope_freqs;
|
| 122 |
+
+ const bool dca_l = use_dca && !is_swa; // bug-2118
|
| 123 |
+
+ if (!dca_l) {
|
| 124 |
+
Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, freq_factors, n_rot_l, rope_type, n_ctx_orig,
|
| 125 |
+
freq_base_l, freq_scale_l, ext_factor, attn_factor, beta_fast, beta_slow);
|
| 126 |
+
cb(Qcur, "Qcur_pos", il);
|
| 127 |
+
+ }
|
| 128 |
+
|
| 129 |
+
// KV-less cross-attention into the shared/aliased target K/V (null k_cur/v_cur -> no store).
|
| 130 |
+
+ if (dca_l) { // bug-2118: Q-only per-regime read of the target's DCA cache
|
| 131 |
+
+ const dca_rope rp = { freq_base_l, freq_scale_l, n_rot_l, freq_factors };
|
| 132 |
+
+ cur = build_attn_dca(inp_attn, inp_dca, model.layers[il].wo, nullptr,
|
| 133 |
+
+ Qcur, nullptr, nullptr, rp, hparams.f_attention_scale, il);
|
| 134 |
+
+ } else {
|
| 135 |
+
cur = build_attn(inp_attn, model.layers[il].wo, nullptr, nullptr,
|
| 136 |
+
Qcur, nullptr, nullptr, nullptr, nullptr, nullptr, hparams.f_attention_scale, il);
|
| 137 |
+
+ }
|
| 138 |
+
|
| 139 |
+
if (il == n_layer - 1 && inp_out_ids) {
|
| 140 |
+
cur = ggml_get_rows(ctx0, cur, inp_out_ids);
|
patches/0100-dca-single-chunk-defer.patch
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0100 β DCA: defer quant lift in the single-chunk regime (bug-2119)
|
| 2 |
+
|
| 3 |
+
dca.cpp gated defer_quant_lift on !single_chunk, so the single-chunk (INTRA)
|
| 4 |
+
regime whole-cache dca_lift_to_f16'd the quantized K/V EVERY forward β a
|
| 5 |
+
per-decode-step full-cache dequant that collapsed A4B 32k q8K/q4V decode to
|
| 6 |
+
4.69 tps. The comment assumed n_kv<=chunk meant "tiny cache"; at chunk 32768
|
| 7 |
+
it is the entire context. Stock ggml_flash_attn_ext converts scalar-quant
|
| 8 |
+
natively, so the single-chunk branch defers exactly like analytical-bands.
|
| 9 |
+
|
| 10 |
+
Gate (bs2, A4B q8K/q4V 32k chunk-32768): decode 4.69 -> 73.74 tps
|
| 11 |
+
(DCA-off 77.13); INTRA text remains byte-identical to DCA-off.
|
| 12 |
+
|
| 13 |
+
--- a/llama.cpp/src/dca.cpp
|
| 14 |
+
+++ b/llama.cpp/src/dca.cpp
|
| 15 |
+
@@ -255,16 +255,16 @@
|
| 16 |
+
// kernel dequants scalar-quant K/V to f16 IN ITS LAUNCH (single-pass to_fp16_nc, see
|
| 17 |
+
// ggml_cuda_flash_attn_ext_mma_f16_dca_fused_case), so pass the quantized cache THROUGH instead of
|
| 18 |
+
// whole-cache materializing it to f16 via dca_lift_to_f16 every forward (the prefill cliff). Gate:
|
| 19 |
+
- // - !single_chunk: only the fused op carries the in-launch convert; single_chunk uses stock
|
| 20 |
+
- // ggml_flash_attn_ext (launch_fattn already converts scalar-quant natively) and runs only while
|
| 21 |
+
- // n_kv <= chunk (a tiny cache) so its lift cost is negligible β keep it on the safe lift path.
|
| 22 |
+
- // - dca_is_inkernel_liftable: scalar quants the fused launch's to_fp16_nc can read.
|
| 23 |
+
+ // - dca_is_inkernel_liftable: scalar quants the fused launch's to_fp16_nc can read. bug-2119: the
|
| 24 |
+
+ // single_chunk branch defers too β it uses stock ggml_flash_attn_ext, whose launch_fattn
|
| 25 |
+
+ // converts scalar-quant natively (the ordinary DCA-off decode path). The old !single_chunk gate
|
| 26 |
+
+ // assumed n_kv <= chunk means "tiny cache, lift is negligible", but chunk can be 32k+: the
|
| 27 |
+
+ // whole-cache two-leg graph lift ran EVERY decode step (A4B 32k/chunk-32768 q8q4: 76 -> 4.7 tps).
|
| 28 |
+
// - !v_trans: a transposed quant V view breaks the dim-0 (block) contiguity to_fp16_nc requires; the
|
| 29 |
+
// permute(0,2,1,3) below keeps dim-0 intact, but a transpose would not β fall back to the lift.
|
| 30 |
+
// The in-op convert is byte-identical to dca_lift_to_f16, so deferred output == lifted output.
|
| 31 |
+
const bool v_trans_raw = v_raw->nb[1] > v_raw->nb[2];
|
| 32 |
+
const bool defer_quant_lift =
|
| 33 |
+
- !inp_dca->single_chunk &&
|
| 34 |
+
dca_is_inkernel_liftable(k_raw->type) &&
|
| 35 |
+
dca_is_inkernel_liftable(v_raw->type) &&
|
| 36 |
+
!v_trans_raw;
|
patches/0101-kv-attn-rot-inherit.patch
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0101 β shared draft cache inherits attention-rotation state (bug-2120)
|
| 2 |
+
|
| 3 |
+
llamafile 0.10.3 attention rotation (Hadamard-rotate K/V rows before quantized
|
| 4 |
+
store when the cache type is quantized && head_dim%64==0; readers rotate Q and
|
| 5 |
+
un-rotate the output via attn_inp_k_rot/v_rot) is computed per-cache from its
|
| 6 |
+
OWN type_k/type_v and accumulated head dims. The dual-context assistant draft
|
| 7 |
+
cache is created with f16 types AND its shared layers skip the head-dim
|
| 8 |
+
accumulation, so rotation silently disabled: the drafter read the target's
|
| 9 |
+
ROTATED quant K/V with an unrotated Q -> decorrelated attention, flat finite
|
| 10 |
+
logits, accept ~0.000 with ANY quant target KV (f16/f16 unaffected; Qwen NextN
|
| 11 |
+
unaffected β it reads through the target's own cache object).
|
| 12 |
+
|
| 13 |
+
Fix: in the ctor share branch, inherit attn_rot_k/v, n_embd_head_*_all and the
|
| 14 |
+
Hadamard tables from the source cache, overriding the local computation. The
|
| 15 |
+
generic iswa build_attn then applies the rotation on the drafter's Q-only path.
|
| 16 |
+
|
| 17 |
+
Gate (bs2, A4B + assistant Q8_0 drafter, 32k, 24k prompt): accept
|
| 18 |
+
k8_v16 0.000 -> 0.641; k8_v4 (production PolyKV) 0.000 -> 0.729; f16 control
|
| 19 |
+
0.677 unchanged.
|
| 20 |
+
|
| 21 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 22 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 23 |
+
@@ -1106,6 +1106,26 @@
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
+ // opencoti bug-2120: a shared (dual-context drafter) cache must READ with the same
|
| 28 |
+
+ // attention-rotation state the SOURCE cache used to WRITE. The local computation above
|
| 29 |
+
+ // sees this cache's own type_k/type_v (f16 for the drafter) and n_embd_head_*_all == 0
|
| 30 |
+
+ // (shared layers `continue` before accumulating), so it always disables rotation β the
|
| 31 |
+
+ // drafter then reads Hadamard-rotated quant K/V with an unrotated Q and un-unrotated
|
| 32 |
+
+ // output: decorrelated attention, accept ~0 with ANY quant target KV (f16/f16 unaffected
|
| 33 |
+
+ // because no rotation is applied at all). Inherit flags + head-dim extents + Hadamard
|
| 34 |
+
+ // tables from `other` so build_input_k_rot/v_rot emit the matching rotation inputs.
|
| 35 |
+
+ if (share && other) {
|
| 36 |
+
+ attn_rot_k = other->attn_rot_k;
|
| 37 |
+
+ attn_rot_v = other->attn_rot_v;
|
| 38 |
+
+ n_embd_head_k_all = other->n_embd_head_k_all;
|
| 39 |
+
+ n_embd_head_v_all = other->n_embd_head_v_all;
|
| 40 |
+
+ attn_rot_hadamard = other->attn_rot_hadamard;
|
| 41 |
+
+ if (attn_rot_k || attn_rot_v) {
|
| 42 |
+
+ LLAMA_LOG_INFO("%s: shared cache inherits attn_rot_k=%d attn_rot_v=%d from source (bug-2120)\n",
|
| 43 |
+
+ __func__, (int) attn_rot_k, (int) attn_rot_v);
|
| 44 |
+
+ }
|
| 45 |
+
+ }
|
| 46 |
+
+
|
| 47 |
+
const char * LLAMA_KV_CACHE_DEBUG = getenv("LLAMA_KV_CACHE_DEBUG");
|
| 48 |
+
debug = LLAMA_KV_CACHE_DEBUG ? atoi(LLAMA_KV_CACHE_DEBUG) : 0;
|
| 49 |
+
}
|
patches/0102-fattn-bulk-h2d-scratch-ring.patch
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0102 β streaming-FA bulk-H2D tail lift + persistent staging scratch ring (bug-2116, bug-2121)
|
| 2 |
+
|
| 3 |
+
Two changes to the rolling-KV streaming-FA lift in fattn.cu:
|
| 4 |
+
|
| 5 |
+
1. bug-2116 (perf): for a HOST quant tail source at decode (n_b==1), bulk-copy
|
| 6 |
+
the RAW QUANT bytes H2D once at full DMA rate into device scratch, then run
|
| 7 |
+
the nc-converter from device memory. The converter reading the pinned-host
|
| 8 |
+
tail via UVA at word granularity measured 10-16 GB/s vs 57-65 GB/s bulk on
|
| 9 |
+
bs2 β the UVA cliff behind the S0 spill-decode curve. Measured after-curve
|
| 10 |
+
(14B-1M q8q4 window 131k, decode tps): 800MiB 9.05->12.23, 1600 3.96->10.28,
|
| 11 |
+
6400 ~1->5.53.
|
| 12 |
+
|
| 13 |
+
2. bug-2121 (correctness): both bulk paths (the new quant one and the #586 f16
|
| 14 |
+
one) staged through ggml_cuda_pool_alloc scratch FREED on scope exit while
|
| 15 |
+
the async memcpy+convert on the copy stream were still pending. Decode
|
| 16 |
+
(bug-1841 single stream) is stream-ordered and safe; PREFILL runs two
|
| 17 |
+
streams and the FA compute allocates from the SAME pool concurrently (MMA
|
| 18 |
+
stream-k fixup buffers, every tile) -> freed-but-pending scratch re-handed
|
| 19 |
+
cross-stream -> corrupted K/V tiles. Manifested with >=2 host-tail tiles
|
| 20 |
+
(>32768 exercised tail cells): garbage decode / needle-LOST + racy host
|
| 21 |
+
GPFs; <=1 tail tile clean. Fix: persistent per-slot scratch ring
|
| 22 |
+
(g_scrK/g_scrV[4], grow-only, worst-case tile_kv_full capacity so no
|
| 23 |
+
cudaMalloc inside captured decode graphs) β no pool sharing across streams.
|
| 24 |
+
|
| 25 |
+
Gate (bs2, 14B-1M q8q4 window 131k): tails 1600/3200/6400 all needle=PRESENT,
|
| 26 |
+
coherent text, zero crashes across three full 106k prefills (3200 previously
|
| 27 |
+
crashed 2x + garbage decode; 6400 previously needle-LOST).
|
| 28 |
+
|
| 29 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 30 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 31 |
+
@@ -1350,6 +1350,30 @@
|
| 32 |
+
char * const slotK_base = g_slotK;
|
| 33 |
+
char * const slotV_base = g_slotV;
|
| 34 |
+
|
| 35 |
+
+ // opencoti-hook: f5-rolling-kv bug-2121 β PERSISTENT per-slot H2D staging scratch.
|
| 36 |
+
+ // The bulk-H2D lifts below (bug-2116 quant path + #586 f16 path) used
|
| 37 |
+
+ // ggml_cuda_pool_alloc scratch FREED on lambda/loop exit while the
|
| 38 |
+
+ // cudaMemcpyAsync + converter on the copy stream were still pending. On decode
|
| 39 |
+
+ // (single stream, bug-1841) reuse is stream-ordered and safe; on PREFILL the FA
|
| 40 |
+
+ // compute on ctx.stream() allocates from the SAME pool concurrently (MMA
|
| 41 |
+
+ // stream-k fixup buffers, every tile), so a freed-but-pending scratch gets
|
| 42 |
+
+ // re-handed to the other stream β cross-stream aliasing β corrupted K/V tiles.
|
| 43 |
+
+ // Manifested only with β₯2 host-tail tiles interleaving with compute (>32768
|
| 44 |
+
+ // exercised tail cells): 14B-1M q8q4 window-mode tail=3200/6400 MiB β garbage
|
| 45 |
+
+ // decode / needle-LOST (+ racy host GPFs); tailβ€1600 (1 tail tile) clean. A
|
| 46 |
+
+ // persistent ring keyed by slot removes pool sharing entirely; within-ring WAR
|
| 47 |
+
+ // is stream-ordered (memcpy β convert β next lift's memcpy, all on cs).
|
| 48 |
+
+ static char * g_scrK[4] = {}; static size_t g_scrK_cap[4] = {};
|
| 49 |
+
+ static char * g_scrV[4] = {}; static size_t g_scrV_cap[4] = {};
|
| 50 |
+
+ auto scr_get = [](char ** ring, size_t * cap, int slot, size_t need) -> char * {
|
| 51 |
+
+ if (cap[slot] < need) {
|
| 52 |
+
+ if (ring[slot]) CUDA_CHECK(cudaFree(ring[slot])); // cudaFree syncs pending users
|
| 53 |
+
+ CUDA_CHECK(cudaMalloc((void **)&ring[slot], need));
|
| 54 |
+
+ cap[slot] = need;
|
| 55 |
+
+ }
|
| 56 |
+
+ return ring[slot];
|
| 57 |
+
+ };
|
| 58 |
+
+
|
| 59 |
+
// R2-b S3b (#312): with >=2 slots the op runs a double-buffer ping-pong β the
|
| 60 |
+
// per-tile lift is issued on a dedicated copy_stream while FA/lse for the
|
| 61 |
+
// current tile run on ctx.stream(), so tile t+1's DMA overlaps tile t's
|
| 62 |
+
@@ -1445,10 +1469,34 @@
|
| 63 |
+
// below are unchanged. Unifies device + pinned-host (S3c) sources
|
| 64 |
+
// (the kernel reads host via UVA). Slot is f16 β lse half-cast OK.
|
| 65 |
+
// 3c-5: tK/tV + loc select the window (device) or tail (host) source.
|
| 66 |
+
- to_fp16_k((const char *)tK->data + (size_t)loc*tK->nb[1], (half *)sK,
|
| 67 |
+
+ // opencoti-hook: f5-rolling-kv bug-2116 β for a HOST quant source,
|
| 68 |
+
+ // bulk-H2D the RAW QUANT bytes (cell-major block, 2.5Γ fewer bytes
|
| 69 |
+
+ // than f16) into device scratch at full DMA rate, then convert from
|
| 70 |
+
+ // device memory. Calling the converter on the host pointer made it
|
| 71 |
+
+ // read the tail via UVA at word granularity (~10β16 GB/s measured
|
| 72 |
+
+ // vs 57β65 GB/s bulk on bs2). n_b>1 keeps the UVA path (nb[3]
|
| 73 |
+
+ // batch blocks are not spanned by the cell-major copy; unused in
|
| 74 |
+
+ // the spill regime where ne[3]==1).
|
| 75 |
+
+ const bool bulkq = tr.host && n_b == 1;
|
| 76 |
+
+ const char * qsrcK = (const char *)tK->data + (size_t)loc*tK->nb[1];
|
| 77 |
+
+ const char * qsrcV = (const char *)tV->data + (size_t)loc*tV->nb[1];
|
| 78 |
+
+ if (bulkq) {
|
| 79 |
+
+ // bug-2121: persistent per-slot scratch β NOT pool memory (see scr_get above).
|
| 80 |
+
+ // Capacity = worst-case tile (tile_kv_full), not this_kv: this_kv grows per
|
| 81 |
+
+ // decode token, and a realloc inside a captured decode graph would fail.
|
| 82 |
+
+ const size_t kqblk = (size_t)this_kv * tK->nb[1];
|
| 83 |
+
+ const size_t vqblk = (size_t)this_kv * tV->nb[1];
|
| 84 |
+
+ char * qscrK = scr_get(g_scrK, g_scrK_cap, slot, (size_t)tile_kv_full * tK->nb[1]);
|
| 85 |
+
+ char * qscrV = scr_get(g_scrV, g_scrV_cap, slot, (size_t)tile_kv_full * tV->nb[1]);
|
| 86 |
+
+ CUDA_CHECK(cudaMemcpyAsync(qscrK, qsrcK, kqblk, cudaMemcpyDefault, cs));
|
| 87 |
+
+ CUDA_CHECK(cudaMemcpyAsync(qscrV, qsrcV, vqblk, cudaMemcpyDefault, cs));
|
| 88 |
+
+ qsrcK = qscrK;
|
| 89 |
+
+ qsrcV = qscrV;
|
| 90 |
+
+ }
|
| 91 |
+
+ to_fp16_k(qsrcK, (half *)sK,
|
| 92 |
+
head_dim, this_kv, n_head_kv, n_b,
|
| 93 |
+
(int64_t)(tK->nb[1]/ts_k), (int64_t)(tK->nb[2]/ts_k), (int64_t)(tK->nb[3]/ts_k), cs);
|
| 94 |
+
- to_fp16_v((const char *)tV->data + (size_t)loc*tV->nb[1], (half *)sV,
|
| 95 |
+
+ to_fp16_v(qsrcV, (half *)sV,
|
| 96 |
+
dv, this_kv, n_head_kv, n_b,
|
| 97 |
+
(int64_t)(tV->nb[1]/ts_v), (int64_t)(tV->nb[2]/ts_v), (int64_t)(tV->nb[3]/ts_v), cs);
|
| 98 |
+
} else
|
| 99 |
+
@@ -1468,17 +1516,20 @@
|
| 100 |
+
const bool bulk = use_2d && tr.host;
|
| 101 |
+
const size_t kblk = (size_t)this_kv * tK->nb[1]; // contiguous cell-major span
|
| 102 |
+
const size_t vblk = (size_t)this_kv * tV->nb[1];
|
| 103 |
+
- ggml_cuda_pool_alloc<char> scrK(pool, bulk ? kblk : 1);
|
| 104 |
+
- ggml_cuda_pool_alloc<char> scrV(pool, bulk ? vblk : 1);
|
| 105 |
+
const char * baseK;
|
| 106 |
+
const char * baseV;
|
| 107 |
+
if (bulk) {
|
| 108 |
+
+ // bug-2121: persistent per-slot scratch β NOT pool memory (see scr_get above).
|
| 109 |
+
+ // Sequential bb iterations reuse it WAR-safely (copy/repack both on cs).
|
| 110 |
+
+ // Worst-case tile capacity: see the quant path above (capture-safe growth).
|
| 111 |
+
+ char * scrK = scr_get(g_scrK, g_scrK_cap, slot, (size_t)tile_kv_full * tK->nb[1]);
|
| 112 |
+
+ char * scrV = scr_get(g_scrV, g_scrV_cap, slot, (size_t)tile_kv_full * tV->nb[1]);
|
| 113 |
+
const char * hK = (const char *)tK->data + (size_t)bb*tK->nb[3] + (size_t)loc*tK->nb[1];
|
| 114 |
+
const char * hV = (const char *)tV->data + (size_t)bb*tV->nb[3] + (size_t)loc*tV->nb[1];
|
| 115 |
+
- CUDA_CHECK(cudaMemcpyAsync(scrK.ptr, hK, kblk, cudaMemcpyDefault, cs)); // one full-BW H2D
|
| 116 |
+
- CUDA_CHECK(cudaMemcpyAsync(scrV.ptr, hV, vblk, cudaMemcpyDefault, cs));
|
| 117 |
+
- baseK = scrK.ptr; // scratch is cell-major, cell `loc` at offset 0
|
| 118 |
+
- baseV = scrV.ptr;
|
| 119 |
+
+ CUDA_CHECK(cudaMemcpyAsync(scrK, hK, kblk, cudaMemcpyDefault, cs)); // one full-BW H2D
|
| 120 |
+
+ CUDA_CHECK(cudaMemcpyAsync(scrV, hV, vblk, cudaMemcpyDefault, cs));
|
| 121 |
+
+ baseK = scrK; // scratch is cell-major, cell `loc` at offset 0
|
| 122 |
+
+ baseV = scrV;
|
| 123 |
+
} else {
|
| 124 |
+
baseK = (const char *)tK->data + (size_t)bb*tK->nb[3] + (size_t)loc*tK->nb[1];
|
| 125 |
+
baseV = (const char *)tV->data + (size_t)bb*tV->nb[3] + (size_t)loc*tV->nb[1];
|
| 126 |
+
@@ -1677,10 +1728,26 @@
|
| 127 |
+
if (!all_f16) {
|
| 128 |
+
// S3d: one nc-converter launch per tile β packed f16 slot (see
|
| 129 |
+
// the overlap path for the layout proof). Source strides s=nb/ts.
|
| 130 |
+
- to_fp16_k((const char *)K->data + (size_t)kv0*K->nb[1], (half *)sK,
|
| 131 |
+
+ // bug-2116: host quant source β bulk-H2D the raw quant block at
|
| 132 |
+
+ // DMA rate + convert from device scratch; direct converter-on-host
|
| 133 |
+
+ // reads via UVA at word granularity (see the overlap-path note).
|
| 134 |
+
+ const bool bulkq = host_src && n_b == 1;
|
| 135 |
+
+ const size_t kqblk = bulkq ? (size_t)this_kv * K->nb[1] : 1;
|
| 136 |
+
+ const size_t vqblk = bulkq ? (size_t)this_kv * V->nb[1] : 1;
|
| 137 |
+
+ ggml_cuda_pool_alloc<char> qscrK(pool, kqblk);
|
| 138 |
+
+ ggml_cuda_pool_alloc<char> qscrV(pool, vqblk);
|
| 139 |
+
+ const char * qsrcK = (const char *)K->data + (size_t)kv0*K->nb[1];
|
| 140 |
+
+ const char * qsrcV = (const char *)V->data + (size_t)kv0*V->nb[1];
|
| 141 |
+
+ if (bulkq) {
|
| 142 |
+
+ CUDA_CHECK(cudaMemcpyAsync(qscrK.ptr, qsrcK, kqblk, cudaMemcpyDefault, stream));
|
| 143 |
+
+ CUDA_CHECK(cudaMemcpyAsync(qscrV.ptr, qsrcV, vqblk, cudaMemcpyDefault, stream));
|
| 144 |
+
+ qsrcK = qscrK.ptr;
|
| 145 |
+
+ qsrcV = qscrV.ptr;
|
| 146 |
+
+ }
|
| 147 |
+
+ to_fp16_k(qsrcK, (half *)sK,
|
| 148 |
+
head_dim, this_kv, n_head_kv, n_b,
|
| 149 |
+
(int64_t)(K->nb[1]/ts_k), (int64_t)(K->nb[2]/ts_k), (int64_t)(K->nb[3]/ts_k), stream);
|
| 150 |
+
- to_fp16_v((const char *)V->data + (size_t)kv0*V->nb[1], (half *)sV,
|
| 151 |
+
+ to_fp16_v(qsrcV, (half *)sV,
|
| 152 |
+
dv, this_kv, n_head_kv, n_b,
|
| 153 |
+
(int64_t)(V->nb[1]/ts_v), (int64_t)(V->nb[2]/ts_v), (int64_t)(V->nb[3]/ts_v), stream);
|
| 154 |
+
} else
|
patches/0103-kv-cpu-fa-probe-guard.patch
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0103 β rolling-KV CPU-FA cost-probe guard (bug-2125)
|
| 2 |
+
|
| 3 |
+
The Stage-3d-S2 rolling-KV tactic selector runs a synthetic CPU flash-attn
|
| 4 |
+
"companion probe" (opencoti_fa_compute_probe_ms with on_cpu=true) purely to
|
| 5 |
+
MODEL a CPU_FA_TAIL cost for its audit log. CPU_FA_TAIL is gated out of
|
| 6 |
+
auto-select (#354) β the tail always streams on the GPU (POSITION_WINDOW) β so
|
| 7 |
+
the probe's result is never used for the decision.
|
| 8 |
+
|
| 9 |
+
That probe builds a real ggml_flash_attn_ext over the actual cache K/V types and
|
| 10 |
+
runs it on the host CPU backend. For opencoti's CUDA-FA-VEC-only KV types (q6_0,
|
| 11 |
+
turbo{2,3,4,8}_0, turbo{2,3}_tcq) the CPU vec_dot trait is NULL, so the CPU
|
| 12 |
+
flash-attn dereferences a NULL fn-ptr β SIGSEGV at context-init (before any
|
| 13 |
+
client request). Stock types (f16/q8_0/q4_0/q5_0/q5_1/bf16) carry a CPU vec_dot
|
| 14 |
+
and survive, which is why only the custom-K tiers crashed under DCA at 256k/512k.
|
| 15 |
+
|
| 16 |
+
Fix: gate the CPU probe on a host-CPU-FA-capability predicate
|
| 17 |
+
(opencoti_cpu_fa_capable_ktype) so it is never built for a K type the CPU
|
| 18 |
+
cannot process. Returning 0.0f just leaves CPU_FA_TAIL unmodeled; the selector
|
| 19 |
+
keeps its GPU-stream default. The rolling window stays a pure-GPU path for every
|
| 20 |
+
KV tier β no silent CPU fallback. Host-only change (no CUDA DSO impact).
|
| 21 |
+
--- a/llama.cpp/src/llama-kv-cache.cpp
|
| 22 |
+
+++ b/llama.cpp/src/llama-kv-cache.cpp
|
| 23 |
+
@@ -2795,6 +2795,28 @@
|
| 24 |
+
// build_attn_mha FA contract (post-permute q[hd,n_q,n_head], k/v[hd,n_kv,n_head_kv],
|
| 25 |
+
// F16 mask). Never aborts boot: every shortfall (no GPU, unsupported op, non-GQA
|
| 26 |
+
// geometry, alloc miss) returns 0.0f and the selector keeps the stream default.
|
| 27 |
+
+//
|
| 28 |
+
+// opencoti bug-2125 SAFEGUARD: can the HOST CPU flash-attn path
|
| 29 |
+
+// (ggml_compute_forward_flash_attn_ext_f16) process a cache of this K type? It reads K via the
|
| 30 |
+
+// K-type's CPU vec_dot trait, which is NULL for opencoti's CUDA-FA-VEC-only KV types (q6_0,
|
| 31 |
+
+// turbo{2,3,4,8}_0, turbo{2,3}_tcq β GPU in-register readers, no CPU vec_dot / dp kernel). Running
|
| 32 |
+
+// CPU flash-attn over such a K deref's a NULL fn-ptr β SIGSEGV. The rolling-KV tail is ALWAYS
|
| 33 |
+
+// streamed on the GPU (POSITION_WINDOW; CPU_FA_TAIL is gated out of auto-select, #354), so the ONLY
|
| 34 |
+
+// place a host CPU flash-attn is ever built is the Stage-3d-S2 cost-model probe below β and its
|
| 35 |
+
+// result is audit-log only. Gate the CPU probe on this predicate so the rolling window stays a
|
| 36 |
+
+// pure-GPU path with NO silent CPU fallback for ANY KV tier. Stock ggml types (which DO carry a CPU
|
| 37 |
+
+// vec_dot) are the allow-list; every opencoti custom KV type is excluded.
|
| 38 |
+
+static bool opencoti_cpu_fa_capable_ktype(enum ggml_type t) {
|
| 39 |
+
+ switch (t) {
|
| 40 |
+
+ case GGML_TYPE_F32: case GGML_TYPE_F16: case GGML_TYPE_BF16:
|
| 41 |
+
+ case GGML_TYPE_Q4_0: case GGML_TYPE_Q4_1:
|
| 42 |
+
+ case GGML_TYPE_Q5_0: case GGML_TYPE_Q5_1:
|
| 43 |
+
+ case GGML_TYPE_Q8_0:
|
| 44 |
+
+ return true; // stock ggml types: have a host CPU vec_dot
|
| 45 |
+
+ default:
|
| 46 |
+
+ return false; // opencoti custom KV (q6_0 / turbo* / turbo*_tcq): CUDA-only, NULL CPU vec_dot
|
| 47 |
+
+ }
|
| 48 |
+
+}
|
| 49 |
+
static float opencoti_fa_compute_probe_ms(
|
| 50 |
+
const llama_model & model, const llama_hparams & hparams,
|
| 51 |
+
int32_t il, ggml_type type_k, ggml_type type_v, int64_t bench_cells,
|
| 52 |
+
@@ -2823,6 +2845,15 @@
|
| 53 |
+
return 0.0f; // device-type mismatch; selector falls back (stream default / no CPU model)
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
+ // opencoti bug-2125 SAFEGUARD: never build a host CPU flash-attn over a K type the CPU cannot
|
| 57 |
+
+ // process (NULL vec_dot β SIGSEGV). This is audit-only (the tail always streams on GPU), so
|
| 58 |
+
+ // returning 0.0f just leaves CPU_FA_TAIL unmodeled β the selector keeps the GPU-stream default.
|
| 59 |
+
+ // Keeps the rolling window a pure-GPU path for q6_0 / turbo* / turbo*_tcq (and any future
|
| 60 |
+
+ // CUDA-only KV tier), with no silent CPU fallback.
|
| 61 |
+
+ if (on_cpu && !opencoti_cpu_fa_capable_ktype(type_k)) {
|
| 62 |
+
+ return 0.0f;
|
| 63 |
+
+ }
|
| 64 |
+
+
|
| 65 |
+
const int64_t hd_k = (int64_t) hparams.n_embd_head_k(il);
|
| 66 |
+
const int64_t hd_v = (int64_t) hparams.n_embd_head_v(il);
|
| 67 |
+
const int64_t n_head = (int64_t) hparams.n_head(il);
|
patches/0104-fattn-q6q4-vec-instance.patch
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
opencoti patch 0104 β q6_0-K / q4_0-V FA-VEC instance (bug-2125 / #627)
|
| 2 |
+
|
| 3 |
+
q6_0-K + q4_0-V (strong-K, cheap-V β the design invariant K precision >= V) is a
|
| 4 |
+
first-class asymmetric KV combo. Under GGML_CUDA_FA_ALL_QUANTS the kernel
|
| 5 |
+
selector admits K!=V and picks the VEC path, but no (q6_0, q4_0) FA-VEC instance
|
| 6 |
+
existed, so the D<=256 dispatch (Gemma-4 local/SWA layers β the global layers
|
| 7 |
+
use the DCA fused kernel's in-launch to_fp16_nc) fell through to
|
| 8 |
+
GGML_ABORT("fatal error") at ggml_cuda_flash_attn_ext_vec, crashing at boot.
|
| 9 |
+
|
| 10 |
+
Fix (mirrors the q6_0-q5_0 pattern, #512/#81): add the instance TU
|
| 11 |
+
fattn-vec-instance-q6_0-q4_0.cu (head_dim 64/128/256), the
|
| 12 |
+
FATTN_VEC_CASES_ALL_D(Q6_0, Q4_0) dispatch case, and the build-functions.sh 3c
|
| 13 |
+
default-build inclusion (so the symbol is present without FA_ALL_QUANTS too).
|
| 14 |
+
Verified bs2 256k DCA-on: boots HEALTHY (was abort) + niah_single_1 = 100.0.
|
| 15 |
+
CUDA DSO change (needs a cuda rebuild). Only the q6_0/q4_0 direction is added β
|
| 16 |
+
reverse q4_0/q6_0 is intentionally NOT added (K must stay the higher-bit type).
|
| 17 |
+
--- a/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 18 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/fattn.cu
|
| 19 |
+
@@ -353,13 +353,17 @@
|
| 20 |
+
// ik_llama.cpp ships for q6_0 KV), registered OUTSIDE the FA_ALL_QUANTS block so they compile in
|
| 21 |
+
// the DEFAULT build β the matching instance .cu files are added unconditionally in
|
| 22 |
+
// collect_gpu_sources (build-functions.sh section 3c), like the turbo K==V instances below.
|
| 23 |
+
- // q6_0-q6_0 is the symmetric (default-reachable) cell; the 4 mixed cells are reachable only under
|
| 24 |
+
+ // q6_0-q6_0 is the symmetric (default-reachable) cell; the 5 mixed cells are reachable only under
|
| 25 |
+
// FA_ALL_QUANTS (the K!=V early-out in ggml_cuda_get_best_fattn_kernel), same as q5_0's mixed pairs.
|
| 26 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_Q6_0, GGML_TYPE_Q6_0)
|
| 27 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_Q8_0, GGML_TYPE_Q6_0)
|
| 28 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_Q6_0, GGML_TYPE_Q5_0)
|
| 29 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_Q6_0, GGML_TYPE_F16)
|
| 30 |
+
FATTN_VEC_CASES_ALL_D(GGML_TYPE_F16, GGML_TYPE_Q6_0)
|
| 31 |
+
+ // opencoti-hook: q6_0 KV (#627/bug-2125) β strong-K q6_0 + cheap-V q4_0 asymmetric cell. Under
|
| 32 |
+
+ // FA_ALL_QUANTS the selector admits q6_0-K/q4_0-V, so the stock FA-VEC dispatch (local SWA layers)
|
| 33 |
+
+ // needs this instance; without it the D<=256 dispatch fell through to the GGML_ABORT below.
|
| 34 |
+
+ FATTN_VEC_CASES_ALL_D(GGML_TYPE_Q6_0, GGML_TYPE_Q4_0)
|
| 35 |
+
|
| 36 |
+
// opencoti-hook: turboquant perf-lift (Level B) β F5 M6-S2 #392. Fused turbo K/V vec path
|
| 37 |
+
// (always registered, independent of GGML_CUDA_FA_ALL_QUANTS). turbo2/3/4 at head_dim β
|
| 38 |
+
--- /dev/null
|
| 39 |
+
+++ b/llama.cpp/ggml/src/ggml-cuda/template-instances/fattn-vec-instance-q6_0-q4_0.cu
|
| 40 |
+
@@ -0,0 +1,11 @@
|
| 41 |
+
+// opencoti-hook: q6_0 KV (Anbeeld/beellama.cpp port) β strong-K + cheap-V asymmetric cell (#627/bug-2125).
|
| 42 |
+
+// q6_0-K / q4_0-V is a first-class KV combo (6-bit K precision, 4-bit V footprint); the local SWA layers
|
| 43 |
+
+// read it through the stock FA-VEC path (the global layers use the DCA fused kernel's to_fp16_nc dequant),
|
| 44 |
+
+// so this instance must exist or ggml_cuda_flash_attn_ext_vec_case<D,q6_0,q4_0> is missing and the D<=256
|
| 45 |
+
+// dispatch aborts at fattn.cu (GGML_ABORT "fatal error"). Mirrors fattn-vec-instance-q6_0-q5_0.cu.
|
| 46 |
+
+
|
| 47 |
+
+#include "../fattn-vec.cuh"
|
| 48 |
+
+
|
| 49 |
+
+DECL_FATTN_VEC_CASE( 64, GGML_TYPE_Q6_0, GGML_TYPE_Q4_0);
|
| 50 |
+
+DECL_FATTN_VEC_CASE(128, GGML_TYPE_Q6_0, GGML_TYPE_Q4_0);
|
| 51 |
+
+DECL_FATTN_VEC_CASE(256, GGML_TYPE_Q6_0, GGML_TYPE_Q4_0);
|
| 52 |
+
--- a/llamafile/build-functions.sh
|
| 53 |
+
+++ b/llamafile/build-functions.sh
|
| 54 |
+
@@ -162,7 +162,8 @@
|
| 55 |
+
fi
|
| 56 |
+
|
| 57 |
+
# 3c. opencoti-hook: q6_0 KV (Anbeeld/beellama.cpp port). q6_0 is a scalar KV type read by the
|
| 58 |
+
- # FA-VEC path; its 5 Anbeeld instances must compile in the DEFAULT build (same #468/bug-339
|
| 59 |
+
+ # FA-VEC path; its 6 instances (5 Anbeeld + q6_0/q4_0 strong-K/cheap-V, #627) must compile in
|
| 60 |
+
+ # the DEFAULT build (same #468/bug-339
|
| 61 |
+
# reasoning as the turbo block above β the default build does NOT glob fattn-vec, so without
|
| 62 |
+
# this the DSO lacks ggml_cuda_flash_attn_ext_vec_case<D,q6_0,*> and dlopen fails the instant
|
| 63 |
+
# q6_0 KV reaches flash-attention). No-op under FA_ALL_QUANTS=1 (already globbed) and on a
|
| 64 |
+
@@ -171,6 +172,7 @@
|
| 65 |
+
for f in "$ti_dir"/fattn-vec-instance-q6_0-q6_0.cu \
|
| 66 |
+
"$ti_dir"/fattn-vec-instance-q8_0-q6_0.cu \
|
| 67 |
+
"$ti_dir"/fattn-vec-instance-q6_0-q5_0.cu \
|
| 68 |
+
+ "$ti_dir"/fattn-vec-instance-q6_0-q4_0.cu \
|
| 69 |
+
"$ti_dir"/fattn-vec-instance-q6_0-f16.cu \
|
| 70 |
+
"$ti_dir"/fattn-vec-instance-f16-q6_0.cu; do
|
| 71 |
+
[ -f "$f" ] && CUDA_SOURCES="$CUDA_SOURCES $f"
|