Repository navigation
Conversation
… model for 8-12 GB cards The engine reads general.architecture from the GGUF and runs qwen35moe beside qwen4exp (Flash-Next) on the same code path, switching on the model's geometry: one residual stream instead of hyper-connections, dense attention (no indexer), no PLE, a silu(z) DeltaNet output gate, 8 of 256 experts 2048/512 wide. Flash- Next's compile-time sizes stay as capacities; kernels get template instances (8 or 12 query heads, 32/48 DeltaNet heads, 8/10 experts), so its code is unchanged: the same tokens and speed as before (Coder, 3 prompts + 8K, A/B). - decode (verify windows), batched prompt path (NVIDIA sm_75+; elsewhere the prompt is read through the decode windows), MTP draft layer read from the model file's blk.40, server, checkpoints, low-RAM mode - native Q2_K/Q3_K/Q6_K expert formats (Unsloth's mixes); iq_parity covers Q2_K and Q4_0 - greedy tokens match llama.cpp b11438 (UD-IQ4_XS: 3 prompts + 8K exactly) - RTX 4070 Ti 12 GB, 32K, MTP: UD-IQ4_XS 105 tok/s chat, 4,460 tok/s 8K read; 8 GB emulated 75 / 2,669; llama.cpp on the same PC 51 / 708 - small cards: the expert cache keeps the dense attention (grows with the context; 256K failed on every card before) and a 500 MiB floor out of the cache; a cache too small to work stops the start with what is short. UD-IQ3_S starts from 4.17 GB free at 32K, 3.9 GB at 8K + --draft-vocab en - data/expert-profile-qwen36.bin: ranked from 24 prompts' routing (--expert-profile-save, 64-slot cache); 12 GB writes +7-11% against an unranked one setup: families qwen36 (unsloth/Qwen3.6-35B-A3B-MTP-GGUF UD-IQ4_XS, UD-IQ3_S) and ornith (bartowski/Ornith-1.5-35B-A3B-GGUF IQ4_XS, IQ3_XXS), pinned revisions and SHA-256; suggested where no Flash-Next size fits the RAM; on a card under 5.5 GB the smallest size and an 8K context are recommended. The ready-made engines predate qwen35moe, so setup looks for it in the engine and compiles one when it is missing. MCP recommend() follows the same rules. Tests: tools/test_setup_qwen36.py, tools/test_setup_ornith.py. Docs: docs/QWEN36.md (setup, what fits, small cards, speed, llama.cpp, Ornith, limits), MODELS.md, README. Tested on Linux (Debian 12, kernel 6.1) with an NVIDIA RTX 4070 Ti 12 GB (driver 580), CUDA 13.0, Ryzen 9 5950X, 62 GB RAM. Not run on AMD or Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts, resolved for both models: - setup.py: the Flash-Next draft-layer fetch (now with STRATA_SOURCE) stays in the branch the 35B-A3B families skip (their draft layer is in the model file) - verify.cpp: STRATA_DF_BRANCH's query branch only with the indexer (its side 1 is the indexer query); qwen35moe takes the plain path - qsa_decode_attn.cu: the pre-sm_75/HIP kernel takes both template parameters (query heads per K/V head G; NC_/DPP_ for gfx103x) - pool.cpp: the native phases' rows from the format (n_ff/n_embd), tasks from phase_tasks (--pool-tasks) - prefill.cpp: the deferred shared expert and the CPU share's buffers with the model's sizes; the CPU share (opt-in) is off for one-stream models (unmeasured) - generate.cpp: the dense-attention log line and the APU warning both kept Also: QWEN36_ENGINE = 0.1.40.2 (this tree's version, setup's four-number versions); docs/QWEN36.md names 0.1.40.2 among the engines without qwen35moe, and says the llama.cpp match is exact with a fixed cache (a near-tie can move with the cache's contents). ModelScope serves the four 35B-A3B files with the pinned SHA-256. Checked after the merge (RTX 4070 Ti, Linux, CUDA 13.0): Flash-Next Coder tokens identical to upstream's own build (3 prompts + 8K); Qwen3.6 UD-IQ4_XS and Ornith IQ4_XS tokens identical to the pre-merge engine at the same cache sizes; speed unchanged; small-card floor and 256K as before; iq_parity and native_grouped_parity 0 failures; setup/MCP tests pass except test_setup_engine_hash.py's two, which fail the same way on upstream/main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Would it be possible to add UD-Q4_K_XL quant? |
Conflicts, resolved for both models: - prefill.cpp: main's routing debug hook (STRATA_DBG_FEAT, opt-in) and CPU-share arms only for Flash-Next (one-stream models keep the CPU share off); the peer-device inputs, sums and combine with the model's sizes - kernels.cu / kernels.hpp: the opt-in chunked GDN recurrence (STRATA_GDN_CHUNKED) only for Flash-Next's 48 value heads, VPK kept for it; the peer-device kernels beside the templated moe_combine - mtp.cpp: formatting only Also: - generate.cpp: a qwen35moe model refuses --peer-device, --layer-split and --expert-cache-device1..3 at the start (their code carries Flash-Next's shapes) - sycl/src/prefill/kernels.dp.cpp: the prompt kernels take the shared header's new size parameters (they were separate overloads: an ambiguous call); any size but Flash-Next's stops with a message - the SYCL engine has Flash-Next's kernels only - QWEN36_ENGINE = 0.1.41 (this tree); docs/QWEN36.md names 0.1.41 among the engines without qwen35moe Built: CUDA (sm_89, with tests), HIP (gfx1100, gfx1201, gfx1030, gfx1151; and STRATA_HIP_GFX906 for gfx906) with ROCm 7.10 (TheRock wheels), SYCL (the strata-sycl-dev image, every target). Run on an RTX 4070 Ti only. Checked (RTX 4070 Ti, Linux, CUDA 13.0): Qwen3.6 UD-IQ4_XS = llama.cpp b11438 on 3 prompts + 8K (cache 3,000 slots); Ornith IQ4_XS as before; Qwen3.6 speed, small-card floor (4.15 GB free starts) and 256K as before; iq_parity and native_grouped_parity 0 failures; setup/MCP tests pass except test_setup_engine_hash.py's two (they fail the same way on main). Flash-Next Coder: prompts 1 and 2 identical to main's own build, prompt 0 alternates between the same two outputs in both; the 8K prompt varies from run to run in main's own build too (fixed cache, no adaptive swaps, no PCIe share, with and without MTP: 3 runs, 3 outputs), and the merged build's outputs are among main's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged main at fb58e0d (0.1.41) and resolved the conflicts (10 blocks: prefill.cpp, kernels.cu/.hpp, mtp.cpp); the branch merges cleanly again. Following the new AGENTS.md: built CUDA, HIP (gfx1100, gfx1201, gfx1030, gfx1151, and the gfx906 build) and SYCL (every target). The SYCL prompt kernels needed the shared header's new size parameters ( One thing found on the way, not from this PR: on main's own build (fb58e0d) the Coder's 8K prompt is not repeatable on my RTX 4070 Ti — three runs, three different outputs, with a fixed cache (900), |
|
@ohmgloss Yes: Unsloth's Qwen3.6-35B-A3B UD-Q4_K_XL (22.9 GB, with the MTP layer) runs. On an RTX 4070 Ti 12 GB it wrote 82 tok/s (62 with an 8 GB card's VRAM, emulated), against 105 for UD-IQ4_XS, with greedy tokens matching llama.cpp. For Ornith the only UD-Q4_K_XL I found (peculiar-ragdoll) has no MTP layer; bartowski's Q4_K_L has one and runs at 73 tok/s. I'll add both to setup in a separate PR once this one is settled. |
|
Could you add KAT-Coder-V2.5-Dev? |
|
On a GTX 1080 (Pascal, sm_61) with the experimental CUDA 12 engine (engine-cuda12, 0.1.41, toolkit 12), Qwen3.6-35B-A3B cannot start: strata serve: prompt chunk auto: 3328 tokens, a 268-slot ring The message is misleading. In src/prefill/prefill.cpp, Prefill::supports() returns false for a card below sm_75 when !g.has_indexer: // Without an indexer the prompt attention is the tensor-core kernel's dense mode: NVIDIA sm_75+ (elsewhere qwen35moe has no indexer, so on sm_61 this returns false; Prefill::init then reports "geometry differs from the artifact's", which is about the pack and sends you looking in the wrong place. The comment says the prompt is read through the decode windows below sm_75 — the only "elsewhere" in the code is Two asks:
Config and pack are not involved: --prefill 2048 instead of auto, and a rebuilt pack, both give the same failure. The engine version matches the source (0.1.41). Workaround for now: Qwen3.8-Flash-Next (kQwen4Exp path, no CC check) runs on this card. |
|
The emulated 8 GB rows made me curious how the real thing behaves, so here are numbers from an actual Box: RTX 2080 SUPER 8 GB (display card; ~2 GB desktop + a 350 MB co-resident process beside the Model: a community APEX-MTP build of Qwen3.6-35B-A3B with mixed per-layer expert quants Three-turn repo-review workload, same file both engines, llama.cpp b11438-era via LM Studio
131K ctx / int8 KV / 32K kv-resident fits the 8 GB card with ~420 expert slots (~40–50% decode hit Two findings for the PR:
Also confirmed on this card: SM75 MMVQ/prompt kernels run, the hyper-connection staged read |
- 16-bit K/V (no --kv, as setup writes at 8K) stopped at the first append with "qsa: kv_append: geometry is not set up": the size check required the indexer's sizes, which a model without one leaves at 0. Dense attention no longer needs them; Flash-Next keeps the same check. - strata serve on a card without the batched prompt path for the model (qwen35moe below sm_75, or HIP) failed with "prefill: geometry differs from the artifact's". It now says so at start and reads every prompt through the decode windows, as one-shot generate already did. - setup asks before the download on Pascal / Volta cards, as it does on AMD. Tested on Linux + RTX 4070 Ti + CUDA 13: 16-bit K/V at 8K gives llama.cpp's tokens on 4 prompts; the fallback with the sm_75 check raised in a scratch build (server: a 9,802-token prompt in 113 s, sensible answers). Not run on a real Pascal or AMD card. Flash-Next Coder tokens = main's; HIP and SYCL build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you both for the reports, and for the detail in them. Both bugs are fixed in 20f4d4b. @misthema: the 8K crash without @ohmgloss: A limitation: I don't have a pre-RTX 20 card or an AMD card. I tested the fallback on an RTX 4070 Ti with a scratch build that treats it as sm_61. The server started and answered correctly, and a 9,800-token prompt took 113 s, against 3.9 s on the batched path. That shows the code path works, but not that the decode kernels run on Pascal for this model, or how fast it is there. If you can retry on the GTX 1080, I'd be glad to hear how it goes. |
|
Follow-up with more numbers, plus a correction to my first comment. The correction first: my "no GPU kernels for Q6_K experts" note was against an older commit of this New box: the dual-GPU Pascal machine - Tesla P40 24 GB (headless) + GTX 1080 8 GB, i7-9700K
(With my Q5-converted file instead of the original, the PR's numbers are identical within noise on both Reading: decode wins everywhere we've measured - +50–65% on Pascal, +31–42% on the Turing card from Build notes for Pascal users, in case they're useful:
Thanks again. |
|
@ohmgloss @vehystrix Thanks for the requests. Both files now run on a separate branch while #1402 is reviewed (it isn't a pull request yet): https://github.com/aflin/Strata/tree/qwen35moe-more-models Setup installs them by name from that branch, checking each download against its SHA-256 from Hugging Face: (
The branch also covers the other Qwen3.6-35B-A3B files from Unsloth and bartowski. Results for every file, how they were tested, and the one that doesn't load are here: Limits: everything was tested on one PC (Linux, RTX 4070 Ti, CUDA 13). Nothing has been run on Windows, AMD, Intel, or cards older than the RTX 20 series. @ohmgloss, on the GTX 1080 these files would read prompts through the slower decode path, like the fix for your report, and nobody has tried that on a real Pascal card yet. Let me know if there are problems, and good luck. |
|
I just tested out the branch. Command used: It built the new engine, using my installed Visual Studio 2026 and Cuda 13.3, and then ran some tuning to get the optimal settings for my PC. I then pointed it at the Why that file? I needed something to fill up the context a bit to get reliable numbers. With this I got ~43k tokens of prompt. What I also had to do: For comparison, with the same model on llama.cpp I got ~15t/s generation and ~500t/s prefill... Running some agentic coding tasks with it now. If the model degrades into loops or something I'll let you know. |
|
I know this is not hard evidence, but after using the model for a while I noticed it was significantly worse than when running under llama.cpp It was missing tool calls left and right (it never actually got the edit tool call to work, and just decided to rewrite the whole file instead...) I never had any of this when running the model with llama.cpp, or at least not so blatant and often. Same sampling settings used, same chat template, same harness (omp). I'm happy to provide more info or run a controlled benchmark, if you tell me what to run. |
…ense attention) A qwen35moe model attends to every cell, so with KV streaming (--kv-resident, which setup writes from a 64K context) a selection past the resident cells names more blocks than the VRAM slots hold. The resolve kept its misses in a list of n_slots entries and wrote past its end; the blocks it could not place stayed claimed (page -2) for good, and the attention masked every non-resident cell. From 32K tokens on, the model lost about 40% of its context: on a 52.7K-token agentic transcript KL against llama.cpp was 0.57-1.16 (int8 and fp16), with wrong tool calls. - kv_stream.cu: the resolve claims at most n_slots misses; the rest go back to page -1 and count as read from RAM. ctl[3] (overflow) now means "some blocks were read in place", and the end-of-request line says so. - qsa_decode_attn.cu: a selected cell whose block is not resident is read from the layer's host copy in place (QsaAttnPools::host_*, set by qsa_attn_pools for a streamed layer) instead of masked. Flash-Next's indexer selections always fit the slots, so its path never takes it. - kv_stream_parity: a dense selection of 10,000 blocks over 4,828 slots, twice: bitwise the resident pool's output, nothing left claimed, overflow set (int8, fp16, q4_0, k8v4). Checked: KAT-Coder i-quality-v2 on an RTX 4070 Ti 12 GB, 64K context, against llama.cpp on the same file at 12.8K-52.7K tokens: KL 0.0006-0.009, top-1 98-100%, with --kv-resident 32768 in int8 and fp16, as without streaming. Flash-Next IQ3_XXS with --kv-resident 32768 on a 52.7K prompt: the same 143 tokens as the engine before this change (which itself differs from run to run at token 88). Built: CUDA, HIP (gfx1100, gfx1201, gfx1030, gfx1151), gfx906, SYCL. Speed: past the resident cells every token reads the rest over PCIe. 52.7K-token prompt, 64K context, no desktop on the card: 27.0 tok/s streamed vs 59.6 with the KV cache in VRAM (12 GB); 23.3 vs 47.0 (8 GB emulated). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…om 128K, asked otherwise Streamed, a qwen35moe model reads the KV cache past 32K tokens from RAM for every token. Measured with KAT-Coder i-quality-v2 (8-bit KV) on an RTX 4070 Ti 12 GB with no desktop, whole and with 4 GB held: - 52.7K-token prompt, 64K context: 59.6 / 47.0 tok/s with the KV cache in VRAM, 27.0 / 23.3 streamed; - 2K-token prompt: as fast or faster streamed (more expert slots); - 7.9 GB free: 128K and 256K fail at the first prompt unless streamed; 10 GB free: both run. So for qwen36 / ornith / their fine-tunes from a 64K context (not WSL): - under 9 GB of VRAM free (nvidia-smi memory.free, so a desktop counts) from 128K: streaming is turned on, and setup says why, what it costs and that --context 65536 avoids it; --kv-streaming off is kept with a warning that the first prompt fails; - otherwise setup explains the trade-off and asks, recommending no (--yes: no); - both say how to change it later (the config's args, or --kv-streaming on|off). --kv-streaming on|off answers it as before. Flash-Next is unchanged and never asked. Tests: tools/test_setup_qwen36.py KvStreaming (needed vs asked, free VRAM not the card's size, the option, Flash-Next) and FreeVram. test_setup_engine_hash fails as before this change (2 failures). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vehystrix - At least one problem with the --kv-resident has been solved. Please try it out and let me know if there are still problems. |
|
I've been using it for a day now, and haven't really noticed an improvement (I still kept I ran the same prompt trough strata and llama.cpp. Same sampling settings, same .gguf, fp16 on strata & q8_0 on llama.cpp for KV cache, same context size. Prompt was to build a html/css/js webapp, you know, the typical one-shot benchmark stuff. On the llama.cpp transcript I found 2 edit tool faillures, and both times it recovered like so: With every other edit call landing on the first try. I don't know what to make of this, but usage is definitely degraded. Second point I'd like to make: Strata config: {
"exe": "D:\\repos\\aflin-Strata\\engine\\strata.exe",
"args": [
"--pack",
"E:\\Strata\\packs\\kat-coder-i-quality-v2",
"--native",
"C:\\Users\\VEH\\.cache\\huggingface\\hub\\models--Myric--KAT-Coder-V2.5-Dev-MTP-APEX-GGUF\\snapshots\\e4ae9970325413d582ba2c294122fc5567ec1dd7\\KAT-Coder-V2.5-Dev-MTP-APEX-i-quality-v2.gguf",
"--expert-profile",
"D:\\repos\\aflin-Strata\\data\\expert-profile-qwen36.bin",
"--expert-cache",
"auto",
"--prefill",
"auto",
"--spec",
"4",
"--mtp",
"C:\\Users\\VEH\\.cache\\huggingface\\hub\\models--Myric--KAT-Coder-V2.5-Dev-MTP-APEX-GGUF\\snapshots\\e4ae9970325413d582ba2c294122fc5567ec1dd7\\KAT-Coder-V2.5-Dev-MTP-APEX-i-quality-v2.gguf",
"--mtp-draft-vocab",
"D:\\repos\\aflin-Strata\\data\\draft_vocab.bin",
"--max-context",
"262144",
"--kv",
"fp16",
"--kv-resident",
"0",
"--pcie-frac",
"0.00",
"--spec-min-p",
"0.30"
],
"cwd": "D:\\repos\\aflin-Strata",
"tokenizer": "E:\\Strata\\packs\\kat-coder-i-quality-v2\\tokenizer",
"model_name": "kat-coder-v2.5-dev-i-quality-v2",
"log": "D:\\repos\\aflin-Strata\\strata-kat-coder-i-quality-v2.log",
"lib_dirs": [
"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v13.3\\bin",
"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v13.3\\bin\\x64"
],
"port": 8080,
"gpu": 0,
"gpus_asked": true,
"open_browser": false,
"sampling": {
"temperature": 0.7,
"top_p": 0.8,
"top_k": 20,
"min_p": 0.0,
"presence_penalty": 1.5,
"repetition_penalty": 1.1
}
}llama.cpp config: |
|
@vehystrix - I ran ornith1.5 with claude code for a task that took about 140k tokens. I noticed the same problem with an edit tool call, and it worked around it. It also got itself into a loop, but broke out of it when interrupted and prompted, and successfully completed the task. |
Conflicts (8 blocks in 5 files), resolved keeping both sides: - mtp.cpp: upstream's route-resident adjustment (Flash-Next's 512 experts) runs first, then the GGUF-expert / blob branch (qwen35moe's draft layer). - verify.cpp: dense_attention_bytes and upstream's set_logit_bias, side by side. - qsa_decode_attn.cu: the attention kernel takes both the query-head count G (12 Flash-Next, 8 qwen35moe) and upstream's gfx906 QUERY_SWIZZLE / REDUCE12; the gfx906 launches pass G, and the REDUCE12 score path reads a non-resident cell from the host copy (load8r) like the other paths. - prefill.cpp: draft_kv's Flash-Next-only guard uses upstream's decline(); the CPU share keeps qwen35moe out (one_stream) and takes upstream's cpu_dead fallback, with the window's real expert count (KK). - generate.cpp: the verifier's dense-attention reserve beside upstream's split-aware draft binding; the "own buffers" line stays off where there is no prompt path, after upstream's lend-sizing report. Two follow-ups for qwen35moe: - STRATA_ROUTE_TAIL_SKIP's new CUDA default applies to Flash-Next only (g.has_indexer): it was measured on 10-of-512 routing; a qwen35moe model (8 of 256) keeps 0.1.41's answers until measured (STRATA_ROUTE_TAIL_SKIP=7 turns it on). docs/DETAILS.md says so. - setup: QWEN36_ENGINE = (0, 1, 42), this tree's version. Checked: CUDA build (all 264 targets); kv_stream / kv_hybrid / kv_q4 / kv_q8 / kv_steps / qsa parity pass; setup tests pass but test_setup_engine_hash (its 2 failures predate this); the 37 serve test modules pass. Ornith-1.5 IQ4_XS on an RTX 4070 Ti, greedy against the engine before the merge: a 300-token prompt the same 256 tokens; a 12.8K agentic prompt the same first 199 tokens (0.1.42's measured PCIe share moved more experts to the CPU here, 0.55 -> 0.30, which upstream documents can change answers slightly); no tail-skip line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
It's just that I know what to expect from the model from using it a lot in llama.cpp, and it just feels less capable running with strata. |
|
@vehystrix Thanks for the side-by-side runs. What I found:
|
The tensor-core prompt attention ran one block per (query, KV head). With Flash-Next's 2,051-cell selection that is fine, but in dense mode (qwen35moe) every block read the whole context on its own, so each prompt chunk got slower as the context grew: at 150K tokens it was 93% of the prompt with fp16 K/V. A new kernel for dense mode, 8 query heads per KV head, fp16 or int8 K/V, sm_80+: a block takes 8 neighbouring queries (64 MMA rows, no pad rows), so a K/V chunk is read once for all 8. The softmax stays in registers and the q.k fragments become p.v's A fragments. Accuracy is kept as before: q and p as FP16 hi + lo, int8 codes exact with their scales in FP32, each chunk's p.v added to the running sum in FP32. cp.async overlaps the next K with p.v and the next V with q.k. Flash-Next (12 heads per KV head), Turing, Volta, AMD and Q4_0/K8V4 K/V keep their kernels. STRATA_PROMPT_ATTN_DENSE=0 goes back to the old one. RTX 4070 Ti, KAT i-quality-v2, 150K-token transcript, 262K context: fp16 K/V 771 s -> 133 s, 8-bit 170 s -> 113 s (llama.cpp: 417 s). qsa_prompt_attn_parity: new dense checks pass (32K: fp16 5.7x, int8 1.6x, within 4.2e-6 of FP64 like the old kernel). Long-context KL against llama.cpp at 88K/118K/150K unchanged (fp16 <= 0.0036, top-1 584-585 of 585). Builds: CUDA, HIP (gfx1100/1201/1030/1151), gfx906, SYCL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vehystrix The prompt-speed fix is pushed. The prompt attention now handles 8 tokens per block, so each piece of the K/V cache is read once for all 8 instead of once per token. On a 150K-token agentic transcript (KAT i-quality-v2, 262K context, RTX 4070 Ti):
Accuracy is the same as before: against llama.cpp at 88K, 118K and 150K tokens, KL stays at 0.0036 or below and top-1 agreement is 584–585 of 585. It needs an RTX 30 or newer card with fp16 or 8-bit K/V; If you get a chance, I'd like to know how it does at 100K+ on your setup. |
|
@aflin Thanks for the long-context accuracy numbers — that's solid. Regarding What I changed (3 files, no engine recompile):
Verification: 3-message session (user / assistant+reasoning / user). Compare The diff is ready if you want to review it. Happy to submit as a separate PR or include it here.
|
|
Here's the diff: diff --git a/serve/frontend.py b/serve/frontend.py
index 1dccb91..ce61280 100644
--- a/serve/frontend.py
+++ b/serve/frontend.py
@@ -66,9 +66,9 @@ class ChatTemplate:
def _detect_caps(self) -> dict[str, bool]:
"""llama.cpp's capability names, checked at load time against this template and Strata's tool-call format.
These are rendering hints, not a guarantee that the model will follow a request."""
- def render(messages, tools=None):
+ def render(messages, tools=None, **kwargs):
try:
- return self.render(messages, tools=tools)
+ return self.render(messages, tools=tools, **kwargs)
except Exception as exc: # noqa: BLE001 - a hint only: never stop the server for a probe
# A template may reject a role or feature (a custom one may fail in any way): the feature is off,
# discovery still answers for the others, and the start-up goes on.
@@ -108,7 +108,7 @@ class ChatTemplate:
"supports_system_role": "strata_caps_system" in render(
[{"role": "system", "content": "strata_caps_system"}, user]),
"supports_parallel_tool_calls": calls_supported(2),
- "supports_preserve_reasoning": "strata_caps_reasoning" in render(history)}
+ "supports_preserve_reasoning": "strata_caps_reasoning" in render(history, preserve_thinking=True)}
# ------------------------------------------------------------------------------------------------ requests
@@ -393,6 +393,8 @@ def openai_to_messages(req: dict) -> tuple[list[dict], list[dict] | None, dict]:
kwargs = {"enable_thinking": False}
elif k == "reasoning_effort" and "enable_thinking" not in kwargs:
kwargs.update(effort_kwargs(v))
+ elif k == "preserve_thinking":
+ kwargs["preserve_thinking"] = bool(v)
return _late_system_to_user(messages), tools, kwargs
diff --git a/serve/server.py b/serve/server.py
index 553f440..69650b8 100644
--- a/serve/server.py
+++ b/serve/server.py
@@ -2971,6 +2971,12 @@ class Service:
req["thinking"] = {"type": "disabled"}
else:
req["output_config"] = {"effort": effort}
+ if api == "openai" and "preserve_thinking" in s:
+ # No request-level flag: fill it from the shared Chat settings so the launcher's switch reaches the template
+ # (openai_to_messages reads chat_template_kwargs; the request's own value always wins).
+ ctk = req.get("chat_template_kwargs") if isinstance(req.get("chat_template_kwargs"), dict) else {}
+ if "preserve_thinking" not in ctk:
+ req["chat_template_kwargs"] = {**ctk, "preserve_thinking": bool(s["preserve_thinking"])}
return req
def start_telemetry(self):
@@ -5544,6 +5550,9 @@ def clean_shared_defaults(d) -> dict:
elif key == "experimental_speed_projection":
if not isinstance(value, bool):
raise ValueError("experimental_speed_projection: true or false")
+ elif key == "preserve_thinking":
+ if not isinstance(value, bool):
+ raise ValueError("preserve_thinking: true or false")
else:
raise ValueError(f"unknown setting {key!r}")
out[key] = float(value) if key in ("temperature", "top_p") else value
diff --git a/serve/web/app.js b/serve/web/app.js
index 69735fc..8c82072 100644
--- a/serve/web/app.js
+++ b/serve/web/app.js
@@ -739,9 +739,10 @@ function assistantMessages(m) {
// #1392: a turn with no answer text (only reasoning, a stop before the first content token, or an error) still goes
// back as an assistant turn, so the history keeps alternating and a reasoning model sees that it already answered.
// The server leaves such a turn out of the prompt itself (serve/frontend.py, #843); reasoning_content rides along.
+ // The reasoning rides along on every turn too, so a server set to preserve_thinking keeps earlier thinking in the prompt.
if (!ran.length) {
const msg = {role: "assistant", content: m.text || ""};
- if (!m.text && m.reasoning) msg.reasoning_content = m.reasoning;
+ if (m.reasoning) msg.reasoning_content = m.reasoning;
return [msg];
}
const out = []; |
|
Note that the Prefill speed is massively improved. I'm running it trough some more workloads now. |
|
Vision works on qwen35moe with zero engine changes (stock The PR gates vision off for the qwen36/ornith families ( Setup (Windows 11, RTX 5070 Ti sm_120, CUDA 13.3):
"vision": {
"exe": ".../engine/strata-vision.exe",
"mmproj": ".../mmproj-Ornith-1.5-35B-A3B-Heretic-MTP-BF16.gguf",
"model": ".../Ornith-1.5-35B-A3B-Heretic-MTP-APEX-I-Quality.gguf",
"gpu": true,
"max_tokens": 1024
}Results
Caveats One GPU (sm_120), one model file, one test image, GPU encoder only (CPU mode untested). I have not tried Qwen3.6-35B-A3B's own Unsloth files — the family already points |
…ngs or the config Qwen3.6's, Ornith's and KAT-Coder's chat templates leave out the thinking of turns before the latest user message unless preserve_thinking is set; Strata never passed it. Now: - a request's chat_template_kwargs.preserve_thinking reaches the template (OpenAI, and the Anthropic API as a Strata extension, so Claude Code's thinking blocks can stay in the prompt); - "preserve_thinking" in the shared Chat settings (POST /settings) or in strata-<model>.json fills it for requests that leave it out (llama.cpp's --reasoning-preserve); the request wins, then the settings, then the config; - /props reports supports_preserve_reasoning when the template keeps it on request; - the web app sends reasoning_content back on every turn. Off by default: nothing changes unless it is set. Checked with the Ornith template on the mock engine: a 3-message history with earlier reasoning is 33 prompt tokens without it, 114 with it, on both APIs. Based on bizhonggeng's patch in Niko1221#1402 (OpenAI path), extended to the Anthropic API and the config. Co-authored-by: bizhonggeng <30041982+bizhonggeng@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock image encoder (tools/vision) reads Qwen3.6's and Ornith's mmproj as is, and the engine's --vision path needed no change (moondrop12138, Niko1221#1402). Setup now asks about images for these families as it does for Flash-Next (default no; --vision yes|gpu|cpu|no answers it): - Qwen3.6: bartowski's mmproj-Qwen_Qwen3.6-35B-A3B-bf16.gguf (Unsloth's is named mmproj-BF16.gguf), its repo pinned like the others; Ornith: bartowski's mmproj-Ornith-1.5-35B-A3B-bf16.gguf; - the KV-streaming advice counts the encoder's VRAM (1,254 MiB measured, plus the 700 MiB reserve) out of the free VRAM it decides on; - the "--vision cpu reads prompts ~3x faster" tip (Niko1221#469, Flash-Next) is not given for these models: Ornith read a 24K-token prompt at ~4,130 tok/s with or without the GPU encoder. Measured on an RTX 4070 Ti, 32K context: Ornith IQ4_XS and KAT i-quality-v2 (with Qwen3.6's mmproj) described a test picture right over both APIs; with the GPU encoder Ornith wrote 135 vs 143 tok/s (mean of 12 replies). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, both of you. Both additions are in the branch now ( @bizhonggeng: keeping earlier reasoning. Your patch worked as described on the OpenAI API. Claude Code and other Anthropic-API clients didn't get it, so I extended it (you're credited as co-author):
Checked with Ornith's template: a 3-message history with earlier reasoning is 33 prompt tokens without it and 114 with it, on both APIs. @vehystrix you're right that this is the template's choice. With the froggeric template it was already on for you, so it doesn't explain the difference you saw. It matters for the default Qwen3.6, Ornith and KAT templates, which leave the reasoning out unless asked. Glad the prompt speed is better. @moondrop12138: images. Confirmed here, and setup now offers images for Qwen3.6, Ornith, bartowski's Qwen3.6 and KAT-Coder. Setup asks, the default is no, and
Not tested: Qwen3.6's own files with images, AMD. Let me know if there are problems. |
Conflicts: the route tail skip's default (src/program/generate.cpp, docs/DETAILS.md). Upstream's new rule is kept (off from 90% of the experts cached, on for HIP gfx1200/gfx1201) with this branch's exception: qwen35moe keeps it off unless STRATA_ROUTE_TAIL_SKIP=7. Also: 0.1.43's adaptive overlap (on by default on Linux CUDA) is not the default for qwen35moe. Ornith IQ4_XS on an RTX 4070 Ti, 6 prompts x 500 tokens, 2 starts: 128 tok/s with it, 144 without (the same as before the merge); STRATA_ADAPT_OVERLAP=1 turns it on. QWEN36_ENGINE follows the source version (0.1.43). Built: CUDA sm_89, HIP gfx1100/gfx1201/gfx1030/gfx1151 and gfx906, SYCL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged upstream 0.1.43. The two conflicts were both the route tail skip's default; qwen35moe still keeps it off unless One change beyond the conflicts: 0.1.43 turns adaptive overlap on by default on Linux CUDA, and on qwen35moe that was slower. Ornith IQ4_XS on an RTX 4070 Ti, the same 6 prompts of 500 tokens over 2 starts: 128 tok/s with it, 144 without. So it stays off by default for qwen35moe; Built for CUDA, HIP (gfx1100/1201/1030/1151, gfx906) and SYCL. |
|
I've been using it for a while now today. Getting it to properly use the edit tool still fails, I'll tell it not to rewrite the entire file and it'll make a python script to rewrite parts of it instead... What I also found: |
Update — my earlier i-quant hypothesis does not hold up; it looks like the engine version (and the sampling default), not the expert format.Some follow-up of my own, on the latest head (cd1daa4, 0.1.43): What I ran. UD-IQ3_S (i-quant) and UD-Q3_K_M (K-quant), Qwen3.6-35B-A3B, single-turn long context at ~65k, same prompt, same harness (pi code). Result. Neither model looped. No length truncation, no repetition. The thinking stays short and the turns stop normally. Why this matters for the hypothesis. The one common factor behind the loops I reported earlier was not i-quant — it was greedy decoding. Strata's default for an API client that sends no sampling is temperature 0 (serve/server.py:596; the engine spells it out: "Absent keys keep today's behavior: greedy, no penalties"), and setup writes no sampling block, so a code agent that sets nothing runs greedy. With greedy, both i-quant and K-quant loop for me; with temperature 0.6, neither does. That is the opposite of "i-quant is the fragile format". What I cannot separate yet. I changed the engine version and the sampling at the same time, so I cannot tell how much is the version. A clean disentangle would be: latest engine + greedy (should still loop) vs the previous engine + temperature 0.6. If the old engine with sampling is clean, then this is purely sampling; if it still loops, the version fixed something else as well. I'd run that, but I'd rather not over-claim before doing it. On @vehystrix's report just above (same sampling settings, still degraded): those look like two different things — the edit tool failing on its own anchored-edit input format, and prefill dropping from >2,000 to ~150 t/s past 100k — neither is the repetition loop, and that run was on an older engine. If anything, that pushes me toward "the newer engine addressed the loop", not "it is all sampling". Either way, one thing seems worth fixing upstream: the default is greedy for any client that sends no sampling, and setup writes no sampling block. Every code agent that connects and sets nothing lands there. A sampling block in the generated config (or a note at first start) would avoid the whole class of reports. _
_ |
|
Closing this pull request. Thank you all for the testing, reports and suggestions here, and for the patience while it grew: @vehystrix, @ohmgloss, @bizhonggeng, @misthema and @moondrop12138. Thanks also to @Asofwar, whose decode-path and router speedups I've taken. And thanks to @Niko1221 for Strata itself. I've moved this work to a personal fork called MoMoE ("More MoEs"): https://github.com/aflin/momoe. It isn't meant to compete with Strata. It's my own variant, shaped around how I use it, and almost everything that makes it work is still Strata's. The README credits the project and the people whose changes it uses. What's different:
Let me know if there are problems, and good luck with Strata. |
u are welcome) |
Related: #824 (perronemirko's Qwen3.6 engine, closed by the history rewrite). That PR adds a separate
strata-qwen36engine beside the main one; this one teaches the main engine the model instead, so the server, setup,the batched prompt path, MTP and the expert cache all apply to it, and the engine picks the model from the GGUF.
Summary
Qwen3.6-35B-A3B is the same family as Flash-Next (DeltaNet layers, full attention every fourth layer, a MoE with a
shared expert) at 35B parameters: its experts take 12-14 GB of RAM instead of 23-50 GB. It is the model for PCs with
16-32 GB of RAM and 6-12 GB cards. Ornith-1.5-35B-A3B (ornith-ai, MIT) is a coding-agent fine-tune of it with the same
architecture.
The engine reads
general.architectureand runsqwen35moebesideqwen4expon one code path. Nothing on thecommand line says which model it is. Flash-Next's arithmetic is unchanged (see "How it was checked").
Measured on Linux (Debian 12), RTX 4070 Ti 12 GB, Ryzen 9 5950X, 62 GB RAM, CUDA 13.0; 32K context, 8-bit K/V, MTP on.
"8 GB" is the same card with 4 GiB held by another process (emulated, not a real 8 GB card).
--fit on)Greedy tokens match llama.cpp b11438 exactly on UD-IQ4_XS (three short prompts and the 8K prompt, 32 tokens each,
expert cache at 3,000 slots). With a different cache size a near-tie can flip, because experts computed on the GPU
round slightly differently from the CPU's (seen once: llama.cpp's top two at 28% and 26%). More numbers (UD-IQ3_S,
Ornith, small cards, 256K) are in
docs/QWEN36.md.What changed
Engine: model geometry (
include/strata/core/layout.hpp,src/core/layout.cpp,gguf_reader.hpp)ModelGeometrygains the architecture, experts per token, and whether the model has PLE, the sparse-attentionindexer and hyper-connections;
geometry_from_gguffills it and checks that each size fits the kernels' limits(all of Qwen3.6's are at or below Flash-Next's, which stay as compile-time capacities).
Engine: decode (
src/core/verify.cpp,src/kernels/cuda/*.cu,src/kernels/cpu/*,src/core/expert_source.cpp)silu(z)DeltaNetoutput gate, and dense attention: every cached cell is attended (
qsa_select_all), with no indexer projections,snapshots or replay.
CPU pool and expert layout take the model's widths.
iq_row_bytescovers Q2_K (without it theMTP layer's Q2_K experts read as garbage).
Engine: prompt path (
src/prefill/prefill.cpp,kernels.cu)a dense mode for the tensor-core prompt attention. NVIDIA sm_75+; elsewhere (
Prefill::supports) the prompt is readthrough the decode windows.
Engine: MTP (
src/core/mtp.cpp)blk.40(eh_projsplit into its embedding and hidden halves,native projections and experts). No separate download.
Engine: VRAM on small cards (
src/program/generate.cpp,Verifier::dense_attention_bytes)allocated after the cache: without this a 256K context failed on every card.
instantiation at 8K, 32K and 64K, 450 started) and stays 300 for Flash-Next.
the prompt buffers.
--draft-vocab en, from3.9 GB.
Data
data/expert-profile-qwen36.bin: 40 x 256 experts ranked by routing counts from 24 varied prompts (server--expert-profile-savewith a 64-slot cache). Against an unranked profile, on prompts not among the 24 (threeruns): 12 GB short-chat writes 99 -> 106 tok/s, 8K writes 84 -> 93 tok/s; no difference at 8 GB.
Setup (
setup.py)qwen36(unsloth/Qwen3.6-35B-A3B-MTP-GGUF: UD-IQ4_XS, UD-IQ3_S) andornith(bartowski/Ornith-1.5-35B-A3B-GGUF: IQ4_XS, IQ3_XXS), pinned revisions and SHA-256. ModelScope serves the same four
files with the same SHA-256.
size and an 8K context (a recommendation, never forced).
looks for the support in the engine file itself and compiles the engine when a ready-made one lacks it. Flash-Next
keeps the ready-made engine.
tools/test_setup_qwen36.py,tools/test_setup_ornith.py; updatedtest_setup_choices.py,test_setup_unsloth.py.Tools
tools/iq_pack.pyleaves the MTP layer out of the expert table;tools/make_profile.py --n-layer;tools/iq_fixture.pyadds Q2_K and Q4_0;tools/strata_mcp.pyrecommend()follows setup's rules.Docs
docs/QWEN36.md(setup, what fits, small cards, speed, against llama.cpp, Ornith, differences, limits);sections in
docs/MODELS.mdandREADME.md.What it leaves alone
instance is a template next to the existing one, and Flash-Next takes the same instance as before. The setup
families, sizes and defaults for Flash-Next, Swift, the Coder and Unsloth are unchanged. Two things a Flash-Next user
can see: setup's "less than 12 GB of VRAM" warning adds a pointer to
--family qwen36when no family was named, anda start whose automatic expert cache gets no slot at all now stops there with the existing "no VRAM is left" message
instead of going on to fail at the prompt buffers.
stop with a message for any other size (
sycl/src/prefill/kernels.dp.cpp).--peer-device,--layer-splitand--expert-cache-device1..3at thestart; those paths carry Flash-Next's shapes and were not run with it.
share (
STRATA_PREFILL_CPU_SHARE), the chunked GDN recurrence (STRATA_GDN_CHUNKED), the query branch ofSTRATA_DF_BRANCHand the routing debug hook (STRATA_DBG_FEAT).How it was checked
STRATA_HIP_GFX906build for gfx906, with ROCm 7.10 from AMD's TheRock wheels) and SYCL (thestrata-sycl-devimage,every target). Only the CUDA build was run; there is no AMD or Intel card here.
short prompts give main's tokens (prompts 1 and 2 identical; prompt 0 alternates between the same two outputs in
both builds). The 8K prompt is not repeatable on main itself on this PC (with
--adapt-every 0 --pcie-frac 0, withand without the MTP layer, with
STRATA_STAGER_SLEEP=0,STRATA_STAGE_PIN=0orSTRATA_PREFILL_STREAM_AHEAD=0:three runs gave three different outputs, while 0.1.40.2 repeated exactly), so it cannot be compared byte for byte;
the merged build's outputs were among main's. I can open a separate issue with the numbers if that is useful.
floor and 256K as measured before the merge.
iq_parity(with and withoutSTRATA_PARITY_IQ_MMVQ) andnative_grouped_parity0 failures; setup, MCPand profile tests pass except
tools/test_setup_engine_hash.py's two, which fail the same way on main.Extra Notes
NVIDIA-only (elsewhere prompts are read through the decode windows), and setup's compile path for them is untested
on Windows. Test requests for those could go in docs/TEST_REQUESTS.md.
copy of the layer did no better, so it is the fine-tune's draft layer, not its quantization.
Squash or rebase as you prefer.
this one is settled (one change per pull request).
🤖 Generated with Claude Code