Compare commits

...
559 Commits
Author SHA1 Message Date
Niko1221 82cf9b83c8 docs: the cluster decode kernels run on sm_90+ (RTX 50), not RTX 40 2026-10-02 18:24:22 +02:00
Niko1221 2aa6ace036 docs: 0.1.36's speed (fused Q2_0 prompt experts, cluster decode kernels) and their switches 2026-10-02 18:24:15 +02:00
Niko1221 fc21e54214 Merge the 0.1.36 speed work: fused Q2_0 prompt experts on by default (STRATA_PF_FUSED=0 off), the native IQ packs' fused kernels opt-in (=1), cluster decode kernels; the fused test waits for its uploads
The ring rule: 1024 slots for the Q2_0 pack's fused experts (P3), 512 for native packs with fused layers.
Quality (teacher-forced vs the FP16 path, Q2_0): fused as close as MMQ at 8K, closer at 32K (phaseA-tests s18-tf).
2026-10-02 16:31:42 +02:00
Niko1221 9d37a3ad5f Merge branch 'f-136-fused' into rel-0.1.36 2026-10-02 16:30:40 +02:00
Niko1221 60445b58c0 #136: the fused int8 prompt experts for the native IQ packs (gate/up IQ2_XXS IQ2_XS IQ2_S IQ3_XXS IQ3_S IQ4_XS, down Q2_0 IQ4_NL; opt-in STRATA_PF_FUSED=1)
src/prefill/moe_fused_iq.cu: the Q2_0 path's design (moe_fused.cu: the grouping on the GPU, a pointer per expert -
cache slot or ring slot, no gather -, SwiGLU and H's int8 rounding in the gate/up epilogue) for the raw GGUF blobs of
the native packs.  The load stage decodes the i-quant blocks to int8 in shared memory (llama.cpp's codebooks and sign
tables, held in shared memory; a 32-value sub-block per thread per 64 values of K, read one stage ahead with 16-bit
loads and two 256-value blocks ahead into L2), with each sub-block's scale (per 16 values for IQ2_XS / IQ2_S, which
multiply at m16n8k16).  The activations are int8 per 32 values in natural order (quantize_act_native); fragments by
ldmatrix; the magic bias 1.5 * 2^23 enters as the mma's C, so a dot becomes a float with one add.  A work item is 256
weight rows x 64 routed rows, or 128 x 128 when the experts have 56-112 rows each (STRATA_PF_FUSED_TILE=64|128).

Wiring (prefill.cpp): a native layer takes these kernels when both its formats are covered, else MMQ (IQ1_M keeps the
FP16 path); the 512-slot ring of the fused path now also for a native pack with such layers.  sm_75, HIP and the
default (no variable) are unchanged: on IQ2_XS, IQ3_XXS and IQ3_S the default output is 0.1.35's (256/256 greedy
tokens, 4K and 32K).

tests/cuda/prefill_fused_iq_test (exit 77 below sm_80), RTX 5070, 256 tokens x 64 experts against a double-precision
reference from ggml's to_float, rel RMS / worst row - MMQ and fused equal to four digits: IQ2_S/Q2_0 1.489e-2 /
2.93e-2 (fused vs MMQ 2.6e-4), IQ2_XXS/Q2_0 1.464e-2 / 2.96e-2 (1.9e-4), IQ2_XS/IQ4_NL 1.465e-2 / 2.41e-2 (3.5e-5),
IQ3_XXS/IQ4_NL 1.458e-2 / 2.73e-2 (1.6e-4), IQ3_S/IQ4_NL 1.470e-2 / 2.65e-2 (6.5e-5), IQ4_XS/Q2_0 1.446e-2 /
2.72e-2 (4.4e-5); both tile shapes.  One layer (512 experts, top 10), ms, MMQ path -> fused at 2048 / 3584 / 8192
tokens: IQ2_S 10.5 -> 5.2, 12.5 -> 9.3, 21.4 -> 17.5; IQ2_XXS 8.8 -> 4.2, 10.7 -> 7.6, 17.9 -> 13.6; IQ3_S 10.6 -> 5.6,
12.3 -> 8.8, 19.8 -> 18.4; IQ3_XXS 10.2 -> 5.1, 11.9 -> 8.3, 19.1 -> 16.5.  MMQ's products alone (no gathers) at
3584 / 8192: IQ2_S 8.9 / 17.9, IQ3_S 8.1 / 15.6: the kernels are about even with MMQ's, the gain is the path around them.
(The test syncs the device after its uploads: a cudaMemcpy from pageable memory on the legacy stream can still be
queued on Windows while a non-blocking stream runs - the grouping then read zeroed ids, every pair on expert 0.
prefill_fused_moe_test's timing part has the same pattern.)

End to end, RTX 5070, speed_135.py's settings, greedy, A (MMQ) / B (STRATA_PF_FUSED=1) alternating, 2 pairs, prompt
tok/s: IQ2_XS 4K 1,228 -> 1,378 (+12%; a cold first A run of 590 left out), 32K 2,107 -> 2,170 (+3.0%); IQ3_XXS 4K
1,001 -> 1,020 (+1.8%), 32K 1,745 -> 1,739 (-0.4%); IQ3_S 4K 927 -> 925 (-0.2%), 32K 1,382 -> 1,362 (-1.4%).  The IQ3
packs' prompts here wait on the host copies of their unpinned experts (IQ3_XXS 4K: 5,062 of 22,573 streamed, ~3.0 s
of 3.4 s), so faster kernels change little.  Each arm repeats itself (256/256; IQ3_XXS 32K B and IQ3_S 32K A split at
162 / 223, the decode's timing); A vs B greedy agreement, identical prefix of 256: IQ2_XS 204 / 88, IQ3_XXS 182 / 89,
IQ3_S 31 / 256.
2026-10-02 16:25:57 +02:00
Niko1221 d05d26e9c3 Decode on thread-block clusters (sm_90+): the QSA top-k and the greedy argmax, the same ids and tokens
- qsa_block_topk: calls of up to 16 queries (decode windows) run on a cluster of 8 CTAs per query
  (block_topk_cluster_kernel): keys in shared memory, digit histograms pushed through distributed shared memory,
  one cluster barrier per radix pass, a parallel digit search, cluster-wide offsets for the ordered emit. The
  threshold and the cut at it are the one-CTA kernels' (same tie rule, NaN keys 0). STRATA_QSA_CLUSTER=0: off.
  RTX 5070 per call: 21.9 -> 15.6 us at 32K, 58 -> 18 at 128K, 200 -> 22 at 262K.
- sample_tokens greedy without penalties: a cluster of 8 CTAs per row (sampler_greedy_cluster_kernel), four loads
  in flight per thread, results merged in CTA 0 (lowest index on ties, NaN never taken, 0 when none).
  STRATA_ARGMAX_MULTI=0: off. 39.6 -> 5.9 us per call at 248,320 logits.
- Both launch through cudaLaunchKernelEx with the cluster attribute (captured in the decode graphs) only where
  they run: CUDA, compute capability 9+ (STRATA_EMULATE_CC respected), the kernel's own PTX/SASS at sm_90+ (an
  older build's JIT copy is a trap), room for one cluster - checked once per device. HIP and sm_75/86/89 keep the
  one-CTA kernels.
- decode_cluster_parity (ctest; skips below sm_90): bitwise vs the one-CTA dispatch and the reference kernel over
  434 top-k cases (contexts 7..262,144, every tail weight, 1-16 queries, ties / NaN / +-inf / -0 / denormal / all
  NaN) and 216 argmax cases, plus a CUDA graph capturing both (first calls inside the capture); --bench timings.
2026-10-02 14:25:26 +02:00
Niko1221 34d4178952 #136: the fused experts take a 1024-slot streamed ring by default (two layers' experts)
With P3's smaller buffers the 1024-slot ring fits beside an 8192-token chunk at 32K (borrowed 3,153 of 3,690 slots,
under the 90% cap).  RTX 5070, Q2_0 (cfg-q2rel), --prefill auto, prompt tok/s at 512 / 1024 slots (2 alternating
pairs): 4K 1,512 / 1,605, 32K 2,485 / 2,660, 128K (KV streaming) 2,346 / 2,392 - there the chunk falls from 8192 to
6144 and is still faster.  Against MMQ (one more pair on this build): 4K 1,299 -> 1,609 (+23.9%), 32K 2,187 -> 2,656
(+21.5%), 128K 2,128 -> 2,454 (+15.3%).  The ring does not change the numbers (the same output as at 512 slots).
Without STRATA_PF_FUSED nothing changes (384 / 96 slots, cap 512).
2026-10-02 14:04:59 +02:00
Niko1221 30e5ba4a31 #136 P3: with the fused experts the prompt path's MoE buffers hold only what the fused kernels need
In a layout whose chunks run the fused experts (STRATA_PF_FUSED=1, the Q2_0 pack, a streamed chunk) GU holds the
grouping tables, H the int8 H rows and Xq the per-token int8 activations, and Hq nothing - instead of MMQ's FP32 GU / H
and per-slot q8_1 rows: ~100 KB a token less in the MoE set, which then no longer sets the shared region's size (the
GDN set does).  A last chunk below stream_all_min() still runs MMQ in the same buffers, so each keeps MMQ's size for
stream_all_min() - 1 tokens.  The ring may grow to 1024 slots with the fused experts (STRATA_PREFILL_RING; the default
stays 512).  Without STRATA_PF_FUSED nothing changes: the same buffers, ring cap (512) and events.

The fused walk now counts the experts it reads from the cache in experts_resident (it stayed 0): every resident expert
of the layer, routed or not - at a streamed chunk's size nearly all are.

RTX 5070, Q2_0 (cfg-q2rel), --prefill auto, 2 alternating pairs, prompt tok/s (borrowed slots):
  4K:   MMQ 1,305 (1621), fused 1,517 (1749), fused + P3 1,512 (1534), + 1024-slot ring 1,605 (2046)
  32K:  MMQ 2,177 (3071), fused 2,453 (3199), fused + P3 2,485 (2641), + 1024-slot ring 2,660 (3153)
  128K (--max-context 131072 --kv-resident 32768): MMQ 2,108 (chunk 8192), fused 2,140 (6144), fused + P3 2,346
        (8192), + 1024-slot ring 2,392 (6144)
A 16K chunk still does not fit the 12 GB card: at 8192 tokens the loan is 2,641 of 3,690 slots, ~2.1K of them per
8K tokens.
2026-10-02 13:43:19 +02:00
Niko1221 6f66917530 #136: the fused experts take a 512-slot streamed ring (a whole layer's experts) by default
The fused path launches on batches of a layer's streamed experts, so with the 384-slot ring a layer's last batch
waited for slots its own first batch frees.  With STRATA_PF_FUSED=1 (and the canonical Q2_0 layout) the pinned-RAM
ring is 512 slots; without it nothing changes (384 / 96 as before; STRATA_PREFILL_RING still overrides).

RTX 5070, Q2_0 (cfg-q2rel), greedy, 3 alternating pairs, prompt tok/s: 4K 1,275 -> 1,516 (+18.9%), 32K 2,176 ->
2,422 (+11.3%); at 384 slots the fused path gave +4-7% / +3%.  1024 slots: 4K +24-27%, but the 32K chunk falls
from 8192 to 6144 (+6%).  The default path's output is identical to 0.1.35's on both prompts.
2026-10-02 12:43:01 +02:00
Niko1221 47de4a4d83 #136: fused int8 tensor-core experts for the Q2_0 pack's prompt path, grouped on the GPU (opt-in STRATA_PF_FUSED=1)
Strata's own kernels (src/prefill/moe_fused.cu): the layer's input rounded to int8 once per token (a scale per 32
values), the (token, k) pairs grouped by expert on the GPU (counts, offsets, a 64-row tile list - no host sync),
then per batch of up to 128 experts one gate/up launch (mma.sync m16n8k32 s8, the 2-bit codes unpacked in
registers, Q2_0's offset folded into the activation blocks' sums, SwiGLU and H's int8 rounding in the epilogue)
and one down launch into the per-slot rows the existing combine reads.  Experts are read where they are (cache
slot or streamed ring slot, a pointer per expert): no gather.

Used only on the streamed walk (chunks from stream_all_min()) of the canonical Q2_0 layout on sm_80+ CUDA, with
STRATA_PF_FUSED=1; it reuses the MMQ buffers (Xq, H, GU).  Everything else - the default, native/IQ packs, small
chunks, sm_75, HIP - runs the MMQ path unchanged (the old block is only wrapped in the else branch).

tests/cuda/prefill_fused_moe_test (exit 77 below sm_80), RTX 5070: vs a double-precision reference, MMQ rel RMS
1.144e-2 (worst row 4.15e-2), fused 1.144e-2 (4.15e-2); fused vs MMQ 9.0e-5.  One layer at 2048 tokens x top 10
over 512 experts: MMQ path 11.12 ms, fused 3.13 ms.
2026-10-02 12:05:46 +02:00
Niko1221 30875ed297 Engine 0.1.36: a cancelled prompt logged as read so far (#471), the draft-head hint (#474), UPDATE.bat (#475), --expert-profile-save (#477); setup requires it 2026-10-02 11:17:13 +02:00
Niko1221 31da8f5dca Opt-in: keep what the expert cache learned across restarts (--expert-profile-save, #477)
`--expert-profile-save P` (--serve) writes the adaptive tier's learning as a profile in tools/make_profile.py's
format, byte for byte (read_expert_profile reads it back): every pair ranked - the experts resident in VRAM now
first, then the routing counted since the start (the decayed usage summed at each adapt step, before its decay),
then the profile the engine started from. On QUIT / stdin EOF and every --expert-profile-save-every minutes
(default 10, 0 = exit only) between requests, through <P>.tmp and a rename. Without the flag nothing is counted or
written: `heat` stays empty and every added loop is over it.

Server: "expert_profile_save" (and "expert_profile_save_every") in the config pass the flags, and the next start
begins from the saved file instead of the config's --expert-profile when its header matches that profile's
layers x experts. tests/core/expert_profile_save_test.cpp (CPU only) checks the order and the bytes.
2026-10-02 11:16:16 +02:00
Niko1221 4ba7c94d4d UPDATE.bat / update.sh: update Strata without starting the model (#475)
In a git clone they run `git pull --ff-only` (a zip copy is told to download the new zip), then `setup.py --update`:
what a plain start does to an install before it starts the model - the Python packages, the ready-made engine when
MIN_ENGINE is newer (a compiled one when its source changed), each model's config upgrades and its draft subset, the
Windows AMD runtime - and nothing else: no question, no model files, no start. Both scripts run from one parsed
block, since the pull can rewrite the running script. Documented in docs/INSTALL.md (Updating) and the README.
2026-10-02 11:06:51 +02:00
Niko1221 c472ff04c8 "The draft head does not fit" says what to change (#474)
Engine: when the MTP draft head's subset does not fit, it prints what the head needs, the VRAM free, and the smaller
shipped subsets (--draft-vocab cyrillic / en, their MiB from the head's real row size) with how to pick one
(START-HERE.bat / ./setup.sh --draft-vocab en, kept in the model's config). Text only, on the failure path.
Server: a start that ended that way carries the engine's hint (or the same advice for an older engine) in its
"the engine exited before it was ready" error. Setup: a card under 14 GB with the default subset gets a note
recommending --draft-vocab en; nothing is changed, an explicit or saved choice gets no note, and the golden
configs are unchanged.
2026-10-02 11:04:05 +02:00
Niko1221 512ae42f7c A prompt cancelled mid-read is logged and recorded with the tokens it read (#471)
The engine's `strata serve: prompt ...` line now says "= 0 reused + 12288 of 98179 read in ... (cancelled)" with the
rate from the tokens read, and DONE gets a 15th field: the prompt tokens actually read. The server's /metrics history
and totals count reused + read for a cancelled read (prompt_total keeps the whole prompt); an older engine's DONE line
(no such field) is read as before.
2026-10-02 11:01:07 +02:00
Niko1221 5b734e427c Engine 0.1.35: Windows AMD uses its bundled HIP runtime (#468 #461), the low-RAM resident mode on Windows 32 GB PCs (#467), fixes #460 #459 #446 #447 #457 #448 #444; setup requires it 2026-10-02 09:44:21 +02:00
Niko1221 82d7e82926 Merge branch 'f-135-small' into rel-0.1.35 2026-10-02 09:41:57 +02:00
Niko1221 772c9330e9 Merge branch 'f-135-eng' into rel-0.1.35 2026-10-02 09:41:57 +02:00
Niko1221 6f8536aed1 docs: the Coder is weaker outside code, Chinese/CJK text included; the unpruned sizes for that (#438)
README and docs/MODELS.md said only "Weaker outside coding". #438 measured Chinese answers wrong or looping on the
Coder where English was fine; both now say plainly that this includes Chinese and other CJK text and point to the
sizes that keep every expert (Q2_0, IQ2_XS, IQ3_S).
2026-10-02 09:39:14 +02:00
Niko1221 6cc16bee09 setup (--gguf-dir): GGUFs Strata cannot run are named as such, with the --family/--model to use (#444)
With Unsloth's UD-IQ3_XXS or UD-Q2_K_XL in the folder, setup either said only "has no IQ3_XXS model file"
(--family unsloth --model IQ3_XXS) or took the UD-IQ3_XXS file for the GSQ-RCO IQ3_XXS by the size in its name.
Now:
- a chosen shard whose name is a quantization setup has no size for (UD-IQ3_XXS, UD-Q2_K_XL, a K-quant) stops
  with "<file> is UD-IQ3_XXS, a GGUF Strata cannot run" and what Strata runs: ISTA-DASLab's GSQ-RCO files (and
  Swift 1.5's and the Coder's) and Unsloth's UD-Q4_K_XL only;
- a folder without the chosen file but with other GGUFs lists the ones Strata cannot use and the --family/--model
  of the ones it can (an empty folder keeps check_shards' "missing");
- a size the family does not have names the families that do (--family qwen --model IQ3_XXS), and with
  --gguf-dir also what Strata runs.
2026-10-02 09:38:48 +02:00
Niko1221 95f2860231 /metrics: each request's speculative draft counts, and their sums in the totals (#457)
The server already read drafts_offered / drafts_accepted from the engine's DONE line but only returned them in a
response's timings (draft_n / draft_n_accepted). Each /metrics request entry now carries both (null when the
engine did not report them) and the totals keep their running sums since the server started, so a dashboard such
as llm-visuals can show the acceptance. Additive: no existing field changes.
2026-10-02 09:36:41 +02:00
Niko1221 22ee8d0e9a serve, setup: name the small split card that caps the prompt chunk; setup recommends the big card alone (#448)
- engine (layer splits only): when a stage's cache cannot fund the chunk CUDA0 alone would choose, a WARNING
  names it (CUDA index, name, cache slots, the slots the chunk borrows) and points at the helper cache
  (--expert-cache-device1, docs/SECOND_GPU.md).  The chunk choice itself is unchanged: fits() is split into
  fits_one() with the same tests; one GPU never reaches the new log.
- setup: a pair whose second card is under 12 GB beside a first card of 12 GB or more is still offered, but
  the first card alone is recommended (the default with --yes and Enter; offer_together asks "n" by default),
  with a note.  A named --gpus pair is kept.  Equal pairs (2x 8/16/24 GB) are unchanged.
- The automatic drop of the small card from an auto split is not done: stages load their weights before the
  boundary search and the exact prompt-buffer bytes need the sessions made after it.
2026-10-02 09:35:59 +02:00
Niko1221 0e4c3e8727 calibrate: the engine's arguments come from the server's engine_args; a failed tuning says why (#447)
tools/calibrate.py read any "gpu" list as a layer split and added --layer-split auto, so a one-card config with a
helper card for the expert tier ("gpu": [0] + --expert-cache-device1) was refused by the engine before it read a
weight, a "0,2" string never got its split, and split_skip_if_fits was ignored. The tuner now builds the arguments
with serve.server.engine_args, the same as a normal start.

When the tuning fails, setup prints the engine's own last error line from the log (only lines written during the
tuning), and says the failure again where it is not lost: before --calibrate starts the model, and in the
"All set." summary of an install that tuned.
2026-10-02 09:35:26 +02:00
Niko1221 cd49a498c8 setup (--backend hip): a system ROCm without the HIP development files falls back to the wheels (#446)
rocm_root took /opt/rocm when bin/hipcc and lib/libhipblas.so* were there and the version was 7.0 or newer. A
runtime-only ROCm 7 (no -dev packages) has both, and the engine's cmake configure then failed on the missing
hip-lang package. The system ROCm now also needs lib/cmake/hip-lang/hip-lang-config.cmake (lib, lib64 or the
multiarch lib folder, where CMake looks) and include/hip/hip_runtime.h; without them setup warns, naming what is
missing, and takes the TheRock wheels path, as it already does for a ROCm older than 7.0.
2026-10-02 09:33:17 +02:00
Niko1221 ce2d83b62d setup: an earlier config that does not parse is skipped, not a crash; configs are written atomically (#459)
A fresh copy of Strata set itself up like the newest strata-*.json of another Strata folder and crashed with
JSONDecodeError when that file was empty or cut off. previous_config now takes the newest one that parses as a JSON
object; each one that does not gets a one-line warning naming it, and with none the copy is set up as a fresh
install. Every place setup writes a run config now goes through write_config (a .tmp file, then os.replace), so a
setup stopped half-way no longer leaves an empty config behind.
2026-10-02 09:31:55 +02:00
Niko1221 42bc3dd9ad engine: the low-RAM resident mode engages on Windows 32 GB PCs (#467)
- Windows: the process working set is trimmed before the resident complement's RAM reading, so the experts
  the GPU cache pre-fill touched through the mapping count as available again (standby list); one line
  prints the reading before and after.
- --resident-experts (soft): when the whole complement does not fit, a second try keeps what fits, by
  profile rank, through the #403 budget path (RAM room minus the 256 MiB margin) instead of falling back
  to mmap entirely; a WARNING names it.  Same file bytes either way, so the answers are unchanged.
- Only the resident mode calls pin_cache_complement: the default and mmap paths are untouched.
2026-10-02 09:31:35 +02:00
Niko1221 a2edcb12f9 Windows AMD: the HIP runtime next to strata.exe, not an AMD driver's System32 copy (#468, #461)
Windows looks for an imported DLL in the exe's folder, then System32, then PATH (where the zip's rocm/bin is): a
driver that installs its own amdhip64_7.dll in System32 won, and the bundled rocBLAS/hipBLAS ran on that runtime - an
access violation on a W7900, hipErrorInvalidDeviceFunction on a 7900 XTX, on the first prompt. setup copies
amdhip64_7.dll and amd_comgr.dll from engine/rocm/bin next to the exes after the unzip, before the device probe and
on every start (so a 0.1.34 install is fixed too); package_windows.py ships them there; the engine and strata-device
log which runtime they loaded. rocBLAS/hipBLASLt stay in rocm/bin (their kernel libraries and .kpack are found from
there).
2026-10-02 09:31:18 +02:00
Niko1221 8896607268 serve: "messages" sent as a JSON string is decoded; a non-object entry is a 400 (#460)
openai_to_messages and anthropic_to_messages iterated req["messages"] directly, so a client that double-encodes
the array as a JSON string had its characters iterated and the request crashed on m.get. The new _object_list
decodes such a string and requires a list of objects; anything else raises ValueError("messages must be a list
of objects"), which the server answers with a 400. A message's tool_calls get the same guard.
2026-10-02 09:30:24 +02:00
Niko1221 eb8bb8bcd9 README, MODELS: the AMD baseline - RX 9070 XT + Ryzen 9 3900X + 47 GB RAM (Q2_0, IQ2_XS, the Coder)
Measured with setup's own install and the README's speed method on the AMD box.
2026-10-02 03:08:05 +02:00
Niko1221 43685766ae docs: byte-identical server repeats also need --pcie-frac 0 (#410, measured) 2026-10-02 00:59:21 +02:00
Niko1221 3997ca29fd docs (AI setup): AMD on Windows - the ready-made engine, the device check before the download 2026-10-02 00:55:01 +02:00
Niko1221 1867a8d6a1 README: pictures on AMD (Linux through the CPU, not on Windows yet) 2026-10-02 00:31:47 +02:00
Niko1221 dd23aa676c docs (AMD): Windows ships in 0.1.34 2026-10-02 00:30:46 +02:00
Niko1221 348ac7ef0e docs (AMD): the router's decode gain as measured by a user too (+1-4% over A/B/A/B), WMMA +29-34% with TheRock 7.10 (#432, jkuepker) 2026-10-02 00:30:25 +02:00
Niko1221 d5871c1af1 prefill: an MMQ product without a tile on some GPU takes the non-MMQ path instead of aborting (#420, qni-live)
llama.cpp's tile choice aborts the process ("J_best=0") when no tile size of a type fits the card's shared memory.
The plan now asks the same question for every visible GPU (the experts' gate+up and down shapes, the draft layer's
Q8_0 projections) and keeps a product with no tile on the existing FP16 path, saying which type, card and limits.
Where every product fits (all cards tested here), the plan is the same as before.
2026-10-02 00:28:48 +02:00
Niko1221 af53393b94 setup: UD-Q4_K_XL on AMD is asked before the download (#429); a resumed download needs room only for the rest (#425)
#429 (jkuepker): the HIP engine has no prompt kernels for its Q4_K/Q5_K experts and it has not been run on AMD -
--check says so, and setup asks (--model UD-Q4_K_XL --yes tries it). #425 (jctaborda): the finished shards and the
.part files already on the disk count against the download's size.
2026-10-02 00:27:13 +02:00
Niko1221 77668b2e3c serve: a request is cancelled within a second of its client hanging up (#430, #431, jkuepker)
A non-streamed request wrote nothing until its end, and a streamed one only a keep-alive per prompt chunk (the
first write after a hang-up usually still succeeds), so a dropped request kept the engine reading the whole prompt
or generating to max_tokens while the next request waited. A watcher per request checks the client's socket for EOF
every 0.5 s and cancels; the engine then stops at its next prompt chunk.
2026-10-02 00:24:20 +02:00
Niko1221 14dbd1be5f Engine 0.1.34: AMD on Windows (a ready-made HIP engine, #247 #325), an MCP server so an AI assistant can set Strata up (#133), a shorter README; setup requires it 2026-10-02 00:13:57 +02:00
Niko1221 3629d22c76 Merge branch 'f-133-docs' into rel-0.1.34
# Conflicts:
#	README.md
#	docs/AMD_HIP.md
#	setup.py
2026-10-02 00:09:58 +02:00
Niko1221 524b091daf Merge branch 'f-133-mcp' into rel-0.1.34 2026-10-02 00:09:12 +02:00
Niko1221 6edbe9d6ed Merge branch 'f-133-winamd' into rel-0.1.34
# Conflicts:
#	serve/test_server.py
2026-10-02 00:09:12 +02:00
Niko1221 8554b6bc53 hip_compat: cudaDevAttrComputeCapabilityMinor (#371's check, the HIP build) 2026-10-01 22:23:10 +02:00
Niko1221 30a0f3269a Engine 0.1.33: a portable image encoder again (#411 #412), setup recommends instead of forcing (#406 #403 #364 #384), fixes #352 #365 #369 #371 #375 #393 #408 #414; setup requires it 2026-10-01 21:17:17 +02:00
Niko1221 decfc96c98 Merge branch 'f-133-choice' into rel-0.1.33 2026-10-01 21:12:04 +02:00
Niko1221 95ade2d5ee docs: the RAM rule recommends 128K, the low-RAM mode on several GPUs, --resident-budget-gib / --kv-streaming (#406 #403 #364 #384) 2026-10-01 21:03:56 +02:00
Niko1221 f0ee8ce54f setup: recommend, never force - longer contexts, more GPUs, a bigger RAM budget are kept with their risk (#406 #403 #364 #384)
The recommended answers stay the defaults, and --yes / Enter write the same config as before
(tools/test_setup_golden.py, five PCs x five sizes).  What a user asks for beyond the recommendation is kept and
its risk said:

- #406: the RAM rule for IQ3_XXS / IQ3_S (experts + KV + 24 GB) is part of the recommended context (the smaller of
  it and the GPU's rule), no longer a cap: an explicit --context, a pick in the list or the earlier install's
  context is kept with a note ("256K with IQ3_S needs ~78 GB of RAM by setup's estimate; this PC has 64. Kept as
  you chose ..."), and the list marks the sizes past it.  The low-RAM mode keeps its KV cache in VRAM, so there
  the context does not count against RAM.
- #364 #384: the low-RAM mode recommends one GPU (the resident variant has no layer split) and asks: 2 = all the
  chosen GPUs with the experts read through the OS file cache (--mmap-experts, layer_split auto; faster in both
  reports, RAM can fill up during long prompts).  An explicit --gpus (or the earlier install's cards) is kept;
  --yes keeps one GPU.  The GPUs' share counts every card.  The one-GPU text no longer sends users to --low-ram off.
  At start, --gpus on a --resident-experts config (and offer_together's "y", whose default is now n for such a
  config) switches it to --mmap-experts with a note instead of an engine refusal.
- S4: --resident-budget-gib N for UD-Q4_K_XL (default: the RAM less 24 GB, as before); more is kept with a note,
  and a given N is not lowered for KV streaming.
- confirm_risk(msg, explicit, yes): one helper for the stops a user may take on - asked with default n, and with
  --yes an explicit flag is the consent (--yes alone keeps the stop): UD-Q4_K_XL under 48 GB (--model), the RAM
  floor (--model), the paging question (--model, besides --low-ram off), a card under 8 GB named with --gpus.
- --kv-streaming on|off|auto (auto: the RAM test, as before); on past the RAM test with a note, never with k8v4 or
  under WSL.

The refusals that stay are the impossible ones: rope none past 262144, disk space, engine version, driver < 580,
no AVX2, a size outside the family.
2026-10-01 21:03:56 +02:00
Niko1221 7349d9112b engine: --resident-experts with a layer split runs as --mmap-experts with a warning (#364 #384)
The low-RAM mode's resident variant has no layer split yet, and the engine refused the pair.  When the residency
comes only from setup's soft --resident-experts (no --resident-cpu-experts, no --resident-budget-gib, no remote
caches), it now prints a WARNING - the experts the GPUs do not hold are read through the OS file cache and RAM may
fill up during long prompts - and continues exactly as --mmap-experts: the resident flags are cleared before
anything reads them, so the source, the placement and the per-stage caches are the mapped mode's (#364's hand-edited
config).  An explicit --resident-cpu-experts, a budget, or remote caches are still refused.

The explicit --expert-cache reduced under a layer split is now a WARNING that names --vram-reserve-mib.
2026-10-01 21:00:49 +02:00
Niko1221 152f7198b9 engine: a RAM budget clamped to the free RAM no longer fails its own safety check (#403)
FileExpertSource::pin_cache_complement read the available RAM twice: the budget was clamped to the first reading
(less the 4 GiB headroom) and the resident-memory check then failed it on a second reading a few MB lower, so a
64 GB PC with setup's --resident-budget-gib 40 could exit at start.  With a budget, the check now reuses the reading
the budget was sized from, and a clamped budget stops 256 MiB under the room.  An unclamped budget holds the same
experts as before (the lend region is off with a budget, so `budget` there is unused).

If the budget still cannot be kept (no RAM reading, an allocation that fails), generate.cpp warns and serves every
expert the GPU does not hold from the files through the OS file cache (--mmap-experts) instead of exiting; the
complement is left unbuilt on every failure path.
2026-10-01 21:00:48 +02:00
Niko1221 41e9ecb016 docs: byte-identical repeats through the server also need --prompt-cache 0 --adapt-swaps 0 (#410) 2026-10-01 20:53:18 +02:00
Niko1221 18a0ec2d47 tests: golden --yes / Enter configs on five PCs (#403 #406 #364 #384)
setup.main() with --yes, and with Enter for every question, on 64 GB + 32 GB, 32 GB + 2x24 GB, 47 GB + 2x16 GB,
96 GB + 16 GB and 128 GB + 24 GB, for the recommended size, Q2_0, IQ3_XXS, IQ3_S and UD-Q4_K_XL: the run config
recorded by this setup (tools/test_setup_golden.json), so the choices that follow cannot change the default.
2026-10-01 20:52:24 +02:00
sergiywith 17ebb94433 gr_parity: compare the split read (STRATA_GR_V3=1) within float rounding
With STRATA_GR_V3=1 "fused GR multi max-T=8 LDS launch and changing graph replay" failed: the test compares the
multi-token read with the single-token kernel bit for bit, and v3 sums `down` in another order (row group x stream
blocks). It also compared `lo`, the default kernels' workspace between down and up, which v3 keeps in shared
memory and never writes. Under v3 the test now checks R_out, rs, inject and mixed within 2e-6 of the largest
reference value and leaves `lo` out; without it, bit for bit as before. Passes both ways.
2026-10-01 20:51:59 +02:00
Niko1221 e878547256 fused_gr: STRATA_GR_V3=1 keeps the default read where it needs the two-half split (Turing; #375, kenh0u)
Opt-in path only; the default read is unchanged.
2026-10-01 20:51:54 +02:00
Niko1221 950c1199dd expert cache: --expert-cache-per-layer starts on native packs (#369, dag08)
open_sized seeded every layer's cursor at slot 0, so all layers wrote the same slots and the startup check failed;
the profile fill ended at the first full layer. Now each layer starts in its own range, a full layer skips only
its own pairs, and per-layer mode keeps slots of the largest blob (its ranges ignore the rank a sized slot was cut
for). The default (shared) cache is unchanged.
2026-10-01 20:51:16 +02:00
Niko1221 37fe5b5328 serve: the image encoder on its own GPU, "cuda_device" in the vision section (#408, Efs-O)
Opt-in; without it the encoder gets the engine's environment as before.
2026-10-01 20:49:41 +02:00
Niko1221 6aa51c24d8 qsa_prompt_attn: the tensor-core kernels need sm_75 - a V100 (sm_70) keeps the old kernel (#371)
The host check compared the major version only (>= 7) while the device code traps below sm_75, so an sm_70 card
took the m16n8k8 kernel and hit __trap(). sm_75 and newer: the same choice as before.
2026-10-01 20:47:40 +02:00
Niko1221 0c8b8dc4d3 tests: find_nvcc's test ignores a real CUDA toolkit on the host (#414) 2026-10-01 20:46:32 +02:00
Bortlesboat 36beee903b setup: stop on EOF instead of accepting prompt defaults 2026-10-01 20:46:19 +02:00
Niko1221 318d74d9d2 serve: a template refusal answers 400 (#365); the temporary image goes when the encoder's pipe fails (#352) 2026-10-01 20:46:18 +02:00
Niko1221 082d59640f docs (AMD): Windows - the ready-made HIP engine, what it needs, what is validated, how to report 2026-10-01 20:34:37 +02:00
26cc76347a setup: AMD on Windows - the ready-made HIP engine, chosen by itself without a usable NVIDIA card
START-HERE.bat on a Windows PC whose AMD Radeon is the card Strata can use now
sets it up like an NVIDIA one: no compiler, no ROCm install, only the AMD driver.

- Detection without any AMD software: the display adapters present
  (Win32_VideoController: name + PCI id) with each one's 64-bit VRAM size from
  the display-class registry (readable without admin; #247/#325's source),
  the arch by PCI device id, else by name. An integrated or older Radeon is
  listed and refused, as on Linux. The NVIDIA-or-AMD choice is the Linux one:
  AMD by itself when no NVIDIA card can be used, asked when both can.
- The engine: strata-windows-x64-hip.zip from the release (this version's,
  then the latest; --prebuilt for a local folder), unpacked into engine/ and
  checked for this card's arch and version >= 0.1.33; an older one is
  replaced on a later start like the NVIDIA zip. Without a published zip setup
  says how to make one (tools\hip\build_windows.bat + --prebuilt).
- Before the model download, the engine's own strata-device --list-devices
  (with its rocm/bin on PATH) must see the card - otherwise it stops and
  points to the AMD driver - and gives the card's HIP ordinal (#325: an
  integrated Radeon is HIP's device 0). From then on setup lists the AMD
  cards in HIP's order, so "gpu" in the config and --gpu N are HIP numbers.
- The config: backend hip, lib_dirs = engine\rocm\bin, the hipBLASLt table
  for the zip's hipBLASLt version (BUILD.json). Images on the CPU and
  several AMD cards (--gpus) stay Linux-only for now (said, not silent).
- Texts: setup's description and step 1 name NVIDIA or AMD; START-HERE.bat
  needs "an NVIDIA or AMD graphics driver"; the "AMD backend runs on Linux
  only" stop is gone.
- An installed HIP engine's BUILD.json ("gfx1201" archs) is no longer read as
  an NVIDIA one by get_prebuilt.

Linux is unchanged (KFD detection, TheRock wheels, compiled here).
Tests: tools/test_setup_amd.py WindowsDetection (mocked WMI/registry rows,
the list-devices parser on this PC's real output, the iGPU-first ordinal
match, the driver-missing stop, the zip install/refusals, the dispatch);
test_setup_choices' Linux CPU-encoder case pins WIN=False.

Co-authored-by: Dimitri Vasdekis <[email protected]>
Co-authored-by: isshin <[email protected]>
2026-10-01 20:34:03 +02:00
Niko1221 b8349a8144 tools/hip: build_windows.bat and package_windows.py - the ready-made Windows HIP engine zip
build_windows.bat builds strata.exe + strata-device.exe (and with `tests` every
HIP test) for gfx1100;gfx1101;gfx1102;gfx1200;gfx1201;gfx1030 with no admin
rights and no AMD GPU: ROCm 10.2.0a20260930 from AMD's TheRock wheels
(whl-next: rocm[libraries,devel,device-<arch>...], the version #325 ran on an
RX 9070 XT) in a private venv (.rocm-win, ~7 GB), ROCm's clang as host and HIP
compiler with MSVC's headers/linker from vcvars, STRATA_PORTABLE (AVX2 ggml).

package_windows.py makes dist/strata-windows-x64-hip.zip (524 MiB, 810 MiB
unpacked): strata.exe (61 MiB), strata-device.exe, BUILD.json (backend hip,
archs, ROCm and hipBLASLt versions, lib_dirs rocm/bin), rocm/bin with the DLLs
the programs import followed through ROCm (amdhip64_7, hipblas, rocblas,
rocsolver, libhipblaslt, libtensilelite-host, origami, rocm_kpack) + amd_comgr
(loaded by name) + the MSVC runtime, rocBLAS/hipBLASLt kernels for these archs,
rocBLAS's kpack device code (found at ../.kpack from rocblas.dll), licenses and
a NOTICE. The user needs only the AMD driver (the HIP runtime uses its
user-mode components).

Checked on this PC's Ryzen iGPU (gfx1036, a test build of the same tree): with
only the zip's rocm/bin on PATH, strata-device lists the card and
hipblasGemmEx BF16 -> FP32 (T=100) matches the CPU (max err 4.7e-7); ctest
52 of 56 pass, the 4 failures the same as #325's RX 9070 XT run (hip_handoff,
and ple_parity / expert_parity / pool_test without the pack fixture).
2026-10-01 20:33:37 +02:00
36c11637f5 HIP on Windows: the hipBLAS sticky error (Windows only) and the mapped embedding fallback (#247, #325)
- prefill gemm: on Windows hipBLAS can return success with the right BF16/FP16
  product and still leave hipErrorInvalidValue set (#247 on gfx1201: hc up once
  T >= 96, the router), which the next kernel check turns into an exit. That
  one stale error is cleared after a GEMM that succeeded; any other error
  still stops the engine. Only under __HIPCC__ && _WIN32: on Linux a stale
  hipErrorInvalidValue is a real error and keeps being reported (our review
  of #247). CUDA compiles a ((void) 0).
- native embedding (HIP builds): when the runtime refuses a device alias for
  the mapped table, the table goes into VRAM (as when it cannot be pinned)
  instead of failing the start; the pinned copy is freed so the destructor's
  one-of-two rule holds (#325's version leaked it and freed dev_owned_ twice).
  CUDA keeps the old code path.
- tests/hip/mapped_alias (#325): what a hipHostGetDevicePointer alias is on
  this stack. On Windows it is the host pointer itself, and a D2D copy into it
  does not land - hip_handoff's timeout there; kernels reading it work (the
  engine serves on Windows). Rewritten to read the kernel's output back with
  hipMemcpy (the PR's version dereferenced device memory on the host) and to
  try the kernel read in both cases. Diagnostic: exits 0, 77 without a device.

Co-authored-by: Dimitri Vasdekis <[email protected]>
Co-authored-by: isshin <[email protected]>
2026-10-01 20:20:09 +02:00
Niko1221andDimitri Vasdekis 51fdc43c80 strata-device --list-devices, and HIP's numbering for AMD cards on Windows (#325)
On Windows the AMD cards are found in the display-adapter order, but
HIP_VISIBLE_DEVICES counts the devices the HIP runtime enumerates, and an
integrated Radeon takes HIP's device 0 and pushes the discrete card to 1 - so
the engine was pointed at the iGPU, which the arch check then refuses (found
by dvasdekis on an RX 9070 XT + Ryzen iGPU).

- device_count() and device_summary(): enumerate without device_info's arch
  check (the cards this binary has no code for are part of the answer).
- strata-device --list-devices: every device the runtime enumerates, as
  "device N: <name>", then "arch gfx1201, 15.9 GiB, wave32" (CUDA: compute
  capability and VRAM), then "cannot run: <why>" for a card this binary
  cannot run. setup.py parses it (next commit).
- server.py hip_visible(): a one-card config's "hip_ordinal" (written by
  #325's branch) wins over "gpu"; a layer split keeps its list.

Checked on this PC's own Ryzen iGPU through TheRock's Windows HIP runtime:
"device 0: AMD Radeon(TM) Graphics / arch gfx1036, 28.1 GiB, wave32 / cannot
run: ... not an architecture this Strata engine was compiled for (...)".

Co-authored-by: Dimitri Vasdekis <[email protected]>
2026-10-01 20:19:56 +02:00
isshinandDimitri Vasdekis eecb32abf6 HIP on Windows: the build with ROCm's clang as the host compiler (#247, #325)
CMake refuses to mix cl.exe with Clang for HIP, so on Windows ROCm's clang++
compiles the host code too (targeting the MSVC ABI, with MSVC's headers and
linker from vcvars). The CUDA->HIP shim is force-included with /FI when the
host front end is MSVC-style (cl / clang-cl) and with -include otherwise, its
path in forward slashes; CMAKE_HIP_ARCHITECTURES may also be space-separated.
On Windows the shim keeps <windows.h>'s min/max macros out (NOMINMAX,
WIN32_LEAN_AND_MEAN).

Taken from jagsan-cyber's #247 as ported by dvasdekis in #325 (2373c75); the
arch lists and the arch check stay 0.1.32's. Built on this PC with ROCm
10.2.0a20260930 (TheRock wheels, the version #325 ran on an RX 9070 XT) for
gfx1100;gfx1101;gfx1102;gfx1200;gfx1201;gfx1030: strata.exe and all 253
targets with the tests. CUDA builds do not read these files.

Co-authored-by: Dimitri Vasdekis <[email protected]>
2026-10-01 20:19:37 +02:00
Niko1221 885e52052b tools/strata_mcp.py: an MCP server so an AI assistant can install, start, check and stop Strata (#133)
Claude Code, Claude Desktop, Cursor, VS Code, Codex ... add it over stdio (JSON-RPC 2.0, MCP 2025-06-18, also
2025-03-26 / 2024-11-05); standard library only, so it runs before setup made .venv. Eight tools: strata_status
(running server via /health, /v1/status, /status, /metrics; installed configs, engine, data folder; hardware and
setup's recommendation), strata_models, strata_install (setup --yes --no-start in the background, plan first and
confirm=true to start, progress / cancel on later calls), strata_start (the run script's command, waits for
/health), strata_stop (POST /unload, then ends the server it started), strata_logs, strata_benchmark,
strata_connect_info.

Every argument is checked against a schema and setup's own tables; the only path (data_dir) must be the data
folder, inside the Strata folder, or a new Strata* folder outside system folders. Processes are tracked by pid and
start time in .strata-mcp/, so only processes it started are ever ended; a Strata started elsewhere is only
unloaded. docs/MCP_SERVER.md has every client's config; docs/DETAILS.md links it. Tests use a fake setup and
server (Windows and Linux).
2026-10-01 20:18:07 +02:00
Niko1221 f938412499 docs: a short README for regular people; the details move to INSTALL, MODELS, TROUBLESHOOTING, HOW_IT_WORKS
README.md (279 -> ~180 lines): what it is, speed on an NVIDIA and an AMD baseline PC side by side (the AMD
numbers are TODO-AMD placeholders until the RX 9070 XT baseline is measured), what you need with NVIDIA and AMD
as equals, install by asking an AI assistant (docs/AI_SETUP.md, MCP server) or by hand, pick-by-RAM table,
using it, the four common problems, how it works with the two diagrams, Buy Me a Coffee button below the speed
table and at the end. Its anchors other pages use (#which-model-should-i-pick, #install, #using-it,
#something-went-wrong, #how-does-it-work) are kept.

Everything the README had moves, nothing dropped: docs/INSTALL.md (requirements, Windows, Linux, AMD cards,
several cards, Docker, updating, where things are stored, setup's questions incl. rope scaling / images /
speed projection, calibrate, all options), docs/MODELS.md (speed per size, sizes, will it fit, low-RAM, Coder,
Swift 1.5, Unsloth UD-Q4_K_XL, Orca), docs/TROUBLESHOOTING.md (the README's problems plus AMD ones),
docs/HOW_IT_WORKS.md (the longer explanation, credits, license). New docs/AI_SETUP.md (instructions for an AI
agent installing Strata for a user) and AGENTS.md. .github/FUNDING.yml (buy_me_a_coffee: strataengine).
AMD_HIP.md loses "Experimental" in its title and intro; DETAILS.md lists the AMD cards and points to the new pages.
2026-10-01 20:08:58 +02:00
Niko1221 0ad23b584d setup: the AMD backend is no longer called experimental in its messages
--backend's help, the NVIDIA-or-AMD question and the GPU line drop "experimental" (the Unsloth model, the
speed projection and the Pascal/Volta build keep it); the AMD choice says images run on the CPU (0.1.32)
instead of "no images". setup.sh's header comment likewise.
2026-10-01 20:08:58 +02:00
Niko1221 2ca1b6645f docs (AMD): images through the CPU encoder and the Monitor's AMD readings (0.1.32, #304 #301) 2026-10-01 18:45:09 +02:00
Niko1221 3f11e286b4 docs: #337's top-k dispatch is AMD-only 2026-10-01 18:45:00 +02:00
Niko1221 01ec3ac900 qsa_select (#337 follow-up): the top-k dispatch by active blocks on HIP only - CUDA keeps the capacity rule (the 64K prompts read 1-3% slower with it on an RTX 5070; #337 was measured on RDNA4) 2026-10-01 18:44:53 +02:00
Niko1221 5a826638b4 qsa_select (#337 follow-up): the gfx12 WMMA block scorer is opt-in (STRATA_SELECT_WMMA=1) - it selects slightly differently from the warp kernel; the top-k dispatch by active blocks (the same ids) stays on; docs 2026-10-01 17:58:46 +02:00
bsorensen110 dd670cac36 hip: gfx12 (RDNA4) QSA select - WMMA block scorer, and a top-k dispatch that follows the prompt, not the capacity
Two changes to the prompt path's QSA selection (qsa_select.cu), measured on a Radeon AI PRO R9700 (gfx1201) with
--max-context 262144:

1. qsa_block_topk picked its register kernel by max_blocks, a CAPACITY (--max-context / 4 + 2). With a 262144-cell
   capacity every prompt, even a 4K one, took the slow reference top-k (up to ~10x slower at 128K+). The prompt path
   now passes the batch's active block count (the argument qsa_block_scores already takes); decode passes none and
   keeps the capacity rule and the original register width, so it is unchanged. On HIP the register kernel is
   instantiated for 66 blocks per thread (contexts up to 270,336 cells, the 262144 --max-context) and, below
   ~28K cells, where it measured 0.2-0.9x of the reference on gfx1201, the reference is kept. Same ids as the reference
   (checked 256/256 at every size).

2. qsa_block_scores_tc on gfx1200/gfx1201: block_scores_wmma_kernel on v_wmma_f32_16x16x16_bf16 (wave32). Each operand
   is split into three bf16 slices (hi / mid / lo are exact 8-bit mantissa slices of the fp32, so no scaling is
   needed) and the six products of order <= 2 are summed; the hi*hi chain keeps its own accumulator and the five
   corrections another, added once at the end (a single shared accumulator rounded the corrections at the big
   accumulator's ulp: 8e-7 of the score scale, failing the harness's gate at short context; split: 1.6e-7, the warp
   kernel's own error is of the same order). The tail block n_bid is the warp kernel's arithmetic. Like the TF32
   kernel it is not bitwise with the warp kernel (a near-tie can select differently). Gated at compile time
   (__gfx1200__/__gfx1201__) and run time (gcnArchName); other HIP targets keep the warp kernel; CUDA is unchanged.

qsa_select_bench now builds on HIP, takes the engine's capacity as a 4th argument, checks the scorer against an FP64
reference (gate: no worse than 4x the warp kernel's error, floored at 1e-6 of the score scale), exits non-zero on a
failed gate, and is registered with ctest (32K context under a 262144-cell capacity).

R9700, synthetic bench, 256 queries, capacity 262144: scores 0.81 -> 0.18 ms at 32K, 3.5 -> 0.65 ms at 131K, 6.8 ->
1.5 ms at 262K; top-k 3.9 -> 0.51 ms at 131K, 12.9 -> 1.3 ms at 262K. End to end (Swift 1.5 IQ3_XXS, --kv int8,
prefill timing, same binary with STRATA_SELECT_OLD=1 STRATA_TOPK_OLD=1 as the old arm): 107.7K-token prompt 79.5 s ->
59.5 s (1.34x; qsa select 23.3 s -> 3.4 s); 30.9K 1.02x; below 10K about 1.01x. ctest 44/44.
2026-10-01 17:58:17 +02:00
Rafael Grossi 7d15ef727b AMD HIP: RDNA2 gfx1030 (tested), gfx(1031, 1032) untested.
dp4a through the plain signed v_dot4_i32_i8 (RDNA2 has no sudot4).
setup knows the card and the gfx103X-all TheRock index.
docs carry a community report (RX 6900 XT 16 GB: complete build, 28/28
ctest, 38-42 tok/s decode at 131,072 context, 330-339 tok/s prefill)
2026-10-01 17:58:17 +02:00
bsorensen110 a8d57b1f00 hip: address review of the gfx1201 table: current docs, smoke-test wording
docs/AMD_HIP.md: the RDNA4 validation note still said there was no gfx1201 table and none was shipped; it now says
that holds for hipBLASLt 1.4.1 and points to the 1.5.0 table and its measured gain. The "Tuning table" text no longer
says the parity test "exits non-zero when the table does not fit". It describes what the test checks (architecture and
version through the loader, two of the 32 rows against hipBLASEx) and what it does not: a solution id the library
rejects falls back to hipBLASEx and the test still passes, so it says to read STRATA_HIPBLASLT_VERBOSE's
"fallbacks=" summary and to recalibrate for another 1.5.0 build.

tests/hip/prefill_hipblaslt_gemm.cpp: the header comment, the SKIP and missing-row messages and the case comment now
say it is a smoke test over two rows, and it prints "smoke test: 4 cases on 2 of the table's N rows" at the end.
Test behaviour and exit codes are unchanged.
2026-10-01 17:57:41 +02:00
Soren-ldandCopilot Autofix powered by AI cfe6021eb2 Update AMD HIP documentation for hipBLASLt 1.5.0
Clarify usage of hip_prefill_hipblaslt_gemm and tuning process for different 1.5.0 builds.

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
2026-10-01 17:57:24 +02:00
bsorensen110 025602c2be hip: encode the BF16 fixtures of the hipBLASLt tuner and parity test with bf16_from_f32()
Both helpers built their BF16 inputs with hip_bfloat16(x).data. Compiled as host C++ (no __HIPCC__) the SDK's
hip_bfloat16 is a struct holding a uint16_t, and the conversion from a float is not the engine's round-to-nearest-even
BF16 encoding (kernels/bf16_bits.hpp has that one).

Observed effect on gfx1201 with ROCm 10.2.0a, parity test with the 100500 table: the T=37 beta=1 BF16 case printed
relative_l2=0 max_abs=0 with the old encoding and relative_l2=3.6e-07 max_abs=2.6e-06 with this one; the T=4096 BF16
case prints 0 either way, and the f16 cases are unchanged. I did not inspect the old encoder's bit patterns directly,
so the exact fault is inferred from that difference, not shown.

Use strata::kernels::bf16_from_f32() in both, so the BF16 rows of gfx1201-hipblaslt-100500.txt were calibrated on
intended inputs and anyone regenerating a table gets the same. No change to the engine or to the f16 path.
2026-10-01 17:57:24 +02:00
bsorensen110 dd088d3324 hip: hipBLASLt tuning table for gfx1201 (Radeon AI PRO R9700) with ROCm 10.2.0a nightly (hipBLASLt 1.5.0)
tools/hip/gfx1201-hipblaslt-100500.txt: 16 dense GEMM geometries (bf16 and f16) at T=4096 and T=8192, 32 rows,
calibrated on an R9700 with tune_hipblaslt (16 candidates per row, relative L2 <= 1e-4, max abs <= 1e-2, no padding
writes) against rocm-nightly-gfx120x-all 10.2.0a20260914, hipBLASLt 1.5.0 (version number 100500).

The engine refuses a table of another hipBLASLt version, so the existing gfx1201 table in #262 (100401, hipBLASLt
1.4.1) is not used on this ROCm; setup picks tools/hip/<arch>-hipblaslt-<version>.txt by the installed version, so both
can be shipped side by side. Compared with #262's 24 rows this table has the same 24 shapes (with solution ids chosen
for hipBLASLt 1.5.0: 13 of the 24 ids are the same) and 8 more, all bf16 (N=1/K=2560 at T=8192, N=4/K=10240, N=512/K=2560
at T=4096, N=2560/K=2560 at T=4096, and N=10240 with K=2560 and K=320 at T=4096/8192).

docs/AMD_HIP.md lists the shipped tables and how to check one.
2026-10-01 17:57:24 +02:00
Niko1221 99b3bde00a Engine 0.1.32: split prompts faster with the same output (#340), AMD router +12% decode (bit-identical), RDNA4 matrix-core prompt attention (opt-in), Unsloth UD-Q4_K_XL in setup (experimental) with 2.8x faster prompts, fixes #326 #327 #342 #344 #331, the PR batch; setup requires it 2026-10-01 15:25:30 +02:00
Niko1221 97712287fe serve (merge #333 + #344): the engine stays not-alive from the start of __init__ until READY - #333's lazy start set ended=False before READY, so a request could again plan with context 0 (RestartWindow test) 2026-10-01 15:17:14 +02:00
Niko1221 b0abdeb446 Merge branch 'f-132-pr' into rel-0.1.32
# Conflicts:
#	CMakeLists.txt
#	serve/server.py
#	src/program/generate.cpp
2026-10-01 15:10:33 +02:00
Niko1221 aebfd69a2f Merge branch 'f-132-amd' into rel-0.1.32 2026-10-01 15:09:45 +02:00
Niko1221 d9fe3019e0 Merge branch 'f-132-q4' into rel-0.1.32
# Conflicts:
#	setup.py
2026-10-01 15:09:45 +02:00
Niko1221 3b7162131d #278 follow-up: an Anthropic request that doesn't ask for thinking renders without it only with "anthropic_thinking": "on_request" in the config - the default thinks as 0.1.31 did (a client that never asks would otherwise lose the thinking on every turn); docs 2026-10-01 14:42:53 +02:00
Niko1221 e12ecf6ac8 #280 follow-up: the rope kernels without the table are 0.1.31's code exactly (template <bool TAB>)
With STRATA_ROPE_TABLE unset the table was skipped at run time, yet the compiled default kernels changed their
results: the model-level parity run (Swift IQ2_XS, tools/conversation_cache_parity.py, STRATA_STATE_HASH) gave the
same tokens but other GDN / indexer / K/V state hashes than 0.1.31's (bisected to #280). native_rope's apply,
the indexer's append / append_first / append_blocks and the prompt path's rope_kernel are templates on TAB now;
the host launches <false> whenever no table applies, and that instantiation has no table read at all. Checked:
the same tokens and every state hash as round 1 with the whole branch.
2026-10-01 14:34:20 +02:00
Niko1221 dadc9e781a #340: a split's own prompt buffers are opt-in (STRATA_SPLIT_OWN=1, or auto for the 12% rule) - the owner's choice: the default borrows as 0.1.31 did, with the concurrent refill, the 96-blob split ring and own buffers only on a card with free VRAM, so the split's output stays 0.1.31's (2K +27%, 16K +5%, decode unchanged on the R9700 + 9070 XT); docs 2026-10-01 14:30:39 +02:00
Niko1221 e6a06be558 HIP (S6): STRATA_HIP_WMMA=1 also takes a 96-slot prompt expert ring (9070 XT 4K prompts 718 -> 1,211 tok/s with it); docs/AMD_HIP.md: the router and WMMA switches, with credit to PR #329's equal-speed kernel 2026-10-01 13:06:56 +02:00
Niko1221 c92c4540e3 #340: with the split's own prompt buffers (its rule, not --no-prefill-borrow) the boundary is searched as the borrowing configuration would; the reserve only shrinks the caches (STRATA_SPLIT_OWN_PLACE=reserve: the search sees it)
9070 XT + R9700: the reserve moved K 28 -> 32 and split decode 58.5 -> 54.8 tok/s; own buffers at K=28 decode 58.4.
2026-10-01 11:34:19 +02:00
Niko1221 bf7b181a2c prefill: the Q4_K / Q5_K / Q5_1 MMQ kernels are a build option (STRATA_MMQ_KQUANTS, off) - with every kernel loaded at start they took 1-3 expert slots from every model
The engine sets CUDA_MODULE_LOADING=EAGER (a kernel loaded mid-prompt found no VRAM), so the three extra mmq
instances (+24 MB of binary) were resident for every model: IQ3_S 2261 -> 2260 slots, Q2_0 3678 -> 3675 (S1's A/B,
IQ3_S story decode ~1.5% lower). Measured with the IQ3_S setup config against 0.1.31 (wt-131-r9 build-release):
- slots 2261 / free 5.25 GiB, the same as 0.1.31 again (2260 / 5.24 with the instances);
- engine threads the same (23 at READY, 25 after a request, 0.1.31 likewise): nothing new runs for a normal pack -
  the stager's 32 threads are only for a source with file-read blobs, the verify hook only with STRATA_LOGPOS;
- 5 alternating pairs, greedy story / code / third prompt: 0.1.31 39.5 / 44.7 / 32.0, this 40.3 / 45.2 / 33.3
  tok/s (story B >= A in 5 of 5).
The UD-Q4_K_XL prompt is SSD-bound with or without MMQ (29 s per 4K chunk either way), so the released engine keeps
FP16 products for it; prefill_mmq_kquant_test is built with the option.
2026-10-01 11:31:20 +02:00
Niko1221 560e4788a1 #290 follow-up: --bf16-embd's output buffer holds the 97 gathered rows, not only the 61-row table
The gather wrote 97 rows into a 61-row device buffer (and read them back into a 61-row vector); the next launch
reported the overrun as "iq_dequant_f32: invalid argument". The buffer is max(gathered, table) rows now.
2026-10-01 11:06:21 +02:00
Niko1221 0239cffde7 #296 follow-up: only the Q5_0 MMQ instance and case stay (Q4_K / Q5_1 come with S5's STRATA_MMQ_KQUANTS)
S5's branch (f-132-q4) compiles the Q4_K, Q5_K and Q5_1 MMQ instances on CUDA and enables them with the private
define STRATA_MMQ_KQUANTS; with both, moe_mmq.cu would have duplicate case labels. STRATA_ORCA_Q4KS_MMQ now adds only
q5_0 (OrcaRouter's Q5_0 expert downs; HIP included) - supported() and run() take Q5_0 under it.
2026-10-01 10:31:23 +02:00
sergiywith d11ea857df PLE: the n-gram table in FP8 E4M3, as Qwen ships it
The engine read the 51.2e9-value n-gram table only as IQ4_NL (ISTA-DASLab's shard 2, 90 B/row), which is 8.1% off the
checkpoint's own values per row (correlation 0.996-0.997 on rows from all 128 shards: same table, same order). The
checkpoint stores it as 128 shards of [2500012, 160] F8_E4M3 plus one weight_scale.

- tools/ple_fp8_pack.py copies those bytes, unchanged, into a one-tensor GGUF: per_layer_token_embd.weight
  [160, 320001536] as I8, with strata.ple.format = f8_e4m3 and strata.ple.scale. 51.2 GB, streamed.
- PleTable opens either format; the row size (90 / 160) and the decode (IQ4_NL codebook / an E4M3 table times the
  scale) follow the file. PleReader takes the row size at open; its cache sizes itself by it. GGUF reader: type I8.
- A PLE-only table (architecture strata-ple) is not added to the dense-projection sources.
- The start line names the table's format.
- ple_fp8_parity: rows through Direct, Mmap and gather_batch match torch's decode of the checkpoint bit for bit.

First-token KL of the IQ4_NL table against the FP8 one: 0.0009 (1K), 0.0013 (2K), 0.0002 (4K), 0.022 (32K); mean 0.0061, top-1 4/4.

Rebased onto 0.1.31 + #317 + #296 (Niko1221): one row size (`rb`) and one decoder in PleTable for the three table
formats - IQ4_NL, #296's Q5_0 (110 B rows, the mapped reader only, as #296 has it) and FP8 (160 B) - and #317's
SSD keep-alive picks its page inside the table at the table's own row size; 0.1.31's shard list for the dense
sources keeps the PLE-only exception; ple_fp8_parity builds with STRATA_BUILD_TESTS beside #296's checks. The
"PLE on" start line stays 0.1.31's.
2026-10-01 10:31:23 +02:00
Niko1221 2ba4504b71 prefill: UD-Q4_K_XL prompts 2.8x faster (16K: 57 -> 160 tok/s on an RTX 5070) - 32 stager threads and a 128-deep ring when the experts are read from the GGUF in place; MMQ for its Q4_K / Q5_K / Q5_1 experts
- The prompt path streams every expert once per 4K chunk; with the GGUF read in place ~35 GB of that comes from
  the SSD as page faults, and 4 copy threads with 16 blobs in flight read it at ~0.6 GB/s. When the expert source
  has transient (file-read) blobs the stager now defaults to 32 threads and a ring of 4 x threads (the env
  overrides stay); every other source keeps 4 / 16. Measured (4K chunk): 71 s -> 34 s (16/64) -> 29 s (32/128);
  16K prompt 273-282 s -> 99 s.
- moe_mmq: Q4_K, Q5_K and Q5_1 in supported() and the dispatcher, their mmq instances compiled for CUDA
  (STRATA_MMQ_KQUANTS; HIP unchanged). The dispatch follows eddoursul/Strata 7706c1b; main's prompt path already
  picks MMQ per layer. GPU time of the experts' products 4x lower (10 s -> 2.4 s per 4K chunk), not visible in
  the wall time on this PC (SSD-bound either way: 29 s with and without); after a 16K prompt the continuation
  agrees with llama.cpp at 92.2% (KL 0.029) against 90.2-90.6% (0.038) with the FP16 products. The other
  models' formats are not affected (their layers' types decide; none uses these three).
- tests/cuda/prefill_mmq_kquant_test: the four expert formats through MMQ against ggml's dequantized weights
  (synthetic, several experts per group, permuted rows, a zero row; rel. L2 <= 0.34%), and Q6_K stays uncovered.
2026-10-01 10:30:41 +02:00
Niko1221 d70df548ad #340: a layer split keeps 0.1.29's own prompt buffers (2048-token chunks, priced into the split search) when they are at most 12% of every card's VRAM; smaller cards keep borrowing (STRATA_SPLIT_OWN=1/0 forces)
Measured on 9070 XT + R9700 (Coder IQ1_M): 2K/4K/8K/16K prompts 1588/1771/1943/2006 tok/s (0.1.29 1568/1730/1870/1935,
0.1.31 993/1212/1350/1852). One GPU unchanged; --split-skip-if-fits keeps the loans.
2026-10-01 10:25:03 +02:00
Niko1221 b7bb142060 #340 prototype: STRATA_SPLIT_SMALL_MAX=M - a split request of at most M prompt tokens reads in the stages' own S-token chunks (nothing lent) 2026-10-01 10:04:08 +02:00
Niko1221 242248ceed setup: Unsloth UD-Q4_K_XL as an experimental choice (--family unsloth) - the four shards pinned at 38bb39e with their sizes and SHA-256, a RAM budget of the PC's RAM less 24 GB, packed with --compat-bf16, engine 0.1.32 or newer
- FAMILIES["unsloth"] (marked [experimental], not the default) and MODELS["UD-Q4_K_XL"] ("budget": True,
  48 GB of RAM or more); HF_REVISIONS pins unsloth/Qwen3.8-Flash-Next-GGUF; families may have more than two
  shards ("shards": 4).
- The download resumes as before; then verify_sha256 checks each shard's size and SHA-256 once (remembered in its
  finish mark; a wrong file is deleted so the next run downloads it again).
- resident_budget_gib: round(RAM) - 24 GiB (40 on 64 GB, the measured setting), at most the 71 GiB of experts, at
  least 8; a KV cache streamed to RAM (64K+) comes out of it. --resident-budget-gib replaces the low-RAM mode for
  this model (no experts.bin; --low-ram is ignored with a warning); one GPU, no images, no speed projection; 8K
  context recommended on GPUs under 14 GB; no --ple-gguf for a 4-shard file (the engine finds the PLE shard).
- The engine version is checked right after the engine step, before the 111 GB download.
- choices_from_config reads a size with a dash (strata-unsloth-ud-q4_k_xl.json).
- tools/test_setup_unsloth.py: pins, budget, SHA-256 handling, config round trip, and setup.main() end to end with
  every outside effect mocked (URLs, pack flags, engine args, old engine, too little RAM, two GPUs + images).
- README and docs/UNSLOTH_Q4.md / DETAILS.md describe it.

The model support itself follows eddoursul/Strata (PR #245).
2026-10-01 09:33:20 +02:00
Niko1221 796655c6ff docs: UD-Q4_K_XL against llama.cpp on the same GGUF - 99.0% argmax agreement on a code answer, 97.5% on a thinking answer, 90-91% after a 16K prompt, every difference where llama.cpp's own top two are close
Same token sequences through Strata (40 GiB budget, greedy, adaptation off, STRATA_LOGPOS_TOPK=20 on the verify
windows) and llama.cpp 3cf0325 (CPU, mmap, top 20 from llama_get_logits_ith): argmax agreement, top-5/10 overlap,
KL over the top 20, perplexity. Code 99.0% / thinking 97.5% (the fork, eddoursul/Strata: 98.2-99.3% / 95.6-95.8%);
after a 16K prompt 90.2% on Strata's greedy continuation and 91.2% on real text, the same real-text positions with
512 tokens of context 89.2% - so not a long-context loss: all positions with a top-1/top-2 gap of 0.5 or more agree
(and every code/thinking position from 0.2), perplexities within 3%.
2026-10-01 09:32:19 +02:00
Niko1221 88f4146c53 #340 prototype: a split's streamed ring is 96 slots when most experts are resident (STRATA_SPLIT_RING); STRATA_SPLIT_SMALL_OWN=S keeps an S-token chunk's buffers per lending stage for the session; --no-prefill-borrow on a split books the ring (it no longer starves the draft head)
One GPU: unchanged (all three rules are for layer splits only).
2026-10-01 09:22:58 +02:00
Niko1221 5fd4f2670f #296 follow-up: ple_q5_parity / q5_projection_parity build only with STRATA_BUILD_TESTS; the doc says what runs where
The two real-weight checks were added to every CUDA build with native experts (a full release build compiled them);
they are test programs. docs/ORCA_Q4_K_S.md: the build flag for them, and that 0.1.31's GPU expert kernels take
Q4_K gate/up with Q5_1 or Q8_0 downs (Q5_0 is not among them yet: the engine refuses such a pack at start, naming the layer).
2026-10-01 08:49:35 +02:00
Suoriks 75a39eef3a feat: support OrcaRouter Q4_K_S
Load three-shard Uncensored weights while preserving Q5_0 PLE rows
and Q4_K/Q5 expert bytes.

Add native CUDA and MMQ paths with real-weight parity checks.

Rebased onto 0.1.31 (Niko1221): 0.1.31 already loads each expert role from its own shard (the per-role shard column
of native_experts.txt, iq_pack.py, expert_source.cpp, native_expert_parity's multi-shard mode) and has its own
Q4_K/Q5_K/Q5_1/Q8_0 GPU expert decoders, so this PR's gguf_role_file / v4 manifest, its iq_pack.py and
test_iq_pack.py changes and its iq_kernels.cu decoders are dropped. Kept: Q5_1 in native_mmvq (type 7 through
0.1.31's iq_mmvq) and in the BF16 dequantizer, the Q5_0 PLE table (mapped reader), the Q4_K/Q5_0/Q5_1 MMQ
instances under the STRATA_ORCA_Q4KS_MMQ option (OFF), ple_q5_parity / q5_projection_parity and the doc.
2026-10-01 08:49:03 +02:00
Niko1221 cf3b351c19 #272 follow-up: --pool-affinity all (the pool's layout so far) is the default; auto and p-cores are opt-in
#272 made Auto the default, which on a hybrid CPU moves the workers (P-core primaries, then SMT siblings, then
E-cores) and cuts their number to P-cores - 1: a speed change nobody has measured here, on the machines most
users have. The default is All - the same cores in the same order as before on every CPU (Windows: the first
logical processor of each core; Linux: one allowed CPU per (package, core)), and the same worker count - in
Options, ExpertPool, physical_cores() and detect_cpu_topology(). --pool-affinity auto | p-cores opts in; the
hybrid line is printed only then. Usage text says so.
2026-10-01 08:47:56 +02:00
Pravesh Khatana 69743d3a5c feat(cpu): add hybrid architecture awareness and --pool-affinity for P/E-core CPUs
- Add detect_cpu_topology() for Windows (GetLogicalProcessorInformationEx) and Linux (/sys/devices/system/cpu/cpu*/cpu_capacity)
- Identify hybrid P/E-core topology and SMT sibling structures
- Prioritize primary P-cores for worker allocation, reserving core 0 for host thread
- Default pool_workers to (p_cores - 1) on hybrid CPUs when unspecified
- Add --pool-affinity auto|p-cores|all CLI option
- Maintain 100% backward compatibility on non-hybrid CPUs and default configs
2026-10-01 08:47:28 +02:00
Niko1221 029e10d9d9 #290 follow-up: a BF16-row parity test (native_expert_parity --bf16-embd)
iq_embed_rows (97 rows gathered out of order, with repeats) and iq_dequant_f32 on a random BF16 table, every value
compared bit for bit with the exact widening (bits << 16); embed_type_supported(30) / iq_supported(30) /
iq_row_bytes(30). ctest native_expert_parity_bf16_embd.
2026-10-01 08:46:48 +02:00
sergiywith 32727a7a94 Token embedding in BF16 from the checkpoint (--embd-gguf)
- tools/embd_bf16_pack.py copies embed_tokens (BF16, as the checkpoint ships it) into a one-tensor GGUF; only the
  safetensors shard that holds it is needed (3 GB for Qwen/Qwen3.8-Flash-Next).
- --embd-gguf PATH: NativeEmbed reads token_embd from that file; iq_embed_rows and iq_dequant_f32 decode BF16
  (embed_type_supported). Mapped host memory as before (1.2 GiB instead of 0.3 for IQ4_XS), no VRAM.

First-token KL of the GGUF's IQ4_XS embedding against the BF16 one: 0.0015 (1K), 0.0089 (2K; the top token changes), 0.0010 (4K), 0.037 (32K); mean 0.012, ~100x the run-to-run noise. On the NVFP4 fork, whose GGUF embedding is Q8_0, the same comparison gave 0.0025. Decode and prompt speed are unchanged (fork); the start takes ~0.2 s more.

Rebased onto 0.1.31 (Niko1221): NativeEmbed::load takes the shard list (one file for --embd-gguf) and checks the
model's architecture since 0.1.31 - the one-tensor file says "strata-embd", so only its tensor is checked there;
the Q4_K/Q5_K/Q5_1/Q8_0 decoders 0.1.31 added stay beside the BF16 one.
2026-10-01 08:46:48 +02:00
Niko1221 c81244d4e9 #293 follow-up: the drafter stores rotated INT8 K/V as INT8; the rotation is part of the snapshot format
- mtp.cpp record_forward: the `if (st_.kv_rot)` branch rotated K and V and then always appended with
  kv_append_q4_step - for INT8 + STRATA_KV_ROT=1 into Q4_0 pools the state does not have. The rotation and the
  append are separate now: rotate when kv_rot, then append in the state's own format (Q4_0, INT8, FP16).
- conversation_snapshot.cpp: rotated INT8 K/V is its own snapshot format (+16), so a conversation snapshot or prompt
  checkpoint taken with the rotation is refused by a state without it and the reverse ("incompatible K/V
  geometry"); without the rotation the format is unchanged. conversation_snapshot_test checks both directions.
- generate: says when STRATA_KV_ROT=1 applies; the q4_0 line's comment back.
2026-10-01 08:45:09 +02:00
sergiywith f133c655a8 KV: optional Hadamard rotation for int8 K/V (STRATA_KV_ROT=1); the drafter and the batched verify rotation-consistent
INT8 K and V can go through the Walsh-Hadamard rotation --kv q4_0 already uses (kv_q4.hpp): it spreads outlier
channels over the 64-value scale groups before quantization; the queries are rotated too (<Hq, Hk> = <q, k>) and the
output rotated back.

- QsaState::kv_rot: Q4_0 always, INT8 with STRATA_KV_ROT=1 (off by default: the same tokens as main).
- Every writer and reader follows it: decode, the verify window, the prompt path, the MTP drafter, and the drafter's
  batched prompt pass (Prefill::draft_kv) - which rotated only for Q4_0.
- verify.cpp: the batched window path (qb) never rotated its queries; it did not matter while Q4_0 was kept off it.

First-token KL to fp16 K/V, 4 prompts incl. 32K: NVFP4 pack 0.0066 -> 0.0051; IQ2_XS 0.0022 -> 0.0054. Hence opt-in.
2026-10-01 08:44:04 +02:00
Niko1221 0f461187d2 #280 follow-up: the float64 RoPE table is opt-in (STRATA_ROPE_TABLE=1); a freed session's table is forgotten
- The table's angles differ from the fast-math cosf/sinf ones in the last bits, so every output moves: off by
  default (the kernels compute the angle exactly as 0.1.31), STRATA_ROPE_TABLE=1 turns it on (replaces the PR's
  STRATA_ROPE_LEGACY A/B switch).
- The per-device registration was a raw pointer into the session's arena that outlived it: QsaState::owns_rope marks
  the state that built the table (not one that borrowed it with share_rope, like the MTP drafter's), and
  session_release() - to be called before a session's memory is freed - clears every device's entry pointing at
  it (rope_table_release). The latest session of a device now wins, instead of the longest table.
2026-10-01 08:43:38 +02:00
sergiywith 16a369b574 RoPE: every kernel reads the session's float64 angle table
The native decode/verify rope and the indexer's pooled keys computed pos * powf(...) with cosf/sinf under
--use_fast_math (0.0014 rad off at 32K, ~0.02 at 262K), the prompt path with precise float libm, and the float64
table the session builds was read only by the non-native path. rope_table_set registers that table per device
(mrope.hpp); rope_cs reads it, or computes in float64 past its end.

Measured on main with IQ2_XS: first-token KL of the old angles against the table 0.0045 on a 32K prompt (two runs of
main: 0 to 0.00013 on short prompts). On an NVFP4 pack: 0.0063 at 32K and at 125K. Speed unchanged.
STRATA_ROPE_LEGACY=1 brings the old float angles back for A/B.

Rebased onto 0.1.31 (Niko1221): 0.1.31's rope scaling (#84: linear / YaRN, rope_scaled_angle) is in every rope
kernel and in the session's table (build_rope_table with the RopeScaling), so a kernel reads the table only when it
was built with the scaling it was given, and otherwise - or past the table's end - computes the angle exactly as
0.1.31 does (no separate float64 device path); rope_table_set registers the scaling with the table.
2026-10-01 08:43:07 +02:00
Niko1221 5f02176997 verify: STRATA_LOGPOS_TOPK=K adds the K most likely tokens and their log-probabilities to the STRATA_LOGPOS rows
For a per-position comparison with another engine on the same tokens (argmax agreement, top-k overlap, KL on the
top K): docs/UNSLOTH_Q4.md compares UD-Q4_K_XL with llama.cpp this way.  Off unless the variable is set; the
eight existing columns are unchanged.
2026-10-01 08:42:54 +02:00
Niko1221 fc2eb78e0a conversation cache: a turn's parked copy supersedes the previous turn's, so a subagent no longer evicts its parent (#342)
Every request that does not continue the live session parks it, and put() never removed older copies of the
same conversation while make_room evicted oldest-first: a subagent's 9 successive turns (each re-rendering the
previous reply) parked 9 copies and pushed the parent conversation out of 4 slots. drop_superseded() now runs
before make_room (park_current) and in put(): an entry whose DEEPEST checkpoint the outgoing chain (or live
state) still holds - the same conversation a turn back, differing only in the stale reply tail - is dropped
(counted apart from evictions, logged). An entry without checkpoints, of the other steering mode, or whose
deepest checkpoint the outgoing chain lacks (another conversation sharing only the system-prompt root) is kept;
an oversized put drops nothing. The cache is opt-in (#189): with it off nothing runs. Tests in
conversation_cache_test (MAIN -> SUB x9 -> MAIN with 4 slots; the kept cases).
2026-10-01 08:42:33 +02:00
Niko1221 c03efab319 serve: AMD readings in the Monitor tab from Linux sysfs; free VRAM on HIP (#301)
With the AMD backend (config backend "hip") telemetry reads the amdgpu driver's sysfs files instead of NVML:
gpu_busy_percent, mem_info_vram_used/_total, and hwmon temp1_input, power1_average (or power1_input) and
power1_cap. The card is found as setup's amd_gpus() numbers it (KFD topology GPU nodes in order, CPU nodes
skipped, drm_render_minor -> /sys/class/drm/renderD<N>/device), which is HIP_VISIBLE_DEVICES' numbering; a
layer split sums/averages as on NVIDIA. Service.free_vram_mib (min_free_vram_mib) uses it on HIP. NVIDIA
paths unchanged. Test: AmdTelemetry (a fake sysfs tree, two cards and a CPU node). Not yet checked on a real
AMD box.
2026-10-01 08:41:14 +02:00
Niko1221 7d746a0887 #283 follow-up: STRATA_PREFILL_BF16X2 defaults to 0 (off) - the prompt path's numbers stay 0.1.31's
The second (remainder) GEMM changes what the prompt path computes, so it is opt-in: 2 = all but the
hyper-connection's projections, 1 = all. With 0 every lo pointer is null: no extra buffers in carve()/bytes_needed(),
the PLE batch size as before, and every kernel writes exactly the BF16 images it wrote before.
2026-10-01 08:40:29 +02:00
sergiywith a86bc853f3 Prompt path: BF16 projections get the activation's BF16 remainder too
The prompt path fed BF16-rounded activations (2^-9 relative) to the BF16-weight projections, where decode feeds FP32:
the router (a discrete top-10), the indexer (a discrete block selection), SSM alpha/beta, the shared-expert gate, the
PLE key/value and the hyper-connection. Each now takes x as hi + lo, both BF16 (lo = bf16(x - hi)), in two GEMMs
summed in FP32: ~16 mantissa bits, decode's FP32 x to within ~1e-5.

STRATA_PREFILL_BF16X2: 2 (default) all but the hyper-connection, 1 all, 0 off.

IQ2_XS, first-token KL to mode 1: short prompts main 0.0028/0.0069/0.0009, mode 2 0.0014/0.0024/0.0012; a 32K prompt
0.074 and 0.072 (the hyper-connection is what matters there).
2026-10-01 08:40:02 +02:00
Niko1221 da95ad29d8 #282 follow-up: chunks above 8192 are opt-in (--prefill auto:16384 / auto:32768, or STRATA_PREFILL_AUTO_MAX)
A bigger chunk reads a long prompt in a different order of chunks (other expert loans, other rounding of the
batched path), so it stays out of the default: plain --prefill auto picks from 8192 down exactly as 0.1.31 did (the
PR's "no chunk past the context" rule, which also moved the choice below 8192 for small contexts, now applies only
to the new sizes). auto:N (16384 or 32768) or STRATA_PREFILL_AUTO_MAX with auto sets the ceiling; o.prefill_chunk
starts at that ceiling for the buffer estimates. Both kAutoChunks lists - plan_lend and the serve path's per-stage
fits() - take the same sizes under the same rule.
2026-10-01 08:39:58 +02:00
sergiywith 1e5e84d919 prefill auto: chunks up to 32768 (was 8192)
Every chunk streams again each expert it routes to, so a long prompt read in 8192-token chunks crossed PCIe with
nearly all experts of every layer four times per 32K. The auto list now starts at 32768 and 16384, still bounded by
the lend rule and by --max-context; a request lends only what its prompt needs, so short prompts are unaffected.

IQ2_XS, RTX 5090, 64K context, a 32K prompt (3 runs each): 5,624 -> 6,465 tok/s, 40K -> 18K experts streamed.
NVFP4 pack at 262K: 3,535 -> 5,201 tok/s.
2026-10-01 08:39:27 +02:00
Niko1221 6280f653c8 #281 follow-up: the saturated FP16 SwiGLU product keeps a NaN a NaN
fmaxf(NaN, -65504) is -65504, so #281's clamp turned a NaN product into -65504 and the prompt path went on with a
plausible value where it used to show the NaN (STRATA_DBG_NAN and the NaN checks find it there). isnan(x) ? x :
clamp(x): only +-inf (and finite values from 65520 up, which rounded to inf) saturate; everything else is unchanged.
2026-10-01 08:38:59 +02:00
sergiywith c517862ce2 numerics: saturate the prompt path's FP16 SwiGLU products; log1pf in the unfused GDN gate
- prefill/kernels.cu: the SwiGLU products that feed the prompt path's FP16 down projection are clamped to FP16's
  range (+-65504) before rounding. A token with a massive activation otherwise becomes inf there and NaN after the
  GEMM, where decode's q8_1 path (range ~8e6) stays finite.
- native_gdn_preprocess.cu: the unfused gate softplus used logf(1 + expf(v)), which returns 0 for v below ~-16
  (1 + e^v rounds to 1); the fused kernels, gdn.cu and verify_kernels.cu already use log1pf(expf(v)). The two paths
  now agree.

On ordinary prompts nothing moves: first-token KL to main 0, 0.00013, 0 (the run-to-run noise).
2026-10-01 08:38:35 +02:00
BlueKingMuch 4da8bfcbf9 ple: keep the SSD awake while rows are read (STRATA_SSD_KEEPALIVE)
Some SSDs (here a WD_BLACK SN7100 on Windows 11) drop into a power state
after ~250 ms without a command and stall the next reads 50-150 ms; the
NVMe idle timeouts of Windows' power plan do not change it. In decode that
hits the first read after a pause: at the start of a request, or after a
few rounds whose rows all came from the row cache. --ple-io ram, which
avoids the reads altogether, is not available on Windows.

The reader's worker now reads one page of the table (a different one each
time) when no read has gone out for 100 ms, until 60 s after the last
issue; then the SSD may sleep until the next request re-arms it. A
keep-alive read counts when it completes and stays out of the row-read
counters and latencies; the "ple io" line adds "SSD kept awake by N reads
(slowest X ms)". A keep-alive read that fails, or cannot be submitted,
turns the keep-alive off instead of failing the reader.
STRATA_SSD_KEEPALIVE=0 turns it off, =50 shortens the period;
STRATA_SSD_KEEPALIVE_WINDOW sets the window in seconds. Only with the I/O
worker (the default), not with --ple-sync-submit or --ple-io mmap/ram.

The reader's statistics are now copied and reset under the worker's lock
(snapshot()), since the worker may be counting a keep-alive read. The
selftest checks the period, the window, the re-arm by a ticket the row
cache serves, that rows and read counters are untouched, and that the
caller-thread mode has none.

A scan on that PC (reads after pauses of every length): 0 of 211 bursts
stalled after pauses under 200 ms, 35 of 73 after 200 ms and more, 0 of 68
of those with the keep-alive on.
2026-10-01 08:38:27 +02:00
Niko1221 9d0b28df54 #315 follow-up: its variants and switch renamed (plain / split / staged, STRATA_HC_SPLIT) - no clash with STRATA_GR_V3
main already has an opt-in "v3" hyper-connection read (#186, STRATA_GR_V3=1, another summation order); #315's
"v2"/"v3" and STRATA_HC_V2 read as the same thing. Now: the plain read (0.1.31's default), split (the norm per
token and stream) and staged (split + the down projection's activations staged by cp.async), kHcPlain/kHcSplit/
kHcStaged, gr_norm_split_kernel / gr_down_staged_kernel, and STRATA_HC_SPLIT: unset or 2 = the newest the card's
check accepts, 1 = at most split, 0 = the plain read. The check compares against the plain read (launch_multi with
kHcPlain) and the single-token read, never against what STRATA_GR_V3 selects; with STRATA_GR_V3=1 that opt-in read
still runs first, unchanged.
2026-10-01 08:38:22 +02:00
Niko1221 d85ff9dfe9 serve: model aliases - the config's aliases in /v1/models and accepted as model names (#297)
`"aliases": [...]` (or a comma-separated string) in strata-<model>.json: /v1/models lists the model with an
`aliases` field and each alias as its own entry (`alias_of`), /props?model= accepts them, and a request naming
an alias is answered under that name (OpenAI, streamed or not, and Anthropic). Any other name is still served
under the model's own name, as before; without aliases the responses are unchanged. Documented in DETAILS.md.
Test: ModelAliases (serve/test_server.py).
2026-10-01 08:37:46 +02:00
Niko1221 469bdaf891 #340 prototype: a split stage with room for its prompt buffers keeps them as its own (no loan, nothing streamed or refilled for it); STRATA_SPLIT_OWN_BUFFERS=0: every stage borrows
One GPU: unchanged (the rule is for layer splits only; the single-cache loan is computed exactly as before).
2026-10-01 08:37:18 +02:00
BlueKingMuch 2cf197f894 hc: the multi-token hyper-connection read split finer, checked bit for bit on each card (STRATA_HC_V2)
The multi-token read (verify windows, the MTP drafter) keeps v1's
arithmetic exactly - every sum over the same elements in the same order -
and only splits the work differently:
- the norm (v2, v3): one block per token and stream instead of one per
  token. Each thread visits the elements of its stream in the order it did
  in v1, so every sum of squares and rs is v1's; R' * w_norm is recomputed
  and scaled once (the same two roundings as v1's store and in-place
  scale). On an RTX 4080 SUPER: 0.58 -> 0.23 ms per 4-token round.
- the down projection (v3): v1's rows per block and lane order, but the
  activations arrive by cp.async in half-stream tiles, two in flight, in a
  bank-conflict-free two-plane layout, and the next tile's weights load
  while the current one is used. A tile is 160 = 5 x 32 chunks, so a lane
  still accumulates its chunks lane + 32 q in ascending order. Before sm_80
  and on HIP the staging is a plain copy (the same bits).

Verifier::init runs fused_gr_check once per card: v1, v2, v3 and the
single-token read on random bf16 weights and inputs (1..8 tokens, with and
without the pending write), every output compared bit for bit; the newest
variant that agrees runs on that card and the log says which. A card that
has not been checked runs v1. STRATA_HC_V2=0 keeps v1, =2 stops at v2.
The down kernels carry as many tokens per launch as the card's shared
memory allows (v3: 10 KB per token), as v1 did.

Rebased onto 0.1.31 (Niko1221): v1 is 0.1.31's default read unchanged - the TILEV 1280/2560 down kernel (sm_75's
small tile), the pre-Volta capacity rule, STRATA_EMULATE_CC - moved into down_chunk()/launch_multi(); main's opt-in
STRATA_GR_V3 path (#186, another summation order) still comes first in fused_gr_read_multi, and the card's check
calls launch_multi(1) directly, so it compares against the default read whatever STRATA_GR_V3 says.
2026-10-01 08:36:04 +02:00
Niko1221 348d65da7b #340 prototype: a layer split refills every stage's prompt loan concurrently (queue all, then wait); STRATA_REFILL_SERIAL=1: stage after stage; STRATA_TRACE times the loan and the refill
One stage: refill_one exactly as before (single-GPU unchanged). The same copies into the same slots either way.
2026-10-01 08:35:57 +02:00
Niko1221 bd84d0bc6d #284 follow-up: wait for the async commit on an event, at every point that touches the session afterwards
The commit graph writes the GDN state and conv tail, the QSA indexer's tail/dead/pooled/block_pos and the PLE
history; with #284 it may still run when commit() returns. Verifier::commit records commit_done_ after it, and
Verifier::wait_commit() synchronizes that event (not the device; a no-op when nothing is pending, recursing into a
layer split's later stages). A layer split's first stage keeps the synchronous commit (next_ set).

Audited, and where the wait now is:
- the serve decode loop's end (end of turn, max tokens, STOP/cancel): wait before the live session, the state
  fingerprint (STRATA_STATE_HASH) and the DONE line - so the next request's park (conversation snapshot capture),
  restore, checkpoint restore and zeroing never see a running commit;
- the prompt read by verify windows (serve): wait at its end - the batched prompt path (other streams), prompt
  checkpoints (cudaDeviceSynchronize already) and snapshots may follow;
- the start of each serve request: an event wait replaces #284's cudaDeviceSynchronize (covers error paths);
- the end of a generate run: wait right after the decode loop, before the stats and the dumps (#284 synchronized
  only later, after the routing dump);
- unchanged and safe: the next window (same stream), the MTP draft (its own stream; reads the window's final rows
  and its own K/V, which commit does not write), the adaptive tier thread (expert slots and residency tables only),
  STRATA_LOGPOS / STRATA_DUMP_FIRST_LOGITS (read run()'s head logits after run() synchronized), QUIT and /unload
  (the Verifier destructor synchronizes its streams before freeing).
2026-10-01 08:34:33 +02:00
Niko1221 2282393234 setup --backend hip: --vision cpu builds and uses the CPU image encoder (#304)
Setup forced images off on the AMD backend. hip_vision() now takes --vision cpu: build_engine_hip builds
tools/vision without CUDA beside the HIP engine (build_vision_cpu, again when its source changes) and records
vision/vision_src in BUILD.json; the start config is the CPU encoder's, as on NVIDIA. --vision yes/gpu stay off
with a hint (no HIP encoder build yet); without --vision nothing changes. Test: tools/test_setup_choices.py.
2026-10-01 08:34:07 +02:00
sergiywith a1e4fb4c80 Verify: the commit graph no longer waits; it overlaps the MTP draft
The commit's cudaStreamSynchronize held the host ~0.45 ms a round. The next window follows it on the same stream and
the drafter (its own stream) reads only this window's final rows and its own K/V, so a single-GPU session
(Verifier::set_commit_async) returns at once. Whatever touches the session from another stream or the host afterwards
synchronizes the device: the start of each --serve request (checkpoint restore, zeroing, the prompt path) and the end
of a generate run; checkpoint saves already did. A layer split keeps the wait, as does STRATA_COMMIT_SYNC=1.

IQ2_XS, 400-token answers, 3 runs each: 128.6 -> 135.8 tok/s decode (+5.6%), the same tokens. NVFP4 pack +0.8%.
2026-10-01 08:33:05 +02:00
Niko1221 3b7a49a6fd setup: Pascal (6.x) and Volta (7.0) with STRATA_EXPERIMENTAL_SM60=1: the community build with CUDA 12.x (#295)
The engine/CMake part shipped in 0.1.31. Setup now admits compute capability 6.x and 7.0 only when
STRATA_EXPERIMENTAL_SM60=1 is set (gpu_problem; without it the message says so), compiles the engine with
-DSTRATA_EXPERIMENTAL_SM60=ON for cards below sm_75 (engine_defs), and takes the newest CUDA 12.x toolkit
(find_nvcc(below=(13, 0)); CUDA 13 cannot build sm_60/sm_70) - stopping with a hint when there is none, or
when an RTX 50 card (CUDA 13) shares the model with such a card. Without the variable nothing changes.
Test: tools/test_setup_choices.py (mocked toolkits and cards).
2026-10-01 08:32:49 +02:00
Niko1221 272c16c767 #332 follow-up: the API request monitor is opt-in ("api_monitor": true / --api-monitor), off by default
It keeps the last 100 requests' prompts, answers and reasoning in memory. Off (the default): begin_request() keeps
nothing, the streams are not wrapped, GET /api/requests and /api-monitor answer 404. Every record write goes through
_note(), which does nothing without a record (the PR's direct self.record[...] writes in the stream error paths
would have failed with the monitor off). A client disconnect in do_POST is marked and then re-raised, as the server
handled it before. The start line says when it is on (and that anyone can read the history without an API key).
test_monitor: turns it on; MonitorOff checks the default; Anthropic requests ask for thinking with "thinking" (#278).
2026-10-01 08:32:01 +02:00
Ricardo Ambrogi 24d011807c serve: add standalone API request monitor 2026-10-01 08:31:48 +02:00
Niko1221 5a18f83a13 setup --gguf-dir: the shard count from the -of-N part of the name, not two (#305)
--gguf-dir built exactly <model>-00001-of-00002.gguf and -00002-of-00002.gguf, so a model split or named
differently (-00001-of-00003, Unsloth's ...-00001-of-00004.gguf) was not found. gguf_dir_shards() takes the
published name when it is there (unchanged for the existing models), else the one first shard in the folder
whose name has the size in it, and names shards 1..N from it as the engine and iq_pack do. Help text updated.
Test: tools/test_setup_choices.py (2- and 4-shard names).
2026-10-01 08:31:13 +02:00
Niko1221 354212421e setup: the CPU image encoder when the ready-made encoder has no code for the card (#331)
The release zip's strata-vision has no sm_75 code (BUILD.json vision_archs 86/89/120), so on an RTX 20 card
setup fell back to compiling the encoder, which fails on most Windows PCs. prebuilt_vision() now chooses the
CPU encoder (the same program without --gpu) with a warning; --build still compiles a GPU one. The next release
zip should build strata-vision with 75-real as well. Test: tools/test_setup_choices.py.
2026-10-01 08:30:06 +02:00
Niko1221 abfb05cfbe native packs: a BF16 (--compat-bf16) PLE key stays in the arena and is not replaced by the GGUF key (#326)
Since 0.1.25 NativeDense treats an IQ3_XXS/IQ4_XS/Q8_0 GGUF key as native, so for a native pack the loader
skipped the pack's key row and then refused the BF16 row iq_pack --compat-bf16 writes for OrcaRouter's IQ3_XXS
key ("native dense: incompatible matrix blk.1.ple_key.weight"). NativeDense::keep_unquantized_ple_key reads the
key row's code_bits from index.txt (WeightTable::index_code_bits) and keeps an unquantized row in the arena;
NativeDense::load does not upload the GGUF key over it, and the PLE uses the BF16 key as before 0.1.25. A
quantized key row (Q2_0, UD-Q4_K_XL packs) is unchanged; the GSQ packs' GGUF keys are BF16 (not eligible), so
they never reach this path. The reporter's fallback (Gen4536). Test: native_dense_ple_key_test (synthetic GGUF
+ pack, CUDA).
2026-10-01 08:29:03 +02:00
Niko1221 75780af235 mtp_fetch: a range read needs 206 + its Content-Range; pinned per-tensor sha256, corrupt installs fetched again (#327)
A mirror that ignores the Range header answers 200 with the shard's start (its JSON header); cut to the asked
length it passed the size check and the manifest hashed the wrong bytes, so the drafter accepted nothing. get()
now requires HTTP 206 with the requested Content-Range. The 31 MTP tensors' sha256 at the pinned revision
(de4b8e4) ship in SHA256 (two independent fetches agree; 29 re-read from the revision, the two expert tensors'
first/last 16 MiB): a wrong tensor is fetched again once, then refused. An inventory read from another
repository/revision is read again and its tensors are kept only when they hash right. `mtp_fetch.py verify`
(offline, cached by size/mtime) finds a corrupt install; setup then fetches the bad tensors again and rebuilds
the draft layer. From PR #324: a refused engine archive no longer keeps its zip and .done mark.
Tests: tools/test_mtp_fetch.py (fake checkpoint behind a mocked urlopen, a mirror that ignores Range),
test_setup_pins (refused archive).
2026-10-01 08:28:45 +02:00
Niko1221 d3bf912ea0 #334 follow-up: jsonschema is an optional, lazily imported package (no new hard dependency)
- The rebase keeps 0.1.31's requirements.txt install in setup.py and the Dockerfile: jsonschema is not added.
- serve/structured.py imports jsonschema on the first json_schema request, not at server start (a server without it
  started fine before #334 and still does). json_object never needs it; json_schema without it is checked to be one
  JSON object, and the server window says once how to get full schema validation.
- test_structured: the two schema-violation tests skip without jsonschema; new tests for the object-only fallback
  and for "import serve.server" not importing jsonschema. docs/DETAILS.md says it is optional.
2026-10-01 08:27:57 +02:00
Ricardo Ambrogi c87a9cb0ce serve: validate JSON response formats before delivery 2026-10-01 08:27:08 +02:00
Niko1221 bd9a1bc126 #333 follow-up: the unload keeps its terminate/kill fallback; EngineStuck instead of a catch-all RuntimeError
- StrataEngine.close() (now also the unload and the restart): QUIT and EOF, then terminate, then kill, each with 20 s
  as the unload had (#333 went from QUIT straight to kill after 10 s; an engine unpinning tens of GB can need more).
  A process that outlives all three raises EngineStuck and stays owned.
- do_POST caught every RuntimeError as a 503 - EngineDied, GpuBusy and ModelBusy are RuntimeErrors too, and any other
  failure was turned into a vague 503. Only EngineStuck is caught now (also around POST /unload, and the idle-unload
  thread survives it); EngineDied keeps its own answer.
- test_lifecycle: terminate before kill, EngineStuck, an EngineDied on load answered as such.
2026-10-01 08:26:28 +02:00
Ricardo Ambrogi 7f3a091895 serve: add lazy startup and reliable Windows model cleanup 2026-10-01 08:24:02 +02:00
Niko1221 a8d5a16c35 #321 follow-up: CORS only on /v1/* for configured origins; no own-page bypass for proxy headers
- The Origin check of /settings and the MCP tools let any page through when the request carried X-Forwarded-For,
  CF-Ray or CF-Connecting-IP (headers every reverse proxy or tunnel adds), and compared hosts without their ports
  (a page on another local port passed): any web page could then change the shared settings or run MCP tools with the
  user's rights. Removed. The Origin must be this server's host:port, or an origin in the config's new
  "trusted_origins" (the web app behind a proxy or tunnel); a proxy header alone opens nothing.
- CORS is opt-in: "cors_origins" in the config (an origin, a list, or "*"). Only /v1/* answers with
  Access-Control-Allow-Origin (the origin itself, or "*"), never /settings, /unload or the app's files; the
  preflight echoes the headers asked for ("*" does not cover Authorization). OPTIONS is answered 204 either way.
- X-Accel-Buffering: no on SSE stays. origins_of() checks both lists at start (scheme://host[:port], no paths;
  "*" only for cors_origins). docs/DETAILS.md; tests for the bypasses, the trusted origin, CORS off/on, the header.
2026-10-01 08:23:27 +02:00
BlitzenCatsandGemini 392e32c46b serve: add CORS preflight (OPTIONS), reverse-proxy header support, and X-Accel-Buffering for SSE
- Implement do_OPTIONS (204 No Content) and add Access-Control-Allow-* headers to allow web-based clients (Open WebUI, LibreChat, browser extensions) to query endpoints.
- Update _own_page to recognize reverse proxies and tunnels (Cloudflare Tunnel, Tailscale Funnel, Nginx) via X-Forwarded-Host, CF-Ray, and forwarded headers, preventing 403 CSRF false-positives when serving remotely.
- Add X-Accel-Buffering: no to SSE streaming responses to prevent reverse proxies from buffering real-time token generation.
- Add unit tests in serve/test_server.py covering CORS preflight, CORS headers, and reverse proxy origin checks.

Co-authored-by: Gemini <[email protected]>
2026-10-01 08:22:10 +02:00
Niko1221 ca85ebdee2 #278 follow-up: an Anthropic request with its own reasoning_budget_tokens still thinks
"thinking" absent meant no thinking, also for a request that gives reasoning_budget_tokens (#123), which asks for
thinking with a cap (serve.test_server's test_anthropic_stream failed: no thinking at all). Such a request renders
as before. test_anthropic_thinks_only_when_asked pins the five cases.
2026-10-01 08:18:19 +02:00
Niko1221 40eaa123d4 serve: a restarting engine is not alive until READY; context 0 is a 503 "starting" (#344)
restart() set ended=False before __init__, which zeroes max_context and blocks until READY, so a request in that
window skipped load() and failed with a 400 "leaves no room to answer in the context (0)". The engine is now
marked alive by __init__ once READY; prepare() answers 503 "the engine is starting" when max_context <= 0. The
output thread is bound to its own process and queue, so a killed engine's thread no longer marks its successor
dead or writes into its queue. Tests: RestartWindow (a fake strata --serve with a held READY).
2026-10-01 08:17:54 +02:00
Developer 6cd29d9850 serve: keep retained K/V through the cache capacity gate
park_current dropped the retained K/V whenever can_fit failed and fell
back to a full snapshot capture. The gate duplicates what make_room
decides immediately after, with harsher arithmetic: the slot term fires
when make_room enforces the same cap, and the byte term compares the
with-reuse estimate against headroom that ignores the parked image
replacing the retained buffers. make_room already implements the
stated policy - pressure drops the optional retained storage before
evicting any parked conversation (the priority the cache tests pin).
With a full cache the fallback buys nothing: the fresh estimate forces
the same or more eviction, plus a whole-snapshot recapture instead of
the growth - hundreds of ms per park at ~100K tokens where one long
conversation round-trips while short-lived conversations keep the
cache full.

The with-reuse estimate stays uncapped: it counts the retained buffers'
capacity and directories, and put() charges that same true size, so a
capped figure would under-evict and overfill the budget.
2026-10-01 08:15:27 +02:00
Pravesh Khatana b0711943b2 feat(mtp): add --coupled-draft and --no-coupled-draft CLI options for coupled speculative sampling
- Add set_coupled_draft() and coupled_draft_state() in include/strata/core/coupled_draft.hpp
- Add --coupled-draft and --no-coupled-draft CLI options to src/program/generate.cpp
- Default option to STRATA_SPEC_COUPLED environment variable for full backward compatibility
- Update usage() help text with option descriptions
2026-10-01 08:15:27 +02:00
Hozák Martin 43cd140ecd bench: BF16 GEMV via Turing tensor cores on sm_75 - measured negative
Isolated benchmark (no inference-path changes) comparing the production
bf16_gemv warp kernel against an mma.sync.m16n8k8 tensor-core variant with
BF16->FP16 conversion, on the real decode GEMV shapes (n_in = 2560). Three
TC variants (W preconverted / inline conversion / fused output) and a full
k-split occupancy sweep; parity against an fp64 reference.

Result: the TC variant is 1.3-2.2x slower on every real shape even at its
best config - the warp kernel is already bandwidth-bound (374 GB/s) and a
single-token GEMV wastes 15/16 of the m16 MMA tile. Keeping the warp
kernel; the benchmark is checked in so this does not have to be measured
twice.
2026-10-01 08:15:27 +02:00
Zac Chen 4e1a7c5982 verify: profile columns stop wrapping (unsigned stamp deltas) and the hc-read second half stops overwriting the next layer's base stamp
kProfPer 32 -> 33: fused_gr_read_multi writes three stamps per half and the second
half's base was 30, so its up-stamp landed at slot 32 = (l+1)*32 + 0, the next layer's
stage-0 base.  Derived columns (gap/head/hc0) also wrapped to ~1.8e19 ns whenever a
slot was unstamped, printing ~1e14 ms per window.  A delta now counts only when both
stamps are nonzero and monotonic; the hc0 split replaces the single hc-read0 column
only when the split chain is complete.  Metrics-only: no computed model value changes.
Reason recorded in PORTING.md 19.
2026-10-01 08:15:27 +02:00
sergiywith 7f2c76e18d iq_pack: keep experts.bin only when its blobs match the GGUF
An existing experts.bin was kept whenever its size matched the layout. A different checkpoint with the same geometry
(a re-quantization or a fine-tune packed into the same folder) has the same size, so the engine silently served the
old experts with the new model's dense weights.

Before keeping the file, the first and last blobs of the first, middle and last layers are now compared with the
GGUF; a mismatch rewrites it. The check reads six blobs, so an unchanged pack is still reused at once.

Test: test_experts_bin_is_kept_only_with_this_ggufs_blobs packs two GGUFs of the same geometry into one folder (it
fails on the previous code).

Rebased onto 0.1.31 (Niko1221): 0.1.31 already refuses an experts.bin whose sidecar (experts.bin.src.json: shard
names and sizes, the hash of native_experts.txt) does not match, so the six-blob comparison runs only when the
sidecar says "reuse" - it catches the case the sidecar cannot (a re-quantized or fine-tuned checkpoint under the same
file names and sizes). It reads each of the six expert blobs on its own instead of concatenating the whole layer.
The test packs the second model under the first one's file name, so it reaches the new check.
2026-10-01 08:15:22 +02:00
Niko1221 d672a5f443 #287 follow-up: setup run again without --draft-vocab keeps the model's earlier choice
The full setup rewrites the model's config and refreshed the MTP folder's subset with `a.draft_vocab or "cjk"`, so
running it again without the flag put the CJK subset back over a cyrillic (or en) choice, and dropped draft_vocab from
the config. saved_draft_vocab() reads the choice the config holds; the flag still wins. tools/test_setup_draft_vocab.py.
2026-10-01 08:14:00 +02:00
sergiywith 79aa0bb11b setup: --draft-vocab cyrillic - the English/code subset plus the whole Cyrillic script
The shipped draft subsets hold 142 of the vocabulary's 18,580 Cyrillic tokens, so an answer in Ukrainian, Russian,
Bulgarian or Serbian drafted almost nothing. data/draft_vocab_cyrillic.bin is draft_vocab_en.bin plus every token
whose text is Cyrillic (58,963 ids), built with the repository's own tool:
  python tools/draft_vocab.py --gguf <IQ2_XS shard 1> --base data/draft_vocab_en.bin --add cyrillic

- tools/draft_vocab.py: a "cyrillic" script (U+0400-052F, 1C80-1C8F, 2DE0-2DFF, A640-A69F).
- setup.py: --draft-vocab cyrillic; docs/DETAILS.md.

On the NVFP4 fork: 1.4 -> 2.1 tokens per round, 83 -> 109 tok/s in Ukrainian answers, English unchanged.
2026-10-01 08:13:17 +02:00
sergiywith fcef1b0dbd strata-vision: --flash-attn on|off|auto; on the CPU no CUDA context, no warm-up, a thread per core
- --flash-attn on|off|auto (mtmd's flash_attn_type): flash attention keeps K and V in FP16. On the NVFP4 fork the
  vision tower's output was 11.2% off with it on the GPU; FP32 attention (CPU) 0.1%.
- On the CPU a CUDA build keeps the GPU unseen (CUDA_VISIBLE_DEVICES=-1 before the runtime starts): it otherwise
  opened a context there, 0.4-0.7 GB of VRAM the expert cache loses.
- On the CPU without --threads: one thread per core (mtmd's default is 4); no warm-up encode (~6 s of start).
- serve/server.py: relative exe/mmproj/model paths in the vision config are the config's cwd's, as the engine's are.
2026-10-01 08:12:47 +02:00
Niko1221 7f49b76680 #278 follow-up: the request-line follower starts once per log; its time field is prompt_read, not ttft
- StrataEngine.restart() runs __init__ again (after a crash, an idle unload's reload), which started one more
  STRATA_REQUEST_LINES follower each time and printed every request line once per restart: a follower is started
  once per log path (the log is opened for append, so the first follower keeps reading the same file).
- The field the follower printed as "ttft" is the engine's prompt read time (the new tokens only, without the queue,
  the template, the first decode step): it is named prompt_read.
2026-10-01 08:12:35 +02:00
sergiywith 4c2cd14f5f serve: /v1/messages/count_tokens, Anthropic thinking only when asked, STRATA_REQUEST_LINES, a relative exe
Found by running Claude Code against the server (claude -p, three tool turns) and by starting a config with a
relative engine path:

- POST /v1/messages/count_tokens answered 404; Claude Code asks it for its context figures. It now renders and
  tokenizes the request exactly as /v1/messages would, without running the model.
- A /v1/messages request with no "thinking" (and no effort) got the template's default, which thinks: Claude Code's
  helper calls (a ~60-token session title) came back with a thinking block and no text. Anthropic's thinking is
  opt-in, so such a request now renders with enable_thinking false. A config's reasoning_effort still applies;
  "thinking" set, or an effort, behave as before.
- STRATA_REQUEST_LINES=1: a thread follows the engine log and re-states each request's summary line on stdout, for
  supervisors that only see the server's stdout (a tray, llama-swap). Off by default.
- A relative "exe" in the config failed in Windows' CreateProcess (WinError 2): it is made absolute against the
  config's cwd, as the engine's cwd already is.

Tests: count_tokens equals the input_tokens /v1/messages reports for the same request; the request line pattern
parses the engine's summary. python -m unittest serve.test_server: 44 OK.
2026-10-01 08:12:11 +02:00
Niko1221 158e85bf43 #289 follow-up: a placeholder GPU name when the properties query fails; HIP builds compile (no device_code_error probe)
- generate.cpp: the startup line names "(an unnamed GPU)" when cudaGetDevice/cudaGetDeviceProperties fail (the
  error is cleared), and the "no code for" message uses the same name; a HIP build prints the gfx architecture
  instead of a CUDA compute capability.
- device.cu: device_code_error() returns "" on HIP - cudaFuncAttributes/cudaFuncGetAttributes have no mapping in
  hip_compat, and gpu_arch_problem() already stops a HIP run on an architecture the build lacks.
- fused_gr.cu: STRATA_EMULATE_CC also answers the two compute-capability queries there (small tile, the sm_70
  capacity rule), as the PR's description promises; with the variable unset nothing changes.
2026-10-01 08:11:39 +02:00
sergiywith a606e3a966 Start: name the GPU and stop at once when the build has no code for it; STRATA_EMULATE_CC for tests
- generate.cpp: before the expert arena loads, the engine names the card and its compute capability and checks that
  the build has code for it (device_code_error: cudaFuncGetAttributes on a kernel). A binary built for other GPUs
  used to fail at the first kernel, after the whole arena had loaded; now it stops in ~0.3 s and says why.
- include/strata/core/emulate.hpp: STRATA_EMULATE_CC=75|80|86|89 makes the host's device queries (compute
  capability, shared memory per block) answer as that generation, for a PTX build of it JIT-compiled on a newer
  card - ggml's MMQ config, the QSA kernels' choice, fused_gr's shared memory. Tests only.
- STRATA_QSA_WARP=1|select|attn: the pre-sm_80 QSA kernels on any card (A/B).
2026-10-01 08:11:23 +02:00
sergiywith aea6e45c64 diagnostics: STRATA_DUMP_FIRST_LOGITS writes the first generated token's logits
STRATA_DUMP_FIRST_LOGITS=<file>: the logits of the first window's first position (the token the prompt predicts),
n_vocab float32, written once per run. Two engine variants run on the same prompt then give a first-token KL
divergence: how far a prompt-path change moves the model's distribution, not only whether its top token changed.
On a layer split the last stage's verifier writes it.
2026-10-01 08:09:42 +02:00
Niko1221 cfb1866ec5 HIP (S6): router_top10.cu - drop a nested #if that left the HIP block unterminated 2026-10-01 08:07:46 +02:00
Niko1221 a2be1670bb HIP (S6): router_top10 without its serial parts on AMD, bitwise the same (default on HIP; STRATA_HIP_ROUTER_OLD=1: old)
On RDNA4 the router took 39 us per call in decode (16% of the decode GPU time): 512 dependent FP64 adds, ten
selection passes with 20 block barriers, a renormalisation re-reading its weights from global memory. The sum's only
use is inv = (float)(1.0 / sum): all terms are positive with the largest exactly 1, so the serial sum and a tree sum
are within 2^-43 of each other; when the bracket tree*(1 -+ 2^-40) maps to one float, that float is the serial sum's
(1/x and the cast are monotone), otherwise thread 0 runs the serial sum as before. The selection runs on wave 0 from
registers (the argmax with the lowest index on ties is unique), the renormalisation from registers in rank order.
hip_router_fast checks ids and weights bitwise against the portable kernel (65,536 rows with ties, near-ties, NaN
rows, the forced serial path; and 256 experts / k 8). CUDA builds unchanged (HIP-only code).
2026-10-01 07:24:19 +02:00
Niko1221 73ba449679 HIP (S6): RDNA4 matrix-core prompt attention for int8 KV, opt-in (STRATA_HIP_WMMA=1, gfx12 only)
The v2 tensor-core design with gfx12's v_wmma_f32_16x16x16_f16: one block per (query, KV head), wave w owns
dims [64w, 64w+64) (int8 scale group w), q and p split into FP16 hi + lo parts, int8 codes exact as FP16, scales in
FP32, the four groups' q.k partials added in a fixed order, online softmax over 32-cell chunks. Masked
(non-resident) cells get no weight, as in the decode kernel. FP32-level accuracy, not bitwise equal to the default
kernel, hence opt-in. hip_prompt_attn_wmma = qsa_prompt_attn_parity built for HIP (skips off gfx12).
NVIDIA builds unchanged (all new code is under __HIPCC__ / the HIP test section).
2026-10-01 06:44:23 +02:00
Niko1221 6ac1a2cc5e Engine 0.1.31: Unsloth UD-Q4_K_XL runs on a 64 GB PC (experimental: split GGUF, Q4_K/Q5_K/Q5_1/Q8_0 experts, RAM budget + SSD tier with routing prefetch), the low-RAM mode reads GGUF experts in place, Windows GGUF load 2x (#230), server status race (#266) and tokenizer (#268) fixes, truncated tool calls (#211/#231), AMD intrinsics (#262); setup requires it 2026-10-01 06:27:52 +02:00
Niko1221 9195d989a4 Merge branch 'f-q4-on242' into r131-final 2026-10-01 03:11:54 +02:00
Niko1221 2903837bbe docs: Unsloth UD-Q4_K_XL manual import (docs/UNSLOTH_Q4.md); DETAILS: the mapped mode without experts.bin, --resident-budget-gib, the tier counters
- docs/UNSLOTH_Q4.md: what UD-Q4_K_XL is (Hugging Face revision 38bb39e, the four shards with sizes and SHA-256),
  disk/RAM/GPU needs (111.3 GB download, 1.4 GB pack, no experts.bin), the iq_pack.py command and why
  --compat-bf16, the numerical policy (native experts/embedding/head/projections/PLE; the converted small tensors
  listed in conversions.json), the MTP draft layer, a server config (--native shard 1, --ple-gguf found by name,
  --resident-budget-gib N with N = RAM minus 20-24 GB), measured speed on an RTX 5070 / 64 GB PC (7-8.5 tok/s at
  40 GiB; ~60 s to the first answer), the switches (STRATA_LOOKAHEAD=0, STRATA_KQ256=1, STRATA_PARTIAL_PIN=1,
  STRATA_FETCH_THREADS), scope and validation, credits (eddoursul #245, gopinath87607 #255, jagsan-cyber #247).
  Experimental, not in setup.
- docs/DETAILS.md: the mapped low-RAM mode reads a native pack's experts from the GGUF when it has no experts.bin
  (checked, identical answers on the Coder); --resident-budget-gib and the routing-aware prefetch; where the tier
  counters are (--stats, the server log, /metrics' ram_blobs / file_blobs / file_mb); a pointer from "Which model?".
2026-10-01 03:11:36 +02:00
Niko1221 0699e466ad Merge branch 'f-q4-on242' into r131-final 2026-10-01 03:03:40 +02:00
Niko1221 a74acb6dd2 CS-T: the partial pin of the RAM tier opt-in (STRATA_PARTIAL_PIN=1)
On the RTX 5070 / 64 GB PC the GPU computing a share of the misses over PCIe measured no faster than the CPU:
7.30 and 7.44 tok/s with 24 and 16 GiB of a 40 GiB budget registered, against 7.05-7.74 unpinned, and
registering costs startup time and driver memory.  The default budget mode is now locked-only; the routing-aware
prefetch (multi-run A/B: 6.93 -> 7.91 tok/s mean, identical answers) stays on with the GGUF in place.
2026-10-01 03:03:17 +02:00
mad9home 140a8730a3 Bench: RTX 3090 / dual RTX 3090 on EPYC 7453
Add preliminary native v0.1.26 results, raw run JSON, scrubbed guest hardware facts, and the local API harness. Prompt and memory topology limitations are documented.
2026-10-01 02:52:39 +02:00
Omar Al-Jadda b73e770212 docs: add RTX 5090 community benchmark results 2026-10-01 02:52:39 +02:00
Omar Al-Jadda e51da24c52 docs: add community benchmark submission guide 2026-10-01 02:52:39 +02:00
Niko1221 0dac2b9aa1 CS-T: routing-aware prefetch of the file tier - the next layer's router on this layer's MoE input warms its predicted experts' pages
- RouterLookahead: when the CPU pool starts layer l, a worker thread applies layer l+1's router (a BF16 host copy
  of ffn_gate_inp, 126 MB for 48 layers) to layer l's MoE input - the router's own input - for each token of the
  window, takes the top STRATA_LOOKAHEAD_K (default 10), drops the experts the GPU cache (host_res) or the RAM
  copy holds, and asks the source to warm the rest.  It never waits: a prediction still running makes the next
  one skip.  STRATA_LOOKAHEAD=0 is the A/B arm.
- ExpertSource::warm / warms(): FileExpertSource with the GGUF in place issues one PrefetchVirtualMemory for the
  predicted slices (madvise(WILLNEED) elsewhere).  Pages only: blob(), the experts computed and their bytes do not
  change (the same answers in every A/B run).  Every other source: no-op, and the predictor is not started.
- bf16_rows_dot_multi (kq_avx2.cpp, AVX2): the router read once for all tokens of the window.
- Stats: experts warmed, how many of the decode's file-tier reads had been warmed, the predictor's time and skips.

UD-Q4_K_XL at a 40 GiB budget (single-token-router version): ~48-54% of the file tier's reads had been warmed;
reading time 850-968 -> 630-730 ms per verify round; decode 5.31/6.60 tok/s off vs 7.08/6.79/6.91 on.
2026-10-01 02:30:24 +02:00
Niko1221 a37491ede4 tests (#264 follow-up): iq_parity skips (exit 3) when no fixture was generated, as its generator does; a partly missing set still fails; the open handle of a half-present fixture is closed 2026-10-01 02:27:40 +02:00
Andy aa0777f7aa decode: hc read v3 is opt-in (STRATA_GR_V3=1: another summation order); its shared memory is sized from the card's opt-in limit - one or two column halves, else the default kernels (Turing/HIP 64 KB fit with two; capability-based split as proposed by @Adamyno); the neutral knobs (DOWN_R2, UP_PF, KSPLIT) dropped (review) 2026-10-01 02:14:16 +02:00
Andy 784cdc5d08 decode: hyper-connection read in 2 kernels - norm folded into a stream-split down kernel (164 blocks), rs/lo/inject in up's prologue (STRATA_GR_V3=0 = old) 2026-10-01 02:14:16 +02:00
Niko1221 7d8fc8fd04 CS-T: the partial pin locks only what it does not register and ends at an expert boundary; the Q4_K/Q5_1/Q8_0 multi-token kernels opt-in (STRATA_KQ256=1)
- Locking the whole 40 GiB arena in the working set and then registering its prefix made the next device
  allocation fail (the prompt path's token id buffer); the registered prefix is page-locked by the driver
  already, so only the rest is locked now.  24 GiB registered out of a 40 GiB budget then starts and runs.
- The registered prefix ends at an expert boundary: a blob that began inside it and ran past it was taken as
  page-locked by cudaMemcpyAsync and refused (an adaptive refill failed, with 16 GiB registered).
- The prompt path's token id buffer error names the CUDA error.
- kq_avx2 multi-token kernels: bit-exact, but no faster in the engine (the rows phases stayed at 72 ms per
  verify round at a 40 GiB budget; a window's expert groups hold ~1.4 tokens and ggml's per-token calls find the
  weights in L1), so they are opt-in with STRATA_KQ256=1; native_expert_parity still checks them bit for bit.
2026-10-01 01:58:30 +02:00
Niko1221 a88904e530 Revert #257 (AVX2 two-block Q2_0 unpack): bit-exact but not faster on an AVX2-only CPU (Ryzen 9 3900X: gate/up -2.6..-4.7% at 1 token, down -3..-5% at 2 tokens)
This reverts commit f121667.
2026-10-01 01:33:15 +02:00
Niko1221 9305322ff5 Merge branch 'f-131-amd' into r131-amd
# Conflicts:
#	src/core/pinned.cu
2026-10-01 01:33:10 +02:00
Niko1221 18ad40c0dc --split-skip-if-fits (opt-in): an auto split runs on the main card alone when it holds every profiled expert + KV
Before any later stage loads, split-auto checks whether CUDA0 alone holds every profiled expert pair plus the whole
session (KV of every layer), the drafter + head, the verify windows and the reserve; if so it clears the split (one
GPU) and logs why, else it logs how much of the profile CUDA0 would hold and keeps the split. Default behaviour is
unchanged: the engine flag is opt-in, and the server passes it only for a config with "split_skip_if_fits": true
(and a GPU list). Not with remote expert caches.

Measured on amdbox (Coder IQ1_M, --expert-cache auto --prefill auto, 128 greedy tokens, gpu [1, 0] = R9700 first,
2 starts per arm, warm = the second request of each size):
  | arm                          | 4K warm   | 16K warm  | decode    |
  | split (flag off)             | 1,244     | 1,897     | 50.5-53.0 |
  | --split-skip-if-fits (on)    | 1,776     | 1,791     | 59.7-61.8 |
  | R9700 alone                  | 1,787     | 1,801     | 59.9-61.3 |
The flag's run is the R9700-alone run (same tokens, all four requests); with the 9070 XT first it keeps the split
("would hold only 9.84 of the profile's 23.42 GiB"). On this pair the split still reads 16K prompts ~5% faster.
2026-10-01 01:31:22 +02:00
Niko1221 9f8fa9088e #253: the 8 GiB arena pin cap only under WDDM; STRATA_ARENA_PIN_GIB; the split's prompt chunk steps down
- The expert arena was pinned at most 8 GiB whenever a layer split (or remote experts) ran, on every OS - a
  Windows/WDDM workaround that cost a Linux 4090 + 3060 split two thirds of its prompt speed (#253,
  bettercallcaleb). The cap now applies only under WDDM (_WIN32, or WSL2 detected by /dev/dxg), for multi_gpu and
  expert_cache_remote alike. STRATA_ARENA_PIN_GIB overrides it on any OS: "0" pins the whole arena, N > 0 at most
  N GiB (arena_pin_cap_gib(), -1 when unset: the same name and meaning as the S2 branch's helper, file-local here).
- The split's prompt chunk no longer stops the start when its buffers do not fit: a prompt path without a loan
  first gets the largest chunk whose buffers (Prefill::bytes_needed) leave 512 MiB free on its device, and an init
  that still fails steps down one size (8192 -> 6144 -> ... -> 2048 -> 1536 -> 1024 -> 512): Prefill::reset() frees
  what the failed init made, the loans are re-priced, and the failed allocation's sticky error is consumed (else the
  next launch check reported "out of memory").
- pinned.cu: the capped note no longer says "for CUDA1".
- docs: MULTI_GPU.md, SECOND_GPU.md.

Measured on amdbox (Linux, R9700 + RX 9070 XT, gpu [1, 0], Coder IQ1_M, 128 greedy tokens; tok/s):
  | cache                  | arm          | 4K cold | 4K warm | 16K cold | 16K warm | decode    |
  | auto (holds ~all)      | 8 GiB cap    | 678     | 1,355   | 1,709    | 1,880    | 44.5-51.5 |
  |                        | whole arena  | 703     | 1,234   | 1,729    | 1,864    | 44.6-51.4 |
  | fixed 1500 on CUDA0    | 8 GiB cap    | 608     | 1,101   | 1,287    |          | 34.5-36.1 |
  |                        | whole arena  | 716     | 1,160   | 1,803    |          | 34.8-36.0 |
(2 starts per arm for auto, 5 for fixed.)  With most experts streamed the whole arena reads a 16K prompt 40%
faster; with every expert already on the cards the two are within noise except 4K warm (-9%: the pinned share
raises the auto lend to 90% and the streamed ring to 384 slots).  Identity: the fixed-cache arms gave the same
tokens in all 5 + 5 starts (4K, 16K, 4K again), and the auto arms 2 + 2.  The step-down was exercised on the
9070 XT with --expert-cache 3000 --no-prefill-borrow --prefill 8192 ("need 4528 MiB, 2910 MiB free: 2048-token
chunks"; with the init retry alone it went 8192 -> ... -> 2048, and 1536 ran all four requests).  Known, also on
main: that own-buffer configuration at 2048 makes hipBLAS pick hipBLASLt kernels whose code objects this ROCm 7.14
lacks (TensileLibrary_..._gfx1201-xnack+.co not found, "launchKernel failed") and the prompt hangs.  The WDDM side (Windows, WSL2) is unchanged and not re-measured here.
2026-10-01 01:31:22 +02:00
Niko1221 d484ec735f CS-T: the GGUF-in-place prefetch asks Windows for all of a layer's missed slices at once (PrefetchVirtualMemory)
The copies' page faults read a few clusters each; one PrefetchVirtualMemory call for every role slice about to
be copied lets the memory manager queue large reads at once, and the parallel copies then find the pages
resident or in flight.  Only the GGUF-in-place source's prefetch uses it; STRATA_FETCH_PVM=0 is the A/B arm.
2026-10-01 01:12:36 +02:00
Niko1221 f8525fa0b0 CS-T: the partial pin at most 24 GiB (STRATA_PARTIAL_PIN_GIB), the Q4_K/Q5_1/Q8_0 multi-token kernels from two tokens on
- Registering 30 GiB of a 40 GiB RAM tier left the driver unable to page-lock the prompt path's token id buffer
  afterwards (RTX 5070, WDDM: "prefill: the token id buffer"); the prefix is now capped at 24 GiB, the size a
  whole 24 GiB budget page-locks with, and STRATA_PARTIAL_PIN_GIB sets it.
- A verify window's expert groups hold ~1.4 tokens on average; one token goes to ggml's own dot (the same bits,
  without the multi-token kernel's per-token arrays).
2026-10-01 01:11:50 +02:00
Niko1221 4167bd17ac Merge branch 'f-q4-on242' into r131-q4
# Conflicts:
#	serve/server.py
#	src/core/expert_source.cpp
2026-10-01 01:01:04 +02:00
Niko1221 13b76cc5f8 Merge branch 'f-131-srv' into r131-q4 2026-10-01 01:00:27 +02:00
Niko1221 003ec62140 CS-T speed: the file tier fetched in parallel, PCIe planning without file reads, a partial pin of the RAM tier, bit-exact AVX2 multi-token kernels for Q4_K / Q5_1 / Q8_0 experts, decode-only tier stats
Where the 288 ms/token at a 40 GiB budget went (UD-Q4_K_XL, --stats): of the 945 ms CPU pool call per verify
round (3.46 tokens), 871 ms were the dispatch thread assembling missed experts from the mapped GGUF one after
another (~391 blobs, 1.3 GB per round, ~1.5 GB/s: page faults from the SSD); the kernels were 42 ms gate/up and
30 ms down.  At 24 GiB, 771 ms/round more went into the PCIe planning, which asked blob() - a file read with the
GGUF in place - of every miss just to learn it was not pinned.

- ExpertSource::prefetch: the dispatch hands the source the layer's missed experts before its jobs loop;
  FileExpertSource with the GGUF in place assembles them on STRATA_FETCH_THREADS threads (default 8) so the
  SSD sees a queue instead of one fault at a time.  A no-op for every other source.  Staging buffers being
  filled are never reused, and a blob asked while its fill runs waits for it.
- The PCIe planning asks pinned() before blob(): the same decisions and bytes for every source.
- --resident-budget-gib: when the driver refuses to page-lock the whole budget, the locked arena's hottest
  prefix (profile order) is registered with cudaHostRegister, as much as it takes (from the whole down in 2 GiB
  steps), so the GPU can read those misses over PCIe and the prompt path can DMA them; pinned() answers for
  that prefix only.  STRATA_PARTIAL_PIN=0 is the A/B arm.
- src/kernels/cpu/kq_avx2.cpp: ggml-cpu's AVX2 vec_dot for Q4_K x Q8_K, Q5_1 x Q8_1 and Q8_0 x Q8_0 with the
  token loop inside the block loop - the weight side decoded once per verify window, each token's integer chain
  and float operations exactly ggml's, so the results are ggml's to the bit for any group size (no #152 rule).
  native_gu_rows / native_down_rows use them for those types (only UD-Q4_K_XL has them); STRATA_NO_KQ256 is the
  A/B arm.  native_expert_parity checks them bit for bit against vec_dot for 1..8 tokens and times 4 tokens.
- Stats: the decode's file tier alone (blobs, MB, MB/round, reading ms/round and GB/s per thread) and the
  prompt path's copies separately.
2026-10-01 00:51:30 +02:00
Niko1221 f73b0e062a pinned (#243 follow-up): the shared-memory-budget cap on the sliced pin is opt-in (STRATA_ARENA_PIN_GIB=auto) - by default the arena pins as in 0.1.30; on this 64 GB PC the cap pinned 26 instead of 30 GiB and cost ~20% prompt speed and 2-4% IQ3_S decode 2026-10-01 00:49:49 +02:00
Niko1221 ebe52edd8b setup: AMD layer splits with --gpus (every chosen card a supported AMD arch; the engine compiled for each)
setup refused AMD GPU lists at install and at start ("NVIDIA only for now"), although the engine's layer split runs
on two AMD cards (0.1.30, from a hand-written config).
- amd_parse_gpus(): --gpus "1,0" (setup's HIP numbering, the first card the main one) or "all" (every supported
  card, the most VRAM first); every card must be in AMD_ARCHS, else a plain message with what can be used.
- install: --backend hip --gpus writes "gpu": [..] + "layer_split" like the NVIDIA path; build_engine_hip compiles
  for the set of the chosen cards' archs (CMAKE_HIP_ARCHITECTURES "gfx1100;gfx1201" already worked) and records
  it in BUILD.json; an engine whose archs cover the cards is not rebuilt; update_installed_engine keeps every arch.
- rocm_root(archs): cards of two families need a system ROCm (TheRock's wheels hold one family) - said plainly.
- start: --gpus on an AMD install is saved like on NVIDIA (refused when the engine has no code for a card);
  --gpus all finds the AMD cards on a PC without NVIDIA cards that can share.
- docs/AMD_HIP.md, README; tools/test_setup_amd.py: lists, "all", refusals, the one-family wheel rule, the arch set
  in the build and BUILD.json.
2026-10-01 00:35:39 +02:00
489623c7d3 AMD: gfx1101 (RX 7800 XT, #254 by jhohertz) and gfx1200 (RX 9060 XT, #256 by Efeisot) as community-validated cards
Cut down from #254 and #256 onto 0.1.30's AMD tables:
- setup.py: AMD_ARCHS / AMD_NAMES / ROCM_INDEXES / AMD_CARDS list gfx1101 (TheRock gfx110X-dgpu) and gfx1200
  (gfx120X-all); amd_gpus() takes the sysfs root so the detection can be tested.
- cmake/hip_backend.cmake: gfx1101 and gfx1200 build without the "not validated" warning (a status line names
  them community-validated); gfx1102 (#192: ctest only) keeps the warning.
- docs/AMD_HIP.md (Community-validated cards: the owners' setups, ctest and numbers) and README.
- tools/test_setup_amd.py: detection on a mocked KFD topology (every family, the CPU node, an iGPU and gfx1102
  listed as not supported, product-name fallbacks, the ROCm index per family).
Left out: the PRs' own cmake/device.cu arch checks (0.1.30's compiled_for covers them), the dp4a change (main has
it), math_constants.h, the bench JSONs, the launcher, and both hipBLASLt tables (neither PR measured the prompt speed
without its table).

Co-Authored-By: Joseph Hohertz <[email protected]>
Co-Authored-By: efeisot <[email protected]>
2026-10-01 00:35:39 +02:00
Niko1221andttio2tech 600d73070c HIP: __byte_perm as v_perm_b32 and SWAR __vsub4/__vsubss4/__vcmpne4 (from #262 by ttio2tech)
Cut down from #262 (ttio2tech): only the intrinsics and their test.
- HIP's __byte_perm indexes a byte array in private memory (scratch traffic in the IQ4_XS/Q2_0 table
  lookups); byte_perm() is the same selection (nibble & 7 of the pair {y:x}) in one v_perm_b32.
- __vsub4, __vsubss4 and __vcmpne4 work on all four byte lanes at once (Hacker's Delight 2-18) instead
  of a per-lane loop.
- tests/hip/intrinsics checks them on 1,024 random operands against host references (passes on gfx1201).
Left out: the arch/setup/device.cu parts (0.1.30 already builds and checks gfx1201; main's dp4a covers
gfx1100-1102/1200/1201) and the gfx1201 hipBLASLt table (none is shipped for gfx1201).

A/B vs main on amdbox (Coder IQ1_M, 128 greedy tokens): identity with a fixed cache (--expert-cache 1500
--prefill 2048), 5 + 5 starts per card, 4K / 16K / 4K again: the modal outputs are identical on the R9700
and on the RX 9070 XT (one start of 20 differed from its arm's mode, the known HIP start drift). Speed with
setup's settings (auto cache/chunk), 2 + 2 starts per card, tok/s:
  | card         | arm  | 4K cold | 4K warm | 16K cold | 16K warm | decode    |
  | R9700        | main | 1,027   | 1,792   | 1,695    | 1,812    | 46.0-52.2 |
  |              | #262 | 1,023   | 1,786   | 1,681    | 1,808    | 53.0-61.0 |
  | RX 9070 XT   | main | 781     | 1,018   | 1,421    | 1,517    | 32.7-41.4 |
  |              | #262 | 780     | 1,017   | 1,424    | 1,517    | 34.9-44.0 |
Decode +15% on the R9700, +5-7% on the 9070 XT; prompts unchanged.

Co-Authored-By: ttio2tech <[email protected]>
2026-10-01 00:35:39 +02:00
Niko1221 5865998af9 verify (#267 follow-up): the window's final wait stays a blocking sync - the cudaStreamQuery poll cost IQ3_S ~3% decode; the stall watchdog's release of every verifier's GPU waits is what keeps a spin kernel from outliving the engine 2026-10-01 00:26:23 +02:00
Niko1221 1e663cc70e CS-T: native packs read their experts from the GGUF shards in place (--mmap-experts without experts.bin), --resident-budget-gib N, per-tier counters
- FileExpertSource: a native pack without experts.bin, given the --native shard, maps every GGUF file
  native_experts.txt names (after check_experts_gguf) and assembles a blob [gate | up | down] from the
  three role slices when asked: into a small pool of buffers for blob() (a buffer is reused once its blob
  has not been asked for during 3 layers (begin_layer) or 256 assemblies), or straight into the caller's
  memory for copy_blob().  No 30-77 GB experts.bin on the disk.  With experts.bin present nothing changes.
- ExpertSource::transient / copy_blob: the prompt path's stager copies a transient blob itself (its queue
  holds a whole chunk's experts), everything else keeps using blob().
- --resident-budget-gib N (the resident mode with a budget): the experts the GPU cache does not hold are
  copied into RAM in expert-profile order while they fit N GiB (clamped to available RAM minus the
  headroom), the rest are read from the files; no lend region then.  Without the flag the resident mode
  and --mmap-experts are unchanged.
- Counters: blobs served from the RAM copy, from the files, and bytes read from the files; in the stats
  table ("expert tiers"), the serve log, and appended to DONE (RAM blobs, file blobs, file MB for the
  request), which serve/server.py puts into /metrics' request history (older DONE lines still parse).
- expert_layout_test: the GGUF-in-place source gives the loaded arena's bytes through blob() and
  copy_blob(), an assembled blob stays put while in use, and experts.bin still wins when present.
2026-09-30 22:51:03 +02:00
Niko1221 ee7c802664 iq_kernels: Q4_K / Q5_K / Q5_1 / Q8_0 native experts (Unsloth UD-Q4_K_XL), per-role format lists, native_expert_supported at startup, Q8_0 embedding and PLE key
- Dots: llama.cpp's vec_dot_q4_K / q5_K / q8_0_q8_1 transcribed (VDR 2), via eddoursul/Strata
  8029fa9 and #255 (gopinath87607, Q8_0), which agree.  Q5_1's min term multiplies the sum of the
  QUANTIZED activations (ggml-cpu's convention, as the fork does), not llama.cpp CUDA's ds.y.
- Dequantizers dq_q4_k / dq_q5_k / dq_q5_1 / dq_q8_0 (llama.cpp's formulas) for the prompt path's FP16
  fallback, f32 dumps and the Q8_0 embedding; iq_row_bytes / is_iq know the four types;
  iq_dequant_gu_f16 now refuses an unknown type instead of leaving its output unwritten.
- The grouped gate/up, down and iq_mmvq switches are generated from STRATA_GU_FMTS /
  STRATA_D_FMTS / STRATA_MMVQ_FMTS; the existing entries launch the same kernels as before.
- native_expert_supported(gu, d, n_embd, n_ff): generate checks every layer of a native pack right
  after expert_layout_load and refuses an unsupported pair by name before anything is allocated
  (it used to exit from inside the first verify window).
- Native PLE key: Q8_0 accepted (NativeDense, generate, ple.cu); native_mmvq already runs it.
- native_expert_parity: --synthetic GU/DOWN (random ggml-quantized blobs at 2560/640; port of the
  fork's mode), GPU dequant f32/f16 and embedding rows vs ggml to_float bit for bit, split models via
  GgufModel, --q5_1-min.  CTests: q4_K/q5_1, q4_K/q8_0, q5_K/q8_0, q8_0/q8_0, the Q5_1 min term, and
  q6_K refused.  RTX 5070: GPU and CPU within 1.25-1.37e-2 of the float reference for all three real
  pairs; every dequantized value identical; Q5_1 GPU vs ggml-cpu 7.5e-5 (vs 1.95e-2 for the ds.y
  variant); IQ3_XXS/IQ4_NL and IQ2_XS/Q2_0 unchanged.
- Not yet: #242's decode-once traits for the new types (they take the per-entry kernel; see
  phaseA-tests/s5_cs3_design.md), prompt MMQ for Q4_K/Q5_K/Q5_1, identity gates.
2026-09-30 22:31:09 +02:00
Niko1221 dc24332249 tests: --real modes for gguf_split_test and expert_layout_test (a real model's shards / a real pack's spans, headers only)
`gguf_split_test --real SHARD` opens a real model's shards (GgufModel), checks the architecture on the
metadata shard and prints where output / token_embd / the PLE table and key / layer 11's experts are.
`expert_layout_test --real PACK NATIVE_SHARD` checks a real pack's native_experts.txt against its model
(check_experts_gguf; no expert byte is read).  Both pass on the Q2_0, IQ3_XXS (v2), IQ3_S, Coder IQ1_M
and Swift IQ2_XS packs and models on the reference PC.
2026-09-30 22:30:11 +02:00
Niko1221 24bba1743b iq_pack: each tensor in the form the engine reads (FORM), exact F32->BF16 by default, PLE conv F32 -> F16 by the loader, conversions.json; the engine refuses a wide conv1d
- FORM (eddoursul/Strata's list of which tensor the engine reads as BF16, F16 or F32) decides the
  pack form; the conversion itself is upstream's bf16_bytes (gguf-py, finite check, round-to-nearest-
  even).  By default only lossless conversions happen: F32 whose values are exactly BF16 (low 16 bits
  zero; before, only the routers) and F16/BF16 widened to F32.  Rounding (inexact F32, Q8_0 hyper-
  connection / PLE value / output_hc, F16 -> BF16) still needs --compat-bf16.  NaN/Inf are refused
  even where the low bits are zero.
- A Q8_0 PLE key stays native (served from the GGUF, like Q2_0); other quantized keys take the BF16
  path as before.  (The engine's native PLE key learns Q8_0 in the formats change.)
- blk.1.ple_conv1d stored F32 (Q8_0 / UD-Q4_K_XL files) goes into dense.bin as index kind 3 and the
  loader narrows it to F16 (#255, gopinath87607), with or without --compat-bf16; a value beyond
  F16's range is refused.  generate refuses a pack whose conv1d is not F16 (#255's check, which also
  accepts the canonical Q2_0 pack's verbatim-F16 kind 0 - #255 as written refuses that pack).
- conversions.json (schema 1): the source shards' names and sizes, and per converted tensor its
  shard, source/destination type, method, exact flag, largest absolute error and source sha256.
  compat-bf16.json is written as before.  Refusals happen before anything is published.
- Byte identity: index.txt, native_experts.txt and dense.bin of Q2_0, IQ3_XXS, IQ3_S, Coder IQ1_M and
  Swift IQ2_XS are identical to main's packer (Swift's 96 exact F32 routers are now listed in
  conversions.json).
- Tests: exact F32->BF16 by default, inexact only with --compat-bf16 (error bound), NaN/+-Inf
  refused with and without the flag (previous pack untouched), conv1d kind 3 (bytes, record, F16
  kept, overflow refused), Q8_0 projections need the flag and are recorded, Q8_0 key native.
2026-09-30 22:30:11 +02:00
Niko1221 21844a3ebf iq_pack: per-role shards and data_start (v4 only for a split layer), split keys checked, experts.bin reused only with its source sidecar, atomic publication
- Each expert role is resolved in the shard that holds it and its offset is absolute in THAT shard
  (the first role's data_start was used for all three; #255, gopinath87607).  A layer whose roles
  are in different shards gets the per-role column `gate,up,down` and the file becomes v4; every
  other pack keeps the v3 header and bytes (checked on Q2_0, IQ3_XXS, IQ3_S, Coder IQ1_M, Swift
  IQ2_XS: index.txt, native_experts.txt and dense.bin identical to main's packer).
- The split keys are checked as the engine does (shard 1 has the metadata; split.count / split.no /
  split.tensors.count consistent).  The expert table is computed and checked before any file of
  the pack changes.
- experts.bin is written under a temporary name and gets experts.bin.src.json (the shards' names
  and sizes, the sha256 of native_experts.txt); it is reused only when that matches, never by size.
  An experts.bin of another source is refused without --experts-bin (the engine would read it).
- dense.bin and index.txt are published together after the old completion marker
  (native_experts.txt) is removed, so a stop part-way never mixes files of two runs.
- .absolute() stays (not .resolve()): symlinked HF snapshot names keep the split naming.
- Tests: a synthetic 4-shard GGUF (shard 1 metadata only, layer 1 split per role; offsets per
  shard, experts.bin byte for byte), v3 kept when nothing is split, missing shard, duplicate tensor,
  a shard of another split, truncated shard, wrong type (pack untouched), interrupted pack (experts
  and index), stale same-size experts.bin.  Idea of per-role names also in #247 (jagsan-cyber).
2026-09-30 22:30:11 +02:00
Niko1221 65a1111e3a Split GGUF artifacts: every shard found (a missing one is an error), tensors from their owning shard, per-role expert files (native_experts.txt v4)
Groundwork for Unsloth's UD-Q4_K_XL (4 shards: shard 1 metadata only, output/token_embd/PLE table in
shard 2, layer 11's down in shard 2 and its gate/up in shard 3).  Existing packs load unchanged.

- include/strata/artifact/gguf_split.hpp: gguf_split_paths (from eddoursul/Strata 8029fa9) finds the
  shards from any shard's name; a missing shard is an error naming it.  It replaces generate.cpp's
  model_shards(), which skipped a shard it could not open.
- gguf_reader.hpp: GgufModel (8029fa9) looks a tensor up across the shards and refuses a name in two
  shards; the split keys are validated as #255 (gopinath87607) does for the head: shard 1 carries the
  architecture, later shards declare split.count / split.no / split.tensors.count consistently, and the
  directories add up to split.tensors.count.  tensor_payload_bytes / in_bounds for payload checks.
- NativeHead::load / NativeEmbed::load take the shard list: the architecture from the metadata shard,
  the tensor from the shard that holds it; the embedding also checks its payload is in the file.
- generate: --native resolves all shards once (native_shards), the head from --native-head-gguf's
  family, the model keys from the metadata shard; --ple-gguf defaults to the shard that holds
  per_layer_token_embd.weight.
- native_experts.txt: the shard column may be per role, `gate,up,down` (#255's comma form; empty =
  the --native shard).  The reader accepts v2-v4 and refuses a higher version; an engine older than
  this one fails to open a "gate,up,down" file loudly.  Fixes the parse's own aliasing hazard
  (parts.assign(3, parts[0])).
- load_experts_gguf reads each role from its file, after check_experts_gguf proved every (file,
  offset) is that tensor, of the pack's type and dimensions, and inside its file.
- Tests (synthetic, no model, no GPU): gguf_split_test (4 shards, missing shard, duplicate, split keys,
  count, truncation); expert_layout_test (copies of the Q2_0/IQ2_XS/IQ3_XXS/IQ3_S/Coder/Swift packs'
  native_experts.txt parse field for field as before; v4 column; v5 / 4 / 2 names refused; the arena
  from split synthetic shards at 2560/640 with Q4_K/Q5_1 and Q5_K/Q8_0, every byte compared, and
  wrong offset / file / type / shape / truncated / missing shard refused).
2026-09-30 22:30:10 +02:00
Niko1221 7dc1fae80b mcp: a server that stops quotes its last log line reliably (the stdout reader waited no time for the stderr reader, so the reason was sometimes missing - the flaky test_a_server_that_fails_is_left_out) 2026-09-30 22:13:46 +02:00
Niko1221 c67c90fa03 Merge branch 'f-131-fix' into r131-fix 2026-09-30 22:03:31 +02:00
Niko1221 cd677f7414 Experimental Pascal/Volta build: admit sm_70, and the device check honours it (#236)
-DSTRATA_EXPERIMENTAL_SM60=ON now names Volta (sm_70) beside Pascal (sm_60)
and is a declared option; below sm_60 is refused even then.  The runtime check
in device_info refused anything below compute capability 7.5 even in that
build, so the flag produced an engine that could not start on the cards it was
built for: strata_core now gets STRATA_EXPERIMENTAL_SM60=1 with the flag and
the check admits 6.0 and up there.  The release engine is unchanged (7.5).

Checked here: configuring for 70-real without the flag still fails with the
7.5 message; with it (CUDA 12.6 - CUDA 13 no longer compiles compute_70, noted
in the comment) it configures and strata-device builds with the define.
2026-09-30 21:07:28 +02:00
Niko1221 e1f12eb852 Refuse a CPU without AVX2 at start, by name
Every CPU expert kernel is AVX2 at least (q2_avx2.cpp, iq_avx2.cpp; the
AVX-512 ones are chosen above them) and so is ggml-cpu in the release build,
but nothing checked for AVX2: a pre-Haswell CPU died of an illegal instruction.
cpu_avx2_ok() (AVX, AVX2, FMA, F16C and the OS's XSAVE state) is checked
before the pack's layout load, and the message names the CPU (cpu_name, the
CPUID brand string).  STRATA_FORCE_AVX2 is unaffected (it only answers
cpu_avx512_ok).
2026-09-30 21:04:53 +02:00
Niko1221 b9f426713a Stall report: name the prompt chunk and layer; mark a finished verify window (#251)
The #251 report stalled in "reading the prompt, from token 0" and printed the
previous request's verify window as if it were the stuck one.

- Verifier::diag says "(last window, not the current stage)" when the stage
  is not a verify window.
- The batched prompt read sets its stage per chunk (preparing, finishing) and
  per layer, with the chunk's first position (Progress::chunk, a third
  progress_at argument; every other call leaves it -1), so the stall report and
  the watchdog line read e.g. "reading the prompt (batched): layer 17 of the
  prompt chunk from token 8192".

Text only: nothing computed changes.
2026-09-30 21:03:36 +02:00
Niko1221 50b4fadb08 Verify window: bounded final wait; release the GPU's flag waits before the engine ends (#267)
A verify window the GPU never finished held the host in an unbounded
cudaStreamSynchronize, and the serve watchdog then aborted the process with
the window's spin kernel (wait_flag_ge / wait_flag_ge_or on a host-mapped
flag) still resident - on Windows the GPU was left "lost" until a power cycle.

- The final wait of Verifier::run polls cudaStreamQuery (the same wait while
  the window runs) and gives up after 20 s like the per-layer wait, through
  the existing error path.
- Verifier::release_gpu_waits writes UINT32_MAX into the mapped flags M/A/B
  (past every ring, so each spin kernel returns; wait_flag_ge_or returns on its
  flag too), then waits for the streams up to 5 s; the verifier refuses later
  windows.  Both timeouts run it before returning their error, and the serve
  watchdog and the expert pool's stall aborts run it (progress.hpp
  release_gpu_fn, every live verifier) before std::abort.
- Test hook: STRATA_TEST_VERIFY_STALL=N withholds the last layer's flag in the
  N-th window; unset it is never taken.

The normal path runs the same kernels in the same order.
2026-09-30 21:02:27 +02:00
Niko1221 00ec1d081d Windows: cap the sliced arena pin at the GPU's shared-memory budget (#243)
When cudaHostRegister refuses the whole expert arena, the sliced fallback
pinned layer after layer until the driver refused one (28 GiB on a 63 GB
single-GPU PC), and WDDM then refused every later device allocation
("native head upload: out of memory").  On Windows the sliced pass now stops
at the adapter's DXGI non-local budget - the process's current usage - 4 GiB
(IDXGIAdapter3::QueryVideoMemoryInfo for the CUDA device's LUID; dxgi.dll is
loaded at run time), or RAM/2 - 8 GiB when that query fails.  The arena line
names the cap and where it came from.

STRATA_ARENA_PIN_GIB: unset = the above; 0 = no cap (the old behaviour);
N > 0 = at most N GiB from the start.  arena_pin_cap_gib() (pinned.hpp)
reads it, -1 when unset, for the multi-GPU cap in generate.cpp as well.
A machine whose whole-arena registration succeeds is unchanged, and a
caller's cap (multi-GPU) still takes precedence.

This PC's whole registration fails too: IQ3_XXS now pins 24 GiB (32 slices;
budget 30.8 GiB - 2.0 in use - 4) instead of 28 GiB (36).
2026-09-30 21:02:17 +02:00
Niko1221 4b8db25763 load_experts_gguf reads with fread on a FILE* (#230)
The GGUF branch of the arena load (a native pack without experts.bin, the
normal install) read through std::ifstream, which MSVC splits into 4095-byte
freads - 0.02 GiB/s on the reporter's PC.  It now uses the same fopen/fread
with a 64-bit seek as load_experts_ranges (#89), with an RAII close.  Same
chunks, same bytes, same scatter.

Measured here (IQ3_XXS pack without experts.bin, RTX 5070, 64 GB): 1.49-1.60
GiB/s before, 3.17-3.25 GiB/s after (start 45-48 s -> 29-34 s); the generated
tokens are the same.
2026-09-30 21:02:08 +02:00
Niko1221 912a931f0f README: where things are stored (#226)
Chats (and the Chat tab's settings and API key) only in the browser's local storage (strata.* keys), the start
config strata-<model>.json and its siblings in the Strata folder, the model files in Strata-data next to it (or
--data-dir), and where that folder is remembered (%APPDATA%\Strata\settings.json, ~/.config/strata/settings.json).
2026-09-30 20:58:59 +02:00
Niko1221 8eff54c845 serve: an opt-in hard thinking budget, reasoning_budget_tokens (#123)
A request (OpenAI or Anthropic) may set "reasoning_budget_tokens": N, and "reasoning_budget_tokens" in
strata-<model>.json sets a default (a request's own value wins; 0 means no budget).  When the thinking reaches N
tokens - at a clean point, no tag held back and no character split across tokens - Service.run stops the engine,
feeds a short wrap-up line and </think> through the detokenizer and parser (the client sees it as the end of the
thinking; it counts as output), and generates again from prompt + what was generated + the wrap-up: a prefix of
what the engine's session already holds, so it continues from it.  With no room left to answer it ends with
"length" as before.  A bad value is a 400 before anything is sent (a bad config value refuses to start).  Off
by default: without the field and the key every request runs exactly as before (one engine pass).  Anthropic's
thinking.budget_tokens still only picks the level.  Documented in docs/DETAILS.md.

Tests (serve/test_server.py ThinkingBudget, a fake engine that continues after </think>): off by default, the
wrap-up and the continued prompt (the first prompt + the kept thinking + the wrap-up), the Anthropic stream, a
budget the thinking stays under, the config default and a request turning it off, thinking off, no room left,
bad values.
2026-09-30 20:58:59 +02:00
Niko1221 afc33c260e setup: reproducible installs - the checkout's own engine release, pinned Hugging Face revisions, pinned Python packages (#214)
- The ready-made engine: with the default --prebuilt, setup looks at releases/download/v<version>/ first (the
  version CMakeLists.txt says, source_version()) and falls back to releases/latest with a message when that
  release is not published (yet).  An explicit --prebuilt / STRATA_PREBUILT_URL is used as before; an
  installed engine new enough is kept without asking the network.
- Hugging Face: every model family's shards and vision encoder come from a fixed commit (HF_REVISIONS, the repos'
  `sha` on 2026-09-30), tools/mtp_fetch.py reads the BF16 checkpoint at its commit (STRATA_MTP_REVISION
  overrides).  A revision a repository no longer has falls back to its current files, with a message.  Files
  already downloaded keep their names and .done marks, so nothing is downloaded again.
- Python: requirements.txt pins the packages and their dependencies (numpy per Python range, 3.10+), used by
  setup's pip step and the Dockerfile.  A venv set up before this (bare names in .strata-pip.json) is left as it
  is; only packages it never had are installed, pinned.

Tests: tools/test_setup_pins.py (mocked network: the tag URL first, latest as the fallback, an installed engine
kept, a gone revision falling back to main in download() and mtp_fetch, the pip step for fresh, old and bumped
installs).
2026-09-30 20:58:44 +02:00
Niko1221 64f30121f4 qsa_prompt_attn (#270 follow-up): the m16n8k8 pair only for sm_75+ CUDA device code (AMD and pre-Turing builds keep the trap, which the host never reaches); Turing chosen per call from the current device instead of a process-wide flag (a split can mix Turing with newer cards) 2026-09-30 20:42:52 +02:00
kenh0u 48ecc6ac3d qsa_prompt_attn: the f16 tensor-core path runs on Turing (sm_75) as two m16n8k8 steps
m16n8k16 needs sm_80, but sm_75 has m16n8k8 with the same A/B/C register mapping, so
each k=16 step becomes two k=8 steps on the fragments as they are already laid out
(a[0]/a[1] with b[0], a[2]/a[3] with b[1]).  The sum now rounds twice, so the two paths
do not agree bit for bit.

cp.async does not exist before sm_80, so Turing takes the v1 kernel (launch<1>, same
accuracy, another summation order) instead of launch_i8; the capability probe now
accepts cc >= 7 and a device without cp.async sets g_turing.

Measured on a Quadro RTX 8000, CUDA 12.9, arch 75: qsa_prompt_attn_parity PASS
(FAILURES: 0; vs FP64 new 6.99e-06, old 2.37e-06, output scale 3.62); the int8 kernel
62.3 -> 19.6 ms per chunk (3.19x), fp16 65.0 -> 25.1 ms (2.59x).  Engine prefill on
three Turing cards: 9.7K-token prompt 1,050 -> 1,177 tok/s, 42K 1,124 -> 1,251 tok/s;
an 80K needle test answers correctly.
2026-09-30 20:42:23 +02:00
Guillaume PUTIER 45f93fc5ee iq_kernels: decode each IQ weight part once for every column and entry
iq_mmvq, native_gu_kernel and native_down_kernel called the format's dot once
per column (or group entry), re-reading the weight words and redoing the grid
lookups and sign unpacking each time. Each dot is now split into load (weight
side) and apply (activation side, same integer chain and float expression), as
native_mmvq.cu's multi-column traits are: the dense kernel takes 1, 2, 4 or 8
columns per pass (8 at a time past 8), the grouped ones 4 entries per pass
(~48 registers; the default verify window, --spec 4 with the split off, fits
in one pass).

Every column keeps the same calls, lane stride, summation order and warp_sum,
so results are bitwise equal to the old kernels, which STRATA_OLD_IQ_MMVQ=1 (or
iq_set_old_kernels) keeps for A/B timing.

Tests: iq_multi_parity (ctest, GPU, synthetic, no model) checks old vs new
bitwise for every format, ncols 1..8 and 11, groups of 0..11 entries; --bench
times both. iq_parity (real rows from tools/iq_fixture.py) now runs the dot for
1 to 8 columns and requires every column of a multi-column call to equal a
one-column call on it bitwise (exact layout, the default). native_expert_parity
runs the GPU expert with both the decode-once and the per-entry kernels on real
blobs and requires the same bits.
2026-09-30 20:41:56 +02:00
Niko1221 abde6fc8c9 s2_expert_grouped (#241 follow-up): the new chunk kernels' dp4a through STRATA_DP4A, so the sm_60 experimental build keeps its exact fallback 2026-09-30 20:41:26 +02:00
Guillaume PUTIER e155b07b9e kernels: faster grouped and per-hit Q2_0 expert kernels, bitwise identical
The grouped kernels (verify window, MTP layer) staged each entry's activations
as xs_q[k][c * 8 + j], an 8-way bank conflict on every read, and recomputed the
chunk sum hx with eight dp4a for every row. The per-hit kernels (token graph)
read each chunk with 44 byte loads.

New kernels, on by default:
- activations regrouped once per chunk so the codes expand with shift+mask
  and hx is summed once per chunk;
- grouped: word-major shared layout xs_w[j][k * NC + c] plus (dx, hx) pairs,
  conflict-free on the staging writes and the reads, two rows per warp;
- per hit: nine aligned word loads per activation chunk, uint2 code loads,
  16-bit scale loads, one warp per (gate, up) pair or pair of down rows.

s, hx and the float expression dw * dx * (float) (s - hx) are unchanged, so
every output matches the previous kernels bit for bit. STRATA_OLD_GROUPED=1
(or moe_grouped_select_old(1)) selects the previous kernels, and so does any
activation row, scratch or blob arena the wide loads cannot take.
STRATA_GROUPED_PAIR_MIN_HITS=N keeps the previous per-hit kernels below N hits
of capacity. moe_grouped_last_path() reports which kernels the last call ran.

The funnel shift that realigns an activation chunk is written as a 64-bit
shift instead of __funnelshift_r, which nothing else in the tree uses.

tests: s2_expert_grouped_parity runs every entry point with the previous
kernels then the new ones on synthetic Q2_0 blobs and Q8_0 activations, checks
the path taken, and requires output and scratch to match byte for byte, plus a
double-precision host reference. It covers the stride and arena fallbacks and
a 2-byte aligned activation row. --bench times previous against new. It
needs a GPU and is registered in ctest.
2026-09-30 20:41:08 +02:00
Niko1221 5a7190de4a setup: the low-RAM mode's one-GPU warning says why, with the RAM math; --low-ram off --yes installs (#250)
Low-RAM mode on a multi-GPU pick dropped to one GPU with only "the low-RAM mode runs on one GPU".  It now says
why: the experts plus ~10 GB for the rest (e.g. 43 + 10 = 53 GB) against this PC's RAM, or that --low-ram was
chosen; that the engine does not split layers in that mode (it refuses --resident-experts with a layer split);
and what keeps all the GPUs (more RAM, a smaller size, --low-ram off).

--low-ram off on a PC short of RAM asked "Install it anyway?" with the default n, so an unattended --yes
install always stopped although the user had turned the mode off explicitly.  With --low-ram off the default is
y (--yes installs, with a warning); with auto the question and its n default are as before (#125), and the stop
message names --low-ram off --yes.  The two blocks are functions now (low_ram_one_gpu_why, confirm_paging);
tests in tools/test_setup_lowram.py with mocked input.
2026-09-30 20:40:02 +02:00
Niko1221 c8e46e80b7 serve: a whole OpenAI answer leaves out a tool call whose arguments don't parse unless it ends in tool_calls (#211)
The follow-up to #231: openai_collect kept a call the output ended inside, with its truncated arguments, in
message.tool_calls (finish_reason "stop" or "length").  It now leaves out every call whose arguments are not
JSON when the finish reason is not "tool_calls" - what anthropic_collect does with such a tool_use block.  The
streamed answers are unchanged.  Tests: the cut call over both whole APIs (at the end of the turn and at the
token limit) and openai_collect directly.
2026-09-30 20:38:29 +02:00
Sunil Srivatsa 67d5ed4277 fix(serve): report a tool call cut off by the end of the output as unfinished
When the model's output ended inside a tool call whose name was already
streamed, finish() closed its JSON and emitted a tool_call event, and the
answer ended with finish_reason "tool_calls" / stop_reason "tool_use", so
clients ran the truncated arguments; an MCP call ran with empty arguments.

The call now stays unfinished: its JSON is not closed and no tool_call
follows its tool_start. Both APIs report "tool_calls" / "tool_use" only when
every streamed call completed, and the non-streamed Anthropic answer leaves
out a call whose input never closed.

Fixes #211
2026-09-30 20:37:57 +02:00
Niko1221 97362183ef tokenizer: a word longer than 64 symbols merges with a heap and a linked list, not a rescan per merge (#268)
_bpe rescanned every adjacent pair after each merge: O(n^2) on one long pre-token piece (a CJK run, a minified
blob).  With the pack's vocabulary a 2,000-character CJK run took 1.6 s and 8K characters tens of seconds;
_bpe_heap takes about 5 ms and 20 ms.  It pops the lowest rank, then the lowest position - the order the scan's
strict `<` picks - and skips pairs whose symbols have merged since they were pushed.  Words up to HEAP_MIN (64)
symbols run the existing scan unchanged.

Tests (serve/test_detok.py HeapBpe): the heap against the scan word by word and through encode (both
parse_special modes, heap forced on for every word and at the default threshold) over random text with CJK and
Hangul runs, conjoining jamo, combining marks, URLs, minified code and specials - with a synthetic vocabulary
(always) and the pack's (when there is one); an 8K-character CJK run round-trips in well under a second.
2026-09-30 20:37:45 +02:00
Niko1221 9556a8dcf4 tests (#264 follow-up): the fixture step skips (exit 3) instead of failing when the vendored gguf-py is absent; no unused SCALE_F16 2026-09-30 20:36:29 +02:00
Niko1221 64dcc59fae gemm (#240 follow-up): setup calls that failed before without a word are logged and survived as before (not fatal - hipBLAS can return not-supported), init_external names its failures too, the MiB stays in the scratch message; no unused resource_failure flag 2026-09-30 20:36:18 +02:00
Niko1221 50d2adaabd serve: a request settles its status, history and totals while it still holds the fifo (#266)
Service.run() recorded the finished request's history row and totals, set busy=False and dropped `tail` in a
finally that ran after `with self.fifo:` had released the lock.  A request waiting for the fifo could start in
that gap: the old request then recorded the NEW request's status as its own, cleared its busy flag and popped
its tail, and the new request crashed in _note (KeyError 'tail').  The block now runs inside the fifo (an
inner try/finally, which also sees the disconnect), and _note reads the tail defensively.  Test: a stream
aborted mid-answer while another request waits, with a fifo that lets the waiter run first on release.
2026-09-30 20:35:46 +02:00
Hozák Martin f121667826 q2_avx2: two-block 256-bit unpack for rows of 1-2 tokens (bit-exact)
Rows that serve 1 or 2 activations (the near-universal shape of routed
decode) read each weight block twice less often: one 256-bit shuffle
ladder covers two consecutive blocks (lanes are per-128-bit, so the
arithmetic is the SSE ladder run twice and the re-homed halves keep the
original byte order) -> the same madd lane mapping, bit-identical
results.  The SSE unpack stays for 3+ tokens: at four accumulators the
extra live unpack vectors spill registers and it measurably loses.
Measured on i7-8700K AVX2, qwen3.8 125B-MoE Q2_0 decode: +10% gate/up and
+5-7% down at 1 token/expert in the standalone harness
(ML-Experiments/bench5/q2_cpu_bench.cpp, bit-exact verified), and -7
ms/verify-window end to end (CPU pool waitCPU ~19 -> ~13 ms), GPU stages
and generation unchanged.
2026-09-30 20:35:28 +02:00
Hozák Martin 765fc13473 fused_gr: sm_75 down kernel with TILE=1280 staging, other archs unchanged
Turing (sm_75) opt-in shared memory is 64 KiB. With the old fixed
TILE=2560 staging the down kernel could hold at most 6 tokens
(2560*6*4 = 60 KiB), so 7+ token windows split into two launches and,
more importantly, a single 40 KiB tile at T=4 filled the SM so only
one block per SM fit next to the resident expert/attention work.

Specialize gr_down_multi_kernel on the per-token tile size:
- sm_75: TILE=1280 -> up to 8 tokens staged in one 40 KiB launch
  (kFusedGrMaxT=8 covered), and T-token tiles fit 1-3 blocks/SM
  depending on T, so concurrent hc lanes can overlap.
- every other arch keeps TILE=2560 exactly as before.

Per-lane weight chunk order within a tile stays strictly increasing
in (tile, chunk), so the accumulation order for every (expert, row)
is identical to the 2560 path and results are bitwise unchanged.

Measured on RTX 2070 (8700K host, engine 0.1.27, Qwen3.6 125B Q2_0,
29K context, temperature 0, identical benchmark): GDN hc0 down
2.73-2.80 -> 1.88-1.98 ms/window (-30%), QSA hc0 down 0.90-0.93 ->
0.62-0.65, up/norm kernels unchanged, GPU util 91.7 -> 93.5%,
decode tok/s slightly higher on average (within per-run noise;
window is CPU expert-scheduling bound).
2026-09-30 20:35:28 +02:00
Jakub Luwierski 2538ed7927 tests: make iq_parity reproducible from a fresh checkout (TODO 24)
The i-quant fixtures were never published, so iq_parity reported ten
"missing fixture" failures on any fresh tree.  tools/iq_fixture.py now
generates them deterministically (seeded rows/cols, RandomState) into
the BUILD directory: for each of the ten types it writes the raw block
bytes (sane fp16 scales at each layout's scale offsets, seeded index and
sub-scale bytes) plus the gguf-py dequantized reference - gguf-py is
dequant-only for the i-quants, so no quantizer is needed, and Q2_0 (the
repository's type 42) uses tools/gguf_writer.py's codec.  The kernel
type ids in the headers match the vendored gguf-py's enum one for one.
Dependencies: python3 with numpy and the vendored gguf-py - nothing is
pip-installed, and CMake probes the configured interpreter and .venv,
registering the tests only when a usable interpreter is found and
saying so otherwise.

CTest now runs iq_parity (previously built but never registered) with
the fixtures as an explicit CTest fixture: generation completes before
the test even under parallel execution, and a generation failure fails
the run.  The missing-fixture diagnostic names both candidate files and
the generating command; the nonzero exit is preserved.
2026-09-30 20:35:02 +02:00
lukmanfauzie 9726c926d6 gemm: name the cuBLAS/CUDA call that failed at init, and flag resource shortages
`Gemm::init` collapsed five distinct failures into three bare strings ("cublasCreate
failed", "workspace", "dequant scratch of N MiB"), so an init failure on a tight machine
could not be told apart from a driver or install problem without a debugger.

Each call is now checked through two small helpers that report the operation and the real
status - `cuBLAS status N` or `cudaGetErrorString` - and optionally set
`resource_failure` when the failure means the machine is out of room
(`CUBLAS_STATUS_ALLOC_FAILED`, `cudaErrorMemoryAllocation`).  A caller can then retry with
a smaller cache or prompt instead of treating a shortage as fatal.

The parameter is defaulted to nullptr, so every existing caller is source-compatible; the
upstream HIP/hipblaslt block is untouched.
2026-09-30 20:35:02 +02:00
Enky 5396360ef7 verify: STRATA_LOGPOS hook for per-position teacher-forced log-probabilities
Verifier::window_logprobs writes one line per row of the last verify window's
head: position, target, its log-probability, the top token and its
log-probability, a hit flag, the log-probability of STRATA_LOGPOS_EXTRA
(default 248046, <|im_end|>) and the target's log-probability renormalized
without that token. read_windows calls it before commit when
STRATA_LOGPOS=<path> is set; otherwise the only cost is one null check.
A layer split's earlier stage forwards to the stage that holds the head,
under OnDevice as commit() does.
2026-09-30 20:35:02 +02:00
Niko1221 a3037d5435 Engine 0.1.30: short prompts up to 28% faster (every expert streams from 1024-token chunks), the resident low-RAM variant, the multi-GPU session carve (#216), AMD RDNA4, idle unload (#208); opt-ins: rope scaling (#84), conversation cache (#189), STRATA_IQ_MT_MIN=1 (#152); setup requires it 2026-09-30 19:34:50 +02:00
Niko1221 1e213213e3 docs: the layer split on two AMD RDNA4 cards (identity with one card, checkpoints, speed; setup offers one AMD card) 2026-09-30 19:20:16 +02:00
Niko1221 8099fdb980 tests/hip: prefill_native_batch passes the indexer a RopeScaling (the #84 signature; plain rope over base 1e4 as before) - a HIP-only test the CUDA build never compiled 2026-09-30 19:18:41 +02:00
Niko1221 31ad962727 #152 as an opt-in (the owner's choice): STRATA_IQ_MT_MIN=1 makes CPU expert rounding independent of the drafting; the default stays 0.1.29's rule (IQ3_S decoded 1-3% slower with it on); docs; the width test runs the opt-in 2026-09-30 19:03:39 +02:00
Niko1221 19a2dc169b tools: the cache-disabled gate compares every resolved setting except the engine version (release vs previous release) 2026-09-30 17:52:12 +02:00
Niko1221 fee66e89cc CPU experts: the multi-token kernels from one token on (#152): a token's expert rows round the same whatever its verify window held, so output no longer depends on the drafts; STRATA_IQ_MT_MIN=2 restores the old rule; native_expert_parity checks the width invariance bit for bit 2026-09-30 17:48:49 +02:00
Niko1221 59b8c45777 Merge f-rdna4 (AMD RDNA4 gfx1201, with doplxyz's patch) into rel-0.1.30
# Conflicts:
#	setup.py
2026-09-30 17:36:03 +02:00
Niko1221 dea2b5a3ec docs: RDNA4 (gfx1201) in AMD_HIP.md with the validation on the RX 9070 XT and R9700; how to calibrate a hipBLASLt table; README and setup's texts name the RDNA4 cards
AMD_HIP.md: the per-arch ROCm wheels, the system-ROCm rule, the table rule, the
arch list and the startup check; an RDNA4 section (ctest 42 pass / 1 skip /
2 non-GPU failures, the arch check, end-to-end 4K/16K numbers on both cards and
with setup's TheRock 7.10 wheels, the 9/9 server smoke on each card, why no
gfx1201 table is shipped, what is not validated); a Tuning table section with
the tune_hipblaslt command at the engine's shapes. setup's no-table message
points there.
2026-09-30 17:32:41 +02:00
Niko1221 70e24f43e3 doorbell_ring: a volatile store, so the host sees the ring on RDNA4 without a stream sync
On gfx1201 a plain store to mapped pinned memory stays in the GPU's L2 until the
stream is synchronized, so hip_handoff failed ("separate copy/ring timeout") on
both RX 9070 XT and R9700. A probe of the ring forms on both cards, graph and
stream launches, 100 rounds each: plain store 0/100 seen without a sync;
volatile store, plain store + trailing system fence, and a system-scope atomic
release store 100/100 (41-157 us), no stale payloads. The ring now uses the
volatile form doorbell_publish_kernel already uses. The ring kernel is the
--no-publish-kernel fallback; the default fused publish was already volatile.
2026-09-30 17:09:57 +02:00
Niko1221 4dcc8f5313 CMake: the D-1 prompt-attention parity tool is CUDA-only; the HIP build with tests configures again
qsa_prompt_attn_parity linked CUDA::cudart unconditionally, so
-DSTRATA_ENABLE_HIP=ON -DSTRATA_BUILD_TESTS=ON stopped at CMake's generate step
("CUDA::cudart ... target was not found"). The tool compares the tensor-core
kernel that HIP compiles out, so it is built with CUDA only.
2026-09-30 17:01:23 +02:00
Niko1221 862c5a4767 HIP: RDNA4 gfx1201 beside gfx1100; the startup check compares the card with the compiled archs; setup picks ROCm and the hipBLASLt table per arch
- CMake: CMAKE_HIP_ARCHITECTURES may list gfx1100 and gfx1201 (validated) and
  gfx1101 / gfx1102 / gfx1200 (build with a warning: same wave32, 64 KiB LDS and
  dot4, not run by us; #192, #176). The list reaches the code as STRATA_HIP_ARCHS.
- Runtime: the engine (every GPU a run uses) and strata-device compare the card's
  gcnArchName (up to the ':' feature suffix) with the compiled archs and require
  wave32, with a message naming the card, its arch and the build's archs, instead
  of a later "invalid device function".
- __dp4a uses __builtin_amdgcn_sudot4 (v_dot4_i32_iu8) on all five archs; the
  portable fallback stays for other compilers/targets.
- setup.py: AMD_ARCHS gfx1100 + gfx1201; TheRock's wheel index per GPU family
  (gfx110X-dgpu, gfx120X-all; the pinned 7.10.0a20251120 is on both), only that
  family's libraries on the path, reinstall when the family changes; a system
  ROCm older than 7.0 is passed over for the wheels; the engine is rebuilt when
  the chosen card's arch is not in its BUILD.json. The hipBLASLt table is
  tools/hip/<arch>-hipblaslt-<version>.txt for the card's arch AND the installed
  hipBLASLt (read from hipblaslt-version.h, 1.4.1 -> 100401), else none with a
  message; before, the highest-named table of the arch was taken whatever the
  installed version, which the engine then refused.
2026-09-30 16:57:59 +02:00
Niko1221 d263ec58f9 Conversation snapshots and checkpoints follow the session carve (#216): a session saves and restores only the layers it owns
conversation_session_sizes() gives one session's running-state sizes: the GDN bytes of its gdn_alloc
rows, the indexer tail/dead/block_pos per owned QSA state; an inconsistent carve is rejected.
Checkpoint save/validate/restore, the snapshot size estimate, capture, validation and restore iterate
the owned QSA ordinals [qsa_ord0, qsa_ord0 + qsa_alloc) instead of every model layer (the spare
pooled row is written back per owned layer only), and a parked snapshot records the carve it was
taken from (layer_lo, layer_hi): a restore into another range is refused before any write.

One GPU (the default full range: gdn_alloc = n_gdn_layers, qsa_ord0 = 0, qsa_alloc = n_qsa_layers)
saves and restores exactly the bytes it did before, in the same order. generate.cpp's
conv_state_sizes uses the same function. The tests' hand-built sessions carry the full-range carve,
and the host-only validation test checks the carved sizes and the rejections.
2026-09-30 16:57:11 +02:00
Niko1221 abbf12caf5 Merge f-189 (PR #189, the conversation snapshot core and RAM cache) into rel-0.1.30
Conflicts in generate.cpp:
- against #216's session carve: conv_state_sizes takes the session (the carve's signature) and scales
  the PR's whole-model GDN size to ss.gdn_alloc rows; checkpoint_save/checkpoint_restore are the PR's
  (conversation_state.cpp), which the next commit makes carve-aware.
- STATE_HASH: the per-GDN-layer line over ss.gdn_alloc (guarded for zero rows), the QSA loop over the
  owned ordinals [qsa_ord0, qsa_ord0 + qsa_alloc) with the PR's dead= and pooled_full= in it, and
  end_cell from ss.max_cells (the primary state's max_cells, and valid with no QSA layer).
- INFO: f-resram's arena_mib (resident bytes under --mmap-experts) and the PR's three
  conversation_cache_* fields.
The --conversation-cache-* options sit in the option parser's second chain (6fb466b); their value is
`number`, not `parsed`, which is that chain's flag.
2026-09-30 16:57:10 +02:00
Niko1221 840f01734e Merge f-resram into rel-0.1.30
# Conflicts:
#	src/program/generate.cpp
2026-09-30 16:54:12 +02:00
Niko1221 6fb466b7cb generate: the option parser's else-if chain in two statements (MSVC C1061: more than 128 nested blocks after the new options); the same order and effects 2026-09-30 16:53:53 +02:00
doplxyz ca574a27ad HIP: enable RDNA4 gfx1201 (RX 9070 / AI PRO R9700)
doplxyz's minimal patch from #178 (their commit 0c59582): CMake, the
runtime device check, the signed dot4 intrinsic and setup's AMD_ARCHS
accept gfx1201 beside gfx1100.
2026-09-30 16:52:13 +02:00
Niko1221 7a2948f9bb Merge f-coupled into rel-0.1.30 2026-09-30 16:51:52 +02:00
Niko1221 f9a31f391b setup: the resident low-RAM variant keeps its RAM copy pageable on ROCm (STRATA_RESIDENT_PIN=0 in the config's env) 2026-09-30 16:51:39 +02:00
Niko1221 12ebc67271 Merge f-84 into rel-0.1.30 2026-09-30 16:51:33 +02:00
Niko1221 5ac6d37e59 Low-RAM mode, resident variant: the experts the GPU does not hold stay in RAM (--resident-experts), chosen by setup
On a PC whose VRAM cache plus RAM can hold all experts but whose RAM alone cannot (e.g. 32 GB + a 24 GB GPU with
Q2_0 / IQ2_XS / the Coder), the plain low-RAM mode (--mmap-experts) reads the non-VRAM experts through the OS file
cache, which such a PC keeps giving back to the SSD. The resident variant copies exactly those experts from
experts.bin into RAM once at start:

- engine: --resident-experts = --mmap-experts --resident-cpu-experts, the copy page-locked when the driver allows
  (else locked in the working set), 4 GiB headroom, plain mmap with a warning when it does not fit. Built after the
  prompt path's lend plan: the lendable slots' experts are kept in RAM too, from the last slot down, as far as RAM
  allows. Adaptive swaps stay on: an evicted expert is copied back from its VRAM slot into the RAM place of the one
  that replaces it (exchange buffers, committed when the swap lands), so no swap reads the file.
- --resident-cpu-experts (the ROCm arm) no longer requires --adapt-every 0 and gets the lend region too; its copy
  stays pageable with 8 GiB headroom as before.
- the PCIe share is gated per source (pcie_layer): the arena answers as before; a page-locked resident copy answers
  for every layer. Same bits as --mmap-experts for the same placement with --pcie-frac 0 or STRATA_RESIDENT_PIN=0.
- INFO arena_mib reports the RAM the experts take in the mmap modes (the resident copy, or 0); the serve log and the
  CLI report the resident GiB, exchanges and blob reads from the file.
- setup: in the low-RAM mode, resident when RAM >= (experts minus the GPU's estimated share at this context, less the
  image encoder's room) + 10 GB; --low-ram resident|mmap forces a variant; needs engine 0.1.30 (else mmap).
- tests: lend-region choice and exchange bookkeeping (CPU only). Default (arena) path unchanged.
2026-09-30 16:51:04 +02:00
Niko1221 3b4230a182 Coupled draft sampling for the MTP drafter (STRATA_SPEC_COUPLED=1, off by default)
Under sampling the draft layer proposed its argmax. With STRATA_SPEC_COUPLED=1 a sampled request's drafts are
sampled with the target's own chain (top_k / top_p / min_p / temperature, penalties over the same history) and the
same Philox uniform the verify window draws for the row that checks the draft: counter = MTP cell + 1 (a draft at
cell p + a + j is checked by row j of the next window, counter p' + j). Verification is still exact-match against
the target's sample, so the output does not depend on the drafts; acceptance should rise.

- sampler.cu: coupled_stage / coupled_penalize / coupled_merge kernels (params, seed, counter and penalty ring from
  device memory, so they live in the drafter's CUDA graphs); the split part kernel reused unchanged; the tail
  (sampled_tail_warp) templated to also return the pick's probability. The five existing sampler kernels compile to
  identical SASS (sm_120).
- mtp.cpp: separate coupled round/step graphs (the argmax graphs are untouched and still used for greedy requests);
  subset-aware (sample over the draft head's subset, id <-> subset maps for the penalties); --spec-min-p gates on the
  draft's probability of its sampled token under the chain.
- generate.cpp: set_draft_sampling per request (serve and CLI), the penalty history base before each draft (serve).
- coupled_draft.hpp + coupled_draft_test.cpp: the counter / ring arithmetic and a host-only test (907k checks).

Switch off: no allocations, no new launches, the same graphs.
2026-09-30 16:46:51 +02:00
Niko1221 4240629dba docs: rope scaling is experimental and opt-in; the measured perplexity at 293K
DETAILS.md: what it does, that no flag means no change, the contributors' paired per-position NLL (yarn 2 -0.18 nats vs none and linear 2 at 293K, no separation at 2.7K/32K, needles not discriminating), the explicit-factor pass-through, fixed per run. The stale 'engine 0.1.18' references go; the setup's 384K/512K menu entries and the README say experimental.
2026-09-30 16:44:48 +02:00
Niko1221 eb77a8c145 rope: one validity rule for the resolved config; none passes the identity constants to every analytic site
rope_scaling_invalid() checks every value with std::isfinite first (a NaN passes every range comparison), factor >= 1, original context > 0, a finite positive freq_scale and mscale; the engine applies it to the resolved config (CLI + model file) and fails at startup with the reason, and native_rope, both native indexer appends and the prefill rope use the same rule instead of three hand-copied lists. RopeScaling::kernel_args() gives the analytic kernels their float constants and returns the identity (1, 0, 0, 0, 1) for none whatever the other knobs hold, as the table builder already ignored them: an unscaled run cannot pick up a stray --yarn-attn-factor on the native path only. none resets the knobs (with a note if some were given). The magnitude-correction note prints whenever mscale != 1, and only then (not for plain linear).
2026-09-30 16:44:48 +02:00
Niko1221 d4526cf156 qsa: the table indexer's spare rotates at position 0 like the native one (TODO 7)
indexer_key_append_kernel skipped the rotation at pos 0, which is only an identity while row 0 of the table is (1, 0). Under YaRN row 0 is (mscale, 0), so dead and pooled[0] stayed the only unscaled keys of a scaled cache and disagreed with the native indexer's spare. The rotation at angle 0 reduces to v * cos_tab[pair] on the rotated dims (the sine is 0 in every scaling), so no partner read and no barrier; unscaled it is v * 1.0f, bit for bit the old value. qsa_parity checks the table spare against the unscaled spare times row 0 (bit-exact), pooled[0] == dead, the native spare's factor, and that YaRN's mscale is observable there.
2026-09-30 16:44:48 +02:00
Niko1221 56c7cd2f80 STATE_HASH: pooled= keeps its 0.1.29 meaning (the completed indexer rows); the extent with the spare row is pooled_full=
pooled= hashes rows [0, L/4) of every QSA layer's idx_pooled again, as in 0.1.29 and earlier, so a
state hash from this engine compares with an older one field by field. The PR's extent (those rows plus
the spare row at L/4, which a conversation restore writes back) is pooled_full=; dead= stays. The
cache gates compare pooled and pooled_full; the cache-off gate against an older engine compares pooled
directly and needs pooled_full only as proof that the candidate is this build.

A failed checkpoint save/restore prints its reason to stderr (the ERR line has none). The cache
harness reads its files as UTF-8 (Windows' default code page stopped it at the tokenizer), and the
isolation gate passes absolute fixture paths (the engine runs in the config's cwd).
2026-09-30 16:43:28 +02:00
Niko1221 81bd596242 wip: version 0.1.30 2026-09-30 16:37:30 +02:00
Gopi 915f74fefb Multi-GPU: the session carve and the per-stage prompt loans
Two things a layer split was paying for and not using.

* THE SESSION CARVE.  A stage carved state for ALL 48 layers whatever slice it ran - every QSA KV pool at
  max_cells, every GDN recurrence - the whole-model-sized-buffer disease the chunked-QSA-prefill PR cured
  upstream.  `session_bytes`/`session_init` take [layer_lo, layer_hi), and a session now owns only that range:
  `qsa_states`/`gdn_state` keep GLOBAL ordinal indexing and simply hold fewer rows (`qsa_ord0`/`qsa_alloc`,
  `gdn_ord0`/`gdn_alloc`), per-layer consumers subtract `gdn_ord0`, and the model-level uses - the shared RoPE
  table, the prefill staging identity, the MTP rope borrow - read `qsa_states[qsa_primary()]`.  A range with no
  QSA layer still carves ONE primary state so those uses stay valid, and the default full range is
  byte-identical to the old carve.  On the 2x3060 + 2x5060 rig, CUDA1's [30, 48) session costs 0.93 GiB:
  7.83 GiB free before the carve, 6.90 GiB after (`session [30, 48) and the head`).

* PER-STAGE PROMPT LOANS.  `no_prefill_borrow = true` for split stages said "each stage's prompt path has its
  own buffers" - true, but that is a reason to give each stage its own LOAN, not to make every stage withhold a
  chunk-sized reserve from its cache for the whole session, and forced on it also collapsed `--prefill auto` to
  2048.  Each prompt path now borrows the tail of ITS OWN stage's cache (`PfPart`), so outside the prompt the
  whole cache is expert cache: `--prefill auto` is the largest chunk every participant can lend (8192 tokens
  here), a participant refills its loan into its own slots and marks only its own layers (a slot refilled into
  another cache, or another stage's rows marked resident, is silent and produces plausible tokens), and the
  chunk is the largest one EVERY participant can lend rather than one card's answer imposed on the others.
  A participant that cannot lend the buffers falls back to the prompt path's own, as a lone one always could;
  with a single participant the plan reduces to `plan_lend` exactly, so the single-GPU loan is unchanged from
  main - the percentage cap stays an AUTO-chunk rule (an explicit `--prefill` is the operator's number and a
  loan of it only has to leave the 128-slot floor).  The flat 1 GiB withheld from every stage after the first
  becomes what is actually held back after the weights, session and drafter exist: 96 MiB of verify windows,
  plus 1000 MiB of drafter and head on the ONE stage that carries them.  CUDA0 lends 2162 slots (3.98 GiB),
  CUDA1 1993 of its 2653 (3.97 GiB).

* REPORTING.  `expert_slots`/`expert_cache_mib` sum every tier: a 4-GPU 256K run reported CUDA0's cache alone
  (3327 experts) while the four cards held 13310, so the Monitor tab was wrong by 4x for every multi-GPU
  config.  `expert_slots_primary`/`expert_cache_primary_mib` keep the old figure.  The helper ranks'
  ms_begin/ms_wait are cumulative since boot and were printed next to per-request deltas; they take deltas now.
  KV-streaming stats iterate the session's OWNED ordinals.

* REFUSALS.  `--gpu-only-full` and `--gpu-stages` replay every layer through CUDA0's session, which a split
  carves to CUDA0's range, so they are refused with a layer split rather than answering about another stage's
  state; the token-graph hit path is disabled under a split (such a graph cannot span stages); and the sessions
  are allocated AFTER the split search, because the search prices each placement by what that range's session
  will cost on that device (`session_bytes` is pure arithmetic).
2026-09-30 16:37:18 +02:00
Go 4f5d0860d7 docs: limit shared arena backing to dev shm 2026-09-30 16:36:33 +02:00
Daehun 80f83268d4 core: add guarded shared expert arena backing
Rebase the shared-arena primitive onto 0.1.26, bind backing files to a pack fingerprint with a small header, and document tmpfs/hugetlbfs placement.
2026-09-30 16:36:32 +02:00
ruibeikaa e200e08f54 Pascal (sm_60): bring the engine up on compute capability 6.0
Two gates stand between this engine and a 6.0 card, plus one attribute the card does
not honour.  None of them needs the kernels rewritten:

* `CMakeLists.txt` refuses to configure below sm_75;
* `__dp4a` (6.1+) appears 29 times in the i-quant kernels (`native_mmvq.cu`,
  `s2_expert_grouped.cu`, `iq_kernels.cu`) and `__nanosleep` (7.0+) in the three
  doorbell waits (`elementwise.cu`, `verify_kernels.cu`);
* the Turing port sizes `fused_gr_read_multi`'s slice from
  `cudaDevAttrMaxSharedMemoryPerBlockOptin`, which on an sm_60 answers 65536 - a card
  whose launch validator enforces the 49152 B per-block limit instead.  The capacity
  comes out 6 tokens and every 6- or 8-token window fails the launch with
  "invalid argument".

## The changes

* **`include/strata/kernels/dp4a.hpp` (new)** - `STRATA_DP4A` and `strata_spin_pause()`.
  The dp4a fallback is llama.cpp's own (`ggml/src/ggml-cuda/common.cuh`): both operands
  read as SIGNED bytes, accumulated into int32 - which is what `__dp4a` does for exactly
  these call sites, the kernels here being transcribed from `vecdotq.cuh`/`mmvq.cu`.  The
  fallback is bit-exact, not approximate; the nanosleep sites only pace single-thread
  doorbell waits, so on sm_6x the loop spins.  Both are `__CUDA_ARCH__` selections, so a
  HIP build takes neither: its `__dp4a` and `__nanosleep` are `hip_compat`'s own.
* **`CMakeLists.txt`** - `-DSTRATA_EXPERIMENTAL_SM60=ON` relaxes the architecture gate,
  and the refusal message names it.  The UTF-8 BOM stays.
* **`fused_gr.cu`** - below sm_70 the slice capacity comes from
  `cudaDevAttrMaxSharedMemoryPerBlock` (49152 B -> 4 tokens) instead of the opt-in.  The
  Turing port's slicing, its no-opt-in fallback and the HIP expression are unchanged.

On sm_70 and newer this is a no-op: the opt-in is honoured there, so the capacity is the
value it already was, and `STRATA_DP4A` and `strata_spin_pause()` compile to `__dp4a` and
`__nanosleep` exactly as before.  Building for a 6.0 card needs a CUDA release that still
targets Pascal (CUDA 13 removed it; 12.x has compute_60), and `CMAKE_CUDA_STANDARD` has
to stay 17 for it: nvcc 12.9 refuses `-std=c++20` when the host compiler is VS2019's `cl`
and silently falls back to C++14, which rejects the nested
`namespace strata::kernels {` declarations.

Rebased onto 0.1.27 (0.1.21 -> 0.1.24 -> 0.1.27).  The 0.1.21 form of this patch clamped
the MTP and verify windows instead, because the fused GR read could not launch a 6-token
window at all.  Now that `fused_gr_read_multi` slices the batch itself, the clamp, its
`fused_gr_max_window` helper and the two `generate.cpp` call sites are gone, and the full
window runs:

    strata verify: window up to 6 tokens, 69.1 MiB of device buffers
    strata verify: captured the 6-token window (upload no error, sync no error)

Measured on a 6.0 card (GP100-class, four dies through the layer split, the model's
IQ3_XXS quant, an 883-token prompt, 256 generated, the shipped `--spec 4`): every one of
the 24576 experts resident, 100% decode cache hit rate, 110 tok/s prefill and 21.3 tok/s
decode (warm median).  This port on the 0.1.21 base measured 110.6 and 17.9 in the same
session with the same prompt, so the full window is worth about 19% of decode here.  One
RTX 3090 Ti with the release engine measures 305 and 79 tok/s for the same model.  The
port is about fitting, not speed: decode is bounded by Pascal's per-layer arithmetic, not
by capacity or memory.  Only the 6.0 path was exercised on hardware.
2026-09-30 16:36:32 +02:00
Strata b782066e3c Docker build (#96): the engine compiled in docker build, STRATA_EXECV so the server is PID 1, SIGTERM stops it cleanly, KV/GPUS/LOW_RAM as env vars
Merged by hand onto 0.1.29: SIGTERM takes #153's Ctrl+C path (the timed sleep loop and the second-Ctrl+C kill stay);
SIGINT keeps Python's handler; the image builds RTX 20 (75) too.
2026-09-30 16:36:25 +02:00
Niko1221 379d7d84b2 server (#208 follow-up): a request stamps last_request_at in load() (the idle thread cannot unload between load and run); unload() sends QUIT before terminate; the VRAM guard checks the first GPU (docs); #163's test engine answers poll() 2026-09-30 16:35:05 +02:00
bytethecookie 376430c7db server: the image encoder unloads and loads with the engine; docs for sharing the GPU 2026-09-30 16:32:11 +02:00
bytethecookie f61e688527 server: optional idle unload, POST /unload and /load, a free-VRAM guard and a before_load hook 2026-09-30 16:32:11 +02:00
Niko1221 3e3d69b4d4 prefill: every expert streams from 1024-token chunks (was 2048): +17-28% on 1-4K-token prompts, the same output; STRATA_PREFILL_STREAM_MIN overrides 2026-09-30 16:32:04 +02:00
Jeremiah Ritchey 7e94070cf2 Document retained snapshot ownership and admission contract 2026-09-30 16:31:46 +02:00
Jeremiah Ritchey 7d93cb6b03 Require fixed binaries and budgets in the incremental capture gate 2026-09-30 16:31:46 +02:00
Jeremiah Ritchey 92a3d5fda7 Bound incremental snapshot fragmentation and preserve cache admission
Reserve accounted space for later appends without moving prior payloads. Prefer full capture when optional capacity would evict useful conversations, and report the actual additional RAM required. Exercise 4096 small appends and non-mutating admission checks under sanitizers.
2026-09-30 16:31:46 +02:00
Jeremiah Ritchey 7f7f886886 Retain unchanged KV storage across repeated conversation parking
Grow snapshot buffers in segments, track the first rewritten token, and charge retained active storage to the existing RAM budget. Refresh running state, partial pages and draft boundary state. Add full-capture versus incremental growth/rewind checks and report snapshot sizes with usable pressure-test budget ranges.
2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 573bb36baa Compare cache-disabled engines against untouched upstream
Replay identical requests and require token, known-answer, reuse, resolved-setting and comparable main-state parity. Retain the complete pooled-state diagnostic and add a completed-row fingerprint matching upstream. Add negative controls that remain active under optimized Python.
2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 0fca77bfe0 Remove planning documents from the core PR 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 3782659274 Ignore llama.cpp dependency directories and symlinks 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 3a5b090014 Record final Linux snapshot and RAM cache acceptance 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 8d46e95ff5 Record linked 0.1.27 core build and host validation 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 876f254c78 Report actual draft residency in snapshot verification 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 75fce92c93 Refresh core validation record for upstream 0.1.27 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 13fd32a227 Verify restored draft KV and fail incomplete state fingerprints
Move core diagnostics from the dependent NVMe work (4d73bb3) into the shared-core review branch. Read back authoritative bytes and resident ring pages without rewriting state; remove a redundant GPU fixture refill that could mask restore failures. Bound hash workspace and check synchronization/copy errors. Opt-in diagnostics record batched versus token draft prefill.

Validation: current core translation units pass C++ syntax checks; 1188 host transfer checks pass with ASan/UBSan. Real GPU and full-model validation remain pending the benchmark window.
2026-09-30 16:31:45 +02:00
Jeremiah Ritchey f3072a95e6 Clear recoverable snapshot errors before batched draft prefill 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 4ac028522e Focus shared-core patch on runtime, regression tests and current documentation 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 28d07f8a1a Record upstream integration, Linux acceptance and controlled Pi results 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 717519d345 Allow bounded Pi task completion and record benchmark provenance 2026-09-30 16:31:45 +02:00
Jeremiah Ritchey 055bc16e2a Match upstream K8V4 flags when sizing conversation snapshots 2026-09-30 16:31:44 +02:00
Jeremiah Ritchey 089d444278 Enforce Pi benchmark request limits and engine memory evidence 2026-09-30 16:31:44 +02:00
Jeremiah Ritchey e0feb8530c Add reproducible Pi cache comparison and document current integration 2026-09-30 16:31:44 +02:00
midhatn 6adb2e5ac4 test: require a real physical-memory sample on Windows 2026-09-30 16:31:44 +02:00
Jeremiah Ritchey 991203f2a9 Integrate validated conversation snapshot core with upstream 0.1.25 2026-09-30 16:31:44 +02:00
Jakub Luwierski a8e56761a6 generate: the in-window scaling note stops claiming YaRN's magnitude correction (TODO 22)
The note fired for every scaled run inside the trained 262144 and its text
named YaRN's magnitude correction even for linear, whose resolved mscale
is exactly 1.  Split in two: the in-window warning now only says that no
position needs the extension while the resolved scaling still applies to
every angle, and the magnitude explanation became its own note, printed
only when YaRN is resolved and its magnitude multiplier differs from 1
(yarn factor 1 inside the window stays quiet; linear never sees it).  The
main rope scaling line is unchanged, the numbers it prints already carry
the resolved mscale.

Diagnostics only.  Checked on the rebased tree: none prints nothing,
linear 2 prints the in-window note without the magnitude line, yarn 2
prints both (mscale 1.069315), yarn factor 1 prints the in-window note
only, and yarn 2 past the trained window prints the magnitude note
without the in-window one.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 8e2f46ca8c prefill: the draft layer's batched RoPE takes the one rope config
v0.1.29's new draft_kv site (the 0.1.26 batched draft prompt pass) kept
upstream's float freq_base form through the rebase, which both misses the
scaling on the draft K/V and does not compile against the RopeScaling
signature the other four sites carry.  It passes rope_scaling() now, like
the prompt K, Q and indexer-query sites.
2026-09-30 16:31:31 +02:00
Jakub Luwierski f8c45322b3 test: the indexer batch-vs-sequential parity covers linear and compares bits
Item 8 verification.  The parity section now runs all three scaling
configs - none, linear 2 (angle shrink only), yarn 2 (ramp + mscale) -
and "bit-identical" is asserted on the BIT representations (memcmp of
the state words), not float !=, which would pass a NaN-payload or -0.0
flip.  Each scaled variant must also differ from the none baseline in
pooled-key bits, so the parity cannot pass because both sides ignore
the scaling.  Green on this tree: 0 of 33409 state words differ in
every config; 128 (linear) and 192 (yarn) pooled words move.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 29f3aec04d test: pin the short-context rope table (§4, no factor-2 fallback in the window)
The implementation landed with the derived-factor change; per the item's
own scope note this is verification and tests, not a second one.  The
table from the item is now pinned case by case - 131072 and 262144 with
yarn and no factor resolve to 1, an explicit --rope-scale 2 stays 2 inside
the window, 393216/524288 keep 1.5/2 - and linear is checked under the
same rule the old fallback applied to it too.  On the real engine the
resolved combo runs as "rope scaling yarn, factor 1 (freq_scale 1, mscale
1.000000)" and a greedy 12-token run is token-identical to the stock
baseline - evidence on one path, not a bit-identity claim.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 79926c0d3f setup: the automatic rope factor derives from the context actually served
The factor ladder (smallest of 1.5/2/3/4 covering the chosen context) is
gone.  The automatic factor is now the final context over the trained
262144, clamped at 1 - max(1.0, final / trained) - computed AFTER the
RAM reductions, so it always matches the context the engine will serve:

  512K + IQ3_S under 90 GB of RAM + yarn, no factor:  final 128K, factor 1
  no reduction:                                       384K -> 1.5, 512K -> 2
  a chosen method inside the trained range:           factor 1, not 2

Factor 1 removes the automatic expansion - the trained angles stand - but
it is not a switch for rope as a whole: an explicitly chosen method's
settings keep their defined behavior.  A user-supplied --rope-scale is
still kept verbatim when a reduction changes the context; the ok() line
now says where the factor came from (final context / trained, or as
requested), and help, docstring and docs/DETAILS.md drop the
"smallest covering factor" wording.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 00db760854 setup: a free-form --context past the menu got a raw StopIteration
The triple-check pass found what the menu could not: --context is not
limited to CONTEXTS, and the factor ladder (1.5, 2.0) ended at 512K, so
`--context 600000` died with a bare traceback instead of a setup message.
The ladder extends (3, 4) and a context past its top refuses cleanly,
naming --rope-scale as the way out.  The interactive explanation block
also stays quiet under --yes, whose ok() line announces the choice
already.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 9b83998d9d setup: the documented automatic rope configuration actually happens now
The comment said the setup adds the flag when the chosen context needs it;
the code called fail().  `START-HERE.bat --setup --context 393216` - the
command the docs give - stopped instead of configuring.  Now an omitted
--rope-scaling with a past-trained context resolves the config itself:
yarn (llama.cpp's extension method, recall-tested here at 512K; one
question when interactive, the silent default with --yes) and the smallest
covering factor (1.5 at 384K, 2 at 512K).  An explicit none is still
refused for such a context, with the reason and the way out spelled out
instead of a bare stop; explicit linear/yarn/scale pass verbatim; inside
the trained 262144 nothing turns on by itself.

The resolution moved AFTER the RAM reduction (the §4 ordering): the factor
must cover the context the engine will serve, so a 384K choice reduced to
128K by the RAM limit gets no scaling at all, and an explicit selection
that survives the reduction says so in a warning.

The rope config is now two pure functions (covering_factor, resolve_rope)
with its own unit tests (tools/test_setup_rope.py); the engine args take
the resolved values; help, module docstring, the context menu note, README
and docs/DETAILS.md describe the implemented behavior.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 9adfaa42e5 indexer: the batched append takes the one rope config, with a parity test
The rebase onto 0.1.24 had brought the QSA indexer's C-2 chunk append,
whose kernels rotated the pooled keys inline - cosf/sinf of the unscaled
angle, no mscale - so a scaled prefill would have written indexer keys
under a different rotation than the per-token append the decode path uses
(rope_scaling.hpp's one-rotation rule, broken silently).  The batch path
now resolves freq_scale/corr_dims/mscale from the same RopeScaling the
single append reads and rotates through the shared rope_scaled_angle; the
raw freq_base parameter is gone from its signature.

qsa_parity pins the contract the C-2 rework claimed: the batch append's
whole end state (pooled, spare, tail ring, block_pos) is BIT-IDENTICAL to
the sequential single appends', under none and yarn 2 alike, and the
scaling is observable in the batched keys.
2026-09-30 16:31:31 +02:00
Jakub Luwierski f4ae11b736 bench: rope-scaling needles past the trained 262K (linear 2, yarn 2)
The first end-to-end check that a scaled run recalls past the trained end:
8 of 8 probes found the needle at mid-depth, temperature 0 - 1k/32k/128k on
the stock model (arm A), 262k and 512k prompts on linear factor 2 (arm B)
and yarn factor 2 (arm C), 512k being 2.01x the trained 262,144.  The yarn
262k probe was repeated green after the rebase onto 0.1.20, next to the
per-arm engine and server logs and the launch recipes.

What it does not establish is written down in the README: one depth, one
needle per length, and no long-document perplexity with and without
scaling at 262K yet - the one open measurement.
2026-09-30 16:31:31 +02:00
Jakub Luwierski c7d2766528 generate: an explicit --rope-scaling none or --rope-scale 1 overrides the GGUF rope keys
The defaults collided with the opt-outs: "none" was both the untouched
--rope-scaling default and the explicit switch-off, and 1.0 the same for
--rope-scale, so qwen4exp.rope.scaling.type/factor from the model file won
even against an explicit flag - the opposite of the stated precedence (and
of docs/DETAILS.md).  Now the empty string and 0 mean the flag is absent
(the same sentinel idiom as --rope-freq-base 0 and --yarn-orig-ctx 0), the
model file fills only what no explicit flag named, and `none` / `1` are
the opt-outs they read as.

The resolution moves above the weight pipeline: the config needs only the
CLI and the model file's metadata, its one hard constraint (set before
session_init builds the table and the graphs capture the constants) is
unchanged, and a bad rope key now dies before the shards are scanned.

Checked on the bench machine against a synthetic GGUF carrying
rope.scaling.type yarn / factor 4: the flags absent -> yarn 4; --rope-scaling
none -> no scaling; --rope-scale 1 -> yarn, factor 1, mscale 1; explicit
linear 2 -> linear 2; a GGUF type other than none/linear/yarn dies before
the weight scan.  The real artifact (no rope keys) starts and generates
unchanged, and rope_parity stays green.
2026-09-30 16:31:31 +02:00
Jakub Luwierski c1d061faa9 docs: context extension (rope scaling)
DETAILS.md gains the 'Context extension' subsection - what linear and yarn do,
the setup path (383K/512K in the context menu pick the factor themselves), the
engine flags with llama.cpp's names and their defaults, why the scaling is fixed
for the whole run (K sits in the cache post-RoPE), and the one honest caveat:
inside the trained range a scaled run is a slightly different model, and the
recall numbers are text-only at one depth.  Troubleshooting grows the row for
the refusal a context past 262,144 now raises without the flag; README points
the context question at it.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 9ede9d94bc setup: --rope-scaling/--rope-scale; contexts past 262144 require scaling
CONTEXTS grows 383K/512K, labelled in the menu, and the setup refuses a context
past the trained 262,144 without --rope-scaling (the engine would refuse it
too).  The factor defaults to the smallest that covers the chosen context
(1.5 at 393K, 2 at 524K) and lands in the engine args like every other flag.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 963679b35e engine: --rope-scaling/--rope-scale/--yarn-* flags; rope keys as GGUF defaults
Nine flags, llama.cpp's names: --rope-scaling none|linear|yarn, --rope-scale,
--rope-freq-base, --rope-freq-scale, --yarn-orig-ctx, --yarn-ext-factor,
--yarn-attn-factor, --yarn-beta-fast, --yarn-beta-slow.  Ranges die in the
validation block at second zero; the config itself resolves after the model file
has had its say and before session_init builds the table or captures a graph
(rope_scaling.hpp's one-writer rule).  Precedence: CLI over the model file's
qwen4exp.rope.* keys over the struct defaults - today's artifact ships no rope
keys, so that channel is a no-op defaults path for future fine-tunes.

A scaled run prints its resolved summary (type, factor, freq_scale, base,
mscale, context vs trained) to stderr, which is what the bench logs quote, and
warns when scaling is on with the context still inside the trained range -
YaRN's magnitude correction applies everywhere, so that run is not the stock
model.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 0011637753 rope: thread scaling through the native, indexer and prefill rotation sites
The table path got scaling for free (it lives in the table contents); the three
ANALYTIC paths computed the angle in-kernel and so need the config explicitly.
Each wrapper now takes the RopeScaling and passes the resolved constants -
freq_scale, the corr_dims pair, ext_factor, attn_factor - as kernel arguments.
They are process constants set before session_init, so baking them at CUDA-graph
capture is correct by the same argument as every other kernel argument: only
per-token values must ride device memory, and positions still do (st.pos_dev).

The shared float32 arithmetic is rope_scaled_angle (rope_scaling.hpp), a
line-for-line rope_yarn: one definition for native_rope, the native indexer's
pooled-key rotation and prefill's rope, instead of three transcriptions that
could drift.  The spare indexer key keeps its zero angle; under YaRN the mscale
rides in through cos(0) on both pooled keys and indexer queries, so top-k is
unmoved.  rope_parity adds the check this whole wiring answers to: native vs
table on the same input, yarn and none alike - which caught the first draft
passing scaling.mscale() into the helper that applies the log term itself, a
double magnitude correction visible as ~7% on every rotated dim.
2026-09-30 16:31:31 +02:00
Jakub Luwierski 304d613cd0 kernels: rope scaling config and the scaled cos/sin table (none/linear/YaRN)
The artifact pins theta 1e7 with no rope.scaling keys, so the engine had exactly
one rope: inv[i] = theta**(-2i/n_rot), no freq_scale, no correction.  Contexts
past the trained 262,144 had no coherent path.

rope_scaling.hpp adds the one process config - none | linear | YaRN, llama.cpp's
types - and build_rope_table grows an overload that folds the whole scaled angle
(ggml's rope_yarn: interpolation, the corr_dims ramp along the pairs, and the
mscale magnitude correction) into the float64 host table.  The rotation kernels
cannot tell a scaled table from an unscaled one, so nothing downstream changes:
for the default decode path and the indexer's pooled key, scaling IS the table.

The math is transcribed from the vendored ggml (ggml.c corr_dims, ggml-cuda/
rope.cu rope_yarn/ramp, MIT headers kept), not re-derived; the one deliberate
deviation is bookkeeping - the ramp takes the PAIR index ggml's i0/2 would
produce, because every Strata rope kernel speaks in pairs.

For type None the new builder calls the original loop verbatim, and the parity
test holds it bit-identical; linear(4) is checked bit-exact against a float64
reference and equal row-for-row to none at p/4; YaRN against a float64
transcription of the ggml spec, plus the structural checks a tolerance cannot
fake: cos_tab[0] == mscale (position 0 is angle 0 for every pair), first pair
extrapolates / last interpolates at a far position (cos for the first, sin for
the last - near zero sin IS the angle, which is where interpolation happens),
and an observability assertion that the fixtures see scaling at all.
2026-09-30 16:31:31 +02:00
Niko1221 0ea2fc3176 Engine 0.1.29: sampled answers up to 40% faster with the same tokens (#197), faster prompt kernels (#187, #188, #207), correctness fixes (#154); setup requires it 2026-09-30 14:01:53 +02:00
Niko1221 bb628ce12f A fatal CUDA fault in the prompt path exits at once instead of hanging until the watchdog (#224); CMake warns when sm_120 is built with CUDA < 13.0 (#220, #224); iq_pack writes native_experts.txt under a temporary name, so a stop part-way leaves no partial pack (#172) 2026-09-30 14:01:52 +02:00
Niko1221 c3242f095c generate: the session state is zeroed for every pack in CLI generate mode (#167 follow-up); no .pi/ ignore line 2026-09-30 14:01:52 +02:00
Enky 15dd501b05 tests: bitwise check of native_mmvq's multi_exact contract, with a negative control (#148; CMake on the backend variables, CUDA-only) 2026-09-30 14:01:52 +02:00
samuelishida 0ace554231 generate: zero session state for a native pack before the prompt path
The canonical path zeroes session state from put_input at position 0, but a
native pack breaks out of that loop before the first put_input and runs verify
windows instead, leaving cudaMalloc'd state uninitialized. On a reused
allocation that produced NaN/overflow and degenerate identical-token output.
Zero the state explicitly when native_pack is set.

Also removes an unused argmax() helper that emitted a compiler warning, and
ignores the local .pi/ harness directory.
2026-09-30 14:01:52 +02:00
architectds b353a06128 setup: the 3-bit models' 262K context is counted against RAM, not a fixed 90 GB
IQ3_S and IQ3_XXS asked for 262K were cut to 128K on any machine under
90 GB of RAM. The limit is now counted: the experts' arena, plus the
context's 8-bit KV (in RAM with KV streaming, ~13.7 KB a token), plus
24 GB of room.

- A 64 GB PC still gets 128K for both models, as before.
- A box with ~84 GB keeps the 262K it asked for. A Colab A100-40G
  (83.5 GiB) runs IQ3_S at 262K, with images, in 56 GiB.
2026-09-30 14:01:52 +02:00
Hendrik PolczynskiandCodex ba1c809948 Show prefill speed alongside decode speed in dashboard
Display both rates in t/s with independent graph scales and a darker
prefill curve. Expose prefill_tok_s_mean using the engine's existing
rate, excluding cached tokens.

Co-authored-by: Codex (gpt-6-astra-medium) <[email protected]>
2026-09-30 14:01:52 +02:00
Joost R. 1564404f62 WSL2: survive the driver's ~1 GiB pinned host budget with 3 GPUs
Under WSL2 the NVIDIA driver can pin/map only about 1 GiB of host memory in
total (dmesg: "dxgk: create_existing_sysmem: establish_gpadl failed: -122").
With --expert-cache-device1/2 the startup ran out of it twice:

- CUDA2's context is created after CUDA0's weights and MTP, whose mapped host
  buffers have spent the budget: "CUDA2 experts: cudaSetDevice(2) failed: out
  of memory". STRATA_EARLY_REMOTE_CONTEXTS=1 now creates every secondary
  context up front, like CUDA1's (opt-in: the default order is unchanged).
- With three contexts up, the 260 MiB mapped native embedding no longer fits:
  "native embedding: cannot pin 260 MiB". It now falls back to a device copy
  on the current GPU instead of failing the start.

Measured in Docker Desktop (WSL2), RTX 5070 Ti + 2x RTX 5060 Ti, Swift 1.5
IQ3_XXS: starts and serves with both helper caches (5000 experts each).
2026-09-30 14:01:52 +02:00
Hasegawa Takuya e258e6ec5b perf(prefill): allow explicit routed-only staging for large chunks 2026-09-30 14:01:52 +02:00
Andy be41a09e5b ple: --ple-io ram: refused on Windows (no mlock), 'locked' only when mlock succeeded else 'loaded (not locked)', neutral comments + --help (review) 2026-09-30 14:01:52 +02:00
dsh 0d40c17d2f ple: --ple-io ram locks the n-gram table in RAM (no SSD read on the prompt/token path) 2026-09-30 14:01:52 +02:00
Andy 802c6619df prefill: GDN recurrence pipelining - the neutral 16-column variant (STRATA_GDN_CB) dropped, one kernel on upstream's split (review) 2026-09-30 14:01:52 +02:00
Andy ab50d19c9b decode: QSA scores multi - comment names every caller that takes it (decode captured or not, prefill's pooled16); no box-specific note (review) 2026-09-30 14:01:52 +02:00
Apposite245 4ff986a0aa serve: on Windows the engine, vision encoder and MCP servers end with the server
Closing the console window (or a crash, or Task Manager) ends the server without its cleanup, and strata --serve,
strata-vision and the MCP servers kept running with their VRAM and RAM. Each child now goes into a job object that
kills everything in it when its only handle - the server's - closes; what the children start joins it too. The
server itself stays out, so a browser --open starts is not tied to it. A no-op elsewhere.

serve/test_winjob.py: a contained child and its grandchild end when the process that contained them is killed.
2026-09-30 14:01:52 +02:00
Guillaume PUTIER 05d4cb11a5 sampler: top_k by lexicographic threshold, split over the whole GPU
The sampled path (penalties, top_k, top_p, min_p, temperature, draw) kept one
row on one SM: sampler_kernel excluded the logits already kept by sweeping the
kept list for every logit of every round, O(k^2 x n_vocab) shared-memory
compares per row, and ran the double-precision tail on all 1,024 threads.

Three implementations now pick the same token:

- Split (new default): stage 1 cuts each row into 4,096-logit blocks (61 per
  row at 248,320 logits), each warp keeps the top_k of its 1,024 penalised
  logits in registers, and the block merges its four warp lists. Stage 2, one
  warp per row, merges the block lists and runs the tail on one warp.
- sampler_one_block_kernel: one block per row, top_k rounds that each take the
  logits strictly after the previous pick in the selection order (value
  descending, id ascending). It is the fallback when the split cannot run
  (stream under graph capture, vocabulary above 262,144, more than 64 rows,
  no scratch) and is selectable with STRATA_SAMPLER_ONE_BLOCK=1.
- sampler_kernel, the previous kernel, kept unchanged as the reference and
  selectable with STRATA_OLD_SAMPLER=1.

The list, its order and the pick are unchanged: the first k of a union lie
within the first k of each part, and a merge of ordered lists is ordered. The
tail keeps the same arithmetic, with the exps and quotients shared across one
warp and the ordered sums and scans run by lane 0.

The block lists live in a per-stream scratch buffer grown on demand. A
grown-out buffer is retired rather than freed, and a failed grow is
remembered so later calls of that size do not retry cudaMalloc.

sampler_parity is the parity test. Fixtures 16 to 18 pin the whole top_k list
position by position under ties (+inf, -inf, NaN, +0/-0, sentinel id 0,
penalised tokens landing on other tokens' values), tied draws through the whole
chain, and the automatic fallbacks (graph capture, row cap). Each pick is an
integer token id compared for equality with a host reference. ctest runs the
binary once per path: sampler_parity (split), sampler_parity_one_block and
sampler_parity_old, so all three paths are held to the same expected ids.

hip_compat/cuda_runtime.h: map cudaStreamIsCapturing, cudaStreamCaptureStatus
and cudaStreamCaptureStatusNone to their hip equivalents, which the new
capture check needs on HIP builds.
2026-09-30 11:46:25 +02:00
Andy 4305fdc3fb decode: QSA block scores read each key block once for all of a window's queries (bit-identical; STRATA_SCORES_MULTI=0 = old grid) 2026-09-30 11:45:45 +02:00
Andy 18106b1c31 prefill: software-pipelined loads in the column-split GDN recurrence (bit-identical; STRATA_GDN_PIPELINE=0 = old kernel) 2026-09-30 11:45:45 +02:00
pipeob0 95883f46e8 E-2 on the AVX-2 path too: prefetch the i-quant expert rows (same switch, CPUs without AVX-512)
24d4013 put the software prefetch in iq_avx512.cpp, so it only reaches CPUs with AVX-512; the AVX-2 kernels, which is what a Zen 3 / Haswell / Skylake-client box actually runs the i-quant rows on, had none.  Same env, same default, same hint: STRATA_IQ_PREFETCH is the distance in bytes, 0 = off, default 2048.

Measured on a Ryzen 7 5700X3D (AVX-2 only, 64 GB DDR4-2666, 4 KB pages - large pages refused) with IQ3_S decode, 4 rounds interleaved, outputs identical:
  gate/up phase   13.571 -> 13.024 ms/round   (-4.0%)
  rows            25.1   -> 26.1 GB/s         (+1.0)
  whole round     45.54  -> 44.94 ms/round    (-1.3%)
and with the pool cold (first round after the arena is built) the round is -2.4%.  The same 4 rounds with STRATA_IQ_PREFETCH=0 land within 0.1% of the unpatched binary, so the patch itself costs nothing.  native_expert_parity: 0 failures with the prefetch on and off.

The non-temporal hint was measured worse than T0 at the same distance (-0.7% vs -1.3%, +0.5 vs +1.0 GB/s), so this keeps T0.  4096 is a little worse than 1024/2048, which is why the default stays 2048 as in the AVX-512 file.
2026-09-30 11:45:45 +02:00
Guillaume PUTIER 5b9a5029dc build: guard the last two bench/micro targets on their file, as upstream does for the others 2026-09-30 11:45:44 +02:00
Guillaume PUTIER 255bf03d3e gguf_reader.hpp: define NOMINMAX before windows.h 2026-09-30 11:45:44 +02:00
Guillaume PUTIER c9d3fd6877 expert_source, s2_expert_grouped: guard the verify window's fixed tables 2026-09-30 11:45:44 +02:00
Guillaume PUTIER a8d79afab7 qsa: mask cells whose page did not resolve; refuse a clock sweep over fewer slots than the resolve block 2026-09-30 11:45:44 +02:00
Guillaume PUTIER e2b0cbcde9 bf16: a NaN stays a quiet NaN in f32 -> bf16, as in ggml 2026-09-30 11:45:44 +02:00
Guillaume PUTIER 91e8d21227 s_gemv: the q8 split kernel reaches its barrier before a rowless warp returns 2026-09-30 11:45:44 +02:00
Niko1221 10409135dd Engine 0.1.28: the expert cache reserves the draft head's VRAM (0.1.27 left 16 GB cards below the stall line), a cancelled request no longer fails the next one, the API key protects /status; setup requires it 2026-09-30 11:18:54 +02:00
Niko1221 03939f1c06 docs: the draft layer's token subset and --draft-vocab 2026-09-30 11:18:54 +02:00
Niko1221 75e6efeb77 setup: --host/--api-key/--draft-vocab on a start are kept for the model (#179); --draft-vocab en keeps the English/code subset (~110 MiB less VRAM); CUDA 13.0 for RTX 50 builds (#220); llama.cpp's web UI is not unpacked (Windows path limit, #206); Swift Q2_0 hidden (#171); the earlier data folder and nested Strata-data move into --data-dir (#198); whole shards without a finish mark count (#173) 2026-09-30 10:54:45 +02:00
Niko1221 fa59b3f3a9 server: /status needs the API key and forgets the answer's end (#212); constant-time key check, an explicitly empty key refuses to start (#213); an engine exit quotes its last log line (#215); tool calls end at their own </parameter> and </tool_call>, so values may contain them (#210) 2026-09-30 10:54:45 +02:00
Niko1221 eef241178c engine: the expert cache reserves the draft head's VRAM (0.1.27's CJK subset came out of the 700 MiB reserve, #199); a clear message when no VRAM is left for the cache (#174); the prompt's verify-window stage has its own label (#217) 2026-09-30 10:54:45 +02:00
Oluwabori Olaleye 7980d3719f Expert cache on WDDM: 1 GiB steps before quarter steps (~4 GiB more cache on a 32 GB card); Ctrl+C stops the server on Windows; no stack trace when a client hangs up (#153)
Squashed from PR #153.
2026-09-30 10:46:55 +02:00
GioStrives 0425829226 verify: cudaFuncGetName only with CUDA 12.3+ (an sm_75 build with CUDA 12.0 failed)
It names kernels only for the STRATA_VERIFY_NODES debug dump.
2026-09-30 10:46:55 +02:00
Pravesh Khatana 481b69246f fix(engine): clear stale error/cancellation state between requests and before prompt prefill
When a request is cancelled during prompt reading (e.g. client disconnect
or timeout), err is set to 'cancelled'. Because err is declared in main()
and was never cleared between requests, any subsequent request in v0.1.26+
evaluated 'if (!e.empty())' in sp.on_chunk on its first chunk, immediately
failing with 'strata serve: cancelled' and terminating the engine with code 1.

Clearing err at the start of each request, before prompt segment runs,
and on entry to Prefill::run prevents state pollution across requests.
2026-09-30 10:46:55 +02:00
Niko1221 99b40973c6 Engine 0.1.27: CJK answers faster (the draft subset with Chinese, Japanese and Korean), RTX 20 cards, the HIP build without CUDA headers; setup requires it 2026-09-29 22:58:59 +02:00
Niko1221 e227683eb9 data/draft_vocab.bin: the draft subset with all Chinese, Japanese and Korean tokens (40,525 -> 106,299 ids; #137). Q2_0 on CJK answers +15-38% decode, English -1-2%, the same expert slots on 12 GB 2026-09-29 22:58:59 +02:00
Niko1221 d62579d482 RTX 20 in the docs and setup's texts; the ready-made image encoder is compiled here when it has no code for the card (BUILD.json vision_archs) 2026-09-29 22:58:59 +02:00
Niko1221 717971d33b tools/draft_vocab.py: the draft head's token subset, with whole scripts added (#137); setup replaces an older shipped subset at setup and at start, a hand-made one is kept 2026-09-29 22:26:45 +02:00
Niko1221 d92c2a3034 setup: AVX2 from the CPU itself when Windows says no (#159); the engine is compiled again for a card it has no code for, at setup and at start (#128) 2026-09-29 22:23:55 +02:00
Niko1221 ed01b4de03 server: only the image markers the template writes are images; the same text in a message stays text (#150) 2026-09-29 22:23:55 +02:00
Hozák Martin ddfb98a6ea Turing port: run on RTX 20 (sm_75) and newer
Rebased onto main (0.1.22): the portable pre-sm_80 QSA scorer and the sm_80
guard on the tensor-core prompt attention are already in main, so this is
the rest of the port.

- the runtime floor: device_info refuses anything older than compute
  capability 7 (main's runtime check still requires sm_120, so a binary
  built for sm_75 would never start),
- fused_gr: the down kernel carries the tokens in slices that fit the
  card's shared-memory opt-in (Turing, 64 KB: 6-token slices; 4 tokens when
  the card reports no opt-in, under the 48 KB default), merged on top of
  main's per-device opt-in - full 8-token windows no longer fail on Turing
  with "invalid argument",
- the setup/CMake floor is 7.5 (RTX 20 / 30 / 40 / 50) without an
  experimental flag: CMake's arch refusals, setup.py's gpu_problem and the
  GPU hints and error texts,
- docs/MULTI_GPU.md says what a Turing card actually runs.

Verified end-to-end on RTX 2070 (sm_75, 0.1.20 base): model loads, OpenAI/
Anthropic API serving works, greedy output identical to the sm_120 build.

Merged (PR #87) with two changes: the runtime floor is 7.5 exactly (major*10+minor < 75; the PR let Volta 7.0/7.2
through), kept beside main's HIP check; and a card reporting no shared-memory opt-in slices the tokens to what fits the
48 KB default for its own tile (4 tokens on CUDA, all 8 with HIP's smaller tile, so gfx1100 keeps one launch).
2026-09-29 22:23:41 +02:00
samuelishida c81819a87c build: make qsa_prompt_attn.cu compile on HIP-only toolchains
The tensor-core prompt-attention kernel is already refused at runtime on AMD
(line 695), but the file unconditionally included <math_constants.h> for
CUDART_INF_F, which is not present on a ROCm-only build. Replace it with the
portable INFINITY macro from <cmath> so the HIP build succeeds without a CUDA
 toolkit installed.
2026-09-29 22:19:51 +02:00
Niko1221 4832dccd09 docs: speed tables for engine 0.1.26 (prompts 8-28% faster at 32K-128K than 0.1.22) 2026-09-29 22:09:44 +02:00
Niko1221 78db15b630 docs: the low-RAM mode 2026-09-29 21:23:21 +02:00
Niko1221 cfc73be8b9 Low-RAM mode: --mmap-experts works with native (IQ) packs built with experts.bin; setup uses it when the model's experts do not fit the RAM beside the OS (a 32 GB PC with a big GPU: the experts come from the file cache, the GPU holds the most-used) 2026-09-29 20:35:16 +02:00
Niko1221 2f8d9be954 Engine 0.1.26: prompts up to 32K 4-10% faster (the draft layer's prompt pass in batches); setup requires it 2026-09-29 20:01:05 +02:00
Niko1221 c9077a7669 setup: a PC with NVIDIA and AMD cards asks which to use (a first run took NVIDIA without mentioning the Radeon) 2026-09-29 19:59:11 +02:00
Niko1221 a402eefdd1 E-9: the draft layer's prompt pass in batches through the prompt path's kernels (its Q8_0 matrices as Q8_1 x Q8_0 MMQ, the drafter's own integer dot products): 0.9 s -> 0.07 s of a 32K prompt, the same draft acceptance; STRATA_MTP_BATCH=0 switches back 2026-09-29 19:59:11 +02:00
Niko1221 d4e047660b docs: AMD through setup (./setup.sh --backend hip), the k8v4 KV option 2026-09-29 19:17:37 +02:00
Niko1221 cb5ea4acc8 kv_hybrid_parity: on HIP the tensor-core prompt attention refuses every pool (CUDA-only), which is the expected path 2026-09-29 19:15:16 +02:00
Niko1221 922d1408b6 Engine 0.1.25: faster prompts (expert grouping off the copy engine, fused hyper-connection kernels), AMD HIP backend (experimental), --kv k8v4; setup requires it 2026-09-29 19:12:10 +02:00
Niko1221 86c904ca42 Revert "E-8: the draft layer's prompt pass in groups of 8 rows (was max_t = 6): -6% of its time, the same K/V and output"
This reverts commit 45d857c7cf.
2026-09-29 18:41:34 +02:00
Niko1221 45d857c7cf E-8: the draft layer's prompt pass in groups of 8 rows (was max_t = 6): -6% of its time, the same K/V and output 2026-09-29 18:22:21 +02:00
Niko1221 d07d04fcae CMake: kv_hybrid_parity through the backend's runtime target (CUDA or HIP) 2026-09-29 18:10:19 +02:00
Niko1221 36ef9d6e6b Merge the AMD HIP backend (PR #121 by Konstantinos Korres, rebased onto 0.1.24) and its setup support 2026-09-29 18:09:50 +02:00
orange 45bd2d0a4d qsa_prompt_attn: mode 3 for hybrid K8V4 - int8 K, V dequantized from q4 at gather
The generic prompt kernel now templates K and V element types separately, so
mode 3 is mode 1's INT8 K path (codes, scales, i8x2 MMA conversion) composed
with mode 0's fp16 V path: the row's q4_0 blocks dequantize straight into the
fp16 V row at gather. The caller's output Hadamard is unchanged. Pure Q4_0
pools still refuse (whole-format q4 stays on the old kernel).

The v2 cp.async int8 kernel (launch_i8) is untouched; mode 3 uses the generic
launch<>, as fp16 does. Parity's [5/5] flips from a refusal check to a numeric
check against the dequant reference (accuracy-level, not bitwise, like the
kernel's own int8 variant).
2026-09-29 18:09:42 +02:00
orange 3597c47ff8 Hybrid K8V4 KV cache: INT8 K + Hadamard-rotated Q4_0 V (--kv k8v4)
K stays INT8 (unrotated - the attention scores stay exactly int8's), V uses the
rotated q4_0 of kv_q4.hpp: 816 B per cell vs int8's 1,056. The K/V quantization
asymmetry is the point: K enters through the softmax scores, V only through a
softmax-weighted average (llama.cpp's q8_K/q4_V hybrids reason the same way).

No new kernels: the append/gather entry points already select K/V by blockIdx.z
and take separate K/V pointers, so the hybrid composes the existing validated
q8/q4 kernels with the unused half's lanes folded onto the used pool (a
bit-identical duplicate write). The fused attention gets KV_MODE 3 (K-side int8
loader, V-side q4); 0.1.22's tensor-core prompt path (qsa_prompt_attn) refuses
the hybrid pools through its !v_q check and the old kernel runs - covered by
the parity test. The MTP drafter stays INT8 (mtp.cpp toggles the format globals
around its state creation, any ring shape); qsa_kv_format exits on a hybrid
state rather than misreporting a layout (the kernels' own unsupported-geometry convention); the hybrid appends pass nullptr for
the host mirror (mode 0 has none). setup.py excludes k8v4 from the whole
auto-streaming decision (the engine refuses --kv-resident with it).

kv_hybrid_parity: bitwise append/gather through a non-identity page table,
step and batched mode-3 attention vs host references (<= 1.1e-07), and the
qsa_prompt_attn refusal - all at machine precision; kv_q8/kv_q4 parity unchanged.

Measured on a RTX 3090, Coder IQ1_M, 118.75k-token prompts, temp 0: decode
82-97 t/s (int8: 75; the band is draft-acceptance variance), prefill unchanged
~1,130-1,170 t/s, 26-needle seed 42 identical to int8's tokens, KV 1.39 GB at
131k (int8: 1.80). At --max-context 204800 on 24 GB: 1,024 t/s prefill,
82-91 t/s decode, 25/26 needles at 197.9k.
2026-09-29 18:09:42 +02:00
Niko1221 3945aac617 F-2: a half's hyper-connection write fused with the next half's norm (bit-identical; with F-1 +4.0% at 32K, +3.3% at 128K) 2026-09-29 17:56:08 +02:00
Niko1221 2a6bbc7304 F-1: the hyper-connection read without its FP32 copy of the normalized rows (gr_mix recomputes them, bit-identical; +1.1% at 32K, +1.7% at 128K) 2026-09-29 17:45:17 +02:00
Niko1221 5cb4ff283d AMD: hipBLASLt table for 100200 (the pinned ROCm), used by setup's configs (+42-59% prompt speed on the 7900 XTX); server: a config's env reaches the engine 2026-09-29 17:26:20 +02:00
Niko1221 1b3bb8f808 prompt path: the per-layer expert grouping tables through mapped pinned memory, off the copy engine (+3.9% at 32K, +4.5% at 128K, bit-identical) 2026-09-29 15:37:52 +02:00
Niko1221 9065ae0b2d AMD: card name, setup headline, docs for ./setup.sh --backend hip 2026-09-29 15:27:22 +02:00
Niko1221 10d5cad82b setup (experimental): AMD RX 7900 XT/XTX on Linux - KFD detection, ROCm from TheRock wheels into .venv, HIP engine compiled here; server: HIP_VISIBLE_DEVICES 2026-09-29 15:22:22 +02:00
Niko1221 8578e6da6f docs (AMD): the settings after the rebase onto 0.1.24 2026-09-29 15:17:50 +02:00
Niko1221 504dc71aa0 HIP test: the batched indexer append within its contract (p0 + n <= max_cells) 2026-09-29 15:12:52 +02:00
Niko1221 32fe13bac9 HIP: build main's post-0.1.21 code (trap, device attribute, window-graph listing and GPU stamp); Windows NOMINMAX 2026-09-29 15:05:55 +02:00
Niko1221 520883e049 HIP tests: main's batched indexer append and I/O pool (C-2, C-4) replace the PR's duplicates 2026-09-29 14:36:32 +02:00
Niko1221 78a5bf70dd HIP: keep the D-1 attention and QSA select tensor-core paths CUDA-only (AMD takes the portable kernels) 2026-09-29 14:34:31 +02:00
Konstantinos Korres fa42f31dbd AMD HIP backend for gfx1100 (PR #121, squashed onto its 0.1.21 base) 2026-09-29 14:33:45 +02:00
Niko1221 aaeb5a4120 Engine 0.1.24: long prompts faster (the QSA selection on tensor cores); setup requires it 2026-09-29 13:53:04 +02:00
Niko1221 2f9c38f0fe QSA select: block scores on tensor cores, top-k keys in registers (+14.6% prompt at 128K)
The prompt path's selection at long context was 18% of a 128K prompt. Two changes:
  * block scores as a GEMM on tensor cores: rows (query, indexer head) x blocks, K = 128, relu per head summed
    in the warp kernel's order; 3xTF32 (hi*hi + hi*lo + lo*hi) keeps FP32-level accuracy; the tail block (the
    `dead` key, +1e9) keeps the warp kernel's arithmetic. sm_80+ (TF32 MMA), shared-memory opt-in per device;
    STRATA_SELECT_OLD=1 (or an older card): the warp kernel. Not bitwise (another summation order).
  * top-k with each query's keys read once into registers (1,024 threads, up to 33 blocks each: contexts up to
    ~135K), per-warp histograms, block scans - the same selection rule, identical ids (exact; decode uses it
    too). STRATA_TOPK_OLD=1: the original kernel.
5070, 256 queries (src/kernels/qsa_select_bench.cpp): 32K 1.18 -> 0.36 ms, 128K 4.68 -> 1.33 ms; synthetic selections
identical 256/256, the register top-k identical to the reference.
Quality gate (Q2_0, vs STRATA_SELECT_OLD=1 + STRATA_TOPK_OLD=1): needles 8K/16K/32K x 5 depths 15/15 with both;
16K continuation 32/32 identical, KL 0.005; 32K top-1 same, KL 0.03 (the size of any FP32-level change, cf. D-1).
Prompt: 32K 1,742 -> 1,797 tok/s (select 897 -> 365 ms), 128K 1,608 -> 1,843 tok/s (select 14.6 -> 4.2 s),
time to first token 82.7 -> 72.3 s.
2026-09-29 13:40:49 +02:00
Niko1221 d17fcba9f6 prefill timing: the GDN layer split into projections in, conv+gates, recurrence, projection out 2026-09-29 13:40:49 +02:00
Niko1221 d32101a617 Speed tables: prompts with engine 0.1.22 (bench/results/2026-09-29-speed-0122) 2026-09-29 13:38:54 +02:00
Niko1221 3a7f188578 Engine 0.1.23: image requests honor sampling, 8 GB cards start, batched verify window, llama.cpp endpoints; setup requires it 2026-09-29 13:10:38 +02:00
Niko1221 66f3f30ba0 setup: a size that needs more RAM than the PC has is a warning and a question, not a stop (#125)
It says what happens (the experts must stay in RAM, so the OS pages part of them: much slower, maybe no start) and
asks, "no" by default, so an unattended --yes install still stops. Thanks @FatheredPuma81.
2026-09-29 12:27:11 +02:00
Niko1221 3b2ced8eb6 The Task Scheduler load hint on Windows only (a slow load elsewhere is the disk) 2026-09-29 12:26:40 +02:00
Niko1221 a3cdb8a2a1 Merge branch 'pr-126' into next-023 2026-09-29 12:26:25 +02:00
Niko1221 02c8092acf D-1: the prompt attention's shared-memory opt-in per device (a layer split with fp16 KV failed on the second card)
The opt-in was set once per process, but it is a per-device setting. The int8 kernel fits the default 48 KB; the
fp16 one (KV of contexts up to 8K) needs 54 KB, so on the second card of a split its launch failed.
2026-09-29 12:06:58 +02:00
Niko1221 21482dfb09 CMake: dequant_bf16_test only with CUDA (a CPU-only configure with the tests failed, #93)
Thanks @Avicennasis.
2026-09-29 11:50:33 +02:00
Niko1221 1977f5952b Merge PR #109 (q8atnight): batch the verify window's per-token kernels
Resolved against 0.1.22: E-6's device-plan waits and copies kept (the row copy of the CPU share is used when the
device plan is off - bit-identical, +0.0 on the GPU rows as the pool leaves them); the multi-GPU per-device
shared-memory opt-in and stage pool callback kept. Left out: the session-wide --pcie-frac 0 skip of the PCIe path,
because a request can ask for a PCIe share (pcie_frac=, used by --calibrate) and the window graph is captured once.
2026-09-29 11:41:04 +02:00
Niko1221 58f6a1aa70 serve: with no expert cache (a full 8 GB card) prompts are read in 1,024-token chunks; a clear message when the prompt buffers don't fit (#85)
--prefill auto sized the chunk only when there were cache slots to lend; with none the buffers stayed at 8,192
tokens and a full card stopped at start with "device buffers ... do not fit". Thanks @sinand99.
2026-09-29 11:39:32 +02:00
Niko1221 12ef286562 Image requests honor sampling (temperature, top_p, top_k, penalties, seed) like text (#75)
The server sent only cvec= with GENI and the engine skipped every other key, so a request with an image decoded
greedily whatever the client asked. Both now pass the text path's keys. Defaults unchanged (greedy unless set).
Thanks @jleaders for pinning it down.
2026-09-29 11:39:03 +02:00
Niko1221 d2a70e8321 serve tests: the page links its files relatively since #82 2026-09-29 11:36:28 +02:00
Niko1221 b839e26b78 Merge branch 'pr-118' into next-023 2026-09-29 11:34:45 +02:00
Niko1221 9aa0362f10 Merge branch 'pr-92' into next-023 2026-09-29 11:34:45 +02:00
Niko1221 9d2309e4e0 Merge branch 'pr-91' into next-023 2026-09-29 11:34:45 +02:00
Niko1221 762daf658a Merge branch 'pr-89' into next-023 2026-09-29 11:34:45 +02:00
Niko1221 0b0dd51362 Merge branch 'pr-82' into next-023 2026-09-29 11:34:45 +02:00
Niko1221 1d10562097 Merge branch 'pr-81' into next-023 2026-09-29 11:34:44 +02:00
Niko1221 946bafaebc Merge branch 'pr-104' into next-023
# Conflicts:
#	serve/server.py
2026-09-29 11:34:44 +02:00
Niko1221 8d4790a953 Merge branch 'pr-113' into next-023 2026-09-29 11:33:57 +02:00
Niko1221andmikicvi 81726d35a5 serve: timings carry the speculative draft counts (draft_n, draft_n_accepted), from PR #83
Co-Authored-By: mikicvi <[email protected]>
2026-09-29 11:33:56 +02:00
Niko1221 2e1efbca31 Merge branch 'pr-107' into next-023
# Conflicts:
#	serve/server.py
2026-09-29 11:32:59 +02:00
Niko1221 24a384da06 Merge branch 'pr-122' into next-023 2026-09-29 11:32:21 +02:00
Niko1221 283fc353e4 D-1: the tensor-core prompt attention only on sm_80+ (the experimental sm_75 build compiles again)
Its MMA and cp.async need sm_80: an sm_75 build stopped with ptxas errors. They now compile to a trap below
sm_80, and qsa_prompt_attn_batch refuses such a device at run time, so the old kernel runs there. RTX 30/40/50:
the same code (parity unchanged: vs FP64 3.17e-6).
2026-09-29 11:26:54 +02:00
Zauberio 027986e8ab Windows: name the Task Scheduler load throttle (24x) and how to fix it
The expert-arena load runs at ~0.05 GiB/s (13-14 min to ready) when the serve
is started from Task Scheduler with its registration defaults (Priority 7 /
Below normal, least-privilege token), versus 1.4-1.5 GiB/s (~35 s) from a
terminal or SSH - same binary, args and cache state.

- docs/DETAILS.md: measured table + the two registration settings that fix it
- src/program/generate.cpp: when load_gib_per_second() < 0.2 GiB/s, print a
  hint naming this cause under the load line
2026-09-29 11:18:54 +02:00
Niko1221 c43a308fed Engine 0.1.22: faster prompts, multi-GPU without flags; setup requires it 2026-09-29 11:04:51 +02:00
Hendrik PolczynskiandCodex 08f8d591da serve: add llama.cpp-compatible discovery endpoints
Expose loaded-model metadata through /models and /v1/models.
Add /props and single-slot /slots responses with existing API-key
enforcement and regression coverage.

Validation: all 30 server tests pass. Pi 0.87.1 discovered the loaded
model with 262144-token context and thinking support, and completed
a chat smoke test successfully.

Fixes #117.

Co-authored-by: Codex (gpt-6-astra-medium) <[email protected]>
2026-09-29 10:45:33 +02:00
Niko1221 0f44fb492d Monitor: every card of a layer split (issue #112)
The GPU, VRAM, temperature and power cards read all the cards the model runs on: their total (VRAM, power, PCIe
traffic), mean (load) or hottest (temperature), with each card's own reading underneath; the GPU name lists them.
One card: unchanged.
2026-09-29 10:44:12 +02:00
Niko1221 68072d30d4 D-4: refill the prompt path's lent cache slots with queued copies and one wait
The same copies on the same (legacy) stream, but queued instead of waited on one by one: the same bytes, ordered the
same against earlier work. Q2_0 on a 5070 (1,782 slots): 162 -> 96 ms per prompt, 4K and 32K outputs identical.
The rest (publishing slots in the background while decode starts) would hide the last ~2% of the time to first
token on a 4K prompt; not done. STRATA_REFILL_BLOCKING=1 keeps the old loop.
2026-09-29 10:43:11 +02:00
Niko1221 4fb14bf923 prefill timing: KV staging on its own line ("kv stage")
D-3 measurement: Q2_0, 128K prompt with KV streaming, 5070: staging 1,331 ms of 81.3 s (1.6%); staging only the
selected union could save part of that, so D-3 is not done. The same prompt: 1,144 tok/s on 0.1.21, 1,596 on this
branch (+39.5%), time to first token 115.7 -> 83.4 s.
2026-09-29 10:33:39 +02:00
Niko1221 bba62249d5 setup: a card that cannot be used names both ways that work (--gpus 0,2, or one card: --gpu 0 or --gpu 2) 2026-09-29 10:18:09 +02:00
Niko1221 0be1ee9df3 setup: compile with Visual Studio 2019/2022, not a newer one installed next to it
vswhere -latest picked Visual Studio 2026 (version 18) on a PC that also has 2022, and CUDA 13.1's nvcc stops with
"unsupported Microsoft Visual Studio version". Now only 2019/2022 are looked for; with only a newer one, the 2022
Build Tools are installed as when none is there.
2026-09-29 10:09:52 +02:00
Vilim Mikic 2c1292f688 Merge branch 'main' into web-relative-paths 2026-09-29 09:01:59 +01:00
Vilim Mikic bec183e28f Merge branch 'main' into sm80-runtime-check 2026-09-29 09:01:50 +01:00
Niko1221 548007f874 setup: several GPUs without typing - a table of the cards, the choice asked, "not supported" said plainly
Several users could pick one GPU but not two. Now START-HERE.bat / setup.sh:
  * lists every NVIDIA card with "can be used" or why not (older than the RTX 30 series; under 8 GB to share a model);
  * asks, when two or more cards can share the model, which to use - the two best together recommended (newest
    generation first), every combination and each single card offered; --yes takes the recommendation;
  * asks once, at the next start of a model installed on one card, whether to use both from now on (kept);
  * --gpus 0,2 / --gpus all / --gpu 0,2 at a start saves the cards for that model; --gpu N is for that start only;
  * a card that cannot be used (or is gone) stops with its reason and the command that works instead.
The engine gets code for every chosen card (a local build compiles all their generations; the ready-made one is
checked against each), the context is recommended from the smallest card, and --calibrate measures the split.
2026-09-29 09:56:16 +02:00
Niko1221 cc02cdef73 Merge mg/limits: images, control vectors, KV streaming, helper GPUs and mid-prompt checkpoints across GPUs 2026-09-29 09:52:37 +02:00
Niko1221 732a3f4c92 D-1: the prompt path's QSA attention on tensor cores
One block per (query, KV head) walks its selected cells in chunks of 32 with an online softmax (no split-K
scratch, no merge kernel). q.k and p.v are FP16 MMAs with FP32 accumulation; int8 codes enter exactly, their
scales in FP32; q and p are split into hi + lo halves after an exact power-of-two prescale, so the result keeps
FP32-level accuracy. Int8 KV: each warp owns one 64-dim scale group and gathers its own K/V slices with
double-buffered cp.async. FP16 KV: the first version. Q4_0 KV, or STRATA_PROMPT_ATTN_OLD=1: the old kernel.

qsa_prompt_attn_parity (5070, synthetic): vs FP64 3.2e-6 (old kernel 2.5e-6); int8 4.0x faster (26.6 -> 6.6 ms per
2,048-query chunk at 32K), fp16 1.45x. Q2_0, 32K prompt: attention 5,216 -> 1,318 ms, prompt 1,386 -> 1,646 tok/s
(+18.8%). Not bitwise: the model amplifies any FP32-level change (v1 vs v2 of this kernel, same accuracy, another
summation order, differ as much as old vs new: residual rel L2 mean 0.060 vs 0.061). Needles 8K/16K/32K x 5 depths:
15/15 with both kernels.

Also STRATA_SEL_OVERLAP (debug): how many of the cells 16-query tiles read compared to one query at a time.
2026-09-29 09:48:42 +02:00
Niko1221 06bee97f7e setup: --gpus on an installed model splits it across those cards; --gpu 0,2 means --gpus 0,2
A user reported that he could pick a GPU but not two. With v0.1.21 (reproduced on a 5080 + 2080 Ti + 3090 PC):
  * START-HERE.bat --gpus 0,2 on an installed model started it on GPU 0 alone: --gpus was saved only with --setup,
    and the start passed its first card as --gpu, which overrides the saved choice;
  * START-HERE.bat --gpu 0,2 stopped with "argument --gpu: invalid int value";
  * an update unzipped next to a layer-split install stopped at step 1: "there is no GPU [0, 2]".
Now the first two start across both cards (the server gets --gpu 0,2 and splits the layers, auto), and an update
sets itself up across the same cards with the saved layer split.  --gpu N still runs one card.
2026-09-29 09:48:30 +02:00
Marmaduke Woodman 7b3087a82f serve: routing traces from the live server (--dump-routing under --serve)
Two gaps kept the routing trace one-shot-only:

- drive_pool_multi, the verify-window dispatch the serve path uses for every
  decode call, had no trace hook; --dump-routing under --serve opened the file
  and wrote nothing. Records now go out per token in drive_pool's format
  (layer, k, ids, weights). The multi dispatch fuses the router weights into
  the kernel and does not surface them, so records carry unit weights:
  make_profile.py ranks pairs by routed frequency, which is the signal that
  matters; a one-shot run still records true weights.
- the trace sat in a stdio buffer until teardown, so a crash lost the whole
  session's data. It now flushes at every request's DONE.

Verified live: a serve-mode trace grows per request, parses cleanly in
make_profile.py's format (all 48 layers, records per decode position), and
captures real multi-session traffic for expert-profile building.
2026-09-29 07:32:22 +00:00
Niko1221 0aa72eabe1 E-6: the device plan opt-in (STRATA_VERIFY_DEVICE_PLAN=1)
Exact on RIBPC (5080 alone and 5080+3090, device plan off/on 10/10 identical), but decode and prompt speed
within noise (1 GPU story 86.7 -> 84.5, code 103.3 -> 102.0 tok/s; 2 GPUs 81.5 -> 82.4, 113.9 -> 109.9).
2026-09-29 09:09:00 +02:00
Niko1221 b5347e3666 D-2: the prompt path's GDN recurrence split over value columns, the output norm in its own kernel
One 512-thread block per head (48 blocks) walked the chunk. The value columns' recurrences are
independent; only the per-token output norm couples a head's columns. Now 4 blocks of 32 columns per
head (192 blocks, 128 threads per __syncthreads), and a parallel norm kernel with the same warp sums.
Per column the same arithmetic in the same order: residual dumps and continuations byte-identical to
0.1.21 (4K, 20K). Q2_0 32K, back to back: GDN 5118 -> 4264 ms, prompt 1258 -> 1308 tok/s.
STRATA_GDN_REC_HEADS=1: the per-head kernel. (A chunked delta-rule kernel - not bitwise - is the
larger step left.)
2026-09-29 09:05:02 +02:00
Niko1221 1c58de5ab2 E-6: a verify layer whose routed experts are all in VRAM needs nothing from the host
After routing, one thread checks the group's entries against the device residency table; when all
are resident it writes the plan the host pool would (same layout, same order) and marks the ring.
The layer's waits on the host (plan, PCIe share, CPU share) then return at once and the CPU rows
are zeros; the host still runs its loop, off the GPU's critical path. STRATA_VERIFY_DEVICE_PLAN=0: off.
2026-09-29 08:56:02 +02:00
Niko1221 459a1d15e6 E-4 (prompt side): the draft layer's pass over a prompt chunk without a host sync per group
A group of <= --spec rows was staged in mapped memory and synced before the next: ~5,500 host round
trips on a 32K prompt. The rows' token / step / position records are now uploaded once per call and
each group is device copies plus a graph on the one stream, one sync at the end (a graph variant
without the mapped staging). The same work in the same order: continuations byte-identical to 0.1.21
(4K, 20K). Q2_0 32K: the draft layer's share 1091 -> 905 ms; prompt 1213 (Phase B) -> 1387 tok/s with
Phase C and D-5. STRATA_MTP_PREFILL_SYNC=1 keeps the old loop.
2026-09-29 08:45:41 +02:00
Niko1221 7d0f3fe79e Layer split limits: docs/MULTI_GPU.md and bench/results/2026-09-29-layer-split-limits 2026-09-29 08:44:35 +02:00
Yunado 8989030db5 serve: keep image parts in tool/assistant messages
Agent clients put screenshots in tool results; flattening non-user messages to text made the model blind to them. The template and images_of already handle them; only the conversion was too strict.
2026-09-29 02:41:30 -04:00
Niko1221 8baae07184 D-5: the prompt path's expert stream is issued by its own host thread
The thread that launches a chunk's kernels also issued the expert copies, and waited there for
the host copies of the unpinned blobs (the arena part Windows would not pin): the GPU idled - the
'wait copy' and 'dequant' time of the i-quant prompts. A per-chunk issuer thread now walks the
stream: the same copies in the same order into the same slots, a slot refilled only after the
compute stream recorded it free. STRATA_PREFILL_ISSUER=0 keeps the inline issue.
IQ3_S 32K prompt: 1143 -> 1213 tok/s, 'wait copy' 15.2% -> 7.5%. Exact: residual dumps and
continuations byte-identical to 0.1.21 at 20K (IQ3_S, IQ3_XXS: +16% prompt tok/s with Phase C).
The stager's ring and threads are tunable (STRATA_STAGER_RING / _THREADS); the defaults stay
(more copy threads were slower: 816 tok/s at 6).
2026-09-29 08:41:08 +02:00
Niko1221 24d4013c87 E-2: prefetch the i-quant expert rows ahead of the AVX-512 decode; an opt-in gather decode
Measured on a Ryzen 5 7600 (IQ3_S, 4K context, CLI decode timeline, outputs identical):
gate/up 37.1 -> 36.5 ms/round with a 2 KB prefetch; the gather decode (same results) is 3-5%
slower than the scalar lookups, so it is opt-in (STRATA_IQ_GATHER=1). More pool threads (SMT)
do not help: the pool reads its rows at ~30 GB/s, near DDR5's practical rate through 4 KB pages
- memory-bound, so decode speed now comes from VRAM hits (E-1) rather than CPU kernels.
2026-09-29 07:59:37 +02:00
Niko1221 07b107403a C-3: the prompt path's GDN short conv tiled over tokens
The 4-tap conv reads inputs, not its own outputs, so 64-token tiles are independent: 160x the
threads of the per-channel walk. Same per-element expression (identical bits: residual dumps and
continuations byte-identical to 0.1.21, also with 1001-token chunks). STRATA_GDN_CONV_SERIAL=1: the walk.
2026-09-29 07:32:59 +02:00
Niko1221 0c26102af4 C-1, C-2: the prompt path's QSA select grid reaches only the active blocks; the indexer appends a chunk at once
- C-1: the block-score grid spans the batch's largest n_bid + 1 blocks instead of the context's
  capacity (the blocks past a query's n_bid returned at once). The graphed decode paths keep the
  capacity grid.
- C-2: the indexer's appends of a chunk in three launches (the first cell's spare, one CTA per
  completed block, the tail) instead of one launch per token and QSA layer (24K per 2048-token chunk).
  The end state is the sequential one's: the same arithmetic per block, only the batch's last block
  writes the spare after it. STRATA_INDEXER_PER_TOKEN=1 keeps the old loop.
Exact: residual dumps and continuations byte-identical to 0.1.21 (4K, 20K, and 5K read in chunks of
1001 so blocks straddle chunks). Coder prompt tok/s with C-4: 4K 1063 -> 1255 (+18%), 20K 1216 -> 1373 (+13%).
2026-09-29 07:29:06 +02:00
architectds c4d549aa25 serve: report the cached prompt tokens, llama.cpp timings and GET /v1/status
The conversation cache's reuse is kept per request (GET /metrics shows it),
but the APIs did not report it, so clients showed a cached prompt as 0%:

- OpenAI: usage.prompt_tokens_details.cached_tokens, streamed and not.
- Anthropic: the final usage splits input_tokens from
  cache_read_input_tokens (message_start can only know the whole prompt).
- llama.cpp's `timings` block on the last chunk and on a whole reply
  (prompt_n, cache_n, prompt/predicted ms and per second), from the
  engine's own clock: what llama.cpp's clients already show as speed.
- GET /v1/status (with the API key when one is set): the model and its
  window, images, the APIs, whether a request is running, the last
  request's timings and the GPU and RAM from the telemetry, for a
  front-end that polls its server rather than guessing.

An engine without a clock (MockEngine) reports no timings and 0 cached.
Tests: serve/test_server.py UsageAndStatus.
2026-09-29 00:28:37 -05:00
Niko1221 0311c91377 Layer split: an explicit --expert-cache leaves room for the prompt path; --mmap-experts says it needs a canonical pack 2026-09-29 07:28:29 +02:00
xyzzing ef14b6ca69 The Monitor's live tok/s is a rate over the last seconds, not the mean since the first token
live.tok_s returned `generated / (now - first_token)`: a cumulative mean whose
first sample is 1/elapsed.  Against an engine paced at 25 tokens/s the Speed
card, the header pill and the tok_s sparkline read 27,235.7 tok/s at that first
sample - about 1090x the real rate - and then undershot for the first second of
every answer (one token counted over up to a second of elapsed time).  Because
the sparkline scales to the maximum of its 60-sample ring, one such sample also
flattened the Speed graph for a minute.

tok_s is now the rate over the last RATE_WINDOW_S (2 s), with the mean so far
reported only while a request is younger than RATE_MIN_SPAN_S (0.25 s) and its
span floored there, so no sample can diverge.  The old formula is kept as
_tok_s_mean() and still reported (live.tok_s_mean, status.tokens_per_s_mean).

The same request fixes a second mis-attribution: a request that ended without a
DONE line (engine death, error, client disconnect) recorded the PREVIOUS
request's generated/decode_ms as its own decode_tok_s.  The engine's counters
are now trusted only when its DONE replaced the object captured at request
start, and the row carries the engine's own token count as engine_generated.

Tests: serve/test_server.py::LiveRate paces a mock engine and polls GET /metrics
at 10 ms.  Both cases fail without the server.py change (990.0 tok/s recorded
for a request that never finished) and pass with it; the rest of the suite is
unchanged at 28 tests, OK.  The engine's own per-request counters - what the
Speed card shows for "last request" - are untouched, and the server's progress
and summary log lines keep their whole-request mean on purpose.
2026-09-29 13:26:28 +08:00
Niko1221 dfe61b1ca0 C-4: the prompt's embedding in one gather, and the n-gram table read by several threads
- One batched gather per chunk instead of a launch per token (same arithmetic: identical bits).
- The prompt path's 'embed+steps' time was mostly the host reading the n-gram (PLE) rows from the SSD
  with the GPU idle: one thread issued every 90-byte read (~12 us of kernel time each, ~80K reads/s).
  DirectFile now only queues a read; a pool issues them (4 threads on Windows, overlapped; 16 blocking
  O_DIRECT preads on Linux, which read them one at a time before - perf-review F-2). STRATA_IO_THREADS.
  Q2_0 4K prompt: PLE 591 -> 291 ms, 941 -> 1010 tok/s.
- STRATA_PREFILL_TIMING also prints the host's share (chunk setup, the per-chunk callback).
Exactness: the prompt path's final residuals (STRATA_PREFILL_DUMP_R) and the continuation are
byte-identical to 0.1.21 at 4K and 20K tokens, IQ and S2 embedding tables.
2026-09-29 07:23:09 +02:00
Niko1221 d22177c610 Layer split: mid-prompt checkpoints (--prompt-cache-every) with the pipelined prompt path
Each stage saves its own part of a checkpoint when it has read the chunk that reaches the
position (Prefill::on_stage_chunk, its state synchronized at that chunk's end), by the same
rule the last stage uses; the last stage puts the parts together in on_chunk. They were off
across GPUs because the first stage is a chunk ahead when the last one reports.
2026-09-29 07:18:20 +02:00
Niko1221 9baff31592 Layer split: images, control vectors, KV streaming, --mmap-experts and the helper GPUs across GPUs
- the image-position (mrope) table and the control vector's tables are per device: every
  stage's rope and steering kernels read their own device's copy; picture rows go through
  the split prompt path (stage 0 embeds them, the later stages take the hand-off)
- KV streaming: each stage's session already streams its own pinned KV copy; no longer forced off
- --mmap-experts: allowed (no pinned arena: every miss the CPU computes, prompts copy on the host)
- --expert-cache-remote with a split: the helpers run on the visible GPUs no stage runs on and
  hold only experts no stage's cache holds
2026-09-29 07:16:53 +02:00
Niko1221 82c6464c92 F-1: the streaming detokenizer decodes each token's bytes incrementally (constant time per token)
It re-decoded every generated id per token: 2 ms per token after 8K tokens, 4 ms after 16K.
Now 0.0005 ms at any length; the text is the same (serve/test_detok.py checks it against the
old algorithm on real and random token streams), and a complete character is no longer held
back behind an incomplete one.
2026-09-29 06:56:26 +02:00
Niko1221 6e1a7be257 Engine 0.1.21: one model across two or three GPUs (layer split, experimental); setup requires it 2026-09-29 06:14:43 +02:00
Andy 4a50d4ce8f decode: batch the verify window's per-token kernels (bit-identical); skip the PCIe path with --pcie-frac 0; decode timing/profile switches 2026-09-29 05:55:18 +04:00
Niko1221 6f1a24defb Layer split: measured results (bench/results/2026-09-29-layer-split) and the recommendations in docs/MULTI_GPU.md 2026-09-29 01:17:30 +02:00
Niko1221 641c62a0e9 Layer split auto: a cost model of the decode window (per-layer time from SMs x clock, missed routed mass)
Fitted on the 5080 + 3090 K sweep (18-32): it picks K=26, the measured best (prompt +18-20%
over the 5080 alone), and gives a slow third card (2080 Ti) a single layer.
2026-09-29 01:00:24 +02:00
Niko1221 52bccfe914 fused_gr: cap the shared-memory opt-in at the card's limit (Turing: 64 KB) 2026-09-29 00:34:46 +02:00
Niko1221 8d09be6051 Layer split: STRATA_SPLIT_TIMING (per-stage host time per window); docs/MULTI_GPU.md 2026-09-29 00:30:16 +02:00
Niko1221 c8b10915aa QSA scorer: an FP32 fallback for Turing (the experimental sm_75 build, a layer-split stage on a 2080 Ti) 2026-09-29 00:14:23 +02:00
Niko1221 500eb977a9 Multi-GPU in the config: "gpu": [0, 2] (setup --gpus 0,2) runs the layer split across the cards
The server sets CUDA_VISIBLE_DEVICES to the list and adds --layer-split ("layer_split" in the
config, auto by default); the Monitor reads the first (main) card.
2026-09-29 00:12:05 +02:00
Niko1221 3d5d96ddce Layer split: keep the 8 GiB pin limit across GPUs (WDDM), report a failed free-memory read 2026-09-29 00:08:55 +02:00
Léon Avic Simmons ab541eb558 strata_pack verify: read back the source sha256 the manifest recorded
build writes source.shard1_sha256 / shard2_sha256 into manifest.json (unless --skip-hash, which is how
setup builds), and nothing read them back: src/, serve/, setup.py, tools/ have 0 readers. A pack
verified against a different shard than it was built from reported tensor MISMATCHes, or passed under
--limit.

A full verify (no --limit) now hashes the shard it is handed and fails naming both digests when they
differ; a manifest without the field, or a --limit run, is unchanged. Shard 2's hash is recorded but not
checked here: verify is not handed that file. tools/test_shards.py: a matching hash passes, a wrong one
fails naming both, an absent hash or --limit skips. On main the wrong-hash case returns 0.
2026-09-28 17:58:24 -04:00
Léon Avic Simmons 392fc17287 gguf reader: a duplicate tensor name is refused at open, naming the tensor and the file
find() is first-match over the tensor directory, so a GGUF that carries one name twice resolves it to
whichever entry came first; the other tensor's bytes are never read and nothing says so. GGUF has no
index that could arbitrate between the two, so refusal is the whole fix: parse() keeps the names it has
seen and throws on the second sighting ("GGUF: duplicate tensor name 'blk.0.attn_q.weight' in <path>"),
as llama.cpp's gguf_init_from_file does.

gguf_reader_test (STRATA_BUILD_TESTS; no model, no GPU) writes a two-tensor GGUF v3 by hand: distinct
names open and find() sees both; the same name twice is refused and the error names the tensor and the
file. On main the second file opens: 3 of 4 checks fail.
2026-09-28 17:58:24 -04:00
Léon Avic Simmons 896586e365 setup: a missing or short shard is named with its numbers before anything reads it
setup checked a shard by presence only, and a download by Content-Length only: a shard copied short into
--gguf-dir, a .part renamed by hand or a download an older setup finished without a mark passed as a
model file, and the failure came later from the packer or the engine, at the first tensor past the end,
naming neither the file nor the shortfall.

check_shards() reads each shard's header (not its data) and compares the file's length with what its own
tensor directory needs: "m-00001-of-00002.gguf is short: 1,234 of 1,274 bytes (40 missing)"; a header
that does not parse is refused by name; a missing file stays "missing <path>". The download's "could not
finish" line now says the bytes on disk and the bytes the server announced.

tools/test_shards.py (unittest over a minimal GGUF written by hand; no download): whole shards pass; a
missing, a short and a header-truncated shard each stop setup with the file named.
2026-09-28 17:58:24 -04:00
Niko1221 a2d13aa5b3 Layer split: N stages, the batched prompt path, checkpoints, swaps and PCIe per stage, auto placement
--layer-split K1[,K2..]|auto with --split-device D1[,D2..]: each later stage runs its layer
range on its own GPU (weights, session, cache, verify window, prompt path). The prompt path
hands each chunk's rows to the next stage, which reads it on a thread while the previous
stage reads the next chunk. Checkpoints carry every stage's session state; adaptive swaps
copy into the layer's own cache; each GPU probes its own PCIe share. "auto" scores every
placement (up to 3 GPUs) by the profile-weighted pairs each cache would hold.

STRATA_EXPERIMENTAL_SM75: lets CMake build for a Turing card (the 2080 Ti A/B).
2026-09-28 23:56:30 +02:00
Niko1221 4572b8f221 Layer split v1 (step 4): the second stage on its own GPU
--split-device D (default: the second visible GPU) runs layers [K, n) on device D with
its own copy of the dense weights, session, head, drafter and an expert cache filled
from the profile's pairs of its layers. Verifier and MtpDrafter switch to the device
they were made on (OnDevice), the pool wrapper swaps the stage's cache pointers, the
penalty history lives with the head, and the s2 LUT is per device.

v1 switches off what does not cross devices yet: checkpoints, adaptive swaps, the PCIe
share and the batched prompt path (prompts read through the verify windows).
2026-09-28 23:37:29 +02:00
Niko1221 45266e06fc Layer split v1 (step 1-2): a verify window can run a range of layers and hand its residual to the next stage
--layer-split K runs layers [0, K) and [K, n_layers) as two verify stages on the same device (sharing weights,
session and expert cache). The hand-off carries R, the last layer's pending write and inject, so the next stage
folds it in exactly as the unsplit window does. The GPU shared-memory opt-ins are now per device.
2026-09-28 23:11:45 +02:00
Niko1221 962beb84e3 multi-GPU: the helper spins instead of sleeping, and reads/writes its staging zero-copy
Each layer waits for the helper GPU on the CPU pool's critical path. Measured on RIBPC (Windows): ~0.3 ms
per round trip, more than the fast CPU needs for the same experts. The helper's context is created with
cudaDeviceScheduleSpin (STRATA_REMOTE_SPIN=0: the default), and its input/compact-output staging is mapped:
the kernels read and write it directly, two copies fewer per layer (STRATA_REMOTE_ZEROCOPY=0: copies).
2026-09-28 22:52:21 +02:00
Niko1221 4cd2461849 multi-GPU: time the helper GPU's staging/launch and wait per request 2026-09-28 22:47:06 +02:00
Niko1221 7dbe2bf85b PCIe probe: below 4 GB/s (x1 links) the PCIe share is 0 - every miss goes to the CPU pool 2026-09-28 22:42:40 +02:00
Niko1221 6cd274f03a CMake: STRATA_BUILD_TESTS=ON works without the unpublished tests/ tree 2026-09-28 22:42:25 +02:00
Denis Chirkov 2f78d84362 mtp: bulk-read the drafter shards as well
The drafter loader had the same defect as the expert arena: read_file() pulled the whole
dense.bin through std::ifstream, and the 512-expert loop read 64 MiB chunks through the
same 4095-byte-per-call path. Same fix (fopen/fread, _fseeki64/_ftelli64, short-read
check), so the drafter stage stops paying per-read overhead.
2026-09-28 23:17:31 +03:00
Denis Chirkov edeb92848c loader: read the expert arena in bulk instead of std::ifstream
On MSVC, std::ifstream::read does not issue one fread per call: basic_filebuf::xsgetn
loops over fread(_Ptr, 1, 4095, _Myfile) (__msvc_filebuf.hpp, _Read_size = 4095).
A 44.7 GB expert arena therefore costs ~11.4 million 4095-byte reads instead of a few
thousand bulk reads, and the load is dominated by per-call overhead.

Read the arena with fopen/fread, an explicit 64-bit seek (_fseeki64) and a short-read
check that fails the load (LoadStats.ok/error) instead of silently returning partial
data. Behaviour on Linux/glibc is unchanged in substance: same bytes, same call sites.
2026-09-28 23:17:30 +03:00
Daerdaal 109a209e53 Add optional multi-GPU expert caches and compact transfers 2026-09-28 22:05:16 +02:00
mikicvi 5c5a059574 Web app works behind path-prefixed reverse proxies
The page referenced its stylesheet, script, sprite, fonts and every API call
with absolute paths (/web/app.css, fetch("/health"), url("/fonts/...")).
Behind a reverse proxy that mounts the app under a sub-path - llama-swap's
/upstream/<model>/, an nginx subpath - those resolve to the proxy root and
404: an unstyled page stuck at Connecting, or a Monitor that polls /metrics
at the proxy root and reports 'Server not reachable'.

Make every same-origin reference relative:

- index.html: (href|src)="/web/... -> web/...
- app.js: fetch("/... -> fetch("..., plus the endpoint strings used outside
  a plain fetch(" call - the Monitor's ternary fetch(reqShowAll ?
  "/metrics?requests=all" : "/metrics") and the SPRITE constant - because
  the quote does not always sit right after 'fetch('
- tokens.css: url("/fonts/... -> url("../fonts/... (fonts have their own
  top-level /fonts/ route, one level above the CSS)

Standalone at / resolves the same, so nothing changes there.

Verified through llama-swap's /upstream/<model-id>/ prefix in a browser:
page, stylesheets, fonts, sprite icons, /health, /settings, /metrics, /mcp
and a streamed /v1/chat/completions POST all 200; the Monitor tab connects
and streams live telemetry. Same checks pass at / standalone.
2026-09-28 19:25:59 +00:00
mikicvi 941f671d65 Accept sm_80+ GPUs at runtime, matching the build guard and the README
The CMake arch guard already lets RTX 30 (sm_86) / 40 (sm_89) build - its own
comment says the kernels need sm_80 or newer (tf32 mma, bf16 math) - and the
README lists RTX 30/40/50 as supported. But device_info() still refused
anything with cc_major != 12, so a working Ampere/Ada build died at startup
with 'Strata targets sm_120 only'. Relax the runtime check to the kernels'
real floor (compute capability 8.0) and align the stale 120-only comments.

Verified on RTX A2000 12GB + RTX 3080 10GB (both sm_86), CUDA 12.0, gcc-13,
engine 0.1.20 serving daily: TG 21-24 t/s flat 1k-128k, 29.6 t/s code-gen at
81% draft acceptance, PP 375/445 @8k/32k, 8/8 needle retrieval at 113k.
2026-09-28 19:14:51 +00:00
Niko1221andcode-martin bcd62942f1 Engine 0.1.20: a checkpoint at the end of the system prompt (PR #65 on top of #62); setup requires it
A prompt read from token 0 is also checkpointed at its first turn boundary when that is 2,048 tokens or
more (--prompt-cache-root N, 0 = off). It becomes the chain's root, which #62's retention policy pins,
so a new chat of the same client reads only what follows the system prompt and tools. Without it, #62's
root existed only for system prompts past --prompt-cache-every (16K).

Co-Authored-By: code-martin <[email protected]>
2026-09-28 19:17:28 +02:00
Niko1221 93c6ba5d78 Merge PR #62 (j-luwierski): the conversation cache keeps its shared prefix; the rest rotates LRU 2026-09-28 19:16:34 +02:00
Niko1221 1838ead7e2 PR #67 follow-up: name type 42 (Q2_0); its tests run from the repo root and on Windows (no symlinks, open memmaps) 2026-09-28 19:15:31 +02:00
Niko1221 f827ddd1c4 Merge PR #67 (acrogenesis): OrcaRouter IQ3_XXS with explicit BF16 compatibility packing 2026-09-28 19:15:09 +02:00
Niko1221 85118e318d PR #44 follow-up: keep the default from 20 GB/s up, probe native packs only, the share is read by SMs 2026-09-28 19:14:46 +02:00
Niko1221 fdf8a6249f Merge PR #44 (pipeob0): generate: probe the real host->device bandwidth for the PCIe share 2026-09-28 19:14:23 +02:00
Niko1221andMIDHAT NASHAR 3e1145d2f3 serve: report the engine's own version (INFO engine=) over BUILD.json's (from PR #41, midhatn)
Co-Authored-By: MIDHAT NASHAR <[email protected]>
2026-09-28 19:14:23 +02:00
Niko1221 f086119902 PR #63 follow-up: move the unpacked llama.cpp to third_party/llama.cpp itself, retrying a locked move 2026-09-28 19:14:09 +02:00
Niko1221 be4be48935 Merge PR #63 (alfirus): Windows PermissionError in get_llama_cpp() 2026-09-28 19:13:45 +02:00
Niko1221 763d57ddf8 PR #69 follow-up: '(hits / lookups)' in the log; what the hit rate counts (not the PCIe share) 2026-09-28 19:13:40 +02:00
Niko1221 cb708580a5 Merge PR #69 (code-martin): telemetry: per-request decode expert cache hit rate in DONE and web mo 2026-09-28 19:13:21 +02:00
Niko1221 5a60f99cc0 Merge PR #64 (pipeob0): parity tool: count a new-kernel mismatch as a failure, not just print 2026-09-28 19:13:21 +02:00
Niko1221 0ba22c7145 docs: --calibrate in the README; penalized requests are a little slower in 0.1.19 2026-09-28 19:01:31 +02:00
pipeob0 7a375041d1 parity tool: count a new-kernel mismatch as a failure, not just print it
The gate/up row and IQ4_NL down comparisons printed their distance from ggml's vec_dot without affecting the exit code, so a kernel that silently diverged still reported 0 failures. Gate both at 1e-5 (measured 3e-8 and 1e-7, so 100x headroom). Verified: 0 failures at 1e-5, and with the threshold dropped to 1e-9 the gate fires on every layer.

Also corrects the comment above the gate/up dispatch: STRATA_NO_IQ512 drops an AVX-512 CPU to the AVX-2 kernel, not to ggml; ggml's vec_dot needs both switches set.
2026-09-28 18:34:44 +02:00
Niko1221 67492fb32b Merge branch 'mcp': tools from MCP servers in the web app's chat 2026-09-28 18:25:37 +02:00
code-martin 51fce4c83e telemetry: per-request decode expert cache hit rate in DONE and web monitor
- Reset cache hits/lookups baseline at start of autoregressive decode to exclude prefill distortion
- Append req_hits and req_look to engine DONE line and log decode hit rate to stderr
- Parse decode hit metrics in server.py and record hit_rate per request in history
- Add Hit rate column to Recent requests table in web monitor
2026-09-28 18:59:58 +03:00
Niko1221 4c623f1d78 Tools from MCP servers in the web app's chat (serve/mcp.py)
A small MCP client (JSON-RPC 2.0, stdio and Streamable HTTP, standard library
only): servers from the run config ("mcp_servers", or Claude Desktop's
"mcpServers" block) or --mcp-config start with the server; their tools are
offered as <server>__<tool>. A request that sends "strata_mcp": true (the web
app does) gets them: the server runs the calls, feeds the results back and lets
the model continue, up to mcp.max_rounds; the stream carries strata_mcp events.
Other clients are unchanged. Errors, crashes and timeouts become "error: ..."
results; long results are cut at mcp.max_result_chars; Stop cancels a running
tool. GET /mcp lists servers and tools; the web app shows each call as a
collapsible block, a switch in the Sampling drawer and a Monitor card.
The mock engine takes --script more than once (answers in turn).
2026-09-28 17:28:01 +02:00
Niko1221 c1afbdac8a Tune for this PC: setup --calibrate measures the PCIe share, draft floor and CPU workers
tools/calibrate.py starts the engine through serve's StrataEngine and measures decode speed with the
three settings that depend on the machine: --pcie-frac and --spec-min-p through new per-request GEN
keys (pcie_frac=, spec_min_p=; the server forwards a request's "strata_tune"), then 2-3 worker counts
(a restart each). A setting is kept only when it beats the product default by more than 3% in an
interleaved re-measurement. Setup offers it after an interactive install and runs it with --calibrate;
the result is saved per PC + model + context in the settings file and re-applied when a config is
written, so updates keep it. The engine's INFO line now says pool_workers, pcie_frac and spec_min_p.
tools/test_calibrate.py tests it with a stand-in engine (no GPU).
2026-09-28 17:16:53 +02:00
Niko1221 069b92b38d Expert cache: retry smaller when the allocation fails; setup warns about a tiny page file (#60)
On Windows every allocation on the graphics card is also charged to the system commit (RAM + page file).
With the page file off or tiny, the expert cache's one big cudaMalloc failed while the VRAM was free and
the engine stopped. An auto-sized cache now retries at three quarters of the size (up to 8 times) and
says how much commit Windows has left; a failure that remains names the page-file setting. Setup warns
when the page file is under 4 GB; DETAILS has a troubleshooting row. STRATA_TEST_CACHE_FAIL=N fakes N
failed opens to test the retry.
2026-09-28 17:11:57 +02:00
Niko1221 de71982be9 Engine 0.1.19: penalties on every drafted token; the CLI samples; sampler hardening
- serve: a verify window samples T rows, but only row 0's penalty history was staged; rows 1..T-1 read
  unwritten slots (past the allocation at penalty_last_n=4096). Every row now gets its own history
  (strata::kernels::penalty_rows: the consumed tail, the fed-back token, the drafts before that row).
- prompt windows (their picks are discarded) skip the head sampling: Verifier::set_head_sampling.
- sampler: history ids outside [0, n_vocab) are ignored (they set bits past the shared bitmap); sampled
  top_k 0 or > 64 means 64 and every row is written (0 used to leave the row unwritten).
- server: a client's top_k 0 or > 64 is sent as 64 (it fell back to the default 20).
- CLI: the speculative loop gets the CLI's sampling (it was always greedy), the per-token path draws
  Philox(seed, position) like a verify window, and the effective sampling is printed.
- sampler_parity: fixtures 12-15 (per-row histories T=1..8 x H=1..4096, out-of-vocab ids, the top_k
  contract, penalty_rows); outputs are pre-filled so an unwritten row fails.
- setup requires engine 0.1.19.

Found by the engineering performance review (C1, C2).
2026-09-28 16:53:36 +02:00
acrogenesis 6c812b2c51 Merge upstream Strata 0.1.18 while preserving Orca compatibility
Bring in 45 upstream commits through f32577e. Update the split-GGUF test fixture with router geometry required by upstream's dynamic expert counts. Preserve the Orca BF16 conversion and native PLE compatibility paths.

Validation: Release CUDA build for sm_120; 8 packing tests; 23 server tests; sampler, gated residual, and KV streaming GPU parity checks.
2026-09-28 08:52:08 -06:00
Alfir b45c40be6a Fix Windows PermissionError in get_llama_cpp() by using shutil.move instead of pathlib.replace 2026-09-28 22:40:36 +08:00
Jakub Luwierski 5f8ad8284e serve: the conversation cache keeps its shared prefix; the rest rotates LRU
The retained checkpoints are always a prefix chain - the request loop drops
any checkpoint that is not a prefix of the current prompt, because its
positional cells are gone (the KV cache is one arena, one branch of history
at a time).  A radix cache's tree collapsed onto the one branch the session
can hold, and the chain's root is the node a radix cache keeps while its
leaves rotate: in practice the end of the system prompt, which every new
chat of the same client mounts through.

First-in-first-out kept that node only by accident of being first: after
prompt_cache newer checkpoints it was gone, and the next chat re-read the
whole prefix at prefill speed (~30 s per 30K tokens).  Now the root is
pinned while the budget allows root and leaf apart (cap >= 2; below that
the old behaviour stands), the rest rotates by least recent use, and a
mount re-dates the checkpoint it mounted through.

The policy is one pure function (conv_cache.hpp) with its scenarios in
conv_cache_test.cpp; checkpoint_save/restore and the state format are
untouched, so STRATA_CKPT_REREAD's token-for-token guarantee holds.
2026-09-28 16:27:51 +02:00
Niko1221 f32577e3c2 Engine 0.1.18; setup explains an engine that is not published yet (#58); SETUP.bat 2026-09-28 15:50:24 +02:00
Niko1221 55e05f585d sampler: PR #59's test is fixture 11; the second kernel's comment says non-empty window 2026-09-28 15:48:15 +02:00
Jakub Luwierski 81601a5124 sampler: an empty penalty window must not touch the unsized shared bitmap
`use_bits` required only a non-null history, but the launch sizes the bitmap
for `penalty_last_n > 0` alone - a caller handing over a stale history buffer
with the penalties disabled made the kernel zero `bits_words` words of a
ZERO-BYTE dynamic shared allocation.  Unreachable from the serve path
(set_history passes nullptr when the window is empty) and invisible to
memcheck (the write lands in the unallocated SMEM window), but undefined per
the contract of the public `sample_tokens`.  The gate now also requires a
non-empty window (`hlen > 0`, both kernels), and fixture 10 pins a stale
history with last_n = 0 to the no-history result in both kernels.
2026-09-28 15:46:00 +02:00
Niko1221 ec4cebfe52 Engine 0.1.17; setup requires it (the sampler fix, #53) 2026-09-28 15:25:13 +02:00
Niko1221 ae8e6e6ab5 Choosing the GPU (#51): --gpu N, setup picks the one with the most VRAM
- A config's "gpu" (or the server's --gpu N) runs the engine and the image
  encoder on that card: CUDA_VISIBLE_DEVICES=N with CUDA_DEVICE_ORDER=PCI_BUS_ID,
  so N means what nvidia-smi and the Monitor's NVML mean (CUDA's own default,
  fastest first, can number the cards differently). The Monitor reads that card.
- setup lists every NVIDIA GPU, picks the one with the most VRAM (or --gpu N),
  says so when there is more than one, and saves the choice; START-HERE.bat
  --gpu N also overrides it when starting. A number that does not exist stops
  with the list of cards. Configs without "gpu" run as before.
- Tests: the child environment; the choice on a simulated 4060 + 5070 PC; end to
  end on the real engine with --gpu 0.
2026-09-28 15:01:16 +02:00
Niko1221 7d7b5d7cf7 sampler: one penalties stage and top_p before min_p, as llama.cpp (#53)
The sampled chain applied the repetition/frequency/presence penalties a second
time after the temperature, and ran min_p before top_p: both came from the
order of the case labels in llama.cpp's common/sampling.cpp, not the order its
chain runs (params.samplers: PENALTIES ... TOP_K ... TOP_P, MIN_P ...
TEMPERATURE). With presence_penalty 1.5 at T 0.7 a repeated token was drawn at
2.6% instead of 10.5% (@hendrikp's example). Greedy decoding is unchanged.

sampler_parity: the host reference follows the same order; two new fixtures
check against an independently computed distribution (#53's example over 8,000
draws: 0.1040 vs 0.1050) and that top_p-before-min_p is observable. Both fail on
the old kernel (642 and 91 draws differ).
2026-09-28 14:57:54 +02:00
Niko1221 9a11b3eb70 tools/needle_bench.py: needle-in-a-haystack recall through a running server (#33)
Builds its long texts from this repository's own files at run time, hides a
code word at the chosen depths and asks through the API. Checked on the Coder:
1K, 32K (10/50/90%) and 128K found.
2026-09-28 14:53:30 +02:00
Niko1221 e4ee33f921 docs: 32 GB of RAM runs the Coder 2026-09-28 14:48:21 +02:00
Niko1221 c99cede026 serve: Claude Code works - /v1/messages?beta=true (#55), mid-conversation system messages (#56)
- POST routes ignore the query string (Claude Code calls /v1/messages?beta=true
  and got a 404, which it reports as a model problem); /settings too.
- A system (or late developer) message after the first one becomes a user
  message in place: the chat template only takes a system message at the start
  and raised "System message must be at the beginning" (the client saw a dropped
  connection). Kept in place, not merged into the first, so the conversation
  cache still reuses the prompt.
- Tests with the issues' own requests (they fail on the old code); DETAILS:
  how to point Claude Code at Strata.
2026-09-28 14:48:12 +02:00
Niko1221 a63d5ce78d README: How does it work? in plain words, with illustrations in the app's style (colors, icons, font; light and dark) 2026-09-28 14:38:40 +02:00
Niko1221 b500a81e4e License: MIT (the ggml code, the font and the projection vector keep their own licenses) 2026-09-28 14:29:00 +02:00
Niko1221 26cdd4cde7 Engine 0.1.16; setup requires it (the Coder needs the model's own expert count, PR #54) 2026-09-28 14:27:45 +02:00
Niko1221 47a83696e9 The GSQ-RCO Coder (256 of 512 experts): setup, profile, speeds (PR #54)
- setup: a third family, "coder" (IQ1_M), with its own expert profile; its
  shard 2 and vision encoder are the original's files and are reused; 32 GB of
  RAM is now enough to install (the Coder's experts are 23 GB).
- data/expert-profile-coder.bin: the shipped ranking mapped onto the kept
  experts through the release's rco-allocation.txt (72% GPU hits, 12 GB card);
  tools/make_profile.py takes --n-expert.
- README/DETAILS: the Coder's speeds (prompts 599-1,350 tok/s, output
  43-53 tok/s, 1K-262K) and what it is; bench/results/2026-09-28-coder.
- The experimental speed projection can be turned on for the Coder too.
2026-09-28 14:20:58 +02:00
Niko1221 ef206bb671 Merge PR #54 (pjgmobile): pruned expert variants (GSQ-RCO Coder: 256 of 512 experts) 2026-09-28 13:45:47 +02:00
Niko1221 71c60dafce setup: model files in a data folder next to Strata (no re-download after an update); freeze warnings
- The model files (models/, packs/, mtp/: 70-120 GB) live in Strata-data next to
  the Strata folder, remembered per user (%APPDATA%\Strata\settings.json,
  ~/.config/strata/settings.json). Updating by unzipping a new copy used to give
  an empty folder and a full download again. Model files of this folder and of
  earlier Strata folders on the same drive are moved there (a rename) and their
  configs repointed; files on another drive are used where they are. A new copy
  with no config sets itself up like the last install, reusing the files.
  --data-dir chooses another place.
- The start says, before and while it happens, that the PC can be slow or stop
  responding for 1-3 minutes while the experts are loaded and locked: setup's
  start box, and the server narrates the engine's start from its log (weights,
  experts + the warning, expert cache, ready, a "still starting" line every 20 s).
2026-09-28 13:45:39 +02:00
pete 97ae3b2418 Merge remote-tracking branch 'origin/main' into pruned-geometry-support
# Conflicts:
#	src/prefill/prefill.cpp
2026-09-28 12:34:36 +01:00
pete 8bef4161cf Support pruned expert variants (GSQ-RCO Coder: 256 of 512 experts)
- iq_pack.py: derive n_expert from the router tensor shape (was hardcoded 512)
- expert_layout.cpp: parse the pack header's recorded n_expert instead of the
  caller's compile-time canonical geometry; use it for the contiguity stride
- gguf_reader.hpp: Qwen4ExpGuard expert-count checks become presence-only
- generate.cpp: read qwen4exp.expert_count/expert_used_count from the model
  file into the runtime geometry; MTP drafter keeps canonical geometry (the
  draft is the original 512-expert head); serve gate allows profile-less
  caches; K from metadata
- layer.cpp: native fused router stays 512x10-only, generic top-k fallback
- prefill: expert count taken from the geometry everywhere; route_kernel
  templatized (512->16, 256->8 registers/lane, else generic router_top10)
2026-09-28 12:29:32 +01:00
Niko1221 256485621e docs: troubleshooting row for the IQ3_XXS long-prompt stop (fixed in 0.1.15) 2026-09-28 13:18:18 +02:00
Niko1221 9ac549fd32 Engine 0.1.15; setup requires it (kernels loaded at start: IQ3_XXS at 64K+ no longer runs out of VRAM mid-prompt) 2026-09-28 13:17:58 +02:00
Niko1221 06f41f49a8 docs: speed tables measured with engine 0.1.14 (prompts 1.2-2.4x faster than the old tables) 2026-09-28 13:07:35 +02:00
Niko1221 bef32ed6c3 engine: load every CUDA kernel at start (CUDA_MODULE_LOADING=EAGER)
With lazy loading, a kernel first used mid-prompt found no VRAM left for its code
once the expert cache and the prompt path had taken it: IQ3_XXS at 64K and 128K on
a 12 GB card ended with 'out of memory: cudaFuncSetAttribute' in the MMQ launch.
Loaded at start the kernels cost ~30 MB, accounted before the cache is sized
(~23 fewer slots). A user's own CUDA_MODULE_LOADING is kept.
2026-09-28 12:49:25 +02:00
Niko1221 dcb0c24422 docs: Linux updates, other distributions and WSL; the 0.1.14 stall fix; the full expert profile 2026-09-28 11:46:37 +02:00
Niko1221 235cf4bd09 serve: the prompt progress line says how far the engine is (N of M tokens), not only the prompt's size (#29) 2026-09-28 11:22:55 +02:00
Niko1221 b38d11761a Engine 0.1.14; setup requires it (the PCIe share without host CUDA calls in a verify window, #31) 2026-09-28 11:21:01 +02:00
Niko1221 fbbfb9ce60 Merge PR #43 (pipeob0): AVX2 multi-token i-quant row kernels 2026-09-28 11:04:17 +02:00
Niko1221 5c52c0fd00 Merge PR #42 (pipeob0): MEM_LARGE_PAGES on Windows - enable the privilege, round the size 2026-09-28 11:02:44 +02:00
Niko1221 9253c92ad7 The PCIe share by the copy kernel for every pack (#31, #29); setup and profile fixes (#46, #49, #45)
- --pcie-mode auto is the copy kernel for native packs too. Issue #31's thread
  dumps show the host stuck for good in the DMA path's cudaMemcpyAsync (a driver
  lock) inside a verify window while the GPU spins on the flag those copies
  raise. The copy kernel needs no host CUDA call there; IQ3_S decode 45.3 -> 44.8
  tok/s. --pcie-mode dma keeps the old path.
- setup: a locally compiled engine records its version; one kept after a failed
  compile is recognised by the version in its binary, and a config it cannot
  read (--prefill auto on 0.1.12) goes back to 2048 (#49).
- setup: nvcc in /opt/cuda (Arch), a clearer message off Ubuntu/Debian (#46);
  a failed build is tried once more (#45: ptxas's intermittent PTX parse error).
- data/expert-profile.bin ranks all 24,576 experts (the first 8,000 unchanged):
  the cache stopped at 8,000 slots on 24-32 GB cards (#46). tools/make_profile.py
  builds or extends a profile from --dump-routing traces; --expert-cache 0 with a
  profile now means auto.
2026-09-28 11:02:44 +02:00
pipeob0 f4820f507d generate: probe the real host->device bandwidth for the PCIe share
pcie_frac's defaults (0.55 native / 0.2 canonical) were measured on a PCIe 4.0 x16 link.
A x8 card in a x8 slot carries about half of that, and the copy engine's share then waits
for DMA that arrived late.  Probe the H2D bandwidth once at startup the way the engine uses
it (DMA reads from pinned host memory) and scale the default by it: an RTX 5060 Ti at x8
probes 14.1 GB/s -> pcie_frac 0.29 instead of 0.55.  --pcie-frac still wins when given, and
a machine that cannot run the probe keeps the current default.
2026-09-28 09:53:34 +02:00
pipeob0 d7a66bd33e generate: say which expert kernels an AVX2-only CPU gets
The notice printed 'the expert kernels run on AVX2' whether or not the AVX2 i-quant row
kernels were used, and with STRATA_NO_IQ256 set they are not: the run is on ggml's vec_dot.
Name the kernel, and name the switch that changed it.
2026-09-28 09:51:47 +02:00
pipeob0 d1b30db332 pinned: say how many bytes were refused, and stop blaming the privilege
The refusal note blamed SeLockMemoryPrivilege whatever VirtualAlloc answered, and did not
say what was asked for: 1450 (ERROR_NO_SYSTEM_RESOURCES, the large-page pool has that many
2 MB pages free right now), 87 (a size that is not a multiple of the minimum) and 1314 (the
privilege is genuinely off) all read as the same one line.  Observed for real on a 5700X3D
box: a run minutes after an engine was killed printed 1450 while the next one succeeded -
the killed process had not released its pinned large pages yet, which is only visible if the
byte count and the error are both in the log.
2026-09-28 09:51:47 +02:00
pipeob0 0015035642 parity tool: guard the AVX-512 calls, and check the AVX2 kernels too
The tool called the AVX-512 row kernels and the AVX-512 quantizer unconditionally, so on
an AVX2-only CPU it died with SIGILL before printing anything - the one tool you want on
that machine.  Both calls are now behind cpu_avx512_ok().

Adds a block that runs the new AVX2 kernels directly against ggml's vec_dot on Q8_0
activations (the IQ4_NL down rows' real input) and times them, so a kernel change can be
validated and measured without the engine.
2026-09-28 09:51:47 +02:00
pipeob0 89c62e5361 kernels: AVX2 multi-token i-quant kernels for an AVX2-only CPU
The native pack's i-quant gate/up rows fell back to ggml's single-token vec_dot on a
CPU without AVX-512 (Ryzen 5000 series, Ice Lake, ...): the weights were decoded once
per token, and the 5700X3D's pool was decode bound.

iq_avx2.cpp adds multi-token row kernels for IQ2_XXS/IQ2_XS/IQ3_XXS/IQ3_S/IQ2_S and
IQ4_NL down rows, and for IQ2_XS the vectorized sign decode of ggml's AVX2 vec_dot
(the 8th sign bit by parity through the bit_helper pshufb, the sign vectors out of
shuffles of one 32-byte load, the 8 scale bytes through the unpack trick), with an
even_signs table equivalent to keven_signs_q2xs.  A row is decoded once and every
token in the verify window only pays a load, a sign, a maddubs, a madd and an add.

Measured on a 5700X3D + RTX 5060 Ti, Swift native pack, greedy, 300 tokens, medians of
interleaved A/B pairs: gate/up 15.16 -> 13.84 ms/round, rows 22.7 -> 23.8 GB/s,
48.5 ms/round vs 50.9 (-4.7%).  Single thread, one expert's gate+up: IQ2_XS 3 tokens
536 -> 416 us, IQ2_XXS 390 -> 337 us.  IQ4_NL down rows 248 -> 174 us for 3 tokens.
native_expert_parity: rel 3.1e-08 vs ggml's vec_dot, 0 failures.

STRATA_NO_IQ256 and STRATA_NO_IQ4NL fall back to ggml per format.
2026-09-28 09:51:47 +02:00
pipeob0 97cf327c1e pinned: enable SeLockMemoryPrivilege and round the arena to GetLargePageMinimum
reserve() asked for MEM_LARGE_PAGES without ever enabling SeLockMemoryPrivilege in the
process token, and passed the arena size unrounded.  VirtualAlloc requires an exact
multiple of GetLargePageMinimum, so the call failed with 87 (ERROR_INVALID_PARAMETER)
and the log blamed the privilege.  Both fixed; STRATA_NO_LARGEPAGES skips the attempt
and the note now says what actually happened.
2026-09-28 09:51:46 +02:00
Niko1221 6c06571710 Linux/WSL: engine errors end the engine and reach the client
- The --serve stdin reader reads the descriptor instead of std::cin: glibc's
  exit() waits for stdin's lock, which getline held while waiting for input,
  so an engine ending on any error hung in exit() and the server waited forever.
- session_init fails when a QSA layer could not pin its KV streaming copy; it
  used to continue half-built and hit an illegal memory access at the first
  request (seen under WSL, where the driver pins only about 1 GiB). The message
  says how much was pinned and what to change.
- setup: no KV streaming under WSL (new installs and existing configs).
- server: an engine ERR after an SSE stream started is sent as an error event
  (it was a 400 response written into the open stream: an empty answer), and
  the request is recorded as "error".
2026-09-28 08:51:22 +02:00
Niko1221 fb9b924e5c verify.cpp: include <cmath> (engine 0.1.13 did not compile on Linux: std::isfinite)
MSVC brought <cmath> in through another header; GCC does not. Found by building in WSL (Ubuntu 24.04, CUDA 13.0)
through setup.sh the way a Linux user does.
2026-09-28 08:03:22 +02:00
Niko1221 ffcdb437b8 Linux setup fixes, one pool worker per physical core on Linux, --port for installs, Monitor totals
- setup.sh is executable in the repository (a fresh clone could not run `./setup.sh`: Permission denied).
- setup.sh: Debian/Ubuntu ship Python's venv without ensurepip (the separate python3-venv package); the check
  now needs both, so it installs python3-venv instead of failing to create .venv - and a .venv left without pip
  by a failed run is made again.
- Linux: physical_cores() keeps one allowed CPU per (package, core) from sysfs, like the Windows branch; SMT
  siblings no longer get their own pool worker (issue #40: 23 -> 15 workers on a 13700K, +16% decode).
- START-HERE / setup.sh `--port N` now applies to an existing install; without it the install's own port is
  kept (issue #32).
- Monitor: the server keeps the last 500 requests and totals since it started; the Monitor shows the totals and
  a "Show all" toggle (`/metrics?requests=all`), 12 rows otherwise (issue #35).
2026-09-28 07:55:39 +02:00
Niko1221 2838cbae46 prompt path: never write into the borrowable cache slots while laying out (a resident expert was corrupted)
When serving, the prompt path lays its buffers out at start in the expert-cache slots it may borrow, but those
slots hold resident experts until a request lends them. Writing the MMQ row table at layout time overwrote part
of a resident expert: decode later computed it on the GPU, and the first window that routed to it turned every
logit into NaN ("!!!!" from mid-answer, e.g. at position 138 of an 86-token prompt, even with the prompt read
through the windows). The table is written at the start of every prompt read, while the slots are lent.
STRATA_DBG_NAN=1 also reports the first verify window with non-finite logits and the state a prompt leaves.
2026-09-28 04:56:25 +02:00
Niko1221 9ee557f781 prompt path: zero the tail after the last gathered expert before MMQ (garbage answers after the first request)
MMQ reads up to one 256-value tile past a matrix's last row when the row is not a multiple of it (the down
product: 640 values). Those weights meet zero activations, harmless only if they decode finite; llama.cpp
zero-pads after every tensor. The group buffers held leftovers - after the first request's refill, other
experts' bytes read as IQ4_NL blocks - and a non-finite scale times zero gave NaN logits ("!!!!" answers from
the second request on). The group buffers get a 4 KB tail, zeroed after the last expert before each launch.
The MMQ row table (in the lent slots the refill overwrites) is written at every prompt.
STRATA_DBG_NAN=1 reports the first layer of a chunk whose MoE produced non-finite values.
2026-09-28 04:19:42 +02:00
Niko1221 7cf709e5b1 engine 0.1.13: about 2x faster long prompts; stall reports for issue #31
Prompt path (bench/results/2026-09-28-prefill-speed):
- `--prefill auto`: chunks up to 8192 tokens, sized to the cache slots the prompt path may borrow; a
  request borrows only what its prompt needs (Prefill::relayout). setup writes it and switches old
  `--prefill 2048` configs once the engine supports it.
- The attention and MoE halves share one scratch region (~260 KB less per token).
- Experts through llama.cpp's MMQ kernels (ggml-cuda mmq.cuh, compiled from the same checkout as ggml
  into strata_mmq; Strata Q2_0 blobs converted to GGUF blocks on the gather; IQ1_M layers keep FP16).
- Chunks >= 2048: every non-resident expert streams through a ring in a fixed order across layers, so
  the next layer's arrive during its attention half (384 slots when the arena is pinned, else 96).
- The PLE block for the whole chunk at once; the next chunk's PLE rows read on a thread; unpinned
  experts copied by stager threads (epoch-tagged claims).
- STRATA_PREFILL_TIMING=1 prints a per-phase GPU timeline.
Q2_0 32K prompt 572 -> 1290 tok/s, IQ3_S 383 -> 1208 (RTX 5070 12 GB). Needles 5/5 1K-262K.

Issue #31 / #29: the serve watchdog (now 60 s) prints a stall report - each expert-pool thread's state,
the verify window's flags, layers served during the stall, memory and page faults - and on Windows
writes a minidump with every thread's stack; Release builds keep a PDB. The engine prints its version.
2026-09-28 02:03:00 +02:00
acrogenesis b9da8e7bde Support Orca IQ3_XXS with explicit BF16 compatibility packing 2026-09-27 14:10:52 -06:00
Niko1221 0f3ff4dece setup: recompile a locally built engine when its source changed (git pull on Linux, issue #31)
A compiled engine was stamped only as source=local, so later runs never rebuilt it and a
git pull kept the old binary (on Linux, where there is no ready-made engine, that also meant
no v0.1.12 pool fix). BUILD.json now keeps a hash of the engine's and the image encoder's
source files; on start a changed hash recompiles (incrementally, in build/) before the model
starts, and a failed compile falls back to the installed engine.
2026-09-27 21:40:19 +02:00
Niko1221 6255c8fd14 web app: attach text files - drop them on the chat or use the attach button (issue #30)
Text files (code, notes, logs, data; up to 512 KB) become chips next to
the message and are sent as "File: name" plus a fenced block (a longer
fence when the file contains one). Binary files are refused with a toast;
pictures work as before (when images are on). The attach button is always
there now. Kept small on purpose: the web app is for quick tests and
settings.
2026-09-27 20:53:38 +02:00
Niko1221 9ffd6c9461 engine: fix a race in the CPU expert pool, and never hang silently again (issue #29, engine 0.1.12)
On a card with most experts in VRAM (RTX 4090) generation could stop for
good after thousands of tokens: the GPU "100%" at low power, one CPU core
busy, no tokens, no error.

- The pool's workers sleep after 20 ms without work (issue #4). With most
  experts resident many layers have no CPU work, so they also slept
  mid-request, and a worker that woke late was still counted as parked: it
  could claim from the next batch while the host was writing it, run a job
  twice, or add to `done` after the reset - and `done != n` then never
  ended while the GPU waited on the pool. Every claim now carries its
  batch: `head` is one 64-bit word (epoch | njobs | index), a claim is a
  CAS that only succeeds for the epoch the worker woke for, and the host
  waits with `<` instead of `!=`. Same speed (pool_test 47.2 GB/s).
- Every pool wait is bounded (60 s: a message and the engine stops), and
  --serve runs a watchdog: a request with no progress (tokens, prompt
  chunks, verify windows) for STRATA_WATCHDOG_S (120) seconds ends the
  engine with where it was stuck (strata/core/progress.hpp). The server
  (issue #27) answers with an error, names the cause from the log, and
  starts the engine again on the next request.
- pool_stress: tiny batches with sleeping workers; STRATA_POOL_SPIN_US
  shortens the spin to make them sleep constantly.

Tested: pool_test, pool_stress; 25,000-token generation at 262K with KV
streaming and constantly sleeping workers (73.9 tok/s, unchanged); the
watchdog deliberately tripped (2 s): error, log line, restart, next answer.
2026-09-27 20:53:38 +02:00
pete 898d6a4c3e Merge remote-tracking branch 'origin/main'
# Conflicts:
#	serve/server.py
2026-09-27 17:10:41 +01:00
Niko1221 1e3bded2f7 web app: "Use for other apps too" - the Chat settings as every client's defaults
A switch in the Sampling drawer. With it on, Apply stores the drawer's
settings on the server (POST /settings): thinking level, temperature,
top-p, top-k, seed, max tokens and the experimental speed projection. omp
and other API clients then get them for anything they don't send
themselves, from their next request (no restart); what a client sends still
wins. Off again, clients use their own settings.

- Kept between starts in <config>.shared-settings.json next to the run
  config; the server window says what other apps use.
- Only Strata's own page can change them: JSON only (a cross-site form or
  simple request can't send it without a CORS preflight, which this server
  never grants), no foreign Origin, and the API key when one is set.
- Values are checked (bad ones are a 400 naming the setting).
- Tests: defaults reach the engine, a client's own values win, off clears
  them and the file, 415/403/400/401.
2026-09-27 17:50:38 +02:00
Niko1221 ae3dc218ea web app: a long switch label no longer squeezes the switch
The Sampling drawer's "Experimental speed projection" row has a longer label
than "Show thinking", and the flex row shrank its switch, clipping the knob
at the drawer's edge. Switches in toggle rows keep their size.
2026-09-27 17:45:18 +02:00
Niko 3cba59ea31 Update README.md 2026-09-27 17:48:41 +02:00
Niko ecba7dfc10 Update README.md 2026-09-27 17:46:12 +02:00
Niko1221 1314ebfc7f README: a video up top, speed and model choice first, simpler troubleshooting (engine 0.1.10)
- An animated preview of the pagoda garden demo under the title (the full
  video is a release download: GitHub does not play videos from a
  repository), and a screenshot of the app in "Using it".
- "Is my PC enough?" folded into Install; "How fast is it?" and "Which model
  should I pick?" come first. The model table shows shard 1 (what gets
  loaded) instead of the download, with a plain fit rule: RAM >= shard 1 +
  ~10 GB, since the experts live in RAM.
- "Something went wrong?" as short questions and answers, including the
  first-start freeze and "the engine stopped unexpectedly".
- DETAILS troubleshooting: the same two rows, and the VRAM stall row now
  points to engine 0.1.9.
- setup requires engine 0.1.10 (INFO arena_mib, the start-up RAM warning).
2026-09-27 17:30:09 +02:00
Niko1221 7664149081 serve: an engine that dies mid-answer is an error, and the next request restarts it (issue #27)
On Linux with too little RAM for the model's experts (IQ3_XXS on 47 GB), the
out-of-memory killer ended the engine mid-answer. The server then recorded
the reply as "length" ("Max tokens" in the Monitor, no tok/s), the stream
was cut with a traceback, and every later request failed on the dead pipe.

- The engine's death is EngineDied: the reply ends with a clear error event
  (OpenAI and Anthropic streams; 503 before the answer), the Monitor says
  Error, and the server window names the likely cause (RAM; dmesg).
- The next request starts the engine again (same command) instead of
  failing forever; the engine counts as gone as soon as its output closes,
  and a failed write to it is not mistaken for the client leaving.
- The engine's INFO line reports its expert arena (arena_mib); at start the
  server warns when less than ~6 GB of RAM is left beside it.
- Test: a fake engine dies mid-stream -> error event + [DONE], Monitor
  "error", the next request restarts it and answers. End to end with Q2_0:
  strata.exe killed mid-answer -> error (exit code 1); next request restarts
  it (31 s) and answers.
2026-09-27 17:23:55 +02:00
Niko1221 6abee7f397 Experimental speed projection: an optional control vector (EXPERIMENTAL, off by default)
The engine takes llama.cpp's control-vector flags (--control-vector-scaled
FILE:SCALE, --control-vector-layer-range A B, --cvec-mode add|project,
--cvec-dir per-layer|single:L) and applies the vector after each steered
layer's FFN write on every hyper-connection stream: project h -= s (h.v) v,
or add h += d - where the experimental-speed-projection package's patched
llama.cpp applies it (build_cvec after the layer's second combine).

- strata/kernels/cvec: one kernel per (stream, token); with a pending fused
  write it applies it first with the fused read's own arithmetic, so a
  loaded-but-off vector is bitwise the stock engine. cvec_parity tests it.
- Per request: cvec=0|1 on the GEN/GENI line, the API field
  "experimental_speed_projection", a switch in the web app's Sampling
  drawer; a switch drops the conversation cache once. About and the
  Monitor say which way each request ran.
- setup asks (default off; --experimental-speed-projection on|off|GGUF),
  original Qwen3.8-Flash-Next only. The vector ships in
  data/experimental-speed-projection with a README that says what it is:
  its package describes it as a refusal-direction projection.

Measured (bench/results/2026-09-27-esp): off = stock token for token; on
costs 0.2-0.4% per token; mean KL 0.063 on 2,557 teacher-forced tokens.
2026-09-27 16:47:45 +02:00
Niko1221 868dc724d6 serve: reachable from other devices on the network (issue #26)
--host 0.0.0.0 bound fine, but nothing in the normal flow could set it: the
generated run scripts and setup's start always used the default
127.0.0.1, the config had no host, and the server then printed
http://0.0.0.0:8080, which opens nothing on another device.

- "host" in strata-<model>.json (the run scripts need no change); setup
  takes --host and --api-key and writes them there.
- With a non-local host the server prints this PC's addresses (the default
  route's first), warns when there is no API key, and on Windows says how to
  let it through the firewall. The local lines name 127.0.0.1.
- Documented in DETAILS.
2026-09-27 16:42:27 +02:00
Niko1221 f1b35531c3 engine: size the expert cache around the output head, so VRAM stays free (engine 0.1.9)
The expert cache takes what is free minus --vram-reserve-mib (700), so
everything allocated after it comes out of that reserve. The native output
head (0.44 GB Q2_0, 0.52 GB IQ3_S) was loaded after it and ate most of it:
IQ3_S at 128K ended with 30 MiB free, the driver paged GPU memory, and a
request stalled for good at its first verify window (GPU "100%" at 45 W).
Q2_0 at 32K ended with 75 MiB.

The head now loads before the cache is sized. Same reserve, now actually
free: 480-626 MiB with everything loaded (Q2_0 32K, IQ3_S 128K + vision).
The cache is ~8% smaller (Q2_0 32K: 3,848 slots vs 4,167) and decode is not
slower: 78-79 tok/s median vs 71.7 for 0.1.8 on the same greedy prompts.

Below 256 MiB the startup line now says which --vram-reserve-mib to add.
2026-09-27 16:23:07 +02:00
Niko1221 20351c6030 serve: --fit-max-tokens follow-ups (PR #24)
- A prompt that leaves no room at all is still the clear 400 with the flag
  on (it clamped to 1 token, which the engine then refused as over the
  context).
- "fit_max_tokens": true in the run config switches it on too, since the
  run scripts are generated by setup.
- Tests: an overshoot is clamped to the room on both APIs, a budget that
  fits is unchanged, no room is a 400. Documented in DETAILS.
2026-09-27 16:12:38 +02:00
Niko1221 bbba5bf576 Merge PR #24: optional --fit-max-tokens (clamp the output cap to the context instead of 400) 2026-09-27 16:11:54 +02:00
pete ba8c55e1cc serve: optional --fit-max-tokens clamps the output cap to remaining context instead of rejecting
Agentic clients legitimately request the model's full output cap with a long
prompt (e.g. prompt 131,888 + max_tokens 130,298 on a 262,144 window: rejected
over a 42-token overshoot). Default behaviour is unchanged: without the flag
the server still rejects with 400, like llama.cpp. With --fit-max-tokens the
server clamps max_tokens to the remaining room (max_context - CTX_SLACK -
prompt, floor 1 token) and answers.

Measured on a 262,144-token host: prompt 251,455 + max_tokens 131,072 ->
HTTP 200, completion_tokens 10,681 (= 262,144 - 251,455 - 8), finish length.
Flag off: byte-identical 400. Small requests unaffected in both modes.
2026-09-27 13:24:23 +01:00
pete b78d9da751 Merge remote-tracking branch 'origin/main' 2026-09-27 13:24:23 +01:00
pete 5f563ce3a3 serve: optional --fit-max-tokens clamps the output cap to remaining context instead of rejecting
Agentic clients legitimately request the model's full output cap with a long
prompt (e.g. prompt 131,888 + max_tokens 130,298 on a 262,144 window: rejected
over a 42-token overshoot). Default behaviour is unchanged: without the flag
the server still rejects with 400, like llama.cpp. With --fit-max-tokens the
server clamps max_tokens to the remaining room (max_context - CTX_SLACK -
prompt, floor 1 token) and answers.

Measured on a 262,144-token host: prompt 251,455 + max_tokens 131,072 ->
HTTP 200, completion_tokens 10,681 (= 262,144 - 251,455 - 8), finish length.
Flag off: byte-identical 400. Small requests unaffected in both modes.
2026-09-27 13:11:09 +01:00
Niko1221 6c431c60cc Record PR #21's rebased head as merged (its change is already in via 7f21e2b) 2026-09-27 12:14:51 +02:00
Niko1221 822a5c783f Web app: ship the Outfit font (OFL); the theme follows the system until picked; engine 0.1.8
- serve/web/fonts: Outfit as one variable font (latin + latin-ext, weights
  100-900) with its OFL.txt, in place of the design kit's commercial font.
- The page no longer saves the system's theme on first load (which froze
  it); only a click on the theme button or toggle is remembered, and the
  page follows system changes until then.
- setup: requires engine 0.1.8 (--kv q4_0, the INFO line).
2026-09-27 12:13:45 +02:00
Niko1221 0d721cf87b Web app: Chat, Monitor and About tabs (#22)
The browser page at http://127.0.0.1:8080 is now the Strata app, in the
Strata design language (serve/web, same origin, no CDN):
- Chat: streaming answers, collapsible thinking, Markdown with code copy,
  images when enabled, sampling/thinking settings, export, new chat.
- Monitor: live model state (reading with progress / generating at tok/s),
  GPU load, VRAM, temperature, power, PCIe traffic, CPU, RAM, disk, the
  context fill and the last requests, each with a minute of history.
- About: the model and engine settings, this PC, the API addresses.

Server: GET /metrics (API key when set), /web/* app files, /fonts/* only
when installed locally (not shipped; the page falls back to the system
font). serve/telemetry.py reads NVML via ctypes and psutil (new setup
dependency) with fallbacks. The engine prints an INFO line before READY
(kv, expert slots, speculation); older servers ignore it and the server
works with older engines.

Based on the dashboard idea in PR #22 by code-martin.
2026-09-27 12:00:33 +02:00
Niko1221andcode-martin 40542e8bb3 Merge PR #21: Q4_0 KV with Hadamard rotation (--kv q4_0), integrated with KV streaming; accurate expert-cache note (#23)
PR #21 (code-martin): a 4-bit K/V format (orthonormal FWHT-256 rotation of K, V and Q, then q4_0 blocks). On merge:
- Q4 is a full third format of the KV-streaming code (host copy, block residency, prompt-path staging, MTP ring,
  STATE_HASH): with --kv-resident the PR wrote K/V through the residency map's -1 entries. kv_stream_parity now
  checks Q4 streamed vs resident, bitwise.
- The per-token path ran attention twice (once before the blocks were resident); once now.
- The q4 group maximum breaks |x| ties the same way in every lane (one scale per block).
- Dropped: the duplicate path fixes (in 0.1.7), personal .gitignore entries, the re-encoded CMakeLists.
- Measured (bench/results/2026-09-27-kv-q4): int8 matches fp16; q4_0 costs precision on long documents
  (perplexity +8-12%), needles 5/5, +3.7% decode at 128K. Opt-in: setup asks above 8K context (int8 recommended),
  START-HERE.bat --kv q4_0, or --kv q4_0 in the config.

#23: the start-up warning "the GPU hit path is NOT CORRECT" dated from a long-fixed fault. Measured teacher-forced
on 2,557 tokens (bench/results/2026-09-27-cache-parity): cache on vs off, 95-98% same top-1 and equal perplexity
(-0.005 +- 0.005 nats); the warning is now one accurate line.

Co-Authored-By: code-martin <[email protected]>
2026-09-27 11:36:28 +02:00
code-martin e8b75ff2f6 feat(kv): add Q4_0 KV cache mode with FWHT-256 Walsh-Hadamard rotation
- Implement Q4_0 KV quantization and dequantization kernels with scale/offset
- Implement fast Walsh-Hadamard transform (FWHT-256) rotation for outlier suppression
- Add Q4_0 attention decode support in qsa_decode_attn
- Add kv_q4_parity parity test verifying CPU vs CUDA numerical accuracy
- Support --kv q4_0 in generate CLI and runtime layer allocation
- Update .gitignore for build and repository hygiene
2026-09-27 11:48:16 +03:00
code-martin 7f21e2b401 feat(kv): add Q4_0 KV cache mode with FWHT-256 Walsh-Hadamard rotation
- Implement Q4_0 KV quantization and dequantization kernels with scale/offset
- Implement fast Walsh-Hadamard transform (FWHT-256) rotation for outlier suppression
- Add Q4_0 attention decode support in qsa_decode_attn
- Add kv_q4_parity parity test verifying CPU vs CUDA numerical accuracy
- Support --kv q4_0 in generate CLI and runtime layer allocation
- Update .gitignore for build and repository hygiene
2026-09-27 11:29:08 +03:00
Niko1221 bbe8944ed3 Prompt lookup that pays, sampling fixes, shallow-path setup fix (engine 0.1.7)
- Prompt lookup on by default: the suffix drafter's windows are taken only where a learned policy (measured
  acceptance per match length, measured window cost) expects more tokens per ms than the MTP window; lookup windows
  up to --spec + 2. Code edits +6-11%, other text unchanged, verified output identical. --suffix-draft 0 = off.
- Per-request sampling (#19) keeps greedy requests on the in-graph sampler (no extra launch per window), and draws
  row t of a window with Philox(seed, pos0 + t), so a seed replays the same text whatever the drafts were.
- tools: a shallow install (G:\Strata) no longer crashes the MTP packer and the pack tools (IndexError, #20).
- setup: says at step 1 when the PC has less RAM than the smallest model needs; requires engine 0.1.7.
2026-09-27 10:18:14 +02:00
Niko1221 54adac29a4 Merge branch 'pr-19' into integrate-017 2026-09-27 09:58:30 +02:00
Niko1221 e5eb7b2ae7 Merge branch 'pr-18' into integrate-017 2026-09-27 09:58:30 +02:00
Niko1221 8f279d82a0 KV streaming: the draft layer keeps its KV in VRAM when no pinned RAM is left (engine 0.1.6)
The MTP drafter's ring over a host copy is set up after the expert arena has pinned what it could; when that
pinned allocation fails the drafter now falls back to its fully resident KV instead of stopping the engine
("mtp: state init failed", seen with Swift 1.5 at 262K). setup requires engine 0.1.6.
2026-09-27 08:34:09 +02:00
Niko1221 0616dfb74e KV streaming for the QSA layers (engine 0.1.5)
From 64K context the KV cache lives in pinned RAM and a VRAM block cache holds the part the sparse attention reads
(--kv-resident N; setup turns it on with 32768 when RAM allows). Blocks the indexer selects are made resident on
the device inside the captured graphs (clock eviction, zero-copy reads of misses); writers write through, so the
attention reads exactly the same values. The freed VRAM goes to the expert cache: Q2_0 at 262K decodes 62.6 tok/s
instead of 50.9. The MTP drafter keeps its window in a ring. New ctest kv_stream_parity. Also wires the opt-in
suffix drafter (--suffix-draft) into the engine.
2026-09-27 08:29:03 +02:00
Jakub Luwierski b819fe6934 sampler: the penalty count as a shared bitmap
The penalties made the sampled path pay O(k x n_vocab x hlen) integer
compares per token: every top_k argmax round recounted every
candidate's history occurrences (k=20 rounds, hlen=64, ~318M compares
on the 248,320 vocabulary) - measured 45.2 -> 30.8 tok/s on a real
chat workload.  But the history touches at most hlen DISTINCT tokens,
so membership is a bit: each block now builds a shared bitmap from its
history row (atomicOr, one pass) and a candidate pays the O(hlen)
count scan only when its bit is set - at most hlen x k scans per token
instead of k x n_vocab.  Neutral values and the no-history path skip
the bitmap entirely.

The counts - and therefore every penalised value, kept token and
draw - are exactly what the per-candidate scan produced;
sampler_parity passes unchanged, plus a new fixture pinning the
window clamp (an 8-entry history at penalty_last_n=4 must punish only
the tail half).
2026-09-27 08:24:09 +02:00
Jakub Luwierski e3e1493df4 serve: the sampling block supports min_p and the penalties
min_p, presence_penalty, repetition_penalty and frequency_penalty move
from "warned and ignored" to the sampled path, and penalty_last_n sets
the window they count over - the server sends the engine's default of
64 when a penalty is set and the client left the window out.
sampling_keys forwards each field only when it is not neutral, so a
client (or a config) that leaves them out costs nothing.  The
validator range-checks the new fields and still refuses to start the
server on a value it cannot honour.

Verified: the validator accepts and range-checks min_p/presence/
frequency/repetition/penalty_last_n; the GEN line spells them the
engine's way and omits neutral values; the last-64 default appears
only when a penalty is actually set.
2026-09-27 07:55:19 +02:00
Jakub Luwierski 4703da4363 generate: min_p and the penalties ride the GEN line
The sampled chain's last two gaps close: `min_p` and the llama.cpp
penalty set (repeat/freq/present over a history window) are live end
to end - GEN keys, per-request params, and the token history the
penalties count over.

- GEN accepts min_p=F penalty_last_n=N penalty_repeat=F penalty_freq=F
  penalty_present=F between max_new and the ids (the server's
  spelling); the engine clamps defensively (min_p to 0..1, a negative
  last_n to 0) and skips what it does not know, as before.
- min_p cuts the top_k survivors to the prefix with p >= min_p * p_max
  - in logits, sel_logit >= sel_logit[0] + logf(min_p) - before top_p,
  so the two cuts compose; 0 disables, and the head always survives,
  so the kept count never reaches zero.
- the penalties need the token history: the serve loop uploads the
  last penalty_last_n tokens - the consumed tail plus the fed-back
  head, which joins consumed_seq only after the window commits - into
  one device row before every window.  A request without penalties
  hands the sampler a null row and takes the byte-for-byte neutral
  path (no upload, nothing handed over).

Verified: sampler_parity grows a full-chain host reference (the Philox
draw transcribed host-side) and fixtures for the sampled chain with
penalties and for min_p at 0/0.5/0.9 - the kernel matches the
reference on all of them, and each fixture first asserts it can SEE
the feature before asserting the kernel matches.  On the IQ3_XXS
artifact, same seed: a request with neutral penalty keys is
token-identical to one without them; a six-copy prompt that loops
24/24 on one token clean produces 24 distinct tokens with repeat 1.5
+ present 0.5; min_p 0.9 changes the pick.
2026-09-27 07:55:19 +02:00
Jakub Luwierski 9e6e5b7a1a serve: sampling defaults from the run config
The run config's optional `sampling` block (temperature, top_p, top_k,
seed) sets defaults for the fields a request leaves out, so a client
that sends no sampling keys gets configured sampling instead of greedy.
The request's own fields always win: an explicit temperature=0 still
means greedy, and a field set to null falls back to the default.

Keys the sampled path does not implement yet (min_p, presence_penalty,
repetition_penalty, frequency_penalty) are named at startup and ignored
rather than silently dropped; unknown keys are reported the same way.
An invalid value (top_k outside 1..64, a non-positive seed, a negative
temperature) refuses to start the server - a typo'd config should not
quietly change sampling.

Verified with a spy engine through Service.run: defaults apply when the
request omits the fields, the request's own values win, a null field
falls back to the default, and a server without the block forwards the
request unchanged; the config validator rejects out-of-range values and
warns on the not-yet-implemented keys.
2026-09-27 07:50:10 +02:00
Jakub Luwierski 2e9acd3d8b docs: the v1 limits line still said temperature was ignored
It contradicted the paragraph next to it, and since the sampling keys
now ride the GEN line (and the block-parallel sampler makes a sampled
window cost about what a greedy one does), it was simply false.
2026-09-27 07:33:52 +02:00
Jakub Luwierski b7d73e1b32 serve: forward temperature/top_p/top_k/seed to the engine
StrataEngine.generate accepted the request's sampling parameters and
dropped them, so every served request decoded greedy whatever the client
asked - the README's 'temperature / top_p / top_k / seed are honored per
request' was not true over the HTTP API, and the engine-side per-request
sampling was unreachable from it.

The keys ride the GEN line in the engine's own spelling; an absent
temperature keeps the engine's greedy default and temperature=0 is not
forwarded (it means the same thing); top_k outside the sampled path's
1..64 is dropped rather than sending a value the engine would refuse.

Verified end to end over the HTTP server (static residency for the
seeded leg): temperature=0 and absent agree token for token; a
temperature=0.8 request differs from greedy; two temperature=0.8
seed=99 requests return identical text.
2026-09-27 07:33:17 +02:00
Jakub Luwierski 8f3c21dc02 perf: sample the verify window's head with a block per token
The sampled chain ran in ONE thread per token (64-thread launch): top_k
alone was k sequential full-vocabulary scans with an inner sweep over the
taken list, and a verify window measured ~1.6 s in the sampler - sampled
requests decoded at 1.5 tok/s against 54 greedy.

The selection is k argmax rounds, so it parallelises exactly like the
greedy kernel: one block per token, the rounds back to back inside it,
each a two-level (warp, then block) reduction.  Ties resolve to the
lowest index (the serial scan's strict '>'), so the kept sequence - set
and order - is unchanged, and the top_p cut, temperature and Philox draw
read that order as before.  sampler_parity passes; a 248,320-vocab
GPU-vs-CPU draw check matches the serial reference on five parameter
sets including injected duplicate logits.

Measured (RTX 4070 TS, IQ3_XXS, 128-token generations): sampled decode
1.50 -> 42.7 tok/s; greedy decode unchanged (53.3 vs 54.3 tok/s median,
within run noise; greedy launches the untouched kernel).
2026-09-27 07:33:17 +02:00
Jakub Luwierski 90546d7428 docs: temperature sampling + reproducibility note (static residency) 2026-09-27 07:33:17 +02:00
Jakub Luwierski 7546c36d9d verify: a persistent per-request draw counter; sampled requests default top_k=20
The sampled path requires top_k in 1..64 (the selection loop is
O(k*n_vocab) by design); with the engine-default top_k=0 it returned
without writing the token, leaking stale buffer contents as output.
Sampled requests now default to the sampler's own top_k=20.

The Philox draw counter also must persist across a request's verify
windows - it was read from a run-local variable that reset every
window, so every window redrew the same numbers (identical sampled
output on identical requests).
2026-09-27 07:31:54 +02:00
Jakub Luwierski dea4f4f2b5 serve: per-request temperature sampling for the verify window's head
Temperature was ignored because the verify window's head sampling was
pinned to greedy INSIDE the captured CUDA graph (verify.cpp hardcoded
sp.greedy = true), and the GEN protocol carried no sampling keys.

- the sampling moves OUT of the captured graph: the graph computes the
  head logits, and run() applies sample_tokens host-side after the
  replay, with that request's own parameters and a fresh Philox counter
  per window (a captured sampler would replay the same draws forever)
- the GEN protocol accepts optional key=value pairs before the ids:
  temperature=, top_p=, top_k=, seed=; absent keys stay greedy, exactly
  as before
- server.py forwards the request's temperature/top_p/top_k/seed
- emitted tokens are always the TARGET's own samples (the drafts only
  fill the verified window), so the output distribution is the target's
  regardless of what the drafts propose

Building on the conversation cache: greedy requests stay bit-identical,
sampled requests with the same seed replay identically.
2026-09-27 07:29:40 +02:00
btechnet 656a835682 serve: treat unset/-1 max_tokens as remaining context, not 1024
A missing, null, 0 or -1 max_tokens / max_completion_tokens now means the
rest of the context (minus the 8-token margin strata --serve keeps), so
replies no longer stop at 1024 tokens. prepare() resolves the budget after
tokenizing and returns it; an explicit budget is honoured exactly and still
rejected when prompt + budget exceeds the context. Adds serve/test_server.py.
2026-09-26 20:27:30 -05:00
Niko1221 55e2fb8576 setup: require engine v0.1.4 (IQ3_S) 2026-09-26 23:57:44 +02:00
Niko1221 59b0696208 IQ3_S support: the GSQ-RCO IQ3_S model (engine 0.1.4)
- GPU experts: IQ4_XS gate/up (the IQ3_S file keeps layer 47's routed experts in it), transcribed from llama.cpp's
  vec_dot_iq4_xs_q8_1; checked on all 48 layers against ggml's float reference (native_expert_parity: 0 failures).
- MTP draft head: gather_rows takes rows of any size (the IQ3_S head is Q6_K, 2100-byte rows, not a multiple of 16).
- setup: IQ3_S for the original model (Swift 1.5 has none), 64 GB RAM, context capped at 128K like IQ3_XXS.
- README / DETAILS: IQ3_S speed (RTX 5070, Ryzen 5 7600, 64 GB): 54 tok/s at 4K, 42 at 128K, 374-397 prompt.
2026-09-26 23:55:41 +02:00
Niko1221 0223d32663 setup: update an old ready-made engine when an existing install starts
START-HERE.bat on an installed setup went straight to starting the model, so the MIN_ENGINE check (and the
engine download) only ran when setup was run again with its options.  Now every start checks the installed
engine and replaces it when it is older than MIN_ENGINE.  If it cannot (no internet, the model still running,
no ready-made engine for this GPU) the installed engine is kept and starts as before.
2026-09-26 22:24:29 +02:00
Niko1221 d0017a7c58 setup: require engine v0.1.3 (conversation cache, idle CPU fix, Linux pool fix) 2026-09-26 22:18:20 +02:00
Niko1221 0c2e63e930 Merge pull request #10: Conversation cache + short prompt parts through the decode windows (#8, #10) 2026-09-26 21:25:03 +02:00
Niko1221 888751a0e1 Merge pull request #7: serve: fix zero-token replies from the engine-queue race between requests (#7) 2026-09-26 21:25:03 +02:00
Niko1221 af1c91f245 Merge pull request #9: CPU pool: sleep between requests instead of spinning (#9, fixes #4) 2026-09-26 21:25:03 +02:00
Niko1221 14b22c51c1 Merge pull request #14: CPU pool: fix dangling else in Linux physical_cores() (#14, fixes #13 and #11) 2026-09-26 21:25:03 +02:00
pete ad29df2396 Merge branch 'pr-14' 2026-09-26 19:20:59 +01:00
Vistawizard be028948f4 CPU pool: fix dangling else in the Linux physical_cores() branch
Without braces the `else` bound to `if (CPU_ISSET(i, &set))`, so every CPU id
outside the affinity mask appended all hardware_concurrency() ids again:
16143 pool workers on a 16-CPU box, load > 10000 and a hang at start.
Brace both branches so the fallback only runs when sched_getaffinity fails.

Fixes #13, root cause of #11.
2026-09-26 19:23:15 +02:00
pete 8936605cc5 Merge branch 'pr-8' 2026-09-26 17:39:42 +01:00
pete 3503b5ddd8 Merge branch 'pr-7' 2026-09-26 17:39:42 +01:00
Mirtraxxx 470b3d7c21 serve: read short prompt parts through the decode windows
The batched prompt path costs ~300 ms per run however few tokens it
has (it streams every non-resident expert the chunk routes to over
PCIe), plus ~180 ms to refill the expert-cache slots it borrows. So a
one-word message waited ~1.4 s for its first token, and every chat
follow-up paid the same fixed cost.

Each part of the prompt (up to the checkpoint, then the assistant
header) now picks its own path: parts of at most --short-read N tokens
(default 64, 0 = off) go through the verify windows, S tokens at a
time, as decode reads them (~16 ms a token). Longer parts and parts
with picture rows stay batched. Slots are lent just before a batched
run and refilled before a window read, so windows always see the whole
expert cache. STRATA_CKPT_REREAD keeps every read batched.
2026-09-26 01:02:20 -03:00
Mirtraxxx be0e0c94e3 cpu pool: sleep between requests instead of spinning (fixes #4)
A parked expert-pool worker spun on `epoch_` with `_mm_pause` forever, so every
worker's core stayed at 100% while the engine waited for a request: 7 cores
(44% of a 16-thread Ryzen 7 5700X) with nothing to do.

A parked worker now spins as before for up to 20 ms, then blocks on a
condition variable. Every publish goes through `publish()`, which bumps the
epoch and notifies only when a worker is actually asleep (one uncontended
load otherwise). Inside a request the gaps between batches are microseconds,
so the token path never sleeps.
2026-09-25 21:27:53 -03:00
Mirtraxxx 42810ee8bb serve: conversation cache - keep the chat between requests, read only what is new
Every request used to start from session_zero and read its whole prompt again,
so a long chat or an agent loop waited minutes per turn. The engine now keeps
the live session and up to 6 checkpoints of the running state in RAM and reads
only the part of a prompt after the longest one it starts with.

- A checkpoint is the state that cannot be rewound - the 36 GDN recurrences and
  conv histories, the 12 QSA indexer tails, the PLE history (~118 MB). The KV
  cache and the pooled indexer keys are positional (a query reads only cells
  below it, and `dead` for its own block), so they stay in place.
- Taken at the last <|im_start|> of each prompt (the prompt is read in two parts
  there: the history, then the new turn's header) and every 16K fresh tokens.
- Used only when the prompt starts with exactly its tokens and pictures (images
  are compared by a hash of their embeddings and grid); checkpoints the next
  request would overwrite are dropped.
- New lines on stdout: RESUME <n> before reading, PP <pos> <n> <ms> <tok/s> per
  chunk, REUSED <n> after; DONE gains <drafts accepted> <drafts offered>
  <reused>. The server ignores what it does not know; PP lines now count as its
  heartbeat, so keep-alives keep flowing during a long prompt.
- --prompt-cache N (default 6, 0 = off), --prompt-cache-every N, --turn-token ID.
- Checks: STRATA_CKPT_REREAD=1 reads a checkpoint's tokens again instead of
  restoring it; STRATA_STATE_HASH=1 prints a fingerprint of every state part
  after each request.
2026-09-25 21:18:23 -03:00
btechnet 0f8b5d4938 serve: fix zero-token replies from engine-queue race between requests
Symptom: with two requests in flight (opencode fires a title-generation
request alongside the main tool-calling turn), the second request often
returned zero tokens. The server logged `done: 0 tokens in 0 s (length,
cancel=False)` and opencode showed an empty reply.

Cause: StrataEngine multiplexes every request over one resident
`strata --serve` subprocess and a single shared line queue (self.lines),
with no per-request framing. In Service.run(), a stop token (<|im_end|>)
breaks the token loop with the engine generator still suspended and its
internal `done` flag False. That generator's cleanup - write STOP, then
drain self.lines until DONE - therefore ran only when run()'s frame was
torn down later, AFTER the `with self.fifo` lock had been released. By
then the next request held the lock and had issued its own GEN; the prior
request's drain raced it on the shared queue and consumed the NEW
request's DONE. The new request saw an immediate end-of-stream: 0 tokens,
with `finish` left at its default of "length".

Fix: close the engine generator inside the `with self.fifo` block via
try/finally, so a request's STOP+drain completes while it still holds the
lock and cannot leave stray lines on the shared queue for the next
request to misread as its own DONE.

Also in this change:
- Clamp a non-positive max_new to 1024 in both /v1/chat/completions and
  /v1/messages. Some clients send max_tokens: -1 ("unlimited"); that value
  passed straight through and `GEN -1` made the engine yield 0 tokens.
- Distinguish a client disconnect: catch GeneratorExit and set
  finish="disconnect" instead of leaving it as "length".
- Diagnostics gated on STRATA_DEBUG (off by default): a per-request
  `_debug_req` line (api, msg/tool counts, raw max_tokens, resolved
  max_new, thinking flag, prompt tokens) and a `raw:` dump of the decoded
  model output at completion.
- Progress reporting: add tok/s and cancel state to the `done:` line, and
  emit the progress line every 1 s instead of every 15 s for live
  throughput.
2026-09-25 18:11:01 -05:00
Niko1221 07b4a6263a Engine v0.1.2: streaming tool calls, live status, real cancel; fix requests stalling on a full GPU
Streaming
- tool calls stream as they are written (OpenAI tool_calls deltas, Anthropic input_json_delta)
- keep-alives while a long prompt is read, progress in the server window, GET /status
- closing the connection or pressing stop really stops the engine (STOP)

Stalled requests (mostly image turns)
- the expert cache is sized after its slots are written (Windows only counts GPU memory once it is
  touched), so the reserve really stays free instead of the card running at 0 MiB and paging
- the prompt chunk shrinks when its buffers do not fit in the cache, instead of allocating on top
- the image encoder warms up before the engine starts; images are encoded in request order
- the log shows the VRAM left once everything is loaded

setup.py now requires engine 0.1.2 (downloaded automatically on the next START-HERE.bat).
2026-09-25 06:13:46 +02:00
Niko1221 e550970773 README: a short, beginner-friendly front page (details in docs/DETAILS.md); pictures in WebP/TIFF/AVIF 2026-09-24 23:17:29 +02:00
Niko1221 6801c77ff6 Swift 1.5 support (UkisAI's fine-tune): split shards, per-model chat template, engine v0.1.1
The setup now asks which model first: Qwen3.8-Flash-Next (the original) or Swift 1.5. The engine reads models
whose GGUF shards split the layers; the ready-made engine v0.1.1 is on the Releases page.
2026-09-24 21:47:08 +02:00
480 changed files with 110348 additions and 2362 deletions

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
# Keep the image to the source only: everything generated is re-created on
# first start (inside the container or on the /data volume).
.git
# local Python environment (rebuilt in the image)
.venv/
__pycache__/
*.pyc
.pytest_cache/
# setup.py outputs and downloads (engine, packs, models, logs)
/engine/
/build/
/build*/
/dist/
/packs/
/mtp/
/models/
/Strata-data/
third_party/llama.cpp/
third_party/*.zip
*.done
*.log
strata-*.json
strata-*.txt
run-*.sh
run-*.bat
+3
View File
@@ -4,3 +4,6 @@ bench/prompts/** -text
# launchers keep their platform line endings wherever the repository is cloned
*.sh text eol=lf
*.bat text eol=crlf
# copies of real packs' native_experts.txt, parsed byte for byte by tests/core/expert_layout_test.cpp
tests/data/native_experts/** -text
+1
View File
@@ -0,0 +1 @@
buy_me_a_coffee: strataengine
+6 -1
View File
@@ -18,6 +18,8 @@ __pycache__/
# plan v0.3 P1 (repository hygiene, review E1): profiler captures and large diagnostic dumps stay out of git.
*.nsys-rep
# Pi harness metadata (local session state, not project source)
*.sqlite
*.npz
*.occ1
@@ -26,7 +28,7 @@ td.log
# created by setup.py on the user's machine
/.venv/
/build/
/third_party/llama.cpp/
/third_party/llama.cpp
/models/
/packs/
/mtp/
@@ -35,6 +37,7 @@ td.log
/strata-*.json
/strata-*.log
/build-strata.bat
/.strata-mcp/
# image encoder (setup.py --vision): build output and the generated build script
build-vision*/
@@ -46,3 +49,5 @@ build-vision.bat
/dist/
*.done
/third_party/*.zip
/.rocm-win/
+23
View File
@@ -0,0 +1,23 @@
# AGENTS.md
Strata runs the Qwen3.8-Flash-Next mixture-of-experts model (and its Coder, Swift 1.5 and Unsloth variants) on a
normal PC: one NVIDIA or AMD graphics card plus system RAM, on Windows or Linux. It has a C++/CUDA/HIP engine
(`src/`, `include/`), a Python server with an OpenAI- and Anthropic-compatible API and a web app (`serve/`), and a
one-click installer (`setup.py`, started by `START-HERE.bat` / `setup.sh`).
## Installing Strata for a user
Follow **[docs/AI_SETUP.md](docs/AI_SETUP.md)**: check the PC, pick the model by RAM, run setup non-interactively,
start and verify the server, and connect the user's apps. Never expose the server beyond `127.0.0.1` without
`--api-key`. As an alternative to shell commands, Strata's MCP server ([docs/MCP_SERVER.md](docs/MCP_SERVER.md))
offers the same steps as tools.
## Working on the code
- How the engine works, every measured number, the API and all settings: [docs/DETAILS.md](docs/DETAILS.md) and
the [paper](docs/paper/Strata-Paper.pdf).
- AMD (HIP) build and validation: [docs/AMD_HIP.md](docs/AMD_HIP.md); multi-GPU: [docs/MULTI_GPU.md](docs/MULTI_GPU.md).
- Setup's own tests run without a GPU or downloads: `python tools/test_setup_<name>.py` (for example
`tools/test_setup_amd.py`, `tools/test_setup_choices.py`).
- Keep the docs' style: plain words, measured numbers with what they were measured on, no claims without a
measurement.
+634 -110
View File
@@ -4,11 +4,18 @@
# `strata-pack` are added when their sources exist - a target with no sources would make the build graph
# claim something the tree does not have, which is worse than an absent target.
#
# CUDA targets arrive in Phase 2 and this build REJECTS any architecture other than 120: the engine is
# written against sm_120 (RTX 5000 / Blackwell) and silently building for an older arch would produce a
# binary that runs and is wrong about its own performance, which is the worst of both outcomes.
# CUDA targets arrive in Phase 2. The kernels need sm_80 or newer (tf32 mma, bf16 math) and the guard
# below refuses anything older: silently building for a pre-Ampere arch would produce a binary that runs
# and is wrong about its own performance, which is the worst of both outcomes.
cmake_minimum_required(VERSION 3.24)
project(strata VERSION 0.1.0 LANGUAGES CXX)
project(strata VERSION 0.1.36 LANGUAGES CXX)
add_compile_definitions(STRATA_VERSION="${PROJECT_VERSION}")
# issue #31: release builds keep a PDB beside the exe (not shipped in it), so a minidump from a stalled engine can
# be read back to the exact source line
if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/Zi>)
add_link_options(/DEBUG /OPT:REF /OPT:ICF)
endif()
# The prebuilt release (setup.py downloads it, so users need no compiler): ggml's CPU backend for any AVX2 CPU
# instead of this machine's (Strata's own AVX-512 kernels are per-file and chosen at run time either way), and
@@ -26,36 +33,88 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
endif()
option(STRATA_ENABLE_CUDA "Build the CUDA targets (Phase 2+)" OFF)
option(STRATA_ENABLE_HIP "Build the HIP targets for wave32 AMD GPUs (gfx1100, gfx1201; gfx1030 unvalidated)" OFF)
option(STRATA_PREFILL_MMQ "Build the optional GGML MMQ prefill path for HIP" OFF)
# CUDA: MMQ prompt kernels for Q4_K / Q5_K / Q5_1 (Unsloth's UD-Q4_K_XL experts). Off by default: the engine loads
# every kernel when it starts (CUDA_MODULE_LOADING=EAGER), so these took ~5 MB of VRAM - 1-3 expert slots - from every
# model, and the UD-Q4_K_XL prompt is SSD-bound with or without them (docs/UNSLOTH_Q4.md)
option(STRATA_MMQ_KQUANTS "Build the CUDA MMQ prompt kernels for Q4_K / Q5_K / Q5_1 experts" OFF)
option(STRATA_ORCA_Q4KS_MMQ "Build the GGML Q5_0 MMQ instance for OrcaRouter Q4_K_S" OFF)
if(STRATA_PREFILL_MMQ AND NOT STRATA_ENABLE_HIP)
message(FATAL_ERROR "STRATA_PREFILL_MMQ requires STRATA_ENABLE_HIP=ON")
endif()
if(STRATA_ENABLE_CUDA AND STRATA_ENABLE_HIP)
message(FATAL_ERROR "STRATA_ENABLE_CUDA and STRATA_ENABLE_HIP are separate backends; enable only one")
endif()
if(STRATA_ENABLE_HIP)
include(cmake/hip_backend.cmake)
endif()
# The test suite and the micro benchmarks live in tests/ and bench/micro/, which the published source leaves out.
set(_strata_tests_default OFF)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/bench/micro)
set(_strata_tests_default ON)
endif()
option(STRATA_BUILD_TESTS "Build the test suite" ${_strata_tests_default})
option(STRATA_BUILD_CONVERSATION_TESTS "Build standalone conversation cache tests" OFF)
option(STRATA_WERROR "Treat compiler warnings as errors" OFF)
if(STRATA_BUILD_CONVERSATION_TESTS)
enable_testing()
add_executable(conversation_cache_test src/core/conversation_cache_test.cpp)
target_include_directories(conversation_cache_test PRIVATE include)
add_test(NAME conversation_cache_test COMMAND conversation_cache_test)
add_executable(conversation_memory_test src/core/conversation_memory_test.cpp src/core/conversation_memory.cpp)
target_include_directories(conversation_memory_test PRIVATE include)
add_test(NAME conversation_memory_test COMMAND conversation_memory_test)
endif()
if(STRATA_ENABLE_CUDA)
enable_language(CUDA)
# `enable_language(CUDA)` gives the .cu compile rules but NOT the imported targets, so `CUDA::cudart` has to
# be brought in explicitly for the host-side tests that call cudaMalloc themselves.
find_package(CUDAToolkit REQUIRED)
option(STRATA_EXPERIMENTAL_SM60 "community build for GPUs below compute capability 7.5: Pascal sm_60, Volta sm_70 (#236)" OFF)
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
set(CMAKE_CUDA_ARCHITECTURES 120 CACHE STRING "CUDA architectures" FORCE)
endif()
foreach(_a IN LISTS CMAKE_CUDA_ARCHITECTURES)
string(REGEX REPLACE "-.*$" "" _base "${_a}") # 120-real / 120-virtual are still 120
# Plan v0.3 P6: developed and measured on sm_120 (RTX 50); the kernels need sm_80 or newer (tf32 mma in
# the attention scorer, bf16 math), so RTX 30 (86) and RTX 40 (89) build too. Older cards are refused.
# Turing port: developed and measured on sm_120 (RTX 50); the tf32 mma in the QSA scorer has a
# portable fp32-FMA fallback, so RTX 20 (75), 30 (86), RTX 40 (89) and RTX 50 (120) build too.
# Pre-sm_75 is refused.
if(_base STREQUAL "native" OR _base STREQUAL "all" OR _base STREQUAL "all-major")
continue()
endif()
if(_base LESS 80)
# STRATA_EXPERIMENTAL_SM60: a Pascal (sm_60, GP100-class) or Volta (sm_70, V100 / Titan V, #236) build.
# These sit below every architecture the kernels were written for, so two intrinsics are replaced below
# their arch - `__dp4a` (6.1+) and `__nanosleep` (7.0+) - in include/strata/kernels/dp4a.hpp, which is
# bit-exact for the first and merely a shorter pause for the second (sm_70 has both). Community-tested,
# not in the ready-made engine; RTX 20/30/40/50 cards must use the release engine instead. It needs a CUDA
# 12.x toolkit: CUDA 13 dropped sm_60 and sm_70 (nvcc fails CMake's compiler test with "compute_70").
if(_base LESS 75 AND NOT STRATA_EXPERIMENTAL_SM60)
message(FATAL_ERROR
"Strata needs an NVIDIA GPU of compute capability 8.0 or newer (RTX 30 / 40 / 50 series), but "
"CMAKE_CUDA_ARCHITECTURES is '${CMAKE_CUDA_ARCHITECTURES}'.")
"Strata needs an NVIDIA GPU of compute capability 7.5 or newer (RTX 20 / 30 / 40 / 50 series), but "
"CMAKE_CUDA_ARCHITECTURES is '${CMAKE_CUDA_ARCHITECTURES}'. (A Pascal sm_60 or Volta sm_70 build is "
"-DSTRATA_EXPERIMENTAL_SM60=ON; upstream does not support it.)")
endif()
if(_base LESS 60)
message(FATAL_ERROR "CMAKE_CUDA_ARCHITECTURES '${CMAKE_CUDA_ARCHITECTURES}': even the experimental build "
"needs compute capability 6.0 (sm_60) or newer")
endif()
# #220 / #224: RTX 50 engines built with CUDA 12.8 crashed in the prompt path on Linux (13.0 and newer did not)
if(_base GREATER_EQUAL 120 AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 13.0)
message(WARNING "Building for sm_${_base} (RTX 50) with CUDA ${CMAKE_CUDA_COMPILER_VERSION}: engines built with "
"CUDA 12.8 crashed on long prompts on these cards (issues #220, #224). Use CUDA 13.0 or newer.")
endif()
endforeach()
message(STATUS "Strata: CUDA enabled, arch ${CMAKE_CUDA_ARCHITECTURES}")
set(_strata_gpu_runtime_target CUDA::cudart)
set(_strata_gpu_blas_target CUDA::cublas)
set(_strata_gpu_include_directories ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
elseif(STRATA_ENABLE_HIP)
set(_strata_gpu_runtime_target strata_hip_runtime)
set(_strata_gpu_blas_target roc::hipblas)
set(_strata_gpu_include_directories ${STRATA_HIP_COMPAT_INCLUDE_DIR})
endif()
# Warnings live on an INTERFACE target the project's own code links, NOT in add_compile_options: applied
@@ -71,11 +130,14 @@ target_compile_options(strata_warnings INTERFACE
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-Wall>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-Wextra>
$<$<AND:$<COMPILE_LANGUAGE:CUDA>,$<CXX_COMPILER_ID:MSVC>>:-Xcompiler=/W4>)
$<$<AND:$<COMPILE_LANGUAGE:CUDA>,$<CXX_COMPILER_ID:MSVC>>:-Xcompiler=/W4>
$<$<COMPILE_LANGUAGE:HIP>:-Wall>
$<$<COMPILE_LANGUAGE:HIP>:-Wextra>)
if(STRATA_WERROR)
target_compile_options(strata_warnings INTERFACE
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/WX>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-Werror>)
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-Werror>
$<$<COMPILE_LANGUAGE:HIP>:-Werror>)
endif()
# ---- strata_artifact -------------------------------------------------------
@@ -93,6 +155,16 @@ add_executable(strata-dequant src/artifact/dequant.cpp)
target_link_libraries(strata-dequant PRIVATE strata_artifact)
# this file is now nothing but the CLI entry point, and its inherited guard spells that as a "selftest"
target_compile_definitions(strata-dequant PRIVATE STRATA_DEQUANT_SELFTEST)
if(STRATA_BUILD_TESTS)
# the reader refuses a duplicate tensor name at open, naming it (a synthetic GGUF: no model, no GPU)
add_executable(gguf_reader_test src/artifact/gguf_reader_test.cpp)
target_link_libraries(gguf_reader_test PRIVATE strata_artifact)
add_test(NAME gguf_reader_test COMMAND gguf_reader_test)
# a split model's shards and their joint view: missing shard, split keys, duplicates, truncation (synthetic)
add_executable(gguf_split_test tests/core/gguf_split_test.cpp)
target_link_libraries(gguf_split_test PRIVATE strata_artifact)
add_test(NAME gguf_split_test COMMAND gguf_split_test)
endif()
# ---- strata_plan ----------------------------------------------------------
# Also header-only. A "planner" that hides state would be one whose decision is not inspectable, and the
@@ -104,17 +176,20 @@ target_link_libraries(strata_plan INTERFACE strata_warnings)
add_executable(strata-plan src/plan/plan_main.cpp)
target_link_libraries(strata-plan PRIVATE strata_plan)
# ---- strata_core (CUDA, Phase 2) ------------------------------------------
# Only built when CUDA is on, so the Phase 1 targets keep working on a machine with no toolkit. The arch
# guard above has already refused anything but sm_120 by the time this runs.
if(STRATA_ENABLE_CUDA)
# ---- strata_core (GPU runtime) ---------------------------------------------
# Only built when a GPU backend is on, so the Phase 1 targets keep working without a toolkit.
if(STRATA_ENABLE_CUDA OR STRATA_ENABLE_HIP)
add_library(strata_core STATIC src/core/device.cu src/core/pinned.cu src/platform/memory.cpp src/core/graph.cpp src/core/weights.cpp
src/core/layout.cpp)
target_include_directories(strata_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# CUDA::cudart is PUBLIC because graph.cpp is a HOST translation unit that calls cudaGraphLaunch and
if(STRATA_ENABLE_CUDA AND STRATA_EXPERIMENTAL_SM60)
# the runtime device check (src/core/device.cu) admits the cards this build was made for (#236)
target_compile_definitions(strata_core PRIVATE STRATA_EXPERIMENTAL_SM60=1)
endif()
# The selected runtime is PUBLIC because graph.cpp is a HOST translation unit that calls cudaGraphLaunch and
# cudaEventQuery: the .cu sources get cudart through the CUDA language rules, a .cpp does not, and the
# linker error ("unresolved external symbol cudaGraphInstantiate") names the function rather than the cause.
target_link_libraries(strata_core PUBLIC strata_plan strata_warnings CUDA::cudart)
target_link_libraries(strata_core PUBLIC strata_plan strata_warnings ${_strata_gpu_runtime_target})
add_executable(strata-device src/core/device_main.cpp)
target_link_libraries(strata-device PRIVATE strata_core)
@@ -123,13 +198,13 @@ if(STRATA_ENABLE_CUDA)
target_link_libraries(strata-load PRIVATE strata_core strata_kernels)
add_executable(strata-concurrent src/core/concurrent_main.cpp)
target_include_directories(strata-concurrent PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(strata-concurrent PRIVATE strata_core strata_kernels CUDA::cudart)
target_include_directories(strata-concurrent PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(strata-concurrent PRIVATE strata_core strata_kernels ${_strata_gpu_runtime_target})
add_executable(strata-overlap src/core/overlap_main.cpp)
# a .cpp that calls cudaMemcpyAsync itself needs the toolkit headers and cudart explicitly
target_include_directories(strata-overlap PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(strata-overlap PRIVATE strata_core strata_kernels CUDA::cudart)
target_include_directories(strata-overlap PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(strata-overlap PRIVATE strata_core strata_kernels ${_strata_gpu_runtime_target})
# The load test needs the 34 GB artifact, so it is registered ONLY where that artifact exists. A test that
# silently skips is a test that stops running; an absent registration is visible in the configure output.
@@ -177,8 +252,12 @@ if(STRATA_ENABLE_CUDA)
src/kernels/cuda/native_qsa_score.cu
src/kernels/cuda/ple.cu
src/kernels/cuda/kv_q8.cu
src/kernels/cuda/kv_stream.cu
src/kernels/cuda/kv_q4.cu
src/kernels/cuda/fused_gr.cu
src/kernels/cuda/cvec.cu
src/kernels/cuda/qsa_decode_attn.cu
src/kernels/cuda/qsa_prompt_attn.cu
src/kernels/cuda/fused_gdn.cu
src/kernels/cuda/dequant_bf16.cu
src/kernels/cuda/qsa_select.cu
@@ -188,33 +267,57 @@ if(STRATA_ENABLE_CUDA)
src/ngram/ple_reader.cpp
src/platform/direct_file.cpp)
target_link_libraries(strata_kernels PUBLIC strata_core strata_artifact)
# perf-review D-1: the tensor-core prompt attention vs the FP32 kernel and FP64 (GPU, synthetic, no model); with
# the tests, or alone with -DSTRATA_PARITY_PROMPT_ATTN=ON. CUDA only: HIP compiles the tensor-core kernel out.
if((STRATA_BUILD_TESTS OR STRATA_PARITY_PROMPT_ATTN) AND STRATA_ENABLE_CUDA)
add_executable(qsa_prompt_attn_parity src/kernels/qsa_prompt_attn_parity.cpp)
target_include_directories(qsa_prompt_attn_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(qsa_prompt_attn_parity PRIVATE strata_kernels CUDA::cudart)
endif()
# the QSA selection (block scores on the warp kernel and the tensor-core one, top-k): a GPU bench with a
# scorer-vs-scorer comparison and an FP64 accuracy gate (synthetic, no model); the ctest runs a 32K context under a
# 262144-cell capacity (the top-k dispatch follows the capacity) and exits non-zero on a failed gate
if((STRATA_BUILD_TESTS OR STRATA_PARITY_PROMPT_ATTN) AND STRATA_ENABLE_HIP)
add_executable(qsa_select_bench src/kernels/qsa_select_bench.cpp)
set_source_files_properties(src/kernels/qsa_select_bench.cpp PROPERTIES LANGUAGE HIP)
target_link_libraries(qsa_select_bench PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME qsa_select_bench COMMAND qsa_select_bench 32768 256 3 262144)
endif()
# plan v0.3 P6: llama.cpp's block layouts and codebook grids for the i-quant formats (MIT, third_party/ggml)
target_include_directories(strata_kernels PRIVATE third_party/ggml)
# Match the pinned llama.cpp CUDA arithmetic contract for this adapter only.
set_source_files_properties(src/kernels/cuda/native_mmvq.cu src/kernels/cuda/native_bf16.cu
src/kernels/cuda/native_gr_norm.cu
src/kernels/cuda/native_moe.cu
src/kernels/cuda/native_gdn.cu
src/kernels/cuda/native_gr_postops.cu
src/kernels/cuda/native_router.cu
src/kernels/cuda/native_gdn_preprocess.cu
src/kernels/cuda/native_qsa.cu
src/kernels/cuda/native_rope.cu
src/kernels/cuda/native_ple_postops.cu
src/kernels/cuda/native_flash_attn.cu
src/kernels/cuda/native_qsa_indexer.cu
src/kernels/cuda/native_qsa_score.cu
PROPERTIES COMPILE_OPTIONS "--use_fast_math")
if(STRATA_ENABLE_CUDA)
set_source_files_properties(src/kernels/cuda/native_mmvq.cu src/kernels/cuda/native_bf16.cu
src/kernels/cuda/native_gr_norm.cu
src/kernels/cuda/native_moe.cu
src/kernels/cuda/native_gdn.cu
src/kernels/cuda/native_gr_postops.cu
src/kernels/cuda/native_router.cu
src/kernels/cuda/native_gdn_preprocess.cu
src/kernels/cuda/native_qsa.cu
src/kernels/cuda/native_rope.cu
src/kernels/cuda/native_ple_postops.cu
src/kernels/cuda/native_flash_attn.cu
src/kernels/cuda/native_qsa_indexer.cu
src/kernels/cuda/native_qsa_score.cu
PROPERTIES COMPILE_OPTIONS "--use_fast_math")
elseif(STRATA_ENABLE_HIP)
# Keep Q8_K's multiply and magic-bias rounding as separate FP32 operations on HIP; contraction to an
# FMA changes tie results. The embedding gather also relies on separately rounded operations.
set_source_files_properties(src/kernels/cuda/elementwise.cu src/kernels/cuda/quantize_act.cu
PROPERTIES COMPILE_OPTIONS "-ffp-contract=off")
endif()
set(STRATA_ORACLE_SOURCE_DIR "" CACHE PATH "Pinned llama.cpp source for independent CUDA parity tests")
set(STRATA_ORACLE_BUILD_DIR "" CACHE PATH "Matching existing shared llama.cpp build")
if(STRATA_ORACLE_SOURCE_DIR AND STRATA_ORACLE_BUILD_DIR)
# This oracle is a CUDA ggml build; do not pull CUDA libraries into a HIP build.
if(STRATA_ENABLE_CUDA AND STRATA_ORACLE_SOURCE_DIR AND STRATA_ORACLE_BUILD_DIR)
foreach(parity IN ITEMS native_mmvq native_bf16 native_shared native_gr_norm native_moe native_gdn native_gr_postops native_router native_gdn_preprocess native_gdn_pipeline native_qsa native_rope native_ple_postops native_flash_attn native_qsa_indexer native_qsa_score)
add_executable(${parity}_parity bench/micro/${parity}_parity.cpp)
target_include_directories(${parity}_parity PRIVATE
"${STRATA_ORACLE_SOURCE_DIR}/ggml/include" ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
"${STRATA_ORACLE_SOURCE_DIR}/ggml/include" ${_strata_gpu_include_directories})
target_compile_definitions(${parity}_parity PRIVATE GGML_SHARED GGML_BACKEND_SHARED)
target_link_libraries(${parity}_parity PRIVATE strata_kernels CUDA::cudart)
target_link_libraries(${parity}_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
foreach(component IN ITEMS ggml ggml-base ggml-cpu ggml-cuda)
string(REPLACE "-" "_" variable "STRATA_ORACLE_${component}")
find_library(${variable} NAMES ${component} PATHS
@@ -235,102 +338,303 @@ if(STRATA_ENABLE_CUDA)
# already links `strata_core` - putting `gdn_layer` in core would make the two mutually dependent. A third
# library is the honest shape of the dependency graph: core knows about devices and weights, kernels know
# about arithmetic, and this one knows the ORDER the arithmetic happens in.
add_library(strata_engine STATIC src/core/layer.cpp src/core/session.cpp src/core/expert_source.cpp
src/core/expert_cache.cpp src/core/native_head.cpp src/core/native_dense.cpp src/core/verify.cpp src/core/mtp.cpp)
add_library(strata_engine STATIC src/core/layer.cpp src/core/session.cpp src/core/expert_source.cpp src/core/remote_experts.cpp
src/core/expert_cache.cpp src/core/native_head.cpp src/core/native_dense.cpp src/core/verify.cpp src/core/mtp.cpp
src/core/conversation_snapshot.cpp src/core/conversation_state.cpp src/core/conversation_memory.cpp)
target_include_directories(strata_engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(strata_engine PUBLIC strata_core strata_kernels strata_kernels_cpu CUDA::cudart)
target_link_libraries(strata_engine PUBLIC strata_core strata_kernels strata_kernels_cpu ${_strata_gpu_runtime_target})
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/core/file_expert_source_test.cpp")
enable_testing()
add_executable(file_expert_source_test tests/core/file_expert_source_test.cpp)
target_link_libraries(file_expert_source_test PRIVATE strata_engine)
add_test(NAME file_expert_source_test COMMAND file_expert_source_test)
set_tests_properties(file_expert_source_test PROPERTIES TIMEOUT 60)
endif()
# #477: the learned expert profile (--expert-profile-save): its order and make_profile.py's file format (CPU only)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/core/expert_profile_save_test.cpp")
enable_testing()
add_executable(expert_profile_save_test tests/core/expert_profile_save_test.cpp)
target_link_libraries(expert_profile_save_test PRIVATE strata_engine)
add_test(NAME expert_profile_save_test COMMAND expert_profile_save_test)
set_tests_properties(expert_profile_save_test PROPERTIES TIMEOUT 60)
endif()
if(STRATA_BUILD_TESTS AND STRATA_NATIVE_EXPERTS)
# native_experts.txt v2-v4 (the real packs' copies and the per-role shard column) and the arena filled from
# synthetic GGUF shards at the real expert dimensions
add_executable(expert_layout_test tests/core/expert_layout_test.cpp)
target_link_libraries(expert_layout_test PRIVATE strata_engine)
add_test(NAME expert_layout_test COMMAND expert_layout_test ${CMAKE_CURRENT_SOURCE_DIR}/tests/data/native_experts)
set_tests_properties(expert_layout_test PROPERTIES TIMEOUT 60)
# #326: a native pack's BF16 (--compat-bf16) PLE key stays in the arena; a quantized key row loads as before
add_executable(native_dense_ple_key_test tests/core/native_dense_ple_key_test.cpp)
target_link_libraries(native_dense_ple_key_test PRIVATE strata_engine)
add_test(NAME native_dense_ple_key_test COMMAND native_dense_ple_key_test)
set_tests_properties(native_dense_ple_key_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60)
# #369: per-layer admission on a sized (native pack) cache starts each layer in its own range
add_executable(expert_cache_per_layer_test tests/core/expert_cache_per_layer_test.cpp)
target_link_libraries(expert_cache_per_layer_test PRIVATE strata_engine)
add_test(NAME expert_cache_per_layer_test COMMAND expert_cache_per_layer_test)
set_tests_properties(expert_cache_per_layer_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60)
endif()
if(STRATA_BUILD_CONVERSATION_TESTS)
add_executable(conversation_snapshot_test src/core/conversation_snapshot_test.cpp)
target_link_libraries(conversation_snapshot_test PRIVATE strata_engine)
add_test(NAME conversation_snapshot_test COMMAND conversation_snapshot_test)
set_tests_properties(conversation_snapshot_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 120)
add_executable(conversation_validation_test src/core/conversation_validation_test.cpp)
target_link_libraries(conversation_validation_test PRIVATE strata_engine)
add_test(NAME conversation_validation_test COMMAND conversation_validation_test)
set_tests_properties(conversation_validation_test PROPERTIES ENVIRONMENT "CUDA_VISIBLE_DEVICES=-1" TIMEOUT 120)
if(NOT STRATA_ENABLE_HIP AND UNIX AND NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_executable(conversation_transfer_test src/core/conversation_validation_test.cpp)
target_compile_definitions(conversation_transfer_test PRIVATE CONVERSATION_TEST_TRANSFERS)
target_link_options(conversation_transfer_test PRIVATE
-Wl,--wrap=cudaMemcpy -Wl,--wrap=cudaDeviceSynchronize -Wl,--wrap=cudaGetLastError)
target_link_libraries(conversation_transfer_test PRIVATE strata_engine)
add_test(NAME conversation_transfer_test COMMAND conversation_transfer_test)
set_tests_properties(conversation_transfer_test PROPERTIES ENVIRONMENT "CUDA_VISIBLE_DEVICES=-1" TIMEOUT 120)
endif()
endif()
# `strata generate` (P2.S6). The FIRST user-facing program: everything under it is a component, and this is
# what composes them into a token. No tokenizer - the prompt arrives as ids - because Gate C1 compares logits
# against llama.cpp on the SAME ids and a tokenizer on one side of that comparison is a second variable.
# plan v0.3 P5: batched prompt processing (tensor-core GEMMs through cuBLAS).
add_library(strata_prefill STATIC src/prefill/gemm.cu src/prefill/kernels.cu src/prefill/prefill.cpp)
target_include_directories(strata_prefill PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(strata_prefill PUBLIC strata_engine CUDA::cublas CUDA::cudart)
target_include_directories(strata_prefill PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${_strata_gpu_include_directories})
target_link_libraries(strata_prefill PUBLIC strata_engine ${_strata_gpu_blas_target} ${_strata_gpu_runtime_target})
if(STRATA_ENABLE_HIP AND STRATA_HIPBLASLT_AVAILABLE)
target_compile_definitions(strata_prefill PRIVATE STRATA_HIPBLASLT_AVAILABLE=1)
target_link_libraries(strata_prefill PRIVATE roc::hipblaslt)
add_executable(tune_hipblaslt tools/hip/tune_hipblaslt.cpp)
target_link_libraries(tune_hipblaslt PRIVATE hip::host roc::hipblas roc::hipblaslt)
endif()
add_executable(strata src/program/generate.cpp)
target_include_directories(strata PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(strata PRIVATE strata_engine strata_prefill CUDA::cudart)
target_include_directories(strata PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(strata PRIVATE strata_engine strata_prefill strata_spec ${_strata_gpu_runtime_target})
if(STRATA_ENABLE_HIP)
enable_testing()
set(_strata_hip_tests)
add_executable(hip_expert_cache_staging tests/hip/expert_cache_staging.cpp)
target_link_libraries(hip_expert_cache_staging PRIVATE strata_engine)
add_test(NAME hip_expert_cache_staging COMMAND hip_expert_cache_staging)
list(APPEND _strata_hip_tests hip_expert_cache_staging)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/hip/intrinsics.cpp")
add_executable(hip_intrinsics tests/hip/intrinsics.cpp)
target_link_libraries(hip_intrinsics PRIVATE strata_hip_runtime)
add_test(NAME hip_intrinsics COMMAND hip_intrinsics)
list(APPEND _strata_hip_tests hip_intrinsics)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/hip/handoff.cpp")
add_executable(hip_handoff tests/hip/handoff.cpp)
target_link_libraries(hip_handoff PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME hip_handoff COMMAND hip_handoff)
list(APPEND _strata_hip_tests hip_handoff)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/hip/mapped_alias.cpp")
# #325: reports what a hipHostGetDevicePointer alias is on this stack (on Windows: the host pointer, and a
# D2D copy into it does not land - hip_handoff's timeout there). A diagnosis: exits 0 whenever it ran.
add_executable(hip_mapped_alias tests/hip/mapped_alias.cpp)
target_link_libraries(hip_mapped_alias PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME hip_mapped_alias COMMAND hip_mapped_alias)
set_tests_properties(hip_mapped_alias PROPERTIES SKIP_RETURN_CODE 77)
list(APPEND _strata_hip_tests hip_mapped_alias)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/hip/native_qsa_score.cpp")
add_executable(hip_native_qsa_score tests/hip/native_qsa_score.cpp)
target_link_libraries(hip_native_qsa_score PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME hip_native_qsa_score COMMAND hip_native_qsa_score)
list(APPEND _strata_hip_tests hip_native_qsa_score)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/hip/prefill_native_batch.cpp")
add_executable(hip_prefill_native_batch tests/hip/prefill_native_batch.cpp)
target_link_libraries(hip_prefill_native_batch PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME hip_prefill_native_batch COMMAND hip_prefill_native_batch)
list(APPEND _strata_hip_tests hip_prefill_native_batch)
endif()
# S6: the AMD fast router against the portable kernel, bitwise
add_executable(hip_router_fast tests/hip/router_fast.cpp)
target_link_libraries(hip_router_fast PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME hip_router_fast COMMAND hip_router_fast)
list(APPEND _strata_hip_tests hip_router_fast)
# S6: the RDNA4 matrix-core prompt attention (STRATA_HIP_WMMA) against the FP32 kernel and FP64; skips off gfx12
add_executable(hip_prompt_attn_wmma src/kernels/qsa_prompt_attn_parity.cpp)
target_link_libraries(hip_prompt_attn_wmma PRIVATE strata_kernels strata_hip_runtime)
add_test(NAME hip_prompt_attn_wmma COMMAND hip_prompt_attn_wmma 32768 2048 3)
set_tests_properties(hip_prompt_attn_wmma PROPERTIES TIMEOUT 600 SKIP_RETURN_CODE 77)
list(APPEND _strata_hip_tests hip_prompt_attn_wmma)
if(STRATA_HIPBLASLT_AVAILABLE AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/hip/prefill_hipblaslt_gemm.cpp")
add_executable(hip_prefill_hipblaslt_gemm tests/hip/prefill_hipblaslt_gemm.cpp)
target_include_directories(hip_prefill_hipblaslt_gemm PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/prefill")
target_link_libraries(hip_prefill_hipblaslt_gemm PRIVATE strata_prefill strata_hip_runtime roc::hipblaslt roc::hipblas)
add_test(NAME hip_prefill_hipblaslt_gemm COMMAND hip_prefill_hipblaslt_gemm)
set_tests_properties(hip_prefill_hipblaslt_gemm PROPERTIES TIMEOUT 120 SKIP_RETURN_CODE 77)
list(APPEND _strata_hip_tests hip_prefill_hipblaslt_gemm)
endif()
if(_strata_hip_tests)
set_tests_properties(${_strata_hip_tests} PROPERTIES TIMEOUT 60)
endif()
if(TARGET hip_prefill_native_batch)
set_tests_properties(hip_prefill_native_batch PROPERTIES TIMEOUT 120)
endif()
if(TARGET hip_prefill_hipblaslt_gemm)
set_tests_properties(hip_prefill_hipblaslt_gemm PROPERTIES TIMEOUT 120 SKIP_RETURN_CODE 77)
endif()
endif()
add_executable(dequant_s2_parity src/kernels/dequant_s2_parity.cpp)
# The tests are HOST C++ files that call cudaMalloc/cudaMemcpy themselves, so they need the toolkit headers
# and cudart explicitly: enabling the CUDA language gives those to .cu sources automatically, but a .cpp
# target is just a C++ target and gets neither.
target_include_directories(dequant_s2_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(dequant_s2_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(dequant_s2_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(dequant_s2_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME dequant_s2_parity COMMAND dequant_s2_parity --selftest)
add_executable(s2_gemv_parity src/kernels/s2_gemv_parity.cpp)
target_include_directories(s2_gemv_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(s2_gemv_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(s2_gemv_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(s2_gemv_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME s2_gemv_parity COMMAND s2_gemv_parity --selftest)
add_executable(iq_parity src/kernels/iq_parity.cpp)
target_include_directories(iq_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(iq_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(iq_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(iq_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
# TODO 24: the i-quant fixtures are generated, not shipped. tools/iq_fixture.py needs python3 + numpy
# and the repository's vendored gguf-py (no pip installs); it writes deterministic fixtures into the
# BUILD directory (the source tree is not touched). The CTest fixture makes generation-to-test order
# explicit even under parallel execution; a fixture-generation failure fails iq_parity with it.
find_package(Python3 COMPONENTS Interpreter QUIET)
# the generator needs an interpreter WITH numpy: probe the configured one, the setup's .venv, and the
# system python3; -DIQ_FIXTURE_PYTHON=... overrides the search outright.
set(_iq_python "")
set(_iq_candidates "")
if(IQ_FIXTURE_PYTHON)
list(APPEND _iq_candidates ${IQ_FIXTURE_PYTHON})
else()
if(Python3_Interpreter_FOUND)
list(APPEND _iq_candidates ${Python3_EXECUTABLE})
endif()
list(APPEND _iq_candidates "${CMAKE_CURRENT_SOURCE_DIR}/.venv/bin/python"
"${CMAKE_CURRENT_SOURCE_DIR}/.venv/Scripts/python.exe"
/usr/bin/python3
python3)
endif()
foreach(_cand IN LISTS _iq_candidates)
if(_cand STREQUAL "")
continue()
endif()
execute_process(COMMAND ${_cand} -c "import numpy" OUTPUT_QUIET ERROR_QUIET RESULT_VARIABLE _iq_probe)
if(_iq_probe EQUAL 0)
set(_iq_python "${_cand}")
break()
endif()
endforeach()
if(NOT _iq_python STREQUAL "")
add_test(NAME iq_parity_fixtures
COMMAND ${_iq_python} ${CMAKE_CURRENT_SOURCE_DIR}/tools/iq_fixture.py
--out ${CMAKE_CURRENT_BINARY_DIR}/iq_fixture)
# exit 3: numpy or the vendored gguf-py is missing (a trimmed third_party) - a skip, not a failure
set_tests_properties(iq_parity_fixtures PROPERTIES FIXTURES_SETUP iq_fixture_data SKIP_RETURN_CODE 3)
add_test(NAME iq_parity COMMAND iq_parity ${CMAKE_CURRENT_BINARY_DIR}/iq_fixture)
set_tests_properties(iq_parity PROPERTIES FIXTURES_REQUIRED iq_fixture_data SKIP_RETURN_CODE 3)
else()
message(STATUS "STRATA tests: no python3 with numpy was found (the configured interpreter and .venv "
"were probed) - the iq_parity fixtures and its test are not registered; the fixtures "
"need python3 with numpy and the repository's vendored gguf-py")
endif()
if(STRATA_ENABLE_CUDA)
# native_mmvq's multi_exact contract, bitwise: a T-column call against T single-column calls on the same Q8_1
# bytes, with the flag off as the negative control (synthetic, GPU, no model; the control assumes 32-lane warps)
add_executable(mmvq_multi_parity src/kernels/mmvq_multi_parity.cpp)
target_include_directories(mmvq_multi_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(mmvq_multi_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME mmvq_multi_parity COMMAND mmvq_multi_parity)
endif()
# the IQ kernels that decode a weight part once for every column/entry, bitwise vs the per-column ones
# (GPU, synthetic, no model; --bench adds old/new timings)
add_executable(iq_multi_parity src/kernels/iq_multi_parity.cpp)
target_include_directories(iq_multi_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(iq_multi_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME iq_multi_parity COMMAND iq_multi_parity)
add_executable(shared_expert_parity src/kernels/shared_expert_parity.cpp)
target_include_directories(shared_expert_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(shared_expert_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(shared_expert_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(shared_expert_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME shared_expert_parity COMMAND shared_expert_parity --selftest)
add_executable(gr_parity src/kernels/gr_parity.cpp)
target_include_directories(gr_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(gr_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(gr_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(gr_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME gr_parity COMMAND gr_parity --selftest)
add_executable(gdn_parity src/kernels/gdn_parity.cpp)
target_include_directories(gdn_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(gdn_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(gdn_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(gdn_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME gdn_parity COMMAND gdn_parity --selftest)
add_executable(s2_gemv_q8_parity src/kernels/s2_gemv_q8_parity.cpp)
target_include_directories(s2_gemv_q8_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(s2_gemv_q8_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(s2_gemv_q8_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(s2_gemv_q8_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME s2_gemv_q8_parity COMMAND s2_gemv_q8_parity --selftest)
add_executable(sampler_parity src/kernels/sampler_parity.cpp)
target_include_directories(sampler_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(sampler_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(sampler_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(sampler_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME sampler_parity COMMAND sampler_parity --selftest)
set_tests_properties(sampler_parity PROPERTIES ENVIRONMENT "STRATA_OLD_SAMPLER=0;STRATA_SAMPLER_ONE_BLOCK=0")
# the same fixtures on the other two sampled paths: the one-block kernel and the 0.1.20 kernel
add_test(NAME sampler_parity_one_block COMMAND sampler_parity --selftest)
set_tests_properties(sampler_parity_one_block PROPERTIES ENVIRONMENT "STRATA_OLD_SAMPLER=0;STRATA_SAMPLER_ONE_BLOCK=1")
add_test(NAME sampler_parity_old COMMAND sampler_parity --selftest)
set_tests_properties(sampler_parity_old PROPERTIES ENVIRONMENT "STRATA_OLD_SAMPLER=1;STRATA_SAMPLER_ONE_BLOCK=0")
# S19: the decode QSA top-k and the greedy argmax on thread-block clusters (sm_90+) vs the one-CTA kernels, bitwise,
# captured in a graph too (GPU, synthetic, no model; skips below sm_90); --bench: per-call times
if(STRATA_ENABLE_CUDA)
add_executable(decode_cluster_parity src/kernels/decode_cluster_parity.cpp)
target_include_directories(decode_cluster_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(decode_cluster_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME decode_cluster_parity COMMAND decode_cluster_parity --selftest)
endif()
add_executable(rope_parity src/kernels/rope_parity.cpp)
target_include_directories(rope_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(rope_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(rope_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(rope_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME rope_parity COMMAND rope_parity --selftest)
add_executable(quantize_act_parity src/kernels/quantize_act_parity.cpp)
target_include_directories(quantize_act_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(quantize_act_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(quantize_act_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(quantize_act_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME quantize_act_parity COMMAND quantize_act_parity --selftest)
add_executable(router_top10_parity src/kernels/router_top10_parity.cpp)
target_include_directories(router_top10_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(router_top10_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(router_top10_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(router_top10_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME router_top10_parity COMMAND router_top10_parity --selftest)
add_executable(s_gemv_parity src/kernels/s_gemv_parity.cpp)
target_include_directories(s_gemv_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(s_gemv_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(s_gemv_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(s_gemv_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME s_gemv_parity COMMAND s_gemv_parity --selftest)
add_executable(elementwise_parity src/kernels/elementwise_parity.cpp)
target_include_directories(elementwise_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(elementwise_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(elementwise_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(elementwise_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME elementwise_parity COMMAND elementwise_parity --selftest)
add_executable(bf16_gemv_parity src/kernels/bf16_gemv_parity.cpp)
target_include_directories(bf16_gemv_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(bf16_gemv_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(bf16_gemv_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(bf16_gemv_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME bf16_gemv_parity COMMAND bf16_gemv_parity --selftest)
add_executable(s_gemv_q8k_parity src/kernels/s_gemv_q8k_parity.cpp)
target_include_directories(s_gemv_q8k_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(s_gemv_q8k_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(s_gemv_q8k_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(s_gemv_q8k_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME s_gemv_q8k_parity COMMAND s_gemv_q8k_parity --selftest)
add_executable(qsa_parity src/kernels/qsa_parity.cpp)
target_include_directories(qsa_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(qsa_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(qsa_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(qsa_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME qsa_parity COMMAND qsa_parity --selftest)
# ---- P2.S4: the PLE n-gram hash, the IQ4_NL table read, and the PLE block ----
@@ -342,8 +646,8 @@ if(STRATA_ENABLE_CUDA)
# INSIDE the test. A test that passes by finding nothing to compare is the failure mode this project keeps
# re-learning.
add_executable(ple_parity src/kernels/ple_parity.cpp)
target_include_directories(ple_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(ple_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(ple_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(ple_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
set(STRATA_PLE_FIXTURE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/bench/micro" CACHE PATH
"Directory containing required ple_in.bin and ple_out.bin parity fixtures")
add_test(NAME ple_parity COMMAND ple_parity --selftest
@@ -352,57 +656,99 @@ if(STRATA_ENABLE_CUDA)
# Plan v0.3 P7: INT8 KV storage vs a host reference and vs the FP16 path (GPU, synthetic, no model).
add_executable(kv_q8_parity src/kernels/kv_q8_parity.cpp)
target_include_directories(kv_q8_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(kv_q8_parity PRIVATE strata_kernels CUDA::cudart)
target_include_directories(kv_q8_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(kv_q8_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME kv_q8_parity COMMAND kv_q8_parity)
# KV streaming (docs/kv-streaming-design.md): streamed vs resident attention, bitwise (GPU, synthetic, no model).
add_executable(kv_stream_parity src/kernels/kv_stream_parity.cpp)
target_include_directories(kv_stream_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(kv_stream_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME kv_stream_parity COMMAND kv_stream_parity)
# PR #120: the hybrid K8V4 KV (INT8 K + rotated Q4_0 V) vs host references (GPU, synthetic, no model).
add_executable(kv_hybrid_parity src/kernels/kv_hybrid_parity.cpp)
target_include_directories(kv_hybrid_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(kv_hybrid_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME kv_hybrid_parity COMMAND kv_hybrid_parity)
# PR #21: Q4_0 KV with Walsh-Hadamard rotation vs a host reference (GPU, synthetic, no model).
add_executable(kv_q4_parity src/kernels/kv_q4_parity.cpp)
target_include_directories(kv_q4_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(kv_q4_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME kv_q4_parity COMMAND kv_q4_parity)
add_executable(cvec_parity src/kernels/cvec_parity.cpp)
target_include_directories(cvec_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(cvec_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME cvec_parity COMMAND cvec_parity)
# The rewritten per-hit and grouped expert kernels vs the previous ones, bitwise (GPU, synthetic, no model).
# `--bench` adds previous/new timings on engine-like shapes.
add_executable(s2_expert_grouped_parity src/kernels/s2_expert_grouped_parity.cpp)
target_include_directories(s2_expert_grouped_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(s2_expert_grouped_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME s2_expert_grouped_parity COMMAND s2_expert_grouped_parity --selftest)
# Plan v0.3 P3: native MMVQ for 1..8 tokens (bitwise selftest + --bench timing); synthetic data, no model.
if(STRATA_BUILD_TESTS)
add_executable(native_mmvq_multi bench/micro/native_mmvq_multi.cpp)
target_include_directories(native_mmvq_multi PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(native_mmvq_multi PRIVATE strata_kernels CUDA::cudart)
add_test(NAME native_mmvq_multi COMMAND native_mmvq_multi --selftest)
if(STRATA_BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/bench/micro/native_mmvq_multi.cpp")
add_executable(native_mmvq_multi bench/micro/native_mmvq_multi.cpp)
target_include_directories(native_mmvq_multi PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(native_mmvq_multi PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME native_mmvq_multi COMMAND native_mmvq_multi --selftest)
endif()
# Plan v0.3 P0.1: working-set lock of a host region (CPU only).
add_executable(platform_memory_test src/platform/memory_test.cpp)
target_link_libraries(platform_memory_test PRIVATE strata_core CUDA::cudart)
target_link_libraries(platform_memory_test PRIVATE strata_core ${_strata_gpu_runtime_target})
add_test(NAME platform_memory_test COMMAND platform_memory_test)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_executable(pinned_shared_test src/core/pinned_shared_test.cpp)
target_link_libraries(pinned_shared_test PRIVATE strata_core ${_strata_gpu_runtime_target})
add_test(NAME pinned_shared_test COMMAND pinned_shared_test)
endif()
# Plan v0.3 P2: the n-gram table read straight from the SSD. The selftest needs only the CPU and a temp file.
add_executable(ple_reader_test src/ngram/ple_reader_test.cpp)
target_include_directories(ple_reader_test PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(ple_reader_test PRIVATE strata_kernels CUDA::cudart)
target_include_directories(ple_reader_test PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(ple_reader_test PRIVATE strata_kernels ${_strata_gpu_runtime_target})
add_test(NAME ple_reader_selftest COMMAND ple_reader_test --selftest)
# The GPU test is registered only when CUDA is on, and it runs ON THE TARGET PC. `--selftest` allocates
# The GPU test runs ON THE TARGET PC. `--selftest` allocates
# and writes real VRAM, so a machine without a device fails this test rather than skipping it silently -
# which is correct: an engine that targets one GPU should not report a pass for having no GPU.
add_test(NAME cuda_device_selftest COMMAND strata-device --selftest)
if(STRATA_ENABLE_CUDA)
add_test(NAME cuda_device_selftest COMMAND strata-device --selftest)
else()
add_test(NAME hip_device_selftest COMMAND strata-device --selftest)
endif()
# ---- P2.S5: the GraphRegistry --------------------------------------------------
# A .cu because the test needs real kernels to capture; it links `strata_core` and not `strata_kernels`,
# because what it exercises is the capture machinery and not any kernel in it.
if(STRATA_BUILD_TESTS)
add_executable(graph_registry_test bench/micro/graph_registry_test.cu)
target_include_directories(graph_registry_test PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(graph_registry_test PRIVATE strata_core CUDA::cudart)
add_test(NAME graph_registry_test COMMAND graph_registry_test)
if(STRATA_BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/bench/micro/graph_registry_test.cu")
add_executable(graph_registry_test bench/micro/graph_registry_test.cu)
target_include_directories(graph_registry_test PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(graph_registry_test PRIVATE strata_core ${_strata_gpu_runtime_target})
add_test(NAME graph_registry_test COMMAND graph_registry_test)
endif()
# ---- P2.S5's GATE: is a device<->pinned-memory handoff graph-capturable? --------------------
# The doorbell protocol puts the D2H of `x_f` and the miss list INSIDE G[l] - that is the whole overlap. The
# phase doc says to verify this "before building on it", and this project has already lost two rounds to
# building first (a memory-only spin that DEADLOCKS on Windows, and a `cudaEventRecord` inside a capture that
# is SILENTLY DROPPED). It links only cudart: it is about the capture machinery, not about any kernel.
add_executable(pinned_capture bench/micro/pinned_capture.cu)
target_include_directories(pinned_capture PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(pinned_capture PRIVATE CUDA::cudart)
add_test(NAME pinned_capture COMMAND pinned_capture)
if(STRATA_ENABLE_CUDA AND STRATA_BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/bench/micro/pinned_capture.cu")
add_executable(pinned_capture bench/micro/pinned_capture.cu)
target_include_directories(pinned_capture PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(pinned_capture PRIVATE ${_strata_gpu_runtime_target})
add_test(NAME pinned_capture COMMAND pinned_capture)
endif()
# Tells tests/CMakeLists.txt to add the CUDA-dependent Catch2 target. It cannot be declared there
# unconditionally: tests/ is added at the end of this file, but the flag has to be known before it runs.
set(STRATA_CUDA_TESTS ON)
if(STRATA_ENABLE_CUDA)
set(STRATA_CUDA_TESTS ON)
endif()
endif()
# ---- strata_kernels_cpu (P2.S3) ----
@@ -417,7 +763,7 @@ endif()
# worse opt-out. `cpu_require_expert_support()` therefore refuses at startup on a CPU without the
# features, and the fallback exists for tests, exactly as P2.S3 specifies.
# Plan v0.3 P6: speculation components that need no GPU.
add_library(strata_spec STATIC src/spec/suffix_drafter.cpp src/spec/controller.cpp)
add_library(strata_spec STATIC src/spec/suffix_drafter.cpp src/spec/controller.cpp src/spec/draft_policy.cpp)
target_include_directories(strata_spec PUBLIC include)
if(STRATA_BUILD_TESTS)
add_executable(suffix_drafter_test src/spec/suffix_drafter_test.cpp)
@@ -426,6 +772,23 @@ if(STRATA_BUILD_TESTS)
add_executable(controller_test src/spec/controller_test.cpp)
target_link_libraries(controller_test PRIVATE strata_spec)
add_test(NAME controller_test COMMAND controller_test)
add_executable(draft_policy_test src/spec/draft_policy_test.cpp)
target_link_libraries(draft_policy_test PRIVATE strata_spec)
add_test(NAME draft_policy_test COMMAND draft_policy_test)
endif()
if(STRATA_BUILD_TESTS OR STRATA_BUILD_CONVERSATION_TESTS)
# the serve loop's conversation-cache retention policy (header-only, no library to link)
add_executable(conv_cache_test src/program/conv_cache_test.cpp)
target_include_directories(conv_cache_test PRIVATE include)
add_test(NAME conv_cache_test COMMAND conv_cache_test)
# coupled draft sampling's counter and penalty-ring arithmetic (header-only, CPU)
add_executable(coupled_draft_test src/core/coupled_draft_test.cpp)
target_include_directories(coupled_draft_test PRIVATE include)
add_test(NAME coupled_draft_test COMMAND coupled_draft_test)
# the f32 -> bf16 rounding against ggml's, NaN included, over every f32 bit pattern (header-only, CPU)
add_executable(bf16_bits_test src/kernels/bf16_bits_test.cpp)
target_include_directories(bf16_bits_test PRIVATE include)
add_test(NAME bf16_bits_test COMMAND bf16_bits_test)
endif()
add_library(strata_kernels_cpu STATIC src/kernels/cpu/expert.cpp src/kernels/cpu/pool.cpp
@@ -452,6 +815,12 @@ target_link_libraries(strata_kernels_cpu PUBLIC Threads::Threads)
# with -DSTRATA_GGML_DIR=..., otherwise the pinned upstream commit (third_party/ggml/VERSION.txt).
option(STRATA_NATIVE_EXPERTS "Native (IQ) expert support through ggml-cpu" ON)
set(STRATA_GGML_DIR "" CACHE PATH "llama.cpp checkout to take ggml from (empty: fetch the pinned commit)")
if(STRATA_PREFILL_MMQ AND NOT STRATA_NATIVE_EXPERTS)
message(FATAL_ERROR "STRATA_PREFILL_MMQ requires STRATA_NATIVE_EXPERTS=ON for GGML sources")
endif()
if(STRATA_ORCA_Q4KS_MMQ AND NOT STRATA_NATIVE_EXPERTS)
message(FATAL_ERROR "STRATA_ORCA_Q4KS_MMQ requires STRATA_NATIVE_EXPERTS=ON for GGML sources")
endif()
if(STRATA_NATIVE_EXPERTS)
enable_language(C)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
@@ -485,34 +854,175 @@ if(STRATA_NATIVE_EXPERTS)
endif()
# part of strata_kernels_cpu (the pool calls these and they call its CPU probe: one library, so GNU ld's
# one-pass static linking has no cycle to resolve)
target_sources(strata_kernels_cpu PRIVATE src/kernels/cpu/native_expert.cpp src/kernels/cpu/iq_avx512.cpp)
target_sources(strata_kernels_cpu PRIVATE src/kernels/cpu/native_expert.cpp src/kernels/cpu/iq_avx512.cpp
src/kernels/cpu/iq_avx2.cpp src/kernels/cpu/kq_avx2.cpp)
target_include_directories(strata_kernels_cpu PRIVATE third_party/ggml)
if(MSVC)
set_source_files_properties(src/kernels/cpu/iq_avx512.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX512")
set_source_files_properties(src/kernels/cpu/iq_avx2.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX2")
set_source_files_properties(src/kernels/cpu/kq_avx2.cpp PROPERTIES COMPILE_OPTIONS "/arch:AVX2")
else()
set_source_files_properties(src/kernels/cpu/iq_avx512.cpp PROPERTIES COMPILE_OPTIONS
"-mavx512f;-mavx512bw;-mavx512vl;-mavx512dq;-mavx512vnni;-mavx512vbmi;-mf16c;-mfma")
set_source_files_properties(src/kernels/cpu/iq_avx2.cpp PROPERTIES COMPILE_OPTIONS "-mavx2;-mfma;-mf16c")
set_source_files_properties(src/kernels/cpu/kq_avx2.cpp PROPERTIES COMPILE_OPTIONS "-mavx2;-mfma;-mf16c")
endif()
target_link_libraries(strata_kernels_cpu PUBLIC ggml-cpu ggml-base)
target_compile_definitions(strata_kernels_cpu PUBLIC STRATA_NATIVE_EXPERTS=1)
set(_strata_mmq_types q2_0 iq2_xxs iq2_xs iq2_s iq3_xxs iq3_s iq4_nl iq4_xs q8_0)
# #296: OrcaRouter Q4_K_S's Q5_0 experts (Q4_K and Q5_1 come with STRATA_MMQ_KQUANTS on CUDA, S5's branch)
if(STRATA_ORCA_Q4KS_MMQ)
list(APPEND _strata_mmq_types q5_0)
endif()
if(STRATA_ENABLE_CUDA)
# prompt-speed plan step 2b: llama.cpp's MMQ kernels (ggml-cuda, MIT) for the prompt path's experts, compiled
# from the same checkout as ggml above - only mmq.cuh's instances for the packs' types and the quantizer, with
# the few host symbols of ggml-cuda.cu they use in src/prefill/ggml_cuda_host.cu
if(STRATA_GGML_DIR)
set(_strata_gg ${STRATA_GGML_DIR}/ggml)
else()
set(_strata_gg ${strata_llamacpp_SOURCE_DIR}/ggml)
endif()
set(_strata_mmq ${_strata_gg}/src/ggml-cuda)
# #136: the Q2_0 pack's prompt experts on Strata's own int8 kernels (moe_fused.cu, opt-in STRATA_PF_FUSED=1; CUDA
# only: the HIP branch below keeps MMQ, and an sm_75 device or an sm_75-only build reports them unavailable)
add_library(strata_mmq STATIC src/prefill/moe_mmq.cu src/prefill/moe_fused.cu src/prefill/moe_fused_iq.cu
src/prefill/ggml_cuda_host.cu ${_strata_mmq}/quantize.cu)
# q8_0: the drafter's matrices (E-9); STRATA_ORCA_Q4KS_MMQ adds q5_0 (#296, in _strata_mmq_types), and
# STRATA_MMQ_KQUANTS q4_k q5_k q5_1, the experts of Unsloth's UD-Q4_K_XL (docs/UNSLOTH_Q4.md; the dispatch
# follows eddoursul/Strata 7706c1b)
set(_strata_mmq_cuda_types ${_strata_mmq_types})
if(STRATA_MMQ_KQUANTS)
list(APPEND _strata_mmq_cuda_types q4_k q5_k q5_1)
endif()
foreach(_t IN LISTS _strata_mmq_cuda_types)
target_sources(strata_mmq PRIVATE ${_strata_mmq}/template-instances/mmq-instance-${_t}.cu)
endforeach()
target_include_directories(strata_mmq PRIVATE ${_strata_gg}/include ${_strata_gg}/src ${_strata_mmq}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_options(strata_mmq PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda -use_fast_math>)
if(MSVC)
target_compile_options(strata_mmq PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=/bigobj>)
endif()
target_link_libraries(strata_mmq PUBLIC ggml-base ${_strata_gpu_runtime_target})
if(STRATA_MMQ_KQUANTS)
target_compile_definitions(strata_mmq PRIVATE STRATA_MMQ_KQUANTS=1)
endif()
if(STRATA_ORCA_Q4KS_MMQ)
target_compile_definitions(strata_mmq PRIVATE STRATA_ORCA_Q4KS_MMQ=1)
endif()
target_link_libraries(strata_prefill PUBLIC strata_mmq)
target_compile_definitions(strata_prefill PUBLIC STRATA_PREFILL_MMQ=1 STRATA_PREFILL_FUSED=1)
if(STRATA_BUILD_TESTS)
# #136: the fused int8 experts against a double-precision reference and the MMQ path (exit 77 below sm_80)
add_executable(prefill_fused_moe_test tests/cuda/prefill_fused_moe_test.cpp)
target_link_libraries(prefill_fused_moe_test PRIVATE strata_mmq ggml-base ${_strata_gpu_runtime_target})
add_test(NAME prefill_fused_moe_test COMMAND prefill_fused_moe_test)
set_tests_properties(prefill_fused_moe_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 300)
# #136: the same for the native packs' formats (IQ2_XXS IQ2_XS IQ2_S IQ3_XXS IQ3_S IQ4_XS, down Q2_0 IQ4_NL)
add_executable(prefill_fused_iq_test tests/cuda/prefill_fused_iq_test.cpp)
target_link_libraries(prefill_fused_iq_test PRIVATE strata_mmq ggml-base ${_strata_gpu_runtime_target})
add_test(NAME prefill_fused_iq_test COMMAND prefill_fused_iq_test)
set_tests_properties(prefill_fused_iq_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 600)
endif()
if(STRATA_BUILD_TESTS AND STRATA_MMQ_KQUANTS)
# the UD-Q4_K_XL expert formats through MMQ against ggml's dequantized weights (synthetic, a few seconds)
add_executable(prefill_mmq_kquant_test tests/cuda/prefill_mmq_kquant_test.cpp)
target_link_libraries(prefill_mmq_kquant_test PRIVATE strata_mmq ggml-base ${_strata_gpu_runtime_target})
add_test(NAME prefill_mmq_kquant_test COMMAND prefill_mmq_kquant_test)
set_tests_properties(prefill_mmq_kquant_test PROPERTIES TIMEOUT 120)
endif()
elseif(STRATA_PREFILL_MMQ)
# Compile only the pinned GGML MMQ and activation-quantizer sources used by prefill. This uses
# common.cuh's native HIP path without enabling a second GGML HIP backend.
if(STRATA_GGML_DIR)
set(_strata_gg "${STRATA_GGML_DIR}/ggml")
else()
set(_strata_gg "${strata_llamacpp_SOURCE_DIR}/ggml")
endif()
set(_strata_mmq "${_strata_gg}/src/ggml-cuda")
if(NOT EXISTS "${_strata_mmq}/common.cuh" OR NOT EXISTS "${_strata_mmq}/quantize.cu")
message(FATAL_ERROR "The selected GGML source is missing the ggml-cuda MMQ sources: ${_strata_mmq}")
endif()
set(_strata_mmq_sources
src/prefill/moe_mmq.cu
src/prefill/ggml_cuda_host.cu
"${_strata_mmq}/quantize.cu")
foreach(_t IN LISTS _strata_mmq_types)
list(APPEND _strata_mmq_sources "${_strata_mmq}/template-instances/mmq-instance-${_t}.cu")
endforeach()
set_source_files_properties(${_strata_mmq_sources} PROPERTIES LANGUAGE HIP)
add_library(strata_mmq STATIC ${_strata_mmq_sources})
target_include_directories(strata_mmq
PRIVATE "${_strata_gg}/include" "${_strata_gg}/src" "${_strata_mmq}"
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_definitions(strata_mmq PRIVATE GGML_USE_HIP=1)
if(STRATA_ORCA_Q4KS_MMQ)
target_compile_definitions(strata_mmq PRIVATE STRATA_ORCA_Q4KS_MMQ=1)
endif()
target_link_libraries(strata_mmq PUBLIC ggml-base hip::host roc::hipblas)
target_link_libraries(strata_prefill PUBLIC strata_mmq)
target_compile_definitions(strata_prefill PUBLIC STRATA_PREFILL_MMQ=1)
# Numerical diagnostic: synthetic layouts by default, optional first-expert IQ3_XXS/IQ4_NL check with a pack.
add_executable(hip_prefill_mmq_parity tests/hip/prefill_mmq_parity.cpp)
target_include_directories(hip_prefill_mmq_parity PRIVATE "${_strata_gg}/include")
target_link_libraries(hip_prefill_mmq_parity PRIVATE strata_prefill)
add_test(NAME hip_prefill_mmq_parity COMMAND hip_prefill_mmq_parity)
set_tests_properties(hip_prefill_mmq_parity PROPERTIES TIMEOUT 120)
list(APPEND _strata_hip_tests hip_prefill_mmq_parity)
endif()
if(STRATA_ENABLE_HIP)
# Optional real-model diagnostic: pass the GGUF shard containing the native PLE key.
add_executable(hip_ple_iq4 tests/hip/ple_iq4.cpp)
target_link_libraries(hip_ple_iq4 PRIVATE strata_kernels strata_hip_runtime ggml-base)
endif()
if(STRATA_ENABLE_CUDA OR STRATA_ENABLE_HIP)
add_executable(native_expert_parity src/kernels/native_expert_parity.cpp)
target_include_directories(native_expert_parity PRIVATE ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(native_expert_parity PRIVATE strata_kernels strata_kernels_cpu strata_artifact CUDA::cudart)
target_include_directories(native_expert_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(native_expert_parity PRIVATE strata_kernels strata_kernels_cpu strata_artifact ${_strata_gpu_runtime_target})
if(STRATA_BUILD_TESTS AND STRATA_ENABLE_CUDA)
# Unsloth UD-Q4_K_XL's three expert pairs (and Q8_0 alone, its embedding) on synthetic ggml-quantized weights
# at 2560/640: GPU and CPU experts against the float reference, GPU dequantizers against to_float
foreach(_pair q4_K/q5_1 q4_K/q8_0 q5_K/q8_0 q8_0/q8_0)
string(REPLACE "/" "_" _name "${_pair}")
add_test(NAME native_expert_parity_${_name} COMMAND native_expert_parity --synthetic ${_pair})
set_tests_properties(native_expert_parity_${_name} PROPERTIES TIMEOUT 120)
endforeach()
add_test(NAME native_expert_parity_q5_1_min COMMAND native_expert_parity --q5_1-min)
# #290: the BF16 token embedding (--embd-gguf) gathered and dequantized bit for bit
add_test(NAME native_expert_parity_bf16_embd COMMAND native_expert_parity --bf16-embd)
# a pair without GPU kernels (Q6_K gate/up) is refused by native_expert_supported, not run
add_test(NAME native_expert_parity_refuses_q6_K COMMAND native_expert_parity --synthetic q6_K/q8_0)
set_tests_properties(native_expert_parity_refuses_q6_K PROPERTIES WILL_FAIL TRUE)
endif()
endif()
# #296's real-weight checks (OrcaRouter Q4_K_S files; docs/ORCA_Q4_K_S.md) - test programs, not part of a release
if(STRATA_ENABLE_CUDA AND STRATA_BUILD_TESTS)
add_executable(ple_q5_parity src/kernels/ple_q5_parity.cpp)
target_link_libraries(ple_q5_parity PRIVATE strata_kernels strata_artifact ggml-base ${_strata_gpu_runtime_target})
add_executable(q5_projection_parity src/kernels/q5_projection_parity.cpp)
target_include_directories(q5_projection_parity PRIVATE ${_strata_gpu_include_directories})
target_link_libraries(q5_projection_parity PRIVATE strata_kernels strata_artifact ggml-base ${_strata_gpu_runtime_target})
# #291: the FP8 PLE table (tools/ple_fp8_pack.py) through PleTable vs the checkpoint's own values
add_executable(ple_fp8_parity src/kernels/ple_fp8_parity.cpp)
target_link_libraries(ple_fp8_parity PRIVATE strata_kernels ${_strata_gpu_runtime_target})
endif()
endif()
if(STRATA_ENABLE_CUDA AND STRATA_BUILD_TESTS)
if(STRATA_ENABLE_CUDA AND STRATA_BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/bench/micro/hit_cpu_order_parity.cu")
add_executable(hit_cpu_order_parity bench/micro/hit_cpu_order_parity.cu)
target_link_libraries(hit_cpu_order_parity PRIVATE strata_kernels strata_kernels_cpu CUDA::cudart)
target_link_libraries(hit_cpu_order_parity PRIVATE strata_kernels strata_kernels_cpu ${_strata_gpu_runtime_target})
endif()
if(STRATA_BUILD_TESTS)
# Plan v0.3 P6: one expert applied to several tokens, bitwise vs the single-token kernel (synthetic, CPU only).
# plan v0.3 P5: the device dequantizers vs the artifact's CPU dequantizers on the real shards (not a ctest: needs
# the 38 GB model; run it by hand or from tools/gpu_session.py)
add_executable(dequant_bf16_test src/kernels/dequant_bf16_test.cpp)
target_link_libraries(dequant_bf16_test PRIVATE strata_kernels strata_artifact CUDA::cudart)
if(STRATA_ENABLE_CUDA OR STRATA_ENABLE_HIP) # #93: it needs the device dequantizers and a GPU runtime
add_executable(dequant_bf16_test src/kernels/dequant_bf16_test.cpp)
target_link_libraries(dequant_bf16_test PRIVATE strata_kernels strata_artifact ${_strata_gpu_runtime_target})
endif()
add_executable(expert_multi_test src/kernels/cpu/expert_multi_test.cpp)
target_link_libraries(expert_multi_test PRIVATE strata_kernels_cpu)
@@ -528,9 +1038,23 @@ if(STRATA_BUILD_TESTS)
add_executable(pool_test src/kernels/cpu/pool_test.cpp)
target_link_libraries(pool_test PRIVATE strata_kernels_cpu)
add_test(NAME pool_test COMMAND pool_test --selftest WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
# issue #29: tiny batches with sleeping workers, as on a big-VRAM card; a hang fails it
add_executable(pool_stress src/kernels/cpu/pool_stress.cpp)
target_link_libraries(pool_stress PRIVATE strata_kernels_cpu)
add_test(NAME pool_stress COMMAND pool_stress 20)
if(UNIX AND NOT APPLE)
add_executable(direct_file_async_test tests/platform/direct_file_async.cpp src/platform/direct_file.cpp)
target_include_directories(direct_file_async_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(direct_file_async_test PRIVATE Threads::Threads)
add_test(NAME direct_file_async_test COMMAND direct_file_async_test)
endif()
endif()
if(STRATA_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
# Model-level checks may be absent from source distributions.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt")
add_subdirectory(tests)
endif()
endif()
+115
View File
@@ -0,0 +1,115 @@
# syntax=docker/dockerfile:1
#
# Strata: Qwen3.8-Flash-Next on NVIDIA GPUs (RTX 30/40/50, 12+ GB VRAM; two or
# three cards can share one model, 8 GB each - docs/MULTI_GPU.md).
#
# The engine is compiled during docker build, so the first container start only
# downloads the model (~70 GB) and starts the server. docker build has no GPU,
# so the CUDA architectures are fixed here instead of read from nvidia-smi: the
# engine is a fat binary with a cubin per listed arch, and the runtime picks the
# one matching your card. Narrow CUDA_ARCHITECTURES to your card for a faster
# build; a card outside the set needs a rebuild with its own arch.
#
# Build:
# docker build -t strata .
# docker build -t strata --build-arg CUDA_ARCHITECTURES=89 . # RTX 40 only
#
# Run (host needs an NVIDIA driver >= 580 and nvidia-container-toolkit):
# docker run --rm --gpus all \
# -p 8080:8080 \
# --ulimit memlock=-1 \
# -v strata-data:/data \
# -e MODEL=IQ2_XS \
# strata
#
# Setup choices are env vars, read by docker-entrypoint.sh: FAMILY, MODEL, CONTEXT,
# VISION (no | yes | cpu), KV (int8 | q4_0 | k8v4), GPU (one card) or GPUS ("0,2"
# or "all", with LAYER_SPLIT), LOW_RAM (auto | on | off), HOST, PORT, API_KEY.
#
# Only the model files, the prepared pack, the MTP layer and the install config
# live in the /data volume; the engine is part of the image. Strata loads 32-62 GB
# into RAM, so a capped container needs -e LOW_RAM=on: setup.py reads the RAM from
# /proc/meminfo, which here is the host's total, not the container's limit. Add an
# API key before exposing the port to a network: -e API_KEY=<secret>. Pass
# -e REINSTALL=1 to change the model settings later.
#
# --gpus all on a host with two usable cards: setup takes both (the layer split is
# its recommended default). Pin one card with -e GPU=0, or name them with
# -e GPUS=0,2. A volume set up for one card switches to the pair on its first start
# on a two-card host unless GPU or GPUS pins it. LOW_RAM=on runs on one card.
FROM nvidia/cuda:13.0.0-devel-ubuntu24.04
# STRATA_EXECV=1: setup.py replaces itself with the server, so the server is PID 1
# and docker stop's SIGTERM reaches it (see setup.start). Normal Linux starts, which
# don't set it, keep spawning the server as a child.
ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1 LANG=C.UTF-8 STRATA_EXECV=1
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential ca-certificates curl git libatomic1 libgomp1 \
python3 python3-pip python3-venv unzip \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/strata
COPY . .
# RTX 20 (75), RTX 30 (86), RTX 40 (89), RTX 50 (120), plus 80 for A-series. CMakeLists
# refuses anything below 75. BUILD_VISION=0 skips the image encoder build.
ARG CUDA_ARCHITECTURES=75;80;86;89;120
ARG BUILD_VISION=1
RUN python3 -m venv .venv \
&& .venv/bin/pip install --no-cache-dir --upgrade pip \
&& .venv/bin/pip install --no-cache-dir -r requirements.txt \
&& chmod +x setup.sh docker-entrypoint.sh
# llama.cpp at the pinned commit, then the engine and the image encoder, built
# exactly the way setup.py builds them. BUILD.json is what setup.py reads to
# decide whether an engine is current: source=local with a matching src hash
# means the first start reuses it instead of recompiling.
RUN .venv/bin/python - <<'PYEOF'
import json, os, pathlib, shutil
import setup
llama = setup.get_llama_cpp()
nvcc, _ = setup.find_nvcc()
arch = os.environ.get("CUDA_ARCHITECTURES", "75;80;86;89;120").strip().strip('"').replace(",", ";")
vision = "gpu" if os.environ.get("BUILD_VISION", "1") == "1" else "none"
setup.cmake_build(setup.ROOT, setup.ROOT / "build", "strata",
["-DSTRATA_ENABLE_CUDA=ON", "-DSTRATA_BUILD_TESTS=OFF",
f"-DCMAKE_CUDA_ARCHITECTURES={arch}", f"-DCMAKE_CUDA_COMPILER={nvcc}",
f"-DSTRATA_GGML_DIR={llama}"], None, "build-strata.bat")
if vision != "none":
setup.cmake_build(setup.ROOT / "tools" / "vision", setup.ROOT / "build-vision", "strata-vision",
[f"-DLLAMA_DIR={llama}", "-DSTRATA_VISION_CUDA=ON",
f"-DCMAKE_CUDA_ARCHITECTURES={arch}", f"-DCMAKE_CUDA_COMPILER={nvcc}"], None, "build-vision.bat")
eng = setup.ROOT / "engine"
eng.mkdir(exist_ok=True)
shutil.copy2(setup.ROOT / "build" / setup.EXE, eng / setup.EXE)
if vision != "none":
shutil.copy2(setup.ROOT / "build-vision" / "bin" / setup.VEXE, eng / setup.VEXE)
bindir = pathlib.Path(nvcc).parent
meta = {"source": "local", "version": setup.source_version(),
"archs": [int(a.split("-")[0]) for a in arch.split(";") if a.split("-")[0].isdigit()], "vision": vision,
"cuda_dirs": [str(d) for d in (bindir, bindir / "x64", bindir.parent / "lib64") if d.is_dir()],
"src": setup.source_hash(setup.ENGINE_SOURCES),
"vision_src": setup.source_hash(setup.VISION_SOURCES) if vision != "none" else None}
(eng / "BUILD.json").write_text(json.dumps(meta, indent=1))
PYEOF
# the cmake trees are build-time only; the engine itself is what the container needs
RUN rm -rf build build-vision
VOLUME ["/data"]
EXPOSE 8080
# /health is answered before the API key gate, so it works with or without one.
# The port only opens after the model loads (1-3 minutes, longer on a first run),
# so the start period is generous: a too short one marks a still-loading container
# unhealthy and a restart policy would kill it mid-download.
HEALTHCHECK --interval=30s --timeout=5s --start-period=600s --retries=3 \
CMD curl -fs "http://127.0.0.1:${PORT:-8080}/health" || exit 1
ENTRYPOINT ["./docker-entrypoint.sh"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Niko1221 and the Strata contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+135 -263
View File
@@ -1,313 +1,185 @@
<h1 align="center">Qwen3.8-Flash-Next (125B MoE) on a 12-24 GB NVIDIA GPU + 64 GB RAM: one-click install for Windows / Linux</h1>
<h1 align="center">Strata</h1>
<p align="center"><b>Strata</b>: an inference engine built for exactly one model and exactly this kind of PC</p>
<p align="center"><b>Run a 125-billion-parameter AI model on your own gaming PC</b><br>
NVIDIA or AMD graphics card (12 GB or more) · Windows or Linux · free and open source</p>
A 125-billion-parameter mixture-of-experts model (6B active per token) on a **normal gaming PC**: one NVIDIA card with
12-24 GB of VRAM, 64 GB of RAM and a desktop CPU. Strata keeps the always-used part of the model on the GPU, all 24,576
experts in RAM, computes the experts the GPU does not hold on the CPU at the same time, and checks several tokens per
pass with the model's own multi-token-prediction (MTP) layer. The text it produces is identical to plain greedy decoding.
<p align="center"><a href="https://github.com/Niko1221/Strata/releases/download/v0.1.10/Pagoda.mp4"><img src="docs/media/pagoda-preview.webp" width="720" alt="A voxel pagoda garden that Strata's model wrote, running in the browser"></a><br>
<sub>A voxel pagoda garden, 1 shot prompt running on an RTX 5070 with Strata (IQ3_S, 128K context) ·
<a href="https://github.com/Niko1221/Strata/releases/download/v0.1.10/Pagoda.mp4">full video (49 s)</a></sub></p>
It serves an **OpenAI-** and **Anthropic-compatible API on localhost**, so any chat app, coding agent or script that
talks to those APIs can use it. Optionally it also **reads images** (screenshots, photos, scanned pages) with the
model's own vision encoder.
Strata runs **[Qwen3.8-Flash-Next](https://huggingface.co/Qwen/Qwen3.8-Flash-Next)** - a large, smart AI model that
normally needs a server - on a normal PC. It chats, writes code, reads pictures and works with your apps and coding
agents, and nothing leaves your PC.
> **Jump to:** [Speed](#speed-measured) · [Other GPUs](#other-gpus-estimated) · [Which model?](#which-model) ·
> [Windows](#windows) · [Linux](#linux) · [Using it](#using-it) · [Images](#images-vision) ·
> [Troubleshooting](#troubleshooting) · [How it works](#how-it-works)
## How fast is it?
---
Measured on two ordinary gaming PCs. "Writes answers" is how fast the reply appears in a short chat; "reads your
prompt" is how fast it takes in what you send (a 32K-token document, code or chat history). A token is about ¾ of a
word, so 60 tokens per second is faster than you can read.
## Speed (measured)
<table>
<tr><th>NVIDIA: RTX 5070 (12 GB), Ryzen 5 7600, 64 GB RAM</th><th>AMD: RX 9070 XT (16 GB), Ryzen 9 3900X, 47 GB RAM</th></tr>
<tr><td>
RTX 5070 **12 GB**, Ryzen 5 7600 (6 cores), 64 GB DDR5-5200, Windows. One code-agent prompt per length, 256 generated
tokens, MTP speculative decoding on. "262K" is the model's full context window (a 259,943-token prompt).
| Size | Writes answers | Reads your prompt |
| --- | ---: | ---: |
| **Q2_0** | 94 tokens/s | 2,650 tokens/s |
| **IQ2_XS** | 79 tokens/s | 2,090 tokens/s |
| **IQ3_XXS** | 62 tokens/s | 1,750 tokens/s |
| **IQ3_S** | 53 tokens/s | 1,620 tokens/s |
| **Coder** | 55 tokens/s | 2,180 tokens/s |
### Prompt processing (tokens/s)
</td><td>
| Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| **Q2_0** | 389 | 539 | 571 | 561 | 543 | 496 |
| **IQ2_XS** | 332 | 463 | 495 | 486 | 472 | 437 |
| **IQ3_XXS** | 285 | 410 | 435 | 427 | 414 | - |
| Size | Writes answers | Reads your prompt |
| --- | ---: | ---: |
| **Q2_0** | 60 tokens/s | 1,160 tokens/s |
| **IQ2_XS** | 52 tokens/s | 1,110 tokens/s |
| **Coder** | 44 tokens/s | 1,420 tokens/s |
### Output (tokens/s)
</td></tr>
</table>
| Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| **Q2_0** | 88.7 | 94.6 | 87.5 | 76.4 | 65.1 | 56.3 |
| **IQ2_XS** | 82.0 | 78.0 | 65.3 | 63.7 | 52.0 | 48.0 |
| **IQ3_XXS** | 64.6 | 65.6 | 57.3 | 54.4 | 44.8 | - |
A card with more VRAM is faster: an RTX 3090 (24 GB) should write roughly 100-140 tokens per second. Long chats,
other cards: [speed of each model](docs/MODELS.md#how-fast-is-each-size), [community results](docs/COMMUNITY_BENCHMARKS.md).
IQ3_XXS at 262K is not measured: with its 43 GB of experts, a 260K-token context brings a 64 GB PC to its memory
limit. Use up to 128K with IQ3_XXS on 64 GB.
<p align="center"><a href="https://buymeacoffee.com/strataengine"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50"></a><br>
<sub>Strata is free. If it runs well on your PC, a coffee keeps the work on it going.</sub></p>
Time to first token is prompt length / prompt speed: about 7 s at 4K, 55 s at 32K, 4 minutes at 128K and 9 minutes at
262K. The raw numbers: `bench/results/`. The [paper](docs/paper/Strata-Paper.pdf) explains every number.
## Other GPUs (estimated)
Not measured - estimated from the runs above (same CPU and 64 GB RAM): the GPU part scaled by memory bandwidth, the CPU
part by how many more experts the card's VRAM holds. Treat as **±20%**. Numbers are *prompt / output* tokens/s.
| GPU | Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| RTX 5060 Ti 16GB | Q2_0 | ~341 / ~80 | ~472 / ~87 | ~501 / ~81 | ~492 / ~72 | ~476 / ~62 | ~435 / ~53 |
| | IQ2_XS | ~291 / ~80 | ~406 / ~77 | ~434 / ~63 | ~426 / ~62 | ~413 / ~51 | ~383 / ~47 |
| | IQ3_XXS | ~249 / ~66 | ~359 / ~65 | ~381 / ~56 | ~374 / ~54 | ~363 / ~45 | - |
| RTX 3090 24GB | Q2_0 | ~355 / ~128 | ~491 / ~140 | ~521 / ~130 | ~512 / ~115 | ~495 / ~100 | ~453 / ~85 |
| | IQ2_XS | ~303 / ~131 | ~422 / ~128 | ~451 / ~103 | ~444 / ~102 | ~430 / ~85 | ~398 / ~78 |
| | IQ3_XXS | ~260 / ~106 | ~374 / ~103 | ~396 / ~89 | ~390 / ~85 | ~378 / ~71 | - |
More VRAM matters more than a faster GPU: every extra GB holds ~700 more experts, and every expert on the GPU is one the
CPU does not have to compute. A 3090's 24 GB takes most of the CPU work away.
## Which model?
All three are [ISTA-DASLab's GSQ-RCO quantizations](https://huggingface.co/ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF)
of [Qwen3.8-Flash-Next](https://huggingface.co/Qwen/Qwen3.8-Flash-Next).
| Model | Download | RAM it uses | Speed | Quality |
| --- | ---: | ---: | --- | --- |
| **Q2_0** | 66 GB | ~34 GB experts + ~6 GB | fastest | good |
| **IQ2_XS** | 68 GB | ~36 GB experts + ~6 GB | close to Q2_0 | a bit better |
| **IQ3_XXS** | 76 GB | ~43 GB experts + ~6 GB | slower (more CPU work) | best |
With 64 GB of RAM all three fit (close the browser for IQ3_XXS, and keep its context at 128K or less). With 48 GB only Q2_0 / IQ2_XS may fit. 32 GB is not enough.
## Before you start
You need **only an NVIDIA driver** (version 580 or newer; update it with the NVIDIA App or from
[nvidia.com/drivers](https://www.nvidia.com/drivers)). Everything else is installed for you the first time.
## What you need
| | |
| --- | --- |
| GPU | NVIDIA **RTX 30, 40 or 50 series**, **12 GB VRAM or more** (8 GB runs, slowly). Measured on an RTX 5070; RTX 30/40 are untested. |
| RAM | **64 GB** recommended (see the table above). |
| CPU | x86-64 with AVX2 (any Intel/AMD desktop CPU from the last ~8 years). AVX-512 (Ryzen 7000/9000) is a bit faster. |
| Disk | ~70-80 GB free for the model, ~6 GB for the MTP layer (+1 GB with images). **Q2_0 on an AVX-512 CPU** also writes a one-time ~40 GB copy of its experts for the fast CPU kernel. An NVMe SSD is strongly recommended. |
| OS | Windows 10/11, or Linux (Ubuntu 22.04/24.04 get everything installed automatically). |
| **Graphics card** | **NVIDIA** GeForce RTX 20, 30, 40 or 50 series, or **AMD** Radeon RX 7900 XT / XTX, RX 7800 XT / 7700 XT, RX 9060 XT, RX 9070 / 9070 XT, Radeon AI PRO R9700 or RX 6800 / 6900 series - with **12 GB of VRAM or more** |
| **RAM** | 32 GB or more - how much decides [which model](#which-model-should-i-pick) fits; 64 GB runs every size |
| **Disk** | about 80 GB free, on an SSD if you can (the first start is much faster) |
| **System** | Windows 10 / 11 or Linux, and a current graphics driver from NVIDIA or AMD |
What the first start installs, all inside this folder (`.venv/`, `engine/`, `third_party/`, `models/`, `packs/`, `mtp/`):
Python 3.12 if you have none (for your user account, no admin), a private Python environment, NVIDIA's CUDA libraries
(from pip, ~0.4 GB), the ready-made Strata engine for RTX 30/40/50, the model and the MTP draft layer. If no
ready-made engine fits your PC, it offers to install the build tools (Visual Studio Build Tools + CUDA Toolkit on
Windows, `build-essential` + CUDA on Ubuntu) and compiles the engine for your GPU (asks first; 20-40 minutes once).
Everything else is installed for you. Two or three cards can share the model ([multi-GPU](docs/MULTI_GPU.md)).
The full list: [docs/INSTALL.md](docs/INSTALL.md#what-you-need).
---
## Install
## Windows
### Let your AI set it up
### Double-click `START-HERE.bat`
Use an AI coding assistant (Claude Code, Cursor, Codex, GitHub Copilot, ...)? Paste this into it:
**The first time** it asks three questions and does the rest:
1. **Which model?** Q2_0, IQ2_XS or IQ3_XXS (it recommends one for your RAM).
2. **How much context?** 8K to 256K tokens (it recommends one for your VRAM).
3. **Images?** yes / no (see [Images](#images-vision)).
Then it downloads and prepares everything (the model is 66-76 GB, so the first start takes a while; an interrupted
download continues where it stopped) and **starts the model**: your browser opens `http://127.0.0.1:8080`, a small
page that shows it is running and lets you chat. The API is at `http://127.0.0.1:8080/v1` for your apps.
**Every time after that**, `START-HERE.bat` just starts the model (30-90 s to load 34-43 GB into RAM). Nothing is
downloaded again. Closing the window stops the model.
```
START-HERE.bat --setup install another model, or change context / images
START-HERE.bat --model IQ2_XS --context 32768 --vision yes --yes no questions
START-HERE.bat --gguf-dir D:\models\IQ2_XS use GGUF files you already have
START-HERE.bat --port 8081 another port
```text
Set up Strata on this PC for me: https://github.com/Niko1221/Strata - follow docs/AI_SETUP.md in that repository.
```
With more than one model installed, it asks which one to start. `run-<model>.bat` starts a model directly.
It checks your graphics card, RAM and disk, picks the model that fits, installs it, starts it and tells you how to
connect your apps. AI tools can also install, start and stop Strata themselves through its
[MCP server](docs/MCP_SERVER.md).
### Chat in the terminal (optional)
### Or do it yourself
```
.venv\Scripts\python chat.py
```
[Download Strata](https://github.com/Niko1221/Strata/archive/refs/heads/main.zip) and unzip it (or `git clone` it).
**Windows:** double-click **`START-HERE.bat`**. **Linux:** run **`./setup.sh`** in the Strata folder.
---
The same steps for NVIDIA and AMD: the installer finds your card and sets up the right engine for it. It asks which
model, which size, how much context (how much text it keeps in mind) and whether it should read pictures - press
Enter each time for the recommended answer. Then it downloads the model (~70 GB; you can stop and it continues where
it left off) and starts it. Your browser opens the Strata app at `http://127.0.0.1:8080`.
## Linux
> **While the model starts, your PC can be slow or stop responding for 1-3 minutes** (longest the first time): Strata
> loads 35-55 GB into your RAM and locks part of it for the graphics card. That's normal - wait, and don't close the
> window. The window tells you what it is doing.
```bash
./setup.sh
```
**Next time**, run `START-HERE.bat` (or `./setup.sh`) again: it starts right away, nothing is downloaded twice. Close
its window to stop the model. `UPDATE.bat` (`./update.sh`) updates Strata without starting it. Updating, Docker,
several cards, where the files go and every option:
[docs/INSTALL.md](docs/INSTALL.md).
The same three questions, the same automatic install (it uses `sudo apt` for Python and, only if it has to compile,
for the build tools), and the same start: `http://127.0.0.1:8080`. Later runs of `./setup.sh` (or `./run-<model>.sh`)
start the model directly. Options as on Windows (`./setup.sh --setup`, `--model Q2_0 --yes`, `--gguf-dir /data/Q2_0`).
Terminal chat: `.venv/bin/python chat.py`.
## Which model should I pick?
---
The installer recommends one for your RAM. The same model comes in sizes that are compressed more or less: smaller
is faster, larger is a bit smarter.
| Your RAM | Take | Why |
| --- | --- | --- |
| **32 GB** | **Coder** | it fits 32 GB, and it is made for code (with a 24 GB card, Q2_0 and IQ2_XS run too) |
| **48 GB** | **IQ2_XS** (or Q2_0, the fastest) | the larger sizes do not fit |
| **64 GB** | **IQ2_XS** (recommended), or IQ3_XXS / IQ3_S | every size fits; IQ3_S is the best, and the slowest |
| **96 GB or more** | **IQ3_S**, or Unsloth's 4-bit (experimental) | room for the largest sizes with everything else open |
- **[Coder](docs/MODELS.md#coder)** - a coding version with half of the experts removed: 91% of the full model's
SWE-bench Verified score (by its authors), fits 32 GB of RAM. Weaker outside code, including Chinese and other
CJK text (#438): for those, take Q2_0, IQ2_XS or IQ3_S, which keep every expert.
- **[Swift 1.5](docs/MODELS.md#swift-15)** - a fine-tune that thinks much shorter before it answers, so you get the
answer sooner, at about the same quality.
- **[Unsloth UD-Q4_K_XL](docs/MODELS.md#unsloth-ud-q4_k_xl-experimental)** (experimental) - the closest to the full
model, but most of it is read from the SSD while it answers: 7-8.5 tokens/s on a 64 GB PC.
- **[OrcaRouter's Uncensored IQ3_XXS](docs/MODELS.md#orcarouter-uncensored-iq3_xxs)** - a manual setup, not in the
installer's menu.
Sizes, downloads and what fits where: [docs/MODELS.md](docs/MODELS.md). You can add another model later with
`SETUP.bat` (Linux: `./setup.sh --setup`).
## Using it
The server listens on `http://127.0.0.1:8080` (change with `--port` in setup, or edit the run script).
<p align="center"><img src="docs/media/runpagoda.png" width="900" alt="The Strata app's Monitor tab next to a coding agent"><br>
<sub>The Strata app's <b>Monitor</b> (left) while a coding agent writes the pagoda garden from the video (right)</sub></p>
| API | Endpoint |
| --- | --- |
| OpenAI Chat Completions (stream and non-stream, tools) | `POST /v1/chat/completions` |
| Anthropic Messages (stream and non-stream, tools) | `POST /v1/messages` |
| Model list / health | `GET /v1/models`, `GET /health` |
- **In the browser:** `http://127.0.0.1:8080` - **Chat**, a live **Monitor** of the model and your GPU/CPU/RAM, and
**About** with the settings and addresses.
- **Your apps and coding agents:** add an "OpenAI-compatible" provider with base URL **`http://127.0.0.1:8080/v1`**,
any API key and any model name. Apps that use Anthropic's API: `http://127.0.0.1:8080/v1/messages` (Claude Code:
`ANTHROPIC_BASE_URL=http://127.0.0.1:8080`).
- **Thinking:** choose **off, low, medium or high** in the chat menu or your app's "reasoning effort". Off is
fastest; high is best for hard questions.
- **Pictures:** say yes to "Images?" in setup, then click **Picture** in the chat, or attach them in your app
(AMD cards: on Linux through the processor, not on Windows yet).
- **From your phone or another PC:** `START-HERE.bat --setup --host 0.0.0.0 --api-key <secret>` - always with a key.
- **Good to know:** it answers one request at a time. The first message of a chat is read in full (about 1 minute
per 30,000 tokens); follow-ups start in seconds.
```bash
curl http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "strata", "messages": [{"role": "user", "content": "Write a haiku about GPUs."}], "max_tokens": 512 }'
```
More: [where your chats are stored](docs/INSTALL.md#where-things-are-stored), [the API](docs/DETAILS.md#using-it).
```python
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="none")
r = client.chat.completions.create(model="strata", messages=[{"role": "user", "content": "Hello!"}])
print(r.choices[0].message.content)
```
## Something went wrong?
- **Thinking levels: none, low, medium, high.** The model thinks before it answers (streamed as
`reasoning_content`, Anthropic: `thinking` blocks). Choose how much per request - in the chat page (the "Thinking"
menu), in `chat.py` (`/think low`), or over the API:
- **My PC froze the first time Strata started.** Normal while it loads the model: wait, don't close the window.
Still frozen after 10 minutes? Restart the PC, close other programs and try again, or pick a smaller size.
- **It stopped while downloading or installing.** Run `START-HERE.bat` (or `./setup.sh`) again: it continues where
it stopped.
- **It's very slow and the disk light keeps blinking, or "the engine stopped unexpectedly".** Not enough free RAM:
close other programs (browsers use a lot), or pick a smaller size (Q2_0 or IQ2_XS).
- **It says port 8080 is already in use.** Strata is already running - look for its window.
| API | how |
| --- | --- |
| OpenAI | `"reasoning_effort": "none" \| "low" \| "medium" \| "high"` (also `"reasoning": {"effort": ...}`, or `"chat_template_kwargs": {"enable_thinking": false}`) |
| Anthropic | `"output_config": {"effort": "low" \| "medium" \| "high"}`, `"thinking": {"type": "disabled"}`, or `"thinking": {"type": "enabled", "budget_tokens": N}` (under 2K = low, under 8K = medium, more = high) |
More problems and their fixes: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md). Still stuck? Open an
[issue](https://github.com/Niko1221/Strata/issues) and attach `strata-<model>.log` from the Strata folder.
Without a setting the model uses its own default, **high**. `none` answers at once (fastest); `low` keeps the thinking
short. The levels are instructions the model was trained with, not a hard token limit: on easy questions all three
think briefly, on hard ones `high` thinks longest and is most accurate.
- **Chat apps.** Any app with an "OpenAI-compatible" provider works: base URL `http://127.0.0.1:8080/v1`, any API key.
- **Context.** Chosen in setup (8K-262K). Requests longer than that are refused, never silently cut.
- **From other devices / the internet.** The server listens on your PC only (`127.0.0.1`). To reach it from elsewhere,
put a tunnel in front of it, for example [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/):
`cloudflared tunnel --url http://127.0.0.1:8080`. **Set a key first**, or anyone with the link can use your PC:
add `"api_key": "some-long-secret"` to `strata-<model>.json` (or set the `STRATA_API_KEY` environment variable);
clients then send it as their API key.
## How does it work?
**Current limits (v1):** one request at a time; greedy decoding (temperature is ignored); every request processes its
whole prompt again (no conversation cache yet, so long chats have a long time-to-first-token); images only when set up
with them (below); no video.
Models like this one normally run on servers with hundreds of gigabytes of graphics memory. Your graphics card has
12-24 GB. Strata makes it fit by **sharing the work across your whole PC** - like a kitchen, where the things you use
all the time stay on the counter and the rest waits in the pantry.
---
<p align="center"><img src="docs/media/how-it-works.svg" width="860" alt="The model's 24,576 experts: the busiest on the graphics card, all of them in RAM, a lookup table on the SSD"></p>
## Images (vision)
- **The model is a team of 24,576 small specialists ("experts"),** and each word needs only 10 of them.
- **Your graphics card** keeps the few thousand experts that are asked most often; **your RAM** holds all of them,
and **your processor** works on the rest at the same time. **Your SSD** holds a big lookup table.
The model has a vision encoder: [`mmproj-Qwen3.8-Flash-Next-BF16.gguf`](https://huggingface.co/ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF)
(0.9 GB, a 27-layer ViT plus the projector into the language model). It is **optional**: say yes when the setup asks
"Images?", or run it again with `--vision gpu` (or `--vision cpu`). The setup downloads the encoder, builds a small
helper (`strata-vision`, from llama.cpp's `mtmd` library) and adds it to your start script. Nothing else changes.
<p align="center"><img src="docs/media/guess-and-check.svg" width="860" alt="A small helper guesses the next words; the big model checks them all at once and keeps the right ones"></p>
| Encoder on | Time per picture | Cost |
| --- | --- | --- |
| **GPU** (recommended) | **0.1-0.5 s** (up to 1,024 image tokens) | ~1.4 GB of VRAM is kept free for it, so the expert cache is smaller: text output is a few % slower (table below) |
| CPU | 10-30 s (pictures are scaled down to ~300 image tokens) | nothing on the GPU |
- **Guess, then check:** a small helper guesses the next few words and the big model checks them all at once, so
you get the same answer, 1.6-1.8x sooner.
- **Long texts are read in big pieces** (up to 8,192 tokens at a time): over 1,000 tokens per second.
A picture becomes up to 1,024 tokens of the context (a 640x480 photo: 300). The same picture sent again, as chat apps
do on every turn, is encoded only once.
The longer explanation: [docs/HOW_IT_WORKS.md](docs/HOW_IT_WORKS.md). Every part and its numbers: [the
details](docs/DETAILS.md#how-it-works) and the [paper](docs/paper/Strata-Paper.pdf).
### Sending a picture
## Credits and license
**Terminal chat:** type `/image <path to a picture>`, press Enter, then type your question.
The model is [Qwen3.8-Flash-Next](https://huggingface.co/Qwen/Qwen3.8-Flash-Next) by the Qwen team, compressed by
[ISTA-DASLab](https://huggingface.co/ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF), UkisAI (Swift 1.5) and Unsloth;
Strata is built with parts of [llama.cpp / ggml](https://github.com/ggml-org/llama.cpp). All credits:
[docs/HOW_IT_WORKS.md](docs/HOW_IT_WORKS.md#credits). Strata is open source under the [MIT License](LICENSE); a few
parts and every model carry their own licenses ([which ones](docs/HOW_IT_WORKS.md#license)).
```
you> /image C:\Users\me\Pictures\receipt.jpg
(picture attached: receipt.jpg - now type your question)
you> What is the total on this receipt?
```
## Support Strata
**OpenAI API** (an `image_url` part: a `data:` URL, an `http(s)://` URL or a local file path):
Strata is free and open source. If it is useful to you, you can support its development:
```python
import base64
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="none")
img = base64.b64encode(open("photo.jpg", "rb").read()).decode()
r = client.chat.completions.create(model="strata", messages=[{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img}"}},
{"type": "text", "text": "What is in this picture?"}]}])
print(r.choices[0].message.content)
```
**Anthropic API:** an `image` block with a `base64` (or `url`) source, as usual.
JPEG, PNG, BMP and GIF work (WebP does not). Chat apps with image upload work the same way.
### Speed with images on (4K context, measured)
| Model | Prompt tok/s, images off | Prompt tok/s, images on | Output tok/s, images off | Output tok/s, images on |
| --- | ---: | ---: | ---: | ---: |
| **Q2_0** | 541 | 531 | 90.7 | 86.9 |
| **IQ2_XS** | 468 | 458 | 77.1 | 74.0 |
| **IQ3_XXS** | 411 | 401 | 64.7 | 59.5 |
"Off" is the published setup; "on" runs with the encoder loaded on the GPU and its VRAM kept free, so ~1,000 fewer
experts fit in VRAM and the CPU computes a few more per token: 2-8% slower. For text, turning images on changes
nothing else (the output is bit-identical when the VRAM is the same).
A question about a picture (a 640x480 newspaper page = 300 image tokens, a 328-token prompt; the whole request, encoder
on the GPU, measured through the API):
| Model | Answer ("MEN WALK ON MOON") | Picture encoded | Prompt | Output |
| --- | ---: | ---: | ---: | ---: |
| **Q2_0** | 1.9 s | ~0.1 s | 207 tok/s | 65-73 tok/s |
| **IQ2_XS** | 2.3 s | ~0.1 s | 173 tok/s | 50-60 tok/s |
| **IQ3_XXS** | 2.7 s | ~0.1 s | 144 tok/s | 41-50 tok/s |
(Short prompts run below the 4K prompt speed: a 2,048-token chunk is where the prompt path is efficient. Short answers
run below the long-output speed: the first rounds have no draft yet.)
**How it works inside:** the encoder turns the picture into rows of the same width as the model's word embeddings;
Strata puts them where the prompt has `<|image_pad|>` tokens and gives each one its 2-D position (row and column in the
picture; the model uses interleaved M-RoPE). Answers match llama.cpp's multimodal implementation token for token on our
test images.
---
## Troubleshooting
| Symptom | What to do |
| --- | --- |
| `the NVIDIA driver is too old` | Update the driver (NVIDIA App or nvidia.com/drivers), restart, run `START-HERE.bat` again. |
| Python or the build tools could not be installed | Install what it names (links are printed), then run it again. Everything already done is kept. |
| `port 8080 is already in use` | Strata is already running (look for its window), or another program uses the port: `START-HERE.bat --port 8081`. |
| `cudaHostRegister ... out of memory` in the log | Normal on Windows: the engine pins the experts in per-layer slices instead. Only a problem if the load then fails. |
| The first start takes minutes | It is reading 34-43 GB into RAM; the second start is faster while the files are in the OS cache. |
| Slow output, disk light busy | Not enough free RAM: close other programs, or choose Q2_0 / IQ2_XS. |
| `prompt ... exceeds the context` | The request is longer than the context you chose: run setup again with a bigger `--context`. |
| Slower than the tables | The monitor plugged into the GPU and other GPU programs take VRAM from the expert cache; RAM running below its rated speed (enable EXPO/XMP in the BIOS) slows the CPU half. |
| `this server was started without the vision encoder` | The model was set up for text only: run setup again with `--vision gpu`. |
| A picture is refused or `cannot read the image` | Use JPEG, PNG, BMP or GIF (not WebP). |
| Pictures are slow (10-30 s) | The encoder runs on the CPU: run setup again with `--vision gpu` (needs ~1.4 GB of VRAM). |
| Anything else | The engine log is `strata-<model>.log` in this folder. |
---
## How it works
<p align="center"><img src="docs/paper/tiers.svg" width="760" alt="memory tiers"></p>
- **GPU (VRAM):** attention and DeltaNet mixers, the gated-residual weights, routers, shared experts, output head, the MTP
draft layer, the KV cache, and an **expert cache** that fills the rest of VRAM with the most-used experts (it adapts to
the conversation while you chat).
- **RAM:** all 24,576 experts, pinned. The CPU computes the experts that are not on the GPU **in place**, at the same time
as the GPU works on the cached ones (AVX-512 / AVX2 kernels, ggml's for the i-quants).
- **SSD:** the 28.8 GB n-gram table, read a few rows per token through the OS cache.
- **Speculation:** the model's own MTP layer drafts up to 3 tokens; one pass over all 48 layers checks them. 2.4-3.2
tokens per pass on average.
- **Prompts** are processed in 2,048-token chunks with the experts streamed to the GPU over PCIe.
The full story, with measurements, bottlenecks and what comes next: **[docs/paper/Strata-Paper.pdf](docs/paper/Strata-Paper.pdf)**.
---
## Credits and licenses
- Model: [Qwen/Qwen3.8-Flash-Next](https://huggingface.co/Qwen/Qwen3.8-Flash-Next) by the Qwen team; quantizations:
[ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF](https://huggingface.co/ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF).
Their licenses apply to the weights.
- [llama.cpp / ggml](https://github.com/ggml-org/llama.cpp) (MIT): the i-quant formats, the GPU dot products and
dequantizers transcribed in `src/kernels/cuda/iq_kernels.cu`, the CPU backend linked for the i-quant experts, the
`mtmd` library behind the image encoder (`tools/vision/`), and `gguf-py` used by the tools. See
`third_party/ggml/LICENSE`.
- Ideas from [Splash](https://github.com/incoai/splash), [ninfer](https://github.com/Neroued/ninfer) and
[HyperQwen](https://github.com/syv-ai/HyperQwen); references in the paper.
<p align="center"><a href="https://buymeacoffee.com/strataengine"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50"></a></p>
+3
View File
@@ -0,0 +1,3 @@
@echo off
rem Change settings or install another model (the same as START-HERE.bat --setup).
call "%~dp0START-HERE.bat" --setup %*
+1 -1
View File
@@ -1,6 +1,6 @@
@echo off
rem Strata for Windows: the first run installs everything and starts the model; later runs just start it.
rem Needs only an NVIDIA driver. Python is installed for your user account if it is missing (no admin needed).
rem Needs only an NVIDIA or AMD graphics driver. Python is installed for your user account if it is missing (no admin needed).
setlocal
title Strata
cd /d "%~dp0"
+36
View File
@@ -0,0 +1,36 @@
@echo off
rem Update Strata without starting the model (#475): the newest code (git pull, when this folder is a git clone), then
rem what START-HERE.bat does before a start - the engine (a new one when this version needs it), the Python packages,
rem each installed model's settings and draft subset. The model files are not touched. Start the model later with
rem START-HERE.bat. Options are passed on to setup.py.
setlocal
title Strata - update
cd /d "%~dp0"
rem All of it in one block: cmd reads a .bat file while it runs it, and the git pull can change this very file.
(
if exist ".git" (
where git >nul 2>nul || (
echo This folder is a git clone, but git is not on PATH: install Git for Windows ^(winget install Git.Git^)
echo or run "git pull" here yourself, then run UPDATE.bat again.
pause
exit /b 1
)
echo Getting the newest Strata ^(git pull^) ...
git pull --ff-only || (
echo.
echo git pull did not succeed ^(the reason is above^): nothing was updated. Files you changed here can stop it:
echo "git status" lists them.
pause
exit /b 1
)
) else (
echo This copy of Strata was not made with git, so it cannot fetch new files itself. Download the newest one:
echo https://github.com/Niko1221/Strata/archive/refs/heads/main.zip
echo unzip it anywhere and run START-HERE.bat ^(or UPDATE.bat^) in it: it finds the model files in Strata-data
echo and sets itself up the same way - nothing big is downloaded again.
echo Checking this copy's engine and settings meanwhile ...
)
call "%~dp0START-HERE.bat" --update %*
if not errorlevel 1 pause
exit /b
)
@@ -0,0 +1,29 @@
# 2026-09-27: expert cache on vs off (issue #23)
Question: the engine used to warn at start that "the GPU hit path is NOT CORRECT ... any OUTPUT from it is not". Does
the expert cache make the output worse?
Method: teacher-forced. `strata --dump-logits` runs a text one token at a time and writes the logits at every
position, so both runs see the same text and each position's next-token prediction can be compared:
- cache off: no `--expert-cache`, every routed expert computed by the CPU pool;
- cache on: `--expert-profile pack/profile-decode-8k.bin --expert-cache auto`, 5,130 of the most-used experts on the GPU.
Q2_0, RTX 5070, the first 512 or 1,024 tokens of three frozen prompts.
| Text | Same top-1 | Median KL | Perplexity off | Perplexity on | On - off (nats per token) |
|---|---:|---:|---:|---:|---:|
| Code agent (512) | 97.7% | 3.0e-04 | 2.672 | 2.630 | -0.016 +- 0.019 |
| Long document (1,024) | 96.2% | 4.5e-03 | 29.844 | 29.843 | -0.000 +- 0.005 |
| Chat (1,024) | 95.0% | 4.4e-03 | 18.380 | 18.301 | -0.004 +- 0.006 |
| **All 2,557 tokens** | | | | | **-0.005 +- 0.005** |
Reading:
- The cache changes which of two nearly equal candidates wins at 2-5% of positions. Most of the flips have a top-2
margin under 0.5 logits (`logits-compare.txt`).
- Neither side is more accurate: the perplexity of the actual text is the same, within 1 standard error.
- A GPU expert and the CPU compute the same quantized expert with a different floating-point order. Both are an
equally good approximation of the model.
The start-up warning dates from round 328, when the GPU hit path had a real fault (divergence from token 0). That fault
was fixed long ago and is covered by `expert_parity`, `native_expert_parity` and the grouped kernels' tests. Since
engine 0.1.8 the warning is replaced by one accurate line.
+36
View File
@@ -0,0 +1,36 @@
# 2026-09-27: the experimental-speed-projection control vector
The package's `Qwen3.8-Flash-Next-experimental-speed-projection.gguf` (47 unit directions of 2,560, one per layer),
applied as `--control-vector-scaled FILE:1.0 --control-vector-layer-range 4 44 --cvec-mode project --cvec-dir
per-layer`: after layers 4-44, every hyper-connection stream `h -= (h . v) v`. The package describes it as a
refusal-direction projection. Q2_0, RTX 5070, fixed experts (`--expert-cache 4000 --pcie-frac 0 --adapt-swaps 0`).
## Kernel (`cvec_parity`)
Project and add against a double-precision reference (max error 5e-7); with the switch off, the kernel's pending
residual write is bitwise the fused hyper-connection read's own, so a loaded-but-off vector is the stock engine.
## Teacher-forced, stock vs projection (`esp_kl.py`, per-token path)
| Text | Tokens | Same top-1 | Mean KL | p99 KL | Perplexity stock | With the vector |
|---|---:|---:|---:|---:|---:|---:|
| Code | 511 | 92.8% | 0.098 | 1.69 | 2.594 | 2.982 |
| Document | 1,023 | 89.8% | 0.058 | 0.84 | 30.299 | 30.984 |
| Chat | 1,023 | 89.6% | 0.050 | 0.33 | 18.409 | 18.485 |
| **All** | 2,557 | | **0.063** (max 4.10) | 1.00 | | |
The package reports a mean KL of 0.186 on its own neutral text with UD-Q2_K_XL in llama.cpp.
Cost on the same tokens: 44.34 -> 44.26, 46.74 -> 46.56, 47.02 -> 46.83 tok/s (0.2-0.4%).
## Serve (`esp_serve.py`)
- Loaded and switched off (`cvec=0`) vs an engine without it: 65 / 65 first tokens after prefixes (verify windows
and the batched prompt path) and three 256-token generations **token-identical**.
- On, off, on again: identical (the switch drops the conversation cache, so nothing computed one way is reused the
other way).
- The prompt paths steer like the per-token path: served-with-vector first tokens agree with the per-token path's
teacher-forced argmax with the vector 62 / 65, and with the stock one 57 / 65 (stock served: 64 / 65 and 57 / 65).
- Decode tok/s of 3 x 256-token generations: stock 66.4, on 76.7 / 81.2. **Not a like-for-like comparison:** the
model writes different text with it on (on the code prompt it copied a long stretch of the source, which prompt
lookup drafts at 91% acceptance, where stock stopped after 78 tokens). On the same tokens it is 0.2-0.4% slower.
+85
View File
@@ -0,0 +1,85 @@
"""Teacher-forced logits, stock vs the experimental-speed-projection vector, on the same texts and fixed experts.
python esp_kl.py run # the dumps (per-token path)
python esp_kl.py compare # same top-1, mean/median KL(stock || projection), perplexities
"""
import json
import os
import subprocess
import sys
import numpy as np
S = os.path.dirname(os.path.abspath(__file__))
E = r"C:\Users\AI-Server\Desktop\Strata\Public\Engine"
EXE = E + r"\build-dev\strata.exe"
VEC = r"C:\Users\AI-Server\Desktop\Strata\experimental-speed-projection\Qwen3.8-Flash-Next-experimental-speed-projection.gguf"
TEXTS = {"code": S + r"\p512.ids", "doc": S + r"\4k-02-long-doc-1k.ids", "chat": S + r"\4k-04-chat-1k.ids"}
OUT = S + r"\esp-kl"
ARMS = {"stock": [],
"proj": ["--control-vector-scaled", VEC + ":1.0", "--control-vector-layer-range", "4", "44",
"--cvec-mode", "project", "--cvec-dir", "per-layer"]}
def base_args():
c = json.load(open(S + r"\cfg-rel.json"))
a = list(c["args"])
i = a.index("--expert-cache")
a[i + 1] = "4000"
j = a.index("--prefill") # the prompt one token at a time: a logits row per position
del a[j:j + 2]
return a + ["--pcie-frac", "0", "--adapt-swaps", "0"], c
def run():
os.makedirs(OUT, exist_ok=True)
args, c = base_args()
env = dict(os.environ)
env["PATH"] = os.pathsep.join(c["lib_dirs"]) + os.pathsep + env["PATH"]
for name, path in TEXTS.items():
for arm, extra in ARMS.items():
out = f"{OUT}\\{name}-{arm}.bin"
if os.path.exists(out):
continue
cmd = [EXE, *args, *extra, "--tokens-file", path, "--max-new", "1", "--dump-logits", out]
with open(f"{OUT}\\{name}-{arm}.log", "w") as log:
r = subprocess.run(cmd, cwd=c["cwd"], stdout=log, stderr=subprocess.STDOUT, env=env)
print(name, arm, "exit", r.returncode, flush=True)
def load(p):
h = np.fromfile(p, dtype=np.int32, count=2)
return np.memmap(p, dtype=np.float32, mode="r", offset=8, shape=(int(h[1]), int(h[0])))
def lsm(x):
x = x.astype(np.float64)
m = x.max(axis=-1, keepdims=True)
return x - (m + np.log(np.exp(x - m).sum(axis=-1, keepdims=True)))
def compare():
print(f"{'text':<6}{'tokens':>7}{'same top-1':>11}{'mean KL':>9}{'median KL':>11}{'p99 KL':>8}{'ppl stock':>10}{'ppl proj':>9}")
allkl = []
for name, path in TEXTS.items():
ids = [int(x) for x in open(path).read().replace(",", " ").split()]
a, b = load(f"{OUT}\\{name}-stock.bin"), load(f"{OUT}\\{name}-proj.bin")
n = min(a.shape[0], b.shape[0], len(ids) - 1)
kl, same, la, lb = [], 0, [], []
for r in range(0, n, 64):
pa, pb = lsm(a[r:min(n, r + 64)]), lsm(b[r:min(n, r + 64)])
kl += list((np.exp(pa) * (pa - pb)).sum(axis=1))
same += int((pa.argmax(1) == pb.argmax(1)).sum())
nxt = np.array(ids[r + 1:min(n, r + 64) + 1])
la += list(pa[np.arange(len(nxt)), nxt])
lb += list(pb[np.arange(len(nxt)), nxt])
kl = np.array(kl)
allkl += list(kl)
print(f"{name:<6}{n:>7}{100 * same / n:>10.1f}%{kl.mean():>9.3f}{np.median(kl):>11.4f}{np.percentile(kl, 99):>8.2f}"
f"{np.exp(-np.mean(la)):>10.3f}{np.exp(-np.mean(lb)):>9.3f}")
allkl = np.array(allkl)
print(f"all {len(allkl)} tokens: mean KL {allkl.mean():.3f}, max {allkl.max():.2f}, p99 {np.percentile(allkl, 99):.2f}")
if __name__ == "__main__":
{"run": run, "compare": compare}[sys.argv[1]]()
+129
View File
@@ -0,0 +1,129 @@
"""Serve-mode checks of the experimental-speed-projection vector (exact mode: fixed experts, no PCIe share).
python esp_serve.py run ARM # ARM = stock | vec; writes esp-serve/<arm>.json
python esp_serve.py compare
Per arm: the first greedy token after many prefixes of three texts (short ones go through the verify windows, long
ones through the batched prompt path; the conversation cache reuses the earlier prefix), and three 256-token greedy
generations. The vec arm runs everything with cvec=1, then cvec=0, then cvec=1 again.
"""
import json
import os
import sys
import threading
S = os.path.dirname(os.path.abspath(__file__))
E = r"C:\Users\AI-Server\Desktop\Strata\Public\Engine"
sys.path.insert(0, E)
from serve.server import StrataEngine # noqa: E402
EXE = E + r"\build-dev\strata.exe"
VEC = r"C:\Users\AI-Server\Desktop\Strata\experimental-speed-projection\Qwen3.8-Flash-Next-experimental-speed-projection.gguf"
TEXTS = {"code": S + r"\p512.ids", "doc": S + r"\4k-02-long-doc-1k.ids", "chat": S + r"\4k-04-chat-1k.ids"}
OUT = S + r"\esp-serve"
VEC_ARGS = ["--control-vector-scaled", VEC + ":1.0", "--control-vector-layer-range", "4", "44",
"--cvec-mode", "project", "--cvec-dir", "per-layer"]
def ids_of(name):
return [int(x) for x in open(TEXTS[name]).read().replace(",", " ").split()]
def engine(extra):
c = json.load(open(S + r"\cfg-rel.json"))
a = list(c["args"])
a[a.index("--expert-cache") + 1] = "4000"
env = dict(os.environ)
env["PATH"] = os.pathsep.join(c["lib_dirs"]) + os.pathsep + env["PATH"]
return StrataEngine(EXE, a + ["--pcie-frac", "0", "--adapt-swaps", "0"] + extra, cwd=c["cwd"],
log=OUT + r"\engine.log", env=env)
def gen(eng, ids, n, sampling):
toks = [t for t in eng.generate(ids, n, sampling, threading.Event()) if t is not None]
return toks, dict(eng.last)
def suite(eng, sampling):
out = {"first": {}, "long": {}}
for name in TEXTS:
ids = ids_of(name)
# short prefixes growing by 4 (each continues the live session through the verify windows), then long ones
# SHRINKING (no cached prefix, so each is read from 0 by the batched prompt path)
lens = list(range(32, 72, 4)) + list(range(min(len(ids) - 1, 700), 95, -48))
out["first"][name] = {L: gen(eng, ids[:L], 1, sampling)[0][0] for L in lens}
for name in TEXTS:
toks, last = gen(eng, ids_of(name)[:300], 256, sampling)
out["long"][name] = {"tokens": toks, "last": last}
return out
def run(arm):
os.makedirs(OUT, exist_ok=True)
eng = engine(VEC_ARGS if arm == "vec" else [])
res = {"info": eng.info}
if arm == "stock":
res["stock"] = suite(eng, {})
else:
res["on"] = suite(eng, {"experimental_speed_projection": True})
res["off"] = suite(eng, {"experimental_speed_projection": False})
res["on2"] = suite(eng, {"experimental_speed_projection": True})
eng.close()
json.dump(res, open(f"{OUT}\\{arm}.json", "w"))
print(arm, "done", eng.info, flush=True)
def compare():
import numpy as np
sys.path.insert(0, S)
from esp_kl import load, OUT as KL
st = json.load(open(OUT + r"\stock.json"))
ve = json.load(open(OUT + r"\vec.json"))
print("engine INFO (vec):", ve["info"])
def eq_first(a, b):
tot = same = 0
for name in a:
for L, t in a[name].items():
tot += 1
same += int(b[name][L] == t)
return same, tot
def eq_long(a, b):
return all(a[n]["tokens"] == b[n]["tokens"] for n in a), \
[(n, next((i for i, (x, y) in enumerate(zip(a[n]["tokens"], b[n]["tokens"])) if x != y), None)) for n in a]
print("1. vector loaded, switched off == no vector")
print(" first tokens: %d / %d identical" % eq_first(st["stock"]["first"], ve["off"]["first"]))
print(" 256-token generations identical:", *eq_long(st["stock"]["long"], ve["off"]["long"]))
print("2. repeatable: on, off, on again")
print(" first tokens: %d / %d identical" % eq_first(ve["on"]["first"], ve["on2"]["first"]))
print(" 256-token generations identical:", *eq_long(ve["on"]["long"], ve["on2"]["long"]))
print("3. the prompt paths against the per-token path's teacher-forced argmax (row L-1 predicts token L)")
for arm, first in (("stock", st["stock"]["first"]), ("on", ve["on"]["first"])):
for ref in ("stock", "proj"):
agree = tot = 0
for name in first:
d = load(f"{KL}\\{name}-{ref}.bin")
for L, t in first[name].items():
L = int(L)
if L - 1 < d.shape[0]:
tot += 1
agree += int(int(np.argmax(d[L - 1])) == t)
print(f" served {arm:<5} vs per-token {ref:<5}: {agree} / {tot}")
print("4. how different the text is: 256-token generations, on vs stock, first difference")
print(" ", eq_long(st["stock"]["long"], ve["on"]["long"])[1])
print("5. decode speed (exact mode: fixed experts, no PCIe share - not the normal setup)")
for label, r in (("stock", st["stock"]), ("vec off", ve["off"]), ("vec on", ve["on"]), ("vec on 2", ve["on2"])):
g = sum(v["last"]["generated"] for v in r["long"].values())
ms = sum(v["last"]["decode_ms"] for v in r["long"].values())
acc = sum(v["last"].get("drafts_accepted", 0) for v in r["long"].values())
off = sum(v["last"].get("drafts_offered", 0) for v in r["long"].values())
print(f" {label:<9} {g / ms * 1000:6.1f} tok/s drafts accepted {acc}/{off}")
if __name__ == "__main__":
if sys.argv[1] == "run":
run(sys.argv[2])
else:
compare()
@@ -0,0 +1,20 @@
1. vector loaded, switched off == no vector
first tokens: 65 / 65 identical
256-token generations identical: True [('code', None), ('doc', None), ('chat', None)]
2. repeatable: on, off, on again
first tokens: 65 / 65 identical
256-token generations identical: True [('code', None), ('doc', None), ('chat', None)]
3. the prompt paths against the per-token path's teacher-forced argmax (row L-1 predicts token L)
served stock vs per-token stock: 64 / 65
served stock vs per-token proj : 57 / 65
served on vs per-token stock: 57 / 65
served on vs per-token proj : 62 / 65
4. how different the text is: 256-token generations, on vs stock, first difference
[('code', 11), ('doc', 6), ('chat', 3)]
5. decode speed (exact mode: fixed experts, no PCIe share - not the normal setup)
stock 66.4 tok/s drafts accepted 256/309
vec off 65.7 tok/s drafts accepted 251/318
vec on 76.7 tok/s drafts accepted 366/425
vec on 2 81.2 tok/s drafts accepted 366/423
[exited with code 0]
@@ -0,0 +1,6 @@
text tokens same top-1 mean KL median KL p99 KL ppl stock ppl proj
code 511 92.8% 0.098 0.0031 1.69 2.594 2.982
doc 1023 89.8% 0.058 0.0271 0.84 30.299 30.984
chat 1023 89.6% 0.050 0.0265 0.33 18.409 18.485
all 2557 tokens: mean KL 0.063, max 4.10, p99 1.00
+60
View File
@@ -0,0 +1,60 @@
# 2026-09-27: Q4_0 KV with Hadamard rotation (PR #21, `--kv q4_0`)
PR #21 (code-martin) adds a 4-bit K/V format: an orthonormal 256-point Walsh-Hadamard rotation of K, V and Q, then
ggml `q4_0` blocks (576 B per cell and layer, vs 1,056 B in int8).
## Integration fixes made on merge
- **Q4 + KV streaming (`--kv-resident`).** The PR had no host copy for Q4 and skipped the staging, so with streaming
on, the prompt path wrote K/V through the residency map's -1 entries (out-of-bounds writes).
- Q4 is now a third format in `kv_stream`: host copy, block residency, prompt-path staging, MTP ring and STATE_HASH.
- `kv_stream_parity` checks it bitwise, streamed vs resident, with ring restore.
- **Duplicate attention.** The per-token decode path ran attention twice, the first time before the selection's
blocks were made resident; it now runs once.
- **Tie-break.** The group maximum's tie-break is now deterministic across lanes: equal |x| with opposite signs
could give one block two scales.
- **Unrelated changes dropped:** duplicate path fixes (already in 0.1.7), personal `.gitignore` entries, and a
re-encoded CMakeLists.
`kv_q4_parity` (the PR's test) and all 70 ctest tests pass.
## Quality (teacher-forced, `tools/kv_precision_compare.py`)
Same text, same fixed expert set (Q2_0, 3,000 slots, `--adapt-swaps 0`); only `--kv` differs. fp16 is the reference.
| Text | KV | Same top-1 as fp16 | Mean KL | Perplexity | vs fp16 (nats/token) |
|---|---|---:|---:|---:|---:|
| code agent (512) | int8 | 98.4% | 0.014 | 2.649 | -0.011 +- 0.006 |
| | q4_0 | 95.7% | 0.030 | 2.598 | -0.031 +- 0.014 |
| document (1,024) | int8 | 95.6% | 0.009 | 30.18 | +0.012 +- 0.005 |
| | q4_0 | 91.3% | 0.039 | 32.26 | **+0.078 +- 0.011** |
| chat (1,024) | int8 | 95.5% | 0.009 | 18.24 | -0.003 +- 0.005 |
| | q4_0 | 90.7% | 0.028 | 18.52 | +0.012 +- 0.008 |
| document, 8K (every 8th position) | int8 | 92.3% | 0.114 | 7.63 | -0.013 +- 0.020 |
| | q4_0 | 88.0% | 0.297 | 8.65 | **+0.112 +- 0.037** |
- **int8 is indistinguishable from fp16** (within about 2 standard errors everywhere).
- **q4_0 costs measurable precision, and more as the context grows:**
- document perplexity +8% at 1K and +12% at 8K;
- the KL from fp16 is 2.6-4.2x int8's;
- code and chat are near-neutral.
- **Needles with `--kv q4_0 --kv-resident 32768`: 5/5** (1K, 32K, 128K at 10% and 90% depth, 262K;
`needles-q4.*`). Retrieval holds.
## Speed and memory (Q2_0, 128K, matrix bench, `--kv-resident 32768`)
| KV | Expert slots | Decode tok/s | Tokens/round | Rounds/s | K/V in RAM |
|---|---:|---:|---:|---:|---:|
| int8 (`kv-stream/matrix-stream`) | 4,044 | 62.4 | 2.52 | 24.8 | 1.52 GiB |
| q4_0 (`matrix-q4`) | 4,190 | 67.4 | 2.62 | 25.7 (+3.7%) | 0.83 GiB |
Without streaming (contexts up to 64K, or too little RAM for it) q4_0 halves the whole KV cache in VRAM instead.
## Decision
`--kv q4_0` is a user option:
- `START-HERE.bat --kv q4_0`, or the question setup asks above 8K context;
- or `--kv q4_0` in a `strata-*.json`.
int8 stays the default: every published quality number was measured with it, and q4_0's precision cost is real,
though it passes the needles.
+44
View File
@@ -0,0 +1,44 @@
# The GSQ-RCO Coder (256 of 512 experts) on Strata
RTX 5070 12 GB, Ryzen 5 7600, 64 GB DDR5-5200, Windows 10. Engine 0.1.15 + PR #54 (the expert count from the model
file), the settings setup writes (`--prefill auto`, 8-bit KV above 4K, KV streaming from 64K, MTP, greedy, 256 tokens),
`data/expert-profile-coder.bin`, the same code-agent prompts as `2026-09-28-speed-0114`.
| | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Prompt, tokens/s | 599 | 1,152 | 1,298 | 1,350 | 1,266 | 1,034 |
| Output, tokens/s | 53.3 | 50.6 | 53.3 | 50.8 | 44.0 | 42.8 |
| Draft acceptance | 0.720 | 0.707 | 0.785 | 0.696 | 0.619 | 0.684 |
| Experts in VRAM | 2,570 | 2,615 | 2,418 | 2,382 | 2,326 | 2,208 |
Every run in [`matrix.json`](matrix.json). The 128K and 262K answers were read: coherent (they list and discuss the
files of the prompt).
## Why it is fast
The kept experts are stored like IQ3_S's (IQ2_S / IQ3_XXS / IQ3_S / IQ4_XS gate and up, IQ4_NL / Q2_0 down; "IQ1_M" is
the release's name for 1.89 bits per *original* parameter), so a token costs about what IQ3_S costs where the expert is
on the CPU. But there are half as many experts: the 23 GB of them pin completely (`cudaHostRegister` of the whole
arena), a 12 GB card holds ~20% of them instead of ~10%, and the prompt path streams half as many.
## The expert profile
`data/expert-profile-coder.bin` (48 x 256, all 12,288 pairs ranked): the shipped 48 x 512 ranking, each pair mapped to
its kept expert's new index through section 2 of the release's `tensor-allocation/*.rco-allocation.txt` (the kept
experts in their original order; gate, up and down agree on which were kept), pruned pairs dropped. 5,492 of the
shipped top 8,000 survive (as PR #54 found). With 2,537 slots on the 12 GB card, 72% of the expert reads of a 4K code
prompt hit the GPU - a wrong index mapping would give about the slot share, 21%.
## Images
The Coder ships the original's vision encoder (the same file). With `--vision` on the GPU it described a Strata
Monitor screenshot correctly, windows, labels and the numbers of the dashboard and the requests table (1,079 prompt
tokens with the image, 2,012 generated at ~50 tokens/s).
## The experimental speed projection
The vector was made from the full model's activations; the Coder has the same residual stream. On the Coder it loads
(41 layers steered), costs no measurable speed (47.7-55.7 tokens/s with it on or off), and both settings write correct
answers to three coding prompts. What it is for - fewer refusals - was not measured. (Two runs of the same prompt with
it off also differ after ~130-200 characters in serve mode, so this check cannot show how far it moves ordinary text;
the teacher-forced method of `2026-09-27-esp` needs the token-by-token prompt path, which native packs do not run.)
@@ -0,0 +1,86 @@
[
{
"model": "CODER",
"tier": "1k",
"prompt_tokens": 1017,
"exit": 0,
"wall_s": 18.0,
"prefill_tok_s": 599.05,
"ttft_s": 2.54,
"decode_tok_s": 53.33,
"decoded": 256,
"spec_accept": 0.72,
"tokens_per_round": 2.86,
"vram_slots": 2570
},
{
"model": "CODER",
"tier": "4k",
"prompt_tokens": 3562,
"exit": 0,
"wall_s": 19.6,
"prefill_tok_s": 1152.17,
"ttft_s": 3.94,
"decode_tok_s": 50.6,
"decoded": 256,
"spec_accept": 0.707,
"tokens_per_round": 2.88,
"vram_slots": 2615
},
{
"model": "CODER",
"tier": "32k",
"prompt_tokens": 31566,
"exit": 0,
"wall_s": 40.7,
"prefill_tok_s": 1297.8,
"ttft_s": 25.17,
"decode_tok_s": 53.34,
"decoded": 256,
"spec_accept": 0.785,
"tokens_per_round": 3.02,
"vram_slots": 2418
},
{
"model": "CODER",
"tier": "64k",
"prompt_tokens": 64162,
"exit": 0,
"wall_s": 75.5,
"prefill_tok_s": 1350.09,
"ttft_s": 49.01,
"decode_tok_s": 50.84,
"decoded": 256,
"spec_accept": 0.696,
"tokens_per_round": 2.61,
"vram_slots": 2382
},
{
"model": "CODER",
"tier": "128k",
"prompt_tokens": 128478,
"exit": 0,
"wall_s": 121.5,
"prefill_tok_s": 1266.31,
"ttft_s": 102.95,
"decode_tok_s": 44.03,
"decoded": 256,
"spec_accept": 0.619,
"tokens_per_round": 2.33,
"vram_slots": 2326
},
{
"model": "CODER",
"tier": "262k",
"prompt_tokens": 259943,
"exit": 0,
"wall_s": 280.7,
"prefill_tok_s": 1033.91,
"ttft_s": 252.92,
"decode_tok_s": 42.8,
"decoded": 256,
"spec_accept": 0.684,
"tokens_per_round": 2.6,
"vram_slots": 2208
}
]
@@ -0,0 +1,47 @@
# Faster prompt reading (engine 0.1.13)
RTX 5070 12 GB, Ryzen 5 7600, 64 GB DDR5. 32K-token prompt (`bench/prompts/long/32k-01-code-agent.ids`), one-shot
engine, `--expert-cache auto`, greedy. Numbers are prompt tokens/s.
## What changed, step by step
| Step | What | Result |
|---|---|---|
| Buffers | The attention half and the MoE half of a layer share one scratch region (~260 KB less per prompt token) | bit-identical; chunk 4096 borrows 1.78 GiB instead of 2.84 |
| PLE rows | The next chunk's PLE rows are read from the model file on a thread while the GPU works | bit-identical; 762 -> 790 at chunk 4096 |
| Chunk size | `--prefill auto`: the largest of 8192 / 6144 / 4096 / ... whose buffers fit in the cache slots it may borrow; a request borrows only what its own prompt needs | Q2_0: 4096 791, 6144 878, 8192 973 |
| Host copies | Experts the arena could not pin are copied to pinned buffers by helper threads instead of the launching thread | bit-identical; IQ3_S at 4096: 551 -> 652 |
| PLE block | Computed for the whole chunk (two GEMMs) instead of token by token | Q2_0 981 -> 1053, IQ3_S 831 -> 940 |
| Experts | llama.cpp's MMQ kernels (weights stay quantized, int8 tensor cores) instead of dequantize-to-FP16 + cuBLAS | Q2_0 1052 -> 1130, IQ3_S (8192) 932 -> 1014 |
| Streaming | At chunks of 2048+ every non-resident expert streams in a fixed order through a ring, so the next layer's arrive while the current layer's attention runs | bit-identical; Q2_0 1130 -> 1290, IQ3_S -> 1208 |
Tried and dropped: cuBLAS grouped GEMMs for the experts (faster in isolation, slower inside the engine: 770 vs 790).
## Before and after
| | engine 0.1.12 (`--prefill 2048`) | engine 0.1.13 (`--prefill auto`) |
|---|---:|---:|
| Q2_0, 32K prompt | 572 | 1,290 |
| IQ3_S, 32K prompt | 383 | 1,208 |
| Server, Q2_0, 128K context, 999-token prompt | 353 | 438 |
| Server, 6,927-token prompt | 529 | 1,077 |
| Server, 28,584-token prompt | 584 | 1,249 |
Output speed is unchanged (the decode path is the same). Short prompts (a few hundred tokens) go through the verify
windows as before.
## Quality
- "Bit-identical" steps were checked with the GDN state hash after the 32K prompt (all 36 recurrent layers).
- The steps that change rounding (the batched PLE block, MMQ's 8-bit activations) were compared teacher-forced: the
32K prompt is read by the prompt path up to its last 256 tokens, those go through the token path with their logits
dumped. At 32K every rounding change moves the predictions about this much - even a different chunk size of the
unchanged engine - so that is the yardstick:
| Against the 8192-chunk FP16 path | Same top-1 | Mean KL | Perplexity (reference 9.21) |
|---|---:|---:|---:|
| Chunk 6144, same code (the yardstick) | 89.8% | 0.33 | 9.18 |
| MMQ experts | 85.9% | 0.38 | 8.63 |
- Needles (the answer hidden at a given depth): 5 of 5 at 1K, 32K, 128K (two depths) and 262K, Q2_0, KV streaming.
- A rerun of the same configuration is bit-identical.
@@ -0,0 +1,49 @@
# Rope scaling: needle recall past the trained 262K
The feature (PR #84, experimental): `--rope-scaling none|linear|yarn` extends the context past the model's
trained 262,144 positions by rescaling the rotary angles - llama.cpp's types, ggml's `rope_yarn`
arithmetic, one process config applied at the cos/sin table (default path) and as kernel constants
(`--native-rope`, the native indexer, prefill). The three tables here are the first end-to-end check
that a scaled run actually recalls past the trained end.
**Hardware:** RTX 4070 Ti SUPER 16 GB, Ryzen 9 5900X (no AVX-512 - the IQ3_XXS native pack runs its
expert kernels on AVX-2), 125 GB RAM, Linux. **Engine:** this branch built from source (the log records
`rope scaling ... --max-context ... against a trained context of 262144` at startup). The runs below
were taken on the rope tree built over the 0.1.17-era base; the branch then rebased onto main (engine
0.1.20) - the drift touches none of the rope files - and the rebase was re-validated: `rope_parity`
green, a 64-token greedy run token-identical to the plain 0.1.20 build, and the yarn 262k probe
repeated green (see the last table row). Baseline arm A also re-checked the default is the stock
model: a 64-token greedy run produces the same token ids as the ready-made 0.1.17 engine, and
`rope_parity` holds the `none` table bit-identical to the pre-scaling builder.
**Method:** `tools/needle_bench.py` against a running server (launch recipes in `config-*.json`),
`temperature 0`, thinking off, depth 50%, one probe per length.
| Arm | Scaling | Context | Probe | Prompt tokens | Result | Probe time |
| --- | --- | --- | --- | --- | --- | --- |
| A | none | 262,144 | 1k @ 50% | 1,442 | FOUND | 4 s |
| A | none | 262,144 | 32k @ 50% | 31,763 | FOUND | 27 s |
| A | none | 262,144 | 128k @ 50% | 128,328 | FOUND | 86 s |
| B | linear, factor 2 | 560,000 | 262k @ 50% | 274,222 | FOUND | 232 s |
| B | linear, factor 2 | 560,000 | 512k @ 50% | 527,051 | FOUND | 619 s |
| C | yarn, factor 2 | 560,000 | 262k @ 50% | 274,215 | FOUND | 236 s |
| C | yarn, factor 2 | 560,000 | 512k @ 50% | 526,988 | FOUND | 615 s |
| C | yarn, factor 2 | 560,000 | 262k @ 50% (the rebase re-check) | 273,708 | FOUND | 235 s |
All 8 of 8 probes found the needle; JSON per arm: `needle-A-none.json`, `needle-B-linear2.json`,
`needle-B-linear2-512k.json`, `needle-C-yarn2.json` (+ the rebase re-check in
`needle-C-yarn2-rebased.json`); server and engine logs alongside.
What this establishes, and what it does not:
- **Past the trained end the model still reads.** 512k probes are 2.01x the trained 262,144 - position
interpolation (B) and YaRN (C) both hold a mid-context lookup there.
- **Scaled runs within the trained range are a slightly different model.** `yarn`'s mscale applies at
every position (a 64-token greedy prompt diverges from the stock run after ~19 tokens); arm A is the
stock model, arms B/C are not, by design.
- **One depth, one needle, one machine.** A single probe per (length, arm) at depth 50% says "recall
works there", not "no degradation anywhere" - a full depth sweep and a longer-length ladder (768K,
1M) on the bench machine (64 GB RAM there vs 125 GB here, so 262K IQ3_XXS fits either way) is the
natural follow-up. 512K prompts prefill at ~850-950 tokens/s on this card; the probe time is almost
entirely prefill.
- **The `560,000` context** in arms B/C is headroom for the needle harness's tokenizer overshoot
(a "512k" probe tokenizes to ~527k); 524,288 exactly would refuse the probe.
@@ -0,0 +1,38 @@
{
"exe": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/build/strata",
"cwd": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata",
"tokenizer": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/packs/iq3_xxs/tokenizer",
"model_name": "qwen3.8-flash-next-iq3_xxs-rope-scaling",
"lib_dirs": [
"/usr/local/cuda/bin",
"/usr/local/cuda/lib64"
],
"port": 8130,
"args": [
"--pack",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/packs/iq3_xxs",
"--native",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/models/IQ3_XXS/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_XXS-00001-of-00002.gguf",
"--ple-gguf",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/models/IQ3_XXS/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_XXS-00002-of-00002.gguf",
"--expert-profile",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/data/expert-profile.bin",
"--expert-cache",
"auto",
"--prefill",
"auto",
"--spec",
"4",
"--spec-min-p",
"0.5",
"--mtp",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/mtp/rt",
"--kv",
"int8",
"--kv-resident",
"32768",
"--max-context",
"262144"
],
"log": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/bench/results/2026-09-28-rope-scaling/engine-A-none-262144.log"
}
@@ -0,0 +1,42 @@
{
"exe": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/build/strata",
"cwd": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata",
"tokenizer": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/packs/iq3_xxs/tokenizer",
"model_name": "qwen3.8-flash-next-iq3_xxs-rope-scaling",
"lib_dirs": [
"/usr/local/cuda/bin",
"/usr/local/cuda/lib64"
],
"port": 8130,
"args": [
"--pack",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/packs/iq3_xxs",
"--native",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/models/IQ3_XXS/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_XXS-00001-of-00002.gguf",
"--ple-gguf",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/models/IQ3_XXS/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_XXS-00002-of-00002.gguf",
"--expert-profile",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/data/expert-profile.bin",
"--expert-cache",
"auto",
"--prefill",
"auto",
"--spec",
"4",
"--spec-min-p",
"0.5",
"--mtp",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/mtp/rt",
"--kv",
"int8",
"--kv-resident",
"32768",
"--max-context",
"560000",
"--rope-scaling",
"linear",
"--rope-scale",
"2"
],
"log": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/bench/results/2026-09-28-rope-scaling/engine-B-linear2-524288.log"
}
@@ -0,0 +1,42 @@
{
"exe": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/build/strata",
"cwd": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata",
"tokenizer": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/packs/iq3_xxs/tokenizer",
"model_name": "qwen3.8-flash-next-iq3_xxs-rope-scaling",
"lib_dirs": [
"/usr/local/cuda/bin",
"/usr/local/cuda/lib64"
],
"port": 8130,
"args": [
"--pack",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/packs/iq3_xxs",
"--native",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/models/IQ3_XXS/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_XXS-00001-of-00002.gguf",
"--ple-gguf",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/models/IQ3_XXS/Qwen3.8-Flash-Next-GSQ-RCO-IQ3_XXS-00002-of-00002.gguf",
"--expert-profile",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/data/expert-profile.bin",
"--expert-cache",
"auto",
"--prefill",
"auto",
"--spec",
"4",
"--spec-min-p",
"0.5",
"--mtp",
"/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata-data/mtp/rt",
"--kv",
"int8",
"--kv-resident",
"32768",
"--max-context",
"560000",
"--rope-scaling",
"yarn",
"--rope-scale",
"2"
],
"log": "/home/jakub/workspace/deepseek-harness/harnes_workspace/Strata/bench/results/2026-09-28-rope-scaling/engine-C-yarn2-524288.log"
}
@@ -0,0 +1,121 @@
[
{
"length": "128k",
"round": 0,
"kind": "decode",
"prompt_tok": 128401,
"read": 52,
"gen": 357,
"read_tok_s": 79.2,
"gen_tok_s": 56.3,
"drafts_ok": 182,
"drafts_all": 305,
"seg_blocks": 3284340,
"seg_mib": 370.0,
"seg_misses": 91744,
"seg_hit_pct": 97.207,
"kib_per_token": 1061.29
},
{
"length": "128k",
"round": 1,
"kind": "read",
"prompt_tok": 128357,
"read": 128357,
"gen": 24,
"read_tok_s": 1389.2,
"gen_tok_s": 41.7,
"drafts_ok": 11,
"drafts_all": 23,
"seg_blocks": -3253560,
"seg_mib": -391.7,
"seg_misses": -97147,
"seg_hit_pct": 97.014,
"kib_per_token": -3.12
},
{
"length": "128k",
"round": 1,
"kind": "decode",
"prompt_tok": 128402,
"read": 52,
"gen": 388,
"read_tok_s": 76.5,
"gen_tok_s": 54.6,
"drafts_ok": 195,
"drafts_all": 363,
"seg_blocks": 3752616,
"seg_mib": 463.5,
"seg_misses": 114911,
"seg_hit_pct": 96.938,
"kib_per_token": 1223.26
},
{
"length": "512k",
"round": 0,
"kind": "read",
"prompt_tok": 522983,
"read": 522983,
"gen": 24,
"read_tok_s": 868.4,
"gen_tok_s": 42.4,
"drafts_ok": 13,
"drafts_all": 16,
"seg_blocks": -3808056,
"seg_mib": -387.8,
"seg_misses": -96113,
"seg_hit_pct": 97.476,
"kib_per_token": -0.76
},
{
"length": "512k",
"round": 0,
"kind": "decode",
"prompt_tok": 523028,
"read": 52,
"gen": 402,
"read_tok_s": 63.3,
"gen_tok_s": 48.4,
"drafts_ok": 221,
"drafts_all": 348,
"seg_blocks": 3580056,
"seg_mib": 920.4,
"seg_misses": 228658,
"seg_hit_pct": 93.613,
"kib_per_token": 2344.5
},
{
"length": "512k",
"round": 1,
"kind": "read",
"prompt_tok": 522984,
"read": 522984,
"gen": 24,
"read_tok_s": 857.1,
"gen_tok_s": 35.5,
"drafts_ok": 9,
"drafts_all": 18,
"seg_blocks": -3555408,
"seg_mib": -905.8,
"seg_misses": -225016,
"seg_hit_pct": 93.671,
"kib_per_token": -1.77
},
{
"length": "512k",
"round": 1,
"kind": "decode",
"prompt_tok": 523029,
"read": 52,
"gen": 402,
"read_tok_s": 61.3,
"gen_tok_s": 42.8,
"drafts_ok": 192,
"drafts_all": 388,
"seg_blocks": 4005216,
"seg_mib": 1079.9,
"seg_misses": 268221,
"seg_hit_pct": 93.303,
"kib_per_token": 2750.79
}
]
@@ -0,0 +1,29 @@
[
{
"length": "1k",
"depth": 50,
"prompt_tokens": 1442,
"seconds": 3.7,
"found": true,
"word": "meadow-copper-504",
"answer": "meadow-copper-504"
},
{
"length": "32k",
"depth": 50,
"prompt_tokens": 31763,
"seconds": 27.5,
"found": true,
"word": "falcon-quartz-940",
"answer": "falcon-quartz-940"
},
{
"length": "128k",
"depth": 50,
"prompt_tokens": 128328,
"seconds": 86.1,
"found": true,
"word": "willow-cobalt-696",
"answer": "willow-cobalt-696"
}
]
@@ -0,0 +1,11 @@
[
{
"length": "512k",
"depth": 50,
"prompt_tokens": 527051,
"seconds": 619.0,
"found": true,
"word": "meadow-copper-504",
"answer": "meadow-copper-504"
}
]
@@ -0,0 +1,16 @@
[
{
"length": "262k",
"depth": 50,
"prompt_tokens": 274222,
"seconds": 232.2,
"found": true,
"word": "meadow-copper-504",
"answer": "meadow-copper-504"
},
{
"length": "512k",
"depth": 50,
"error": "HTTP Error 400: Bad Request"
}
]
@@ -0,0 +1,11 @@
[
{
"length": "262k",
"depth": 50,
"prompt_tokens": 273708,
"seconds": 234.8,
"found": true,
"word": "meadow-copper-504",
"answer": "meadow-copper-504"
}
]
@@ -0,0 +1,20 @@
[
{
"length": "262k",
"depth": 50,
"prompt_tokens": 274215,
"seconds": 235.5,
"found": true,
"word": "meadow-copper-504",
"answer": "meadow-copper-504"
},
{
"length": "512k",
"depth": 50,
"prompt_tokens": 526988,
"seconds": 615.0,
"found": true,
"word": "falcon-quartz-940",
"answer": "falcon-quartz-940"
}
]
@@ -0,0 +1,32 @@
# Speed with engine 0.1.14
RTX 5070 12 GB (PCIe 5.0 x16), Ryzen 5 7600, 64 GB DDR5-5200, Windows 10. The ready-made 0.1.14 engine, one-shot runs
with what setup writes: `--prefill auto`, the shipped expert profile, `--expert-cache auto`, 8-bit KV above 4K, KV
streaming (`--kv-resident 32768`) from 64K, MTP (`--spec 4 --spec-min-p 0.5`), greedy, 256 generated tokens. One
code-agent prompt per length (`bench/prompts/*/…-code-agent.ids`, the same prompts as `2026-09-24-final`).
The tables: [`matrix.md`](matrix.md) (prompt and output speed, draft acceptance, experts in VRAM); every run in
[`matrix.json`](matrix.json).
- **IQ2_XS** was measured with Swift 1.5's IQ2_XS (the original's file was not on the machine; the two run at the same
speed: 4K 465 vs 467 prompt tokens/s in `2026-09-24`).
- **IQ3_S at 128K** comes from a first pass during which a large download was running (the clean rerun of that row
was stopped when Windows ran short of memory); the other rows are from runs on an otherwise idle machine.
- **IQ3_XXS and IQ3_S at 262K** are not measured: with 43 / 50 GB of experts, a 260K context exceeds 64 GB of RAM.
## Against 0.1.12
Prompts are 1.2x (1K) to 2.4x (32K-128K) faster: the 0.1.13 prompt path (chunks of up to 8,192 tokens, MMQ experts,
streamed expert copies; see `2026-09-28-prefill-speed`).
Output speed moves with the text: speculative decoding is faster when more drafts are accepted, and the new prompt
path's rounding changes the answer, so the same prompt can land a few percent either way (Q2_0 32K: acceptance 0.865
then, 0.709 now). The engine itself is not slower: on the 4K prompt, run back to back on the same machine state,
0.1.14 writes 88.5 tokens/s and 0.1.12 85.7 (3 runs each, same free VRAM).
## Found on the way
IQ3_XXS at 64K and 128K ended with `out of memory: cudaFuncSetAttribute` in the MMQ launch: CUDA loads a kernel's code
when it is first used, and mid-prompt there was no VRAM left for it. Loading every kernel at start
(`CUDA_MODULE_LOADING=EAGER`, which the engine now sets itself) fixes it for ~30 MB of VRAM; the IQ3_XXS 64K/128K rows
were measured with that fix.
@@ -0,0 +1,322 @@
[
{
"model": "Q2_0",
"tier": "1k",
"prompt_tokens": 1017,
"exit": 0,
"wall_s": 41.0,
"prefill_tok_s": 494.43,
"ttft_s": 3.55,
"decode_tok_s": 84.26,
"decoded": 256,
"spec_accept": 0.709,
"tokens_per_round": 2.72,
"vram_slots": 4176
},
{
"model": "Q2_0",
"tier": "4k",
"prompt_tokens": 3562,
"exit": 0,
"wall_s": 41.2,
"prefill_tok_s": 1006.63,
"ttft_s": 5.05,
"decode_tok_s": 90.26,
"decoded": 256,
"spec_accept": 0.846,
"tokens_per_round": 3.23,
"vram_slots": 4123
},
{
"model": "Q2_0",
"tier": "32k",
"prompt_tokens": 31566,
"exit": 0,
"wall_s": 63.1,
"prefill_tok_s": 1307.6,
"ttft_s": 25.63,
"decode_tok_s": 73.61,
"decoded": 256,
"spec_accept": 0.709,
"tokens_per_round": 2.68,
"vram_slots": 3833
},
{
"model": "Q2_0",
"tier": "64k",
"prompt_tokens": 64162,
"exit": 0,
"wall_s": 89.0,
"prefill_tok_s": 1293.5,
"ttft_s": 51.12,
"decode_tok_s": 69.02,
"decoded": 256,
"spec_accept": 0.679,
"tokens_per_round": 2.62,
"vram_slots": 3780
},
{
"model": "Q2_0",
"tier": "128k",
"prompt_tokens": 128478,
"exit": 0,
"wall_s": 147.4,
"prefill_tok_s": 1208.46,
"ttft_s": 107.81,
"decode_tok_s": 67.21,
"decoded": 256,
"spec_accept": 0.691,
"tokens_per_round": 2.64,
"vram_slots": 3697
},
{
"model": "Q2_0",
"tier": "262k",
"prompt_tokens": 259943,
"exit": 0,
"wall_s": 309.8,
"prefill_tok_s": 966.84,
"ttft_s": 270.35,
"decode_tok_s": 60.3,
"decoded": 256,
"spec_accept": 0.623,
"tokens_per_round": 2.43,
"vram_slots": 3525
},
{
"model": "IQ2_XS",
"tier": "1k",
"prompt_tokens": 1017,
"exit": 0,
"wall_s": 40.4,
"prefill_tok_s": 461.28,
"ttft_s": 2.28,
"decode_tok_s": 74.38,
"decoded": 256,
"spec_accept": 0.772,
"tokens_per_round": 2.84,
"vram_slots": 4386,
"note": "first pass"
},
{
"model": "IQ2_XS",
"tier": "4k",
"prompt_tokens": 3562,
"exit": 0,
"wall_s": 41.8,
"prefill_tok_s": 810.71,
"ttft_s": 4.48,
"decode_tok_s": 73.84,
"decoded": 256,
"spec_accept": 0.79,
"tokens_per_round": 3.24,
"vram_slots": 4336,
"note": "first pass"
},
{
"model": "IQ2_XS",
"tier": "32k",
"prompt_tokens": 31566,
"exit": 0,
"wall_s": 65.0,
"prefill_tok_s": 1237.98,
"ttft_s": 25.59,
"decode_tok_s": 71.49,
"decoded": 256,
"spec_accept": 0.856,
"tokens_per_round": 3.28,
"vram_slots": 4057,
"note": "first pass"
},
{
"model": "IQ2_XS",
"tier": "64k",
"prompt_tokens": 64162,
"exit": 0,
"wall_s": 97.1,
"prefill_tok_s": 1135.63,
"ttft_s": 56.59,
"decode_tok_s": 64.29,
"decoded": 256,
"spec_accept": 0.707,
"tokens_per_round": 2.76,
"vram_slots": 4005,
"note": "first pass"
},
{
"model": "IQ2_XS",
"tier": "128k",
"prompt_tokens": 128478,
"exit": 0,
"wall_s": 158.0,
"prefill_tok_s": 1070.61,
"ttft_s": 120.1,
"decode_tok_s": 59.76,
"decoded": 256,
"spec_accept": 0.726,
"tokens_per_round": 2.69,
"vram_slots": 3923,
"note": "first pass"
},
{
"model": "IQ2_XS",
"tier": "262k",
"prompt_tokens": 259943,
"exit": 0,
"wall_s": 332.4,
"prefill_tok_s": 886.2,
"ttft_s": 293.43,
"decode_tok_s": 52.83,
"decoded": 256,
"spec_accept": 0.628,
"tokens_per_round": 2.53,
"vram_slots": 3565,
"note": "first pass"
},
{
"model": "IQ3_XXS",
"tier": "1k",
"prompt_tokens": 1017,
"exit": 0,
"wall_s": 44.1,
"prefill_tok_s": 414.55,
"ttft_s": 2.54,
"decode_tok_s": 60.26,
"decoded": 256,
"spec_accept": 0.864,
"tokens_per_round": 3.52,
"vram_slots": 3304,
"note": "first pass"
},
{
"model": "IQ3_XXS",
"tier": "4k",
"prompt_tokens": 3562,
"exit": 0,
"wall_s": 45.8,
"prefill_tok_s": 769.57,
"ttft_s": 4.73,
"decode_tok_s": 62.07,
"decoded": 256,
"spec_accept": 0.855,
"tokens_per_round": 3.24,
"vram_slots": 3263,
"note": "first pass"
},
{
"model": "IQ3_XXS",
"tier": "32k",
"prompt_tokens": 31566,
"exit": 0,
"wall_s": 69.7,
"prefill_tok_s": 1107.75,
"ttft_s": 28.61,
"decode_tok_s": 51.4,
"decoded": 256,
"spec_accept": 0.678,
"tokens_per_round": 2.6,
"vram_slots": 3031,
"note": "first pass"
},
{
"model": "IQ3_XXS",
"tier": "64k",
"prompt_tokens": 64162,
"exit": 0,
"wall_s": 104.9,
"prefill_tok_s": 1065.31,
"ttft_s": 61.77,
"decode_tok_s": 49.95,
"decoded": 256,
"spec_accept": 0.741,
"tokens_per_round": 2.94,
"vram_slots": 2965,
"note": "first pass"
},
{
"model": "IQ3_XXS",
"tier": "128k",
"prompt_tokens": 128478,
"exit": 0,
"wall_s": 172.1,
"prefill_tok_s": 1015.11,
"ttft_s": 128.12,
"decode_tok_s": 45.78,
"decoded": 256,
"spec_accept": 0.665,
"tokens_per_round": 2.35,
"vram_slots": 2901,
"note": "first pass"
},
{
"model": "IQ3_S",
"tier": "1k",
"prompt_tokens": 1017,
"exit": 0,
"wall_s": 56.2,
"prefill_tok_s": 396.13,
"ttft_s": 4.14,
"decode_tok_s": 51.52,
"decoded": 256,
"spec_accept": 0.826,
"tokens_per_round": 3.12,
"vram_slots": 2617
},
{
"model": "IQ3_S",
"tier": "4k",
"prompt_tokens": 3562,
"exit": 0,
"wall_s": 57.5,
"prefill_tok_s": 736.9,
"ttft_s": 6.35,
"decode_tok_s": 51.6,
"decoded": 256,
"spec_accept": 0.747,
"tokens_per_round": 2.89,
"vram_slots": 2580
},
{
"model": "IQ3_S",
"tier": "32k",
"prompt_tokens": 31566,
"exit": 0,
"wall_s": 82.5,
"prefill_tok_s": 1069.83,
"ttft_s": 31.33,
"decode_tok_s": 48.19,
"decoded": 256,
"spec_accept": 0.772,
"tokens_per_round": 2.94,
"vram_slots": 2383
},
{
"model": "IQ3_S",
"tier": "64k",
"prompt_tokens": 64162,
"exit": 0,
"wall_s": 115.0,
"prefill_tok_s": 1069.8,
"ttft_s": 61.47,
"decode_tok_s": 48.75,
"decoded": 256,
"spec_accept": 0.786,
"tokens_per_round": 2.9,
"vram_slots": 2347
},
{
"model": "IQ3_S",
"tier": "128k",
"prompt_tokens": 128478,
"exit": 0,
"wall_s": 191.8,
"prefill_tok_s": 931.09,
"ttft_s": 138.11,
"decode_tok_s": 40.48,
"decoded": 256,
"spec_accept": 0.661,
"tokens_per_round": 2.71,
"vram_slots": 2310,
"note": "first pass (a download was running)"
}
]
@@ -0,0 +1,35 @@
### Prompt processing (tok/s)
| Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Q2_0 | 494 | 1,007 | 1,308 | 1,294 | 1,208 | 967 |
| IQ2_XS | 461 | 811 | 1,238 | 1,136 | 1,071 | 886 |
| IQ3_XXS | 415 | 770 | 1,108 | 1,065 | 1,015 | - |
| IQ3_S | 396 | 737 | 1,070 | 1,070 | 931 | - |
### Output (tok/s)
| Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Q2_0 | 84.3 | 90.3 | 73.6 | 69.0 | 67.2 | 60.3 |
| IQ2_XS | 74.4 | 73.8 | 71.5 | 64.3 | 59.8 | 52.8 |
| IQ3_XXS | 60.3 | 62.1 | 51.4 | 50.0 | 45.8 | - |
| IQ3_S | 51.5 | 51.6 | 48.2 | 48.8 | 40.5 | - |
### Draft acceptance
| Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Q2_0 | 0.709 | 0.846 | 0.709 | 0.679 | 0.691 | 0.623 |
| IQ2_XS | 0.772 | 0.790 | 0.856 | 0.707 | 0.726 | 0.628 |
| IQ3_XXS | 0.864 | 0.855 | 0.678 | 0.741 | 0.665 | - |
| IQ3_S | 0.826 | 0.747 | 0.772 | 0.786 | 0.661 | - |
### Experts in VRAM
| Model | 1K | 4K | 32K | 64K | 128K | 262K |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Q2_0 | 4,176 | 4,123 | 3,833 | 3,780 | 3,697 | 3,525 |
| IQ2_XS | 4,386 | 4,336 | 4,057 | 4,005 | 3,923 | 3,565 |
| IQ3_XXS | 3,304 | 3,263 | 3,031 | 2,965 | 2,901 | - |
| IQ3_S | 2,617 | 2,580 | 2,383 | 2,347 | 2,310 | - |
@@ -0,0 +1,59 @@
# Layer split: the features that were off across GPUs (2026-09-29)
**Setup:**
- **Rig:** RIBPC. RTX 5080 16 GB (CUDA0) + RTX 3090 24 GB; RTX 2080 Ti 22 GB as a helper card. Ryzen 9 9950X3D,
62 GB RAM, Windows 11.
- **Model:** the Coder (IQ1_M native pack).
- **Build:** branch `mg/limits`, sm_75 + sm_86 + sm_120.
- **Correctness runs:** `rib_norepeat.py`, with a fixed cache, no swaps, no PCIe share and no suffix drafts where two
runs are compared byte for byte.
- **Speed runs:** `rib_pp.py`.
- Raw results are in `data/`.
## Correctness
| Check | Result |
|---|---|
| One GPU, this branch vs the 0.1.21-equivalent run (fixed cache) | 10/10 byte-identical |
| Speed projection (control vector), one GPU vs the split on the same GPU (K=24) | 10/10 byte-identical |
| KV streaming (128K context, 32K resident), one GPU vs the split on the same GPU | 10/10 byte-identical |
| Images, one GPU vs the split on the same GPU (three questions about a test picture) | identical answers |
| Speed projection / KV streaming / plain, 5080 + 3090 (K=24): no-repeat under presence 100, the 144 sanity case | all pass, 0 repeats |
| The vector acts across GPUs: 5080 + 3090 with vs without the vector | 8 of 10 answers differ (as on one GPU: 7 of 10) |
| Images, 5080 + 3090 (K=24): "What color is the circle?" / "What number?" / describe | "Red" / "42" / "A red circle, the number 42, and a blue square are displayed on a white background" |
| Mid-prompt checkpoint (every 16,384 tokens): a 19.5K prompt, then another sharing its first 18,600 tokens | one GPU and 5080 + 3090: reused 16,384, answer right |
| 5080 + 3090 (K=30) + a 2080 Ti helper holding 3,000 experts | all pass |
| `--mmap-experts` with the Coder (a native pack) | a clear start-up error: it needs a canonical pack's `experts.bin` (with or without a split) |
Across GPUs the text is not byte-identical to one GPU: experts that one card leaves to the CPU run on a GPU in the
split, and the two round differently.
## Speed
| Run | Prompt 16K / 28K / 60K tok/s | Decode story / code tok/s | Hits |
|---|---|---|---|
| KV streaming (128K), 5080 alone | 1,487 / 1,801 / 1,837 | 66.6 / 74.0 | 59-71% |
| KV streaming (128K), 5080 + 3090 auto | 1,685 / 1,957 / 1,968 | 75.8 / 97.0 | 96-97% |
| Speed projection, 5080 + 3090 auto | 1,813 / 2,053 / - | 77.0 / 103.6 | 96-97% |
| 5080 + 3090 (K=30), no helper | - | 62.5 / 85.2 | |
| 5080 + 3090 (K=30) + 2080 Ti helper | - | 43.0 / 63.7 | |
**The helper card makes it slower,** as it did without a split (bench/results/2026-09-29-layer-split). Its per-layer
round trip costs more than the CPU pool needs for the few experts it would take over. It stays supported: the
helpers take the visible GPUs no stage runs on and hold only experts no stage's cache holds. But it isn't
recommended.
## What changed
- **Per-device tables:** the image-position (mrope) table and the control vector's tables are now kept per device;
every stage's rope and steering kernels read their own device's copy. `cvec_set_enabled` switches the vector on
every device.
- **KV streaming:** each stage's session streams its own KV host copy (pinned, portable). The split no longer forces
it off.
- **Mid-prompt checkpoints:** `Prefill::on_stage_chunk` lets each stage save its part of a checkpoint when it has
read the chunk. The last stage assembles the parts: the earlier ones are a chunk ahead by then.
- **Helpers (`--expert-cache-remote`) with a split:** they take the visible devices after the stages, skip every
pair a stage's cache holds, and preflight and open on those devices.
- **Explicit `--expert-cache` with a split:** it is capped so CUDA0 keeps room for the prompt path's own buffers.
Without the cap, the first prompt failed with "device buffers ... do not fit".
- **`--mmap-experts`:** refused at start for a native pack, with the reason.
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 102,
"drafts_offered": 166,
"tok_s": 66.7,
"text": "The wind didn\u2019t just howl; it screamed. It tore through the iron gallery of the Blackwood Point Light, rattling the glass panes like a prisoner testing the bars of a cell.\n\nElias Thorne was not a man who feared the dark. He was a man who feared the silence.\n\nFor forty years, he had lived in the shadow of the Fresnel lens, a rotating eye of glass and brass that swept the Atlantic with a beam of pure, unyielding light. His job was simple: keep the light burning. Keep the logbook current. Keep the foghorn sounding when the mist rolled in, thick as wool.\n\nBut Elias knew something else. He knew that the light was not just for ships. It was for the lost.\n\nIt was November 14th, 1982. The storm was a Category 5, a monster that had swallowed the horizon. The waves were mountains of black water, crashing against the granite"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 134,
"drafts_offered": 180,
"tok_s": 80.8,
"text": "```python\nimport csv\nfrom typing import List, Dict\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, str]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 72.5,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 74.6,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 104,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 92,
"tok_s": 45.4,
"text": "Aardvark \nAlbatross \n\nAlligator \nAnaconda \nAntelope \nBaboon \nBadger \nBat \nBear \nBeaver \nBirds of Paradise (Manucode) \nBoa Constrictor \n\nBuffalo \nCamel \nCapybara \nCat \nChameleon\nCheetah\n\nChicken\n\n\nCrocodile \nDeer \n\nDingo \nDog \nDonkey \nDragonfly \n\nElephant \nEmu \nFalcon \nFerret \nFlamingo \nFox<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 72,
"repeats": 0,
"drafts_accepted": 15,
"drafts_offered": 70,
"tok_s": 46.1,
"text": "The wind didn\u2019t just howl; it screamed against the glass of Lantern House 42, demanding entry into every crack and crevice that had been sealed with tar in previous years but now wept like old wounds on an open sore skin\u2014like a wound left unhealed by time. Elias knew this rhythm better than he remembered his own name<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 76,
"tok_s": 49.3,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in my teeth, searching for something to break its rhythm against stone that refused be broken by anything less than God himself had forged into being willing and able withstand eternity\u2019s cruelty without complaint or mercy from start finish until now forevermore if any such concept even existed within human comprehension let alone divine authority which we all knew did not exist but wished it would because wishful thinking is always easier to imagine hopelessly impossible truths as probable lies than vice versa.\n\nElias Thorne was eighty-three years old today, exactly one hundred and forty-seven days since his last heart attack had been logged by Dr Armitage who himself retired seven months ago after receiving a subpoena from the county clerk indicating he might have overstayed professional courtesy boundaries"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 94,
"repeats": 0,
"drafts_accepted": 27,
"drafts_offered": 56,
"tok_s": 46.7,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKangaroo \nKoala \nHippo \nRhino \nCheetah \nLeopard \nJaguar \nWolf \nFox \nBear \nDeer \nElk \nMoose \nBuffalo \nCamel \nDonkey \nGoat \nSheep \nCow \nBull \nOx \nPig \nBoar \nRabbit \nSquirrel <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 62.4,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 37,
"drafts_offered": 148,
"tok_s": 56.4,
"text": "The phrase \"the History Of The Sea\" is inherently ambiguous, as it could refer to several distinct historical narratives: geological formation (how oceans came into existence), maritime navigation and exploration by humans throughout human civilization; or perhaps more specifically in modern times refers often used metaphorically for economic trends like stock market fluctuations (\"sea change\"). However given your request likely intends an academic-style comprehensive overview covering both natural science aspects AND cultural significance through time periods\u2014especially since this kind question appears frequently on exams asking students write essays discussing evolution from primordial origins up present-day impacts including environmental changes due climate crisis etc.\u2014I will craft such broad yet structured piece combining geology + anthropology/ecological impact together while maintaining coherent flow suitable high-level educational context without overly technical jargon but still accurate enough scientifically grounded facts where appropriate within reasonable scope limits typical school assignments expect (~80\u201312% word count range implied implicitly though not stated explicitly here we aim moderate length ~65 words? Wait no user said \u201clong\u201d so probably >3k+ characters minimum safe bet around 7-9 paragraphs total approximating maybe close-to-full-page standard double-spaced equivalent which translates roughly \u22654K chars safely acceptable). Let me proceed accordingly ensuring clarity structure intro body conclusion style formal tone neutral informative balanced perspective acknowledging uncertainties if any minor ones exist e.g exact dates vary depending sources hence use hedging language sparingly only when necessary avoid overclaiming certainty especially regarding speculative theories vs established consensus knowledge base current mainstream understanding circa early-mid"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 102,
"drafts_offered": 166,
"tok_s": 67.1,
"text": "The wind didn\u2019t just howl; it screamed. It tore through the iron gallery of the Blackwood Point Light, rattling the glass panes like a prisoner testing the bars of a cell.\n\nElias Thorne was not a man who feared the dark. He was a man who feared the silence.\n\nFor forty years, he had lived in the shadow of the Fresnel lens, a rotating eye of glass and brass that swept the Atlantic with a beam of pure, unyielding light. His job was simple: keep the light burning. Keep the logbook current. Keep the foghorn sounding when the mist rolled in, thick as wool.\n\nBut Elias knew something else. He knew that the light was not just for ships. It was for the lost.\n\nIt was November 14th, 1982. The storm was a Category 5, a monster that had swallowed the horizon. The waves were mountains of black water, crashing against the granite"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 134,
"drafts_offered": 180,
"tok_s": 81.0,
"text": "```python\nimport csv\nfrom typing import List, Dict\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, str]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 62.4,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 73.4,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 104,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 92,
"tok_s": 43.5,
"text": "Aardvark \nAlbatross \n\nAlligator \nAnaconda \nAntelope \nBaboon \nBadger \nBat \nBear \nBeaver \nBirds of Paradise (Manucode) \nBoa Constrictor \n\nBuffalo \nCamel \nCapybara \nCat \nChameleon\nCheetah\n\nChicken\n\n\nCrocodile \nDeer \n\nDingo \nDog \nDonkey \nDragonfly \n\nElephant \nEmu \nFalcon \nFerret \nFlamingo \nFox<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 72,
"repeats": 0,
"drafts_accepted": 15,
"drafts_offered": 70,
"tok_s": 46.1,
"text": "The wind didn\u2019t just howl; it screamed against the glass of Lantern House 42, demanding entry into every crack and crevice that had been sealed with tar in previous years but now wept like old wounds on an open sore skin\u2014like a wound left unhealed by time. Elias knew this rhythm better than he remembered his own name<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 76,
"tok_s": 48.9,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in my teeth, searching for something to break its rhythm against stone that refused be broken by anything less than God himself had forged into being willing and able withstand eternity\u2019s cruelty without complaint or mercy from start finish until now forevermore if any such concept even existed within human comprehension let alone divine authority which we all knew did not exist but wished it would because wishful thinking is always easier to imagine hopelessly impossible truths as probable lies than vice versa.\n\nElias Thorne was eighty-three years old today, exactly one hundred and forty-seven days since his last heart attack had been logged by Dr Armitage who himself retired seven months ago after receiving a subpoena from the county clerk indicating he might have overstayed professional courtesy boundaries"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 94,
"repeats": 0,
"drafts_accepted": 27,
"drafts_offered": 56,
"tok_s": 46.8,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKangaroo \nKoala \nHippo \nRhino \nCheetah \nLeopard \nJaguar \nWolf \nFox \nBear \nDeer \nElk \nMoose \nBuffalo \nCamel \nDonkey \nGoat \nSheep \nCow \nBull \nOx \nPig \nBoar \nRabbit \nSquirrel <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 63.1,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 37,
"drafts_offered": 148,
"tok_s": 56.6,
"text": "The phrase \"the History Of The Sea\" is inherently ambiguous, as it could refer to several distinct historical narratives: geological formation (how oceans came into existence), maritime navigation and exploration by humans throughout human civilization; or perhaps more specifically in modern times refers often used metaphorically for economic trends like stock market fluctuations (\"sea change\"). However given your request likely intends an academic-style comprehensive overview covering both natural science aspects AND cultural significance through time periods\u2014especially since this kind question appears frequently on exams asking students write essays discussing evolution from primordial origins up present-day impacts including environmental changes due climate crisis etc.\u2014I will craft such broad yet structured piece combining geology + anthropology/ecological impact together while maintaining coherent flow suitable high-level educational context without overly technical jargon but still accurate enough scientifically grounded facts where appropriate within reasonable scope limits typical school assignments expect (~80\u201312% word count range implied implicitly though not stated explicitly here we aim moderate length ~65 words? Wait no user said \u201clong\u201d so probably >3k+ characters minimum safe bet around 7-9 paragraphs total approximating maybe close-to-full-page standard double-spaced equivalent which translates roughly \u22654K chars safely acceptable). Let me proceed accordingly ensuring clarity structure intro body conclusion style formal tone neutral informative balanced perspective acknowledging uncertainties if any minor ones exist e.g exact dates vary depending sources hence use hedging language sparingly only when necessary avoid overclaiming certainty especially regarding speculative theories vs established consensus knowledge base current mainstream understanding circa early-mid"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 85,
"drafts_offered": 172,
"tok_s": 36.6,
"text": "The wind didn\u2019t just howl; it screamed. It tore through the gaps in the iron railing of the Lantern Room, carrying the scent of brine and rotting kelp. Elias pulled his woolen collar up to his chin, his fingers numb and clumsy inside thick leather gloves.\n\nHe was the last man on the island. The automated systems had failed three winters ago, a glitch in the solar-powered backup that no engineer had been able to fix before the supply boat stopped coming. Now, it was just him, the light, and the sea.\n\nElias climbed the spiral stairs, his boots heavy with salt spray. The iron steps groaned under his weight, a sound that seemed to mock the storm outside. He reached the top, the glass walls vibrating with the force of the gale. Outside, the world was a blur of black and white. The ocean was invisible; it was only a presence, a weight pressing against the stone foundation of the tower.\n\nHe"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 173,
"tok_s": 96.8,
"text": "```python\nimport csv\nfrom typing import List, Dict\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, str]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 88.7,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 94.1,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 104,
"repeats": 0,
"drafts_accepted": 30,
"drafts_offered": 96,
"tok_s": 55.9,
"text": "Aardvark \nAlbatross \n\nAlligator \nAnaconda \nAntelope \nBaboon \nBadger \nBat \nBear \nBeaver \nBirds of Paradise (Manucode) \nBoa Constrictor \n\nBuffalo \nCamel \nCapybara \nCat \nChameleon\nCheetah\n\nChicken\n\n\nCrocodile \nDeer \n\nDingo \nDog \nDonkey \nDragonfly \n\nElephant \nEmu \nFalcon \nFerret \nFlamingo \nFox<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 114,
"tok_s": 55.4,
"text": "The wind didn\u2019t just howl; it screamed against the glass of Lantern House 42, demanding entry into every crack and crevice that had been sealed with tar in previous years but now wept like old wounds at this one\u2019s age: forty-three winters since its construction. My name was Elias Thorne\u2014no middle initial\u2014and I kept no logbook for visitors because there were none; only ships passed by, their hulls cutting through the black water as if they too feared what lay beneath it more than above or beside them in fog so thick that even my own reflection vanished from sight when looked upon directly.\n\nI climbed each morning not to greet dawn but check whether light still lived within me\u2014the bulb\u2019s heart had failed twice before, once during a storm named *Harrow*,"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 110,
"tok_s": 52.2,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in my teeth, searching for something to hold onto until I died.\u201d said Elias Thorne as he adjusted his collar against November\u2019s bite.** He was not merely complaining of cold weather but articulating a poetic metaphor: that even after death (metaphorically), there would be no relief from this relentless gale.\n\nElias had been the last human keeper on any significant stretch coast since automation became standard practice, yet here he stood , stubbornly refusing to let technology replace his vigilance . His cottage perched atop a crumbling granite foundation was more than just shelter; it housed centuries of handwritten logs detailing every ship\u2019s name and cargo load that passed within sight.\n\nOn most nights Elias tended the light mechanically\u2014"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 91,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 69,
"tok_s": 58.7,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKangaroo \nKoala \nHippopotamus \nRhinoceros \nLeopard \nCheetah \nPanther \nWolf \nFox \nBear \nDeer \nElk \nMoose \nBuffalo \nCamel \nDonkey \nGoat \nSheep \nCow \nBull \nOx \nPig \nDog \nCat<|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 12,
"repeats": 0,
"drafts_accepted": 4,
"drafts_offered": 17,
"tok_s": 60.3,
"text": "1 2,3456789<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 41,
"drafts_offered": 168,
"tok_s": 59.9,
"text": "The phrase \"the History Of The Sea\" is inherently ambiguous, as it could refer to several distinct historical narratives: geological formation (how oceans came into existence), maritime navigation and exploration histories (\"history at/over/across/on/in/of/to/from/toward/off by means around above below through under upon alongside against among between within without throughout despite during after before since until while when where who whom whose which what how why whether\"), or perhaps even metaphorical interpretations involving human interaction with water bodies over time periods like ages eras epochs generations centuries decades years months days hours minutes seconds moments instants times occasions instances events episodes incidents occurrences situations circumstances conditions states phases stages steps processes procedures sequences series chains cycles loops repetitions iterations continuations progressions developments evolutions transformations changes alterations modifications adjustments corrections revisions amendments updates upgrades improvements enhancements refinements optimizations perfecting completing finishing concluding ending terminating ceasing stopping halting pausing resuming continuing proceeding advancing progressing moving shifting translating rotating reflecting mirroring flipping turning twisting bending curving arches spirals helix coils twists turns rotations reflections mirrors flips inversions reversings reversion backtracking retracing reversing undo redo do again repeat repeats repeated repeatedly repetition repetitive repetitiveness repete repeater repeatingly recurrent recurrence recurring recurs recur recurred recursion recursive recursively recurse recessed recessionary regress regression regressed retrograde reverse reversed reversal revert reverted reverberate resonated echo echoed echoing echoes echolocation sonar radar radio telecommunication communication communications communicate communicated communicating communicative comms comm chatter chat chats chatting chatted talk talks talked"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 171,
"tok_s": 62.5,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and shadow.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined his life for thirty years.\n\nElias was not a man of many words. He was a man of many tasks. His hands, mapped with scars from broken glass and rusted iron, moved with a rhythm that predated his memory. He checked the clockwork mechanism of the Fresnel lens, ensuring the rotation was smooth, silent, and precise. *Tick. Tick. Tick.* The sound was the heartbeat of the coast."
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 135,
"drafts_offered": 184,
"tok_s": 85.2,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 78.1,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 80.5,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 59,
"repeats": 0,
"drafts_accepted": 13,
"drafts_offered": 48,
"tok_s": 46.6,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBuffalo \n\nCat \nCheetah \n\nChimpanzee \nClam \n\n\nCrocodile \n\n\n\nDolphin \n\n\n\n\nDonkey <|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 30,
"drafts_offered": 101,
"tok_s": 51.3,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral vaults below Elias\u2019s boots smelled faintly brine and old coffee as he climbed spiral stairs worn smooth by decades feet his hands were calloused from gripping iron railings that had been polished to a dull shine\n\nElias was not merely keeping light alive; He kept order intact. Every night, at precisely 18:07 hours local time\u2014adjusted for leap seconds and magnetic drift\u2014he wound the clockwork mechanism inside brass housing behind Fresnel lens This wasn\u2019t vanity It\u2019s necessity The rotation speed must remain constant or ships lose their bearing in fog where compasses fail\n\nHe spoke softly to himself while working, murmuring numbers aloud because silence made him"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 67,
"repeats": 0,
"drafts_accepted": 14,
"drafts_offered": 68,
"tok_s": 41.2,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in his teeth, like he had no mouth to hold back its fury anymore that night.\u201d Elara pulled her wool coat tighter around herself as she climbed spiral staircase of **Blackrock Lighthouse**. Her boots were slick with salt spray from previous days.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 91,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 54,
"tok_s": 48.8,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKoala \nKangaroo \nHippopotamus \nRhinoceros \nCamel \nCheetah \nLeopard \nJaguar \nBear \nWolf \nFox \nDog \nCat \nMouse \nRat \nSquirrel \nDeer \n\nAntelope \n\nBuffalo \n\n\nBison \n\n\n\nElk \n\n\n\n\nMoose <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 11,
"tok_s": 68.8,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 32,
"drafts_offered": 107,
"tok_s": 52.3,
"text": "The ocean is not merely water; it has been, and remains to this day one giant storybook that weaves together geology with biology as wellas human culture into an epic narrative spanning billions years from its earliest beginnings in primordial times through modern-day ecological crises The vast expanse covers more than seventy percent Earth\u2019s surface area making up nearly all life support systems on our planet yet despite being so large humans have only explored less five thousand square kilometers out total 361 million km\u00b2 which means over ninety-nine point nine eight six per cent unexplored territory This makes oceans arguably larger unknown territories compared space exploration where NASA estimates they\u2019ve mapped roughly ten billion stars versus current marine surveys covering just two hundred fifty-six species documented globally against estimated millions undiscovered organisms including deep-sea creatures like anglerfish or vampire squid whose existence was first recorded centuries ago but still remain poorly understood due extreme pressures depths exceeding eleven miles below Pacific Trenches Mariana located near Guam island chain reaching maximum depth known today at approximately thirty-seven feet under pressure equivalent atmospheric levels causing structural collapse if unprotected by specialized vessels capable handling such conditions without failure mechanisms designed specifically for submersible operations operating within these zones require titanium hulls rated withstand forces greater gravity acting upon objects falling vertically downward acceleration rates calculated using Newtonian physics principles applied universally across gravitational fields regardless medium density variations present throughout different layers stratosphere troposphere mesospheres thermoclines etceterally speaking however when discussing aquatic environments instead air masses replace gases dissolved oxygen nitrogen arg"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 94,
"drafts_offered": 169,
"tok_s": 43.0,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy-three years old, and he had been the keeper of the Blackwood Point Light for forty of those years. The other thirty-three had been spent in a small, drafty cottage that smelled perpetually of kerosene and damp wood.\n\nFor decades, Elias had tended the great Fresnel lens, polishing the glass prisms until they gleamed like diamonds, winding the clockwork mechanism that turned the beam, and trimming the wick of the oil lamp. It was a job of patience, of precision, of silence. The light was his charge. The sea was his neighbor.\n\nBut this morning, the sea was not his neighbor. It was his enemy.\n\nThe storm had been forecasted for three days, but it had arrived in one. The barometric pressure had dropped so sharply that Elias\u2019s ears popped."
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 178,
"tok_s": 63.7,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 56.8,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 57.7,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 94,
"repeats": 0,
"drafts_accepted": 24,
"drafts_offered": 49,
"tok_s": 43.3,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nHippopotamus \nRhinoceros \nCheetah \nLeopard \nJaguar \nPuma \nWolf \nFox \nBear \nDeer \nElk \nMoose \nAntelope \nBison \nBuffalo \nCamel \nDonkey \nGoat \nSheep \nCow \nDog \nCat \nRabbit \nMouse \nHamster \nGuinea Pig<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 35,
"drafts_offered": 108,
"tok_s": 41.4,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral vaults below Elias\u2019s boots smelled faintly brine and old coffee as he climbed spiral stairs worn smooth by decades feet his hands were calloused from gripping iron railings that had been polished to a dull shine.\n\nElias wasn\u2019t just keeping light; was guarding silence the beam swept across black water every seven seconds cutting through fog so thick it swallowed sound entirely for three nights straight no ships answered its signal not even fishing trawlers seeking shelter they\u2019d long abandoned this stretch of coast where currents shifted unpredictably pulling debris from sunken wrecks into tangled heaps that snagged anchors and tore hulls open like wounds.\n\nHe remembered the last time someone had"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 59,
"repeats": 0,
"drafts_accepted": 11,
"drafts_offered": 46,
"tok_s": 33.7,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in his teeth, trying to pry them apart like loose planks of weathered oak outside your own mouth again and then you are going away from home is fine but for now let me write a proper story.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 60,
"repeats": 0,
"drafts_accepted": 17,
"drafts_offered": 48,
"tok_s": 43.2,
"text": "Aardvark \nAntelope allonax (Oryx) \u2013 *Note: Using standard names for clarity* \n\nLet me provide a clean list of exactly **1** animal name on each single physical text file/terminal output stream with no other characters except the newline (\\n).<|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 11,
"tok_s": 48.9,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 27,
"drafts_offered": 105,
"tok_s": 40.2,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element\u2014it became civilization itself through its capacity as both barrier (to land) but also conduit between lands\u2014and thus gave rise directly from being part nature\u2019s own evolutionary engine into becoming humanity's greatest historical actor across all time scales since first emergence until now present-day future projections still ongoing today within our current era context window limits allowed here by system constraints set above threshold levels required before output generation begins below line break marker inserted immediately after final punctuation mark ending entire preceding paragraph block structure defined earlier during initial drafting phase prior execution stage completed successfully without errors detected internally via internal validation routines executed automatically upon completion cycle termination signal received end-of-file condition triggered correctly according standard protocol specifications followed strictly adhered-to throughout process flow control logic implemented using structured programming constructs compatible with target runtime environment configuration parameters specified herein explicitly declared beforehand at top-level scope declaration section located near beginning source code file header comments included inline documentation style guidelines observed consistently applied uniformly everywhere else similarly formatted sections appearing later down document body text area scrolling vertically downward directionally aligned left margin justified right edge wrapped around fixed-width column boundaries determined dynamically based available screen real estate measured pixel-per-unit scaling factor adjusted responsively depending viewport dimensions reported back client-side rendering pipeline components responsible"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 94,
"drafts_offered": 167,
"tok_s": 62.7,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the fabric stiff with salt and ice. Outside the thick glass of the lantern room, the Atlantic was a churning cauldron of black and white, the waves crashing against the granite base with a violence that shook the very foundations of the tower. It was November, 1924, and the storm had been raging for three days.\n\nElias was not a man who feared the dark. He was a man who feared the silence.\n\nHe checked the clock on the wall. 11:42 PM. The rotation of the great Fresnel lens was steady, a rhythmic *whirrr-click, whirrr-click* that seemed to hold the chaos of the ocean at bay. The beam swept out, a blade of pure, concentrated light cutting through the rain and sleet, illuminating the jagged teeth of the reef below.\n\n"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 179,
"tok_s": 81.3,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 71.0,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 71.9,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 61,
"repeats": 0,
"drafts_accepted": 18,
"drafts_offered": 52,
"tok_s": 47.9,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBoar \nBuffalo \nButterfly \n\nCamel \nCat \nCheetah \n\nDolphin \n\n\nElephant \n\n\n\nFalcon <|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 122,
"tok_s": 47.4,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral vaults below Elias\u2019s boots were heavy with salt and exhaustion as he climbed one hundred twelve steps to reach his post at dawn every morning for thirty years. His name was written on a brass plaque by door: **Elias Thorne**.\n\nHe wasn\u2019t just keeping light; He kept time, weathered hands gripping the cold iron railing until knuckles turned white against frostbite and windburn that never healed fully because they were always there waiting to be felt again when he stepped outside into storm or calm alike. The lens above him was a Fresnel giant\u2019s eye made of hand-ground crystal prisms each one polished daily with chamois cloth soaked in whale"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 65,
"repeats": 0,
"drafts_accepted": 11,
"drafts_offered": 58,
"tok_s": 42.6,
"text": "The wind didn\u2019t just blow at Cape Hollow; it screamed through the gaps in my cottage\u2019s shutters, rattling them like loose teeth until they slammed against their hinges every night for eleven months of winter and summer alike.\u201d I had never seen calm water here\u2014only gray churning seas that swallowed ships smaller than themselves<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 101,
"repeats": 0,
"drafts_accepted": 32,
"drafts_offered": 53,
"tok_s": 49.4,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKoala \nKangaroo \nCheetah \nHippopotamus \nRhinoceros \nLeopard \nJaguar \nWolf \nFox \nBear \nDeer \nOtter \nBeaver \nBadger \nMongoose \nMeerkat \nHyena \nJackal \nAntelope \nBison \nElk \nCaribou \nLlama \nAlpace <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 12,
"tok_s": 64.7,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 52,
"drafts_offered": 148,
"tok_s": 49.9,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element within our planet\u2019s geography but rather its most fundamental biological engine\u2014a living system whose existence predates humanity by hundreds millions years yet continues shaping every aspect from climate regulation through evolutionary biology down even into modern industrial logistics today's global supply chains depend upon them directly indirectly via atmospheric pressure systems weather patterns migration routes food webs carbon cycles oxygen generation thermal buffering tidal forces salinity gradients depth stratification light attenuation currents circulation gyres upwelling zones thermoclines halothermics bathymetric features continental shelves abyssal plains trenching ridges volcanic arcs island formations atoll structures seamounts submarine canons canyon walls cliff faces overhang ledge platforms reef flats fore-reef back-lagoon inner-shelf outer-slope drop-offs pinnacle pinnacles spires columns arches tunnels caves grottos swim-through passages chimney vents blowholes fissures cracks crevices joints faults fractures breaks gaps holes openings void spaces hollow chambers cavernous interiors subterranean networks underground rivers lakes pools springs wells shaft pits sinks drains sewers pipes conduits tubes channels gutters ditches furrows grooves rills runnels streams brooks rivulets creeks tributaries branches forks splits divergences con"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 94,
"drafts_offered": 167,
"tok_s": 49.2,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the fabric stiff with salt and ice. Outside the thick glass of the lantern room, the Atlantic was a churning cauldron of black and white, the waves crashing against the granite base with a violence that shook the very foundations of the tower. It was November, 1924, and the storm had been raging for three days.\n\nElias was not a man who feared the dark. He was a man who feared the silence.\n\nHe checked the clock on the wall. 11:42 PM. The rotation of the great Fresnel lens was steady, a rhythmic *whirrr-click, whirrr-click* that seemed to hold the chaos of the ocean at bay. The beam swept out, a blade of pure, concentrated light cutting through the rain and sleet, illuminating the jagged teeth of the reef below.\n\n"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 179,
"tok_s": 76.2,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 73.1,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 73.4,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 61,
"repeats": 0,
"drafts_accepted": 18,
"drafts_offered": 52,
"tok_s": 47.1,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBoar \nBuffalo \nButterfly \n\nCamel \nCat \nCheetah \n\nDolphin \n\n\nElephant \n\n\n\nFalcon <|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 122,
"tok_s": 47.0,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral vaults below Elias\u2019s boots were heavy with salt and exhaustion as he climbed one hundred twelve steps to reach his post at dawn every morning for thirty years. His name was written on a brass plaque by door: **Elias Thorne**.\n\nHe wasn\u2019t just keeping light; He kept time, weathered hands gripping the cold iron railing until knuckles turned white against frostbite and windburn that never healed fully because they were always there waiting to be felt again when he stepped outside into storm or calm alike. The lens above him was a Fresnel giant\u2019s eye made of hand-ground crystal prisms each one polished daily with chamois cloth soaked in whale"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 65,
"repeats": 0,
"drafts_accepted": 11,
"drafts_offered": 58,
"tok_s": 42.3,
"text": "The wind didn\u2019t just blow at Cape Hollow; it screamed through the gaps in my cottage\u2019s shutters, rattling them like loose teeth until they slammed against their hinges every night for eleven months of winter and summer alike.\u201d I had never seen calm water here\u2014only gray churning seas that swallowed ships smaller than themselves<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 101,
"repeats": 0,
"drafts_accepted": 32,
"drafts_offered": 53,
"tok_s": 50.0,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKoala \nKangaroo \nCheetah \nHippopotamus \nRhinoceros \nLeopard \nJaguar \nWolf \nFox \nBear \nDeer \nOtter \nBeaver \nBadger \nMongoose \nMeerkat \nHyena \nJackal \nAntelope \nBison \nElk \nCaribou \nLlama \nAlpace <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 12,
"tok_s": 64.6,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 52,
"drafts_offered": 148,
"tok_s": 49.8,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element within our planet\u2019s geography but rather its most fundamental biological engine\u2014a living system whose existence predates humanity by hundreds millions years yet continues shaping every aspect from climate regulation through evolutionary biology down even into modern industrial logistics today's global supply chains depend upon them directly indirectly via atmospheric pressure systems weather patterns migration routes food webs carbon cycles oxygen generation thermal buffering tidal forces salinity gradients depth stratification light attenuation currents circulation gyres upwelling zones thermoclines halothermics bathymetric features continental shelves abyssal plains trenching ridges volcanic arcs island formations atoll structures seamounts submarine canons canyon walls cliff faces overhang ledge platforms reef flats fore-reef back-lagoon inner-shelf outer-slope drop-offs pinnacle pinnacles spires columns arches tunnels caves grottos swim-through passages chimney vents blowholes fissures cracks crevices joints faults fractures breaks gaps holes openings void spaces hollow chambers cavernous interiors subterranean networks underground rivers lakes pools springs wells shaft pits sinks drains sewers pipes conduits tubes channels gutters ditches furrows grooves rills runnels streams brooks rivulets creeks tributaries branches forks splits divergences con"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 97,
"drafts_offered": 176,
"tok_s": 73.0,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was the last of the line, the only man left on the jagged spine of Widow\u2019s Rock. For forty years, he had tended the light. For forty years, he had listened to the ocean\u2019s endless, hungry whisper.\n\nTonight was different. The storm wasn\u2019t just a storm; it was a *tempest*. The waves didn\u2019t crash; they *shattered*. They threw themselves against the granite base of the lighthouse with the force of a battering ram, sending tremors up through the soles of Elias\u2019s boots.\n\nHe climbed the spiral stairs, one hundred and twelve steps of iron and rust. His hand, calloused and scarred from decades of polishing the brass railing, found the familiar groove in the metal. He didn\u2019t look up. He didn\u2019t look down. He looked *forward*."
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 135,
"drafts_offered": 176,
"tok_s": 102.5,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 90.9,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 91.7,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 118,
"repeats": 0,
"drafts_accepted": 38,
"drafts_offered": 103,
"tok_s": 62.8,
"text": "Aardvark \nAlbatross \nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird of Paradise \nBuffalo \nCamel \nCheetah \nChimpanzee \nCrane \nCrocodile \nDeer \nDolphin \nDonkey \nElephant \nFlamingo \nFox \nGiraffe \nGoat \nHippopotamus \nIguana \nJaguar \nKangaroo \nKoala \nLion \nLeopard \nMonkey \nMoose \nOstrich \nOtter\nPanda<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 58,
"repeats": 0,
"drafts_accepted": 9,
"drafts_offered": 63,
"tok_s": 50.4,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty jail cell below sea level three hundred feet down to where Elias kept his boots dry and ready for emergencies when he had no time even think about changing them.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 140,
"repeats": 0,
"drafts_accepted": 22,
"drafts_offered": 90,
"tok_s": 50.1,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in his teeth, demanding entry into every crack and crevice of Stone Harbor Lighthous eKeepers have always been solitary figures against isolation\u2019s greatest adversary: time itself Elias Thorne knew this intimately. For thirty years he had kept watch over one hundred thousand ships each night scanning for lights that might signal disaster or hope\n\nHis job was simple but deadly serious He checked weather logs every day without fail The foghorn echoed through the tower three times before dawn Each morning at 4:02 AM Elias would climb eighty-two steps to reach his office where coffee cooled beside a single candle flickering in drafts from below<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 96,
"repeats": 0,
"drafts_accepted": 31,
"drafts_offered": 69,
"tok_s": 58.9,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKangaroo \nKoala \nHippo \nRhino \nCheetah \nLeopard \nJaguar \nPanther \nCougar \nBear \nWolf \nFox \nDeer \nElk \nMoose \nBison \nBuffalo \nAntelope \nCamel \nDonkey \nGoat \nSheep \nCow \nPig \nHorse <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 71.2,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 37,
"drafts_offered": 119,
"tok_s": 61.3,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth\u2019s surface.\u201d \u2014 *This quote by Rachel Carson serves as an apt introduction.* The story we tell today begins with that first drop falling into primordial pools billions years ago\u2014but what follows next? What happens when those drops become oceans themselves\u2014when they begin moving across continents carrying sediment from mountains down rivers until finally reaching their final resting place at last\u2014the deep abyssal plains where sunlight cannot reach nor pressure crushes everything else alive there below depths greater than six kilometers beneath waves crashing against shores above them all around world forever changing shape over time due plate tectonics shifting landmass slowly apart together again repeatedly throughout geological epochs spanning hundreds millions or even thousands million+ yrs since formation early Hadean era through Archeozoic eons onward present Holocene epoch still ongoing now right here under current conditions observed via satellites monitoring global climate patterns affecting currents temperatures salinity levels biodiversity distribution human impacts altering ecosystems directly indirectly causing extinction events locally globally recently historically prehistorically anciently mythologically culturally spiritually philosoph scientifically mathematically physically chem biol geo astro cosmological sociopolitical economic environmental ecological evolutionary genetic linguistic literary artistic musical cinematic theatrical photographic journalistic digital analog virtual real tangible intangible metaphysical ontosemantic semiotic symbolic allegorical narrative descriptive analytical interpretive evaluative critical reflective introspective contemplatory ponderous thoughtful considerate attentive observant perceptive insightful intelligent knowledgeable informed educated literati learned skilled capable"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 87,
"drafts_offered": 170,
"tok_s": 71.4,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and green.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined his life for thirty years.\n\nElias was not a man of many words. He was a man of many tasks. His hands, mapped with scars from rope burns and broken glass, moved with a rhythm that predated his memory. He checked the clockwork mechanism of the Fresnel lens, listening for the faint, rhythmic *tick-tick-tick* that signaled the rotation was true. If the rhythm faltered, ships"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 178,
"tok_s": 104.1,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 90.1,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 94.8,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 76,
"repeats": 0,
"drafts_accepted": 20,
"drafts_offered": 64,
"tok_s": 61.6,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBuffalo \nButterfly \nCamel \nCat \nCaterpillar \n\nCentipede \n\nChameleon \n\nCheetah \n\nChicken \n\nCricket \n\nCrocodile \n\n\nDolphin \n\n\n\nDonkey \n\n\n\n\nDragon<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 21,
"drafts_offered": 90,
"tok_s": 59.2,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty jail cell below sea level to above cloud height at once\u2014no matter how high you climbed or deep into its foundations down through rock and saltwater. It was November again when Elias first noticed that his logbook had begun recording entries he hadn\u2019t written yet: *Storm surge expected Tuesday, but the sky is clear.*\n\nHe stopped writing for a moment to listen\u2014not with ears alone\u2014but by pressing both palms flat against cold iron walls where condensation formed in slow spirals like fingerprints left behind after touching something invisible. The metal hummed faintly beneath his skin as if remembering old storms long past their dates on paper calendars torn from newspapers dated three years ahead of today"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 17,
"drafts_offered": 63,
"tok_s": 52.6,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in his teeth, trying to get inside where he had spent thirty years refusing entry except for family and friends like him who were old enough too know better than young men ever could be again about what safety meant. It was November 2014 when Elias Thorne first stepped onto that spiral staircase with only one boot on because his other foot hurt after walking from the harbor office all morning long before anyone else thought he needed shoes off there for work even if nobody saw them do it but everyone knew why so they did not ask him anymore once this happened already twice last winter near same season now here again today too soon then tomorrow maybe never will come back unless someone sends message tonight via radio signal code Morse dot dash pattern"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 94,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 60,
"tok_s": 58.8,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nKangaroo \nPanda \nKoala \nCheetah \nWolf \nFox \nBear \nMonkey \nDolphin \nWhale \nShark \nOctopus \nJellyfish \nCrab \nSpider \nRabbit \nCat \nDog \nHorse \nCow \nSheep \nGoat \nChicken \nRooster \nTurkey \nEagle \nFalcon <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 71.8,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 52,
"drafts_offered": 148,
"tok_s": 61.8,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\u201d \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \u201cthe Sea\u201d or \"Ocean\" (terms often used interchangeably but distinct geographically) was never just an element within our planet\u2019s geography\u2014it became its engine itself\u2014a living system whose evolution shaped everything else around us from climate patterns down through evolutionary biology right up into modern civilization's economic structures cultural identities political boundaries ecological systems scientific understanding technological capabilities philosophical frameworks psychological experiences artistic expressions literary metaphors cinematic narratives musical compositions architectural designs urban planning strategies agricultural practices industrial processes medical discoveries legal precedents educational curricula religious traditions spiritual beliefs mythological stories folkloric tales poetic verses dramatic scripts visual arts photographic images digital media social networks behavioral norms emotional responses cognitive models decision-making algorithms predictive analytics statistical probabilities probabilistic reasoning causal inference counterfactual thinking hypothetical scenarios normative ethics deontal logic modal semantics temporal dynamics spatial relations topographical features hydrological cycles meteor phenomena atmospheric chemistry geological formations tectonic shifts volcanic eruptions seismic events tsunamis hurricanes cyclones monsoons typhoons droughts floods storms winds currents waves swells breakers surf foam spray mist fog rain snow ice glaciers berg floes drift pack-ice brine salinity pH alkalines acidity hardness softness temperature pressure depth light darkness color hue saturation brightness contrast luminance chroma tone value shade tint reflection"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 187,
"tok_s": 58.7,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, mapped with veins like the roots of an ancient oak, gripped the cold iron railing.\n\nFor forty years, Elias had tended the light at the end of the world. Not the end of the map, but the end of the land. Here, the Atlantic threw itself against the granite cliffs with such violence that the spray reached the gallery, thirty feet above the rocks.\n\nHe checked the clock. 11:42 PM. The fog was thickening, a gray soup that swallowed the horizon. In the old days, he would have climbed the stairs to the lantern room, polished the Fresnel lens, and wound the clockwork mechanism that turned the great beam. But the automation had changed"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 135,
"drafts_offered": 179,
"tok_s": 80.7,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 71.8,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 73.0,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 61,
"repeats": 0,
"drafts_accepted": 19,
"drafts_offered": 49,
"tok_s": 49.1,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBoar \nBuffalo \nButterfly \n\nCamel \nCat \nCheetah \n\nDolphin \n\n\nElephant \n\n\n\nFlamingo<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 80,
"repeats": 0,
"drafts_accepted": 6,
"drafts_offered": 52,
"tok_s": 46.2,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral hall outside my window at midnight on November third this year\u2014so I pulled up to check what was happening with that storm and why they were calling me out there when all we had left for tonight\u2019s shift is one last pass through our logs before dawn breaks.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 17,
"drafts_offered": 83,
"tok_s": 43.9,
"text": "The wind didn\u2019t just blow at Cape Harrow; it screamed through the gaps in its teeth, hungry and relentless for three centuries of stone salt-spray history that had long since forgotten to be polite or gentle with itself against anything smaller than God Himself was willing acknowledge as divine protection from drowning souls who were already lost before they ever touched water.\n\nElias knew this rhythm better he himself could hear it sometimes; his boots worn smooth by pacing the spiral stair\u2019s center-post groove where thousands upon countless steps had rubbed away texture until all that remained beneath sole leather now felt like polished bone carved out slowly but surely over time into something more hollow than wood ever really should be allowed become when used so often enough within such confined circular spaces meant only for vertical ascent down always descending again back upward never truly leaving"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 62,
"repeats": 0,
"drafts_accepted": 18,
"drafts_offered": 31,
"tok_s": 49.1,
"text": "Lion \nTiger \n\nElephant \n \nGiraffe \n\n\n\nHippopotamus \nZebra \nRhinoceros \n\n\nPanda Bear \n\n\n\n\nKangaroo \nKoala \nSloth \n\n\n\n\n\nCheetah \nLeopard \nPanther \n\n\n\n\n\n\nWolf \nFox \nBear <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 62.7,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 47,
"drafts_offered": 153,
"tok_s": 52.2,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element within our planet\u2019s geography but rather its most fundamental biological engine\u2014a living system whose existence predates humanity by billions years yet continues shaping every aspect from climate regulation through evolutionary biology all way down modern industrial supply chains today's global shipping lanes carry cargo across vast distances because they were once crossed only slowly via coastal hopping routes used since antiquity until steamships revolutionized travel during late nineteenth century when diesel engines replaced wind power entirely allowing container ships carrying thousands TEUs (Twenty-foot Equivalent Units) per voyage crossing entire Pacific basins without stopping at intermediate ports anymore unlike earlier sailing vessels needing favorable winds currents seasonal patterns dictating departure windows based upon monsoon calendars established centuries ago before technological advances changed those constraints fundamentally altering logistics networks worldwide thereby transforming economic structures globally while simultaneously creating ecological disruptions due invasive species transported unintentionally alongside legitimate goods causing cascading ecosystem collapses locally where non-native organisms outcompete native fauna disrupting food webs leading population declines affecting biodiversity indices measured scientifically using standardized metrics like Shannon-Wiener diversity index calculations applied consistently over decades revealing trends showing significant decreases post-introduction events correl strongly with introduction dates matching historical records documenting first sightings confirmed taxonomically classified formally according International Code Nomenclature rules"
}
]
@@ -0,0 +1,84 @@
{
"label": "lim-cv-pp-x2",
"extra": [
"--layer-split",
"auto",
"--control-vector-scaled",
"C:\\Users\\ribbon\\strata-test\\Strata\\data\\experimental-speed-projection\\Qwen3.8-Flash-Next-experimental-speed-projection.gguf:1.0",
"--control-vector-layer-range",
"4",
"44",
"--cvec-mode",
"project",
"--cvec-dir",
"per-layer"
],
"gpus": "0,2",
"needles": [
{
"tokens": 15925,
"prompt_ms": 8783.1,
"pp_tok_s": 1813.1,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27474,
"prompt_ms": 13379.7,
"pp_tok_s": 2053.4,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3652.4,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 246.8,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 77.0,
"reps": [
77.0
],
"hit_rate": 0.9718150054476462,
"text": "The wind didn\u2019t just howl; it screamed. It tore through the gaps in the iron railing of the Lantern Room, carrying the scent of brine and rotting kelp. Elias pulled his woolen collar up to his chin, his fingers numb and clumsy inside thick leather gloves.\n\nHe was the last man on the island. The automated systems had failed three winters ago, a glitch in the solar panel array that no engineer had been able to fix before the supply boat stopped running. Now, it was just him, the light, and the sea.\n\nElias climbed the spiral stairs, his boots heavy with salt spray. The iron steps groaned under his weight, a sound that seemed to mock the storm outside. He reached the top, the glass walls vibrating with the force of the gale.\n\nInside the lantern room, the great Fresnel lens sat like a dormant eye. It was cold, dark, and waiting.\n\nElias moved to the control panel. His"
},
{
"name": "code",
"tok_s": 103.6,
"reps": [
103.6
],
"hit_rate": 0.957731667226356,
"text": "```python\nimport csv\nfrom typing import List, Dict\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, str]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False"
}
],
"stage_timing": [],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: layer split auto: CUDA0 84 SMs at 2.81 GHz -> 0.31 ms per layer, 8.30 GiB for experts",
"strata generate: layer split auto: CUDA1 82 SMs at 1.86 GHz -> 0.48 ms per layer, 14.14 GiB for experts",
"strata generate: layer split auto: K=22 - predicted 19.7 ms per decode window; the caches hold 11298 of 12288 profiled pairs (~99.7% of the routed mass)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 3479 slots, 6.22 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 22-47, expert cache 6656 slots (13.34 GiB), 6656 of its 6656 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-21",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-21 (CUDA0), 22-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 91.6% (5554 hits / 6062 lookups)",
"strata serve: decode expert cache hit rate: 95.3% (3592 hits / 3771 lookups)",
"strata serve: decode expert cache hit rate: 96.5% (3650 hits / 3782 lookups)",
"strata serve: decode expert cache hit rate: 96.3% (25027 hits / 25980 lookups)",
"strata serve: decode expert cache hit rate: 96.5% (3201 hits / 3318 lookups)",
"strata serve: decode expert cache hit rate: 97.2% (129334 hits / 133085 lookups)",
"strata serve: decode expert cache hit rate: 95.8% (108375 hits / 113158 lookups)"
]
}
@@ -0,0 +1,76 @@
{
"label": "lim-kv-pp-single",
"extra": [
"--max-context",
"131072",
"--kv-resident",
"32768"
],
"gpus": "0",
"needles": [
{
"tokens": 15925,
"prompt_ms": 10706.3,
"pp_tok_s": 1487.4,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27474,
"prompt_ms": 15256.7,
"pp_tok_s": 1800.8,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 59725,
"prompt_ms": 32515.5,
"pp_tok_s": 1836.8,
"found": true,
"answer": "falcon-174<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4006.4,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 276.2,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 66.6,
"reps": [
66.6
],
"hit_rate": 0.7101864314556545,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the fabric stiff with salt and ice. Outside the thick glass of the lantern room, the Atlantic was a churning cauldron of black and white, the waves crashing against the granite base with a violence that shook the very foundations of the tower. It was November, 1924, and the storm had been raging for three days.\n\nElias was not a man who feared the dark. He was a man who feared the silence.\n\nHe checked the clock on the wall. 11:42 PM. The rotation of the great Fresnel lens was steady, a rhythmic *whirrr-click, whirrr-click* that seemed to sync with his own heartbeat. He had been doing this for forty years. His hands were mapped with scars from rope burns and broken glass, his eyes permanently squinted against the glare of the bulb."
},
{
"name": "code",
"tok_s": 74.0,
"reps": [
74.0
],
"hit_rate": 0.5873783774988882,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [],
"engine_lines": [
"strata generate: expert cache auto: 8.99 GiB free, 700 MiB reserved -> 3349 slots",
"strata generate: expert cache 2434 slots, 4.67 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 1944 cache slots (3.72 GiB)",
"strata serve: decode expert cache hit rate: 41.8% (1746 hits / 4176 lookups)",
"strata serve: decode expert cache hit rate: 50.4% (1512 hits / 3000 lookups)",
"strata serve: decode expert cache hit rate: 54.9% (1689 hits / 3077 lookups)",
"strata serve: decode expert cache hit rate: 60.3% (1938 hits / 3213 lookups)",
"strata serve: decode expert cache hit rate: 58.3% (11146 hits / 19121 lookups)",
"strata serve: decode expert cache hit rate: 66.1% (2201 hits / 3331 lookups)",
"strata serve: decode expert cache hit rate: 71.0% (74397 hits / 104757 lookups)",
"strata serve: decode expert cache hit rate: 58.7% (54151 hits / 92191 lookups)"
]
}
@@ -0,0 +1,87 @@
{
"label": "lim-kv-pp-x2",
"extra": [
"--max-context",
"131072",
"--kv-resident",
"32768",
"--layer-split",
"auto"
],
"gpus": "0,2",
"needles": [
{
"tokens": 15925,
"prompt_ms": 9452.6,
"pp_tok_s": 1684.7,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27474,
"prompt_ms": 14042.4,
"pp_tok_s": 1956.5,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 59725,
"prompt_ms": 30345.9,
"pp_tok_s": 1968.1,
"found": true,
"answer": "falcon-174<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3806.1,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 245.6,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 75.8,
"reps": [
75.8
],
"hit_rate": 0.9698055042592483,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and shadow.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined Elias\u2019s life for thirty years. He stood before the great Fresnel lens, a cathedral of glass and brass that caught the light of the single, powerful bulb and fractured it into a thousand beams.\n\nHe checked the clock. 11:42 PM. The rotation was smooth, silent, a hypnotic sweep that painted the darkness with a slow, rhythmic pulse. *Flash. Dark. Flash. Dark.*\n\nE"
},
{
"name": "code",
"tok_s": 97.0,
"reps": [
97.0
],
"hit_rate": 0.9562207324057541,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 17.96 GiB free",
"strata generate: layer split auto: CUDA0 84 SMs at 2.81 GHz -> 0.31 ms per layer, 8.13 GiB for experts",
"strata generate: layer split auto: CUDA1 82 SMs at 1.86 GHz -> 0.48 ms per layer, 13.98 GiB for experts",
"strata generate: layer split auto: K=22 - predicted 19.7 ms per decode window; the caches hold 11199 of 12288 profiled pairs (~99.7% of the routed mass)",
"strata generate: expert cache auto: 10.30 GiB free, 700 MiB reserved -> 3277 slots",
"strata generate: expert cache 3409 slots, 6.09 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 22-47, expert cache 6656 slots (13.34 GiB), 6656 of its 6656 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-21",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-21 (CUDA0), 22-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 91.3% (5098 hits / 5585 lookups)",
"strata serve: decode expert cache hit rate: 94.7% (4000 hits / 4225 lookups)",
"strata serve: decode expert cache hit rate: 96.5% (4095 hits / 4245 lookups)",
"strata serve: decode expert cache hit rate: 97.8% (4183 hits / 4278 lookups)",
"strata serve: decode expert cache hit rate: 96.2% (27177 hits / 28250 lookups)",
"strata serve: decode expert cache hit rate: 97.6% (4177 hits / 4281 lookups)",
"strata serve: decode expert cache hit rate: 97.0% (130241 hits / 134296 lookups)",
"strata serve: decode expert cache hit rate: 95.6% (113534 hits / 118732 lookups)"
]
}
@@ -0,0 +1,24 @@
{
"answers": {
"single": [
" Red",
"42",
"A red circle, the number 42, and a blue square are arranged on a white background."
],
"same24": [
" Red",
"42",
"A red circle, the number 42, and a blue square are arranged on a white background."
],
"x2": [
" Red",
"42",
"A red circle, the number 42, and a blue square are displayed on a white background."
]
},
"correct": {
"single": true,
"same24": true,
"x2": true
}
}
@@ -0,0 +1,84 @@
# Layer split across GPUs (2026-09-29)
**Rig (a community member's PC, "RIBPC"):**
- Ryzen 9 9950X3D, 62 GB DDR5, Windows 11, CUDA 13.1.
- RTX 5080 16 GB (x16), RTX 3090 24 GB (x4 gen4, ~6 GB/s), RTX 2080 Ti 22 GB (x4 gen3, Turing sm_75; the
experimental `STRATA_EXPERIMENTAL_SM75` build).
**Model:** the Coder (GSQ-RCO IQ1_M, 48 layers x 256 experts = 12,288 expert pairs, ~23 GB of experts), 32K
context, int8 KV, `--prefill auto`, `--spec 4`. It is the only model that fits the rig's RAM and disk.
**Tools:** `rib_pp.py` (needle prompts at 2.7K/8K/16K/28K tokens with a code word at 50% depth, a 2-turn chat that
must reuse its checkpoint, greedy decode of a story and a code prompt) and `rib_norepeat.py` (the Phase A
no-repeat and sanity cases). Raw results are in `data/`.
## Correctness
| Check | Result |
|---|---|
| This branch vs the 0.1.20 release, one GPU (5080), fixed cache, all 10 cases | **10/10 byte-identical** |
| This branch, one GPU vs the split on the same GPU (K=24, `--split-device 0`) | **10/10 byte-identical**: the hand-off is exact |
| 5080 + 3090 (auto) and 5080 + 2080 Ti + 3090 (auto): no-repeat under presence 100 (greedy and sampled), 144 sanity | all pass, 0 repeats |
| Needles at 2.7K-28K tokens, every configuration | all found |
| 2-turn chat (the checkpoint restored on every GPU) | reused 6,631-6,632 of 6,660 tokens, answer right |
Across GPUs the text is not byte-identical to one GPU: experts the CPU computed on one card run on a GPU in the split,
and they round differently (the same effect as `--expert-cache`, bench/results/2026-09-27-cache-parity).
## Speed
**Prompt tok/s** at 16K / 28K tokens, and **decode tok/s** for story / code (greedy; three repeats averaged
in the K sweep):
| Configuration | Split | Prompt 16K | Prompt 28K | Decode story | Decode code | Decode hits |
|---|---|---|---|---|---|---|
| 0.1.20, 5080 alone | - | 2,045 | 2,005 | 83.2 | 88.2 | 70-84% |
| this branch, 5080 alone | - | 2,017 | 1,974 | 83.1 | 88.0 | 70-84% |
| this branch, 5080 alone, 3 repeats (adaptive swaps learn the prompt) | - | 1,726 | 1,970 | 86.7 | 105.2 | 91-92% |
| 3090 alone | - | 1,253 | 1,211 | 71.7 | 92.9 | 86-93% |
| 5080 + 3090 | K=18 | 1,547 | 1,694 | 77.2 | 103.8 | 99.9% |
| 5080 + 3090 | K=22 | 1,840 | 2,074 | 80.2 | 106.4 | 99.8% |
| 5080 + 3090 | K=24 | 1,978 | 2,258 | 83.5 | 109.5 | 99.5% |
| **5080 + 3090** | **K=26** | **2,039** | **2,357** | **83.8** | **109.7** | 99.3% |
| 5080 + 3090 | K=28 | 2,011 | 2,287 | 83.5 | 112.5 | 99.0% |
| 5080 + 3090 | K=30 | 1,935 | 2,218 | 81.1 | 109.6 | 98% |
| 5080 + 3090 | K=32 | 1,818 | 2,063 | 83.0 | 108.7 | 98% |
| 5080 + 3090 | auto (K=22) | 2,037 | 2,073 | 79.5 | 108.9 | 99.7% |
| 3090 + 5080 | auto (K=30) | 1,672 | 1,876 | 90.3 | 105.1 | 99.4% |
| 5080 + 2080 Ti + 3090 | auto (22, 23) | 1,687 | 1,847 | 67.6 | 89.6 | 99.7% |
| 5080 + 3090 + 2080 Ti | first auto (12, 33) | 1,148 | 1,144 | 51.9 | 69.7 | 100% |
| 5080 + 2080 Ti | first auto | 914 | 906 | 45.5 | 58.0 | 97% |
What decides it:
- **The best two-GPU split for the Coder is K=26-28.** Prompts run +18-20% over the 5080 alone at 16-28K;
decode is on par for story and +4-7% for code.
- **Residency saturates quickly: the per-layer GPU speed decides.** From K=18 up, both caches hold 98-100% of
the routed experts. Moving layers to the faster card is what helps: the 3090 costs 0.50 ms per layer per
window, the 5080 0.33 (`STRATA_SPLIT_TIMING`, the "wait for the GPU" per stage).
- **Decode is bounded by that GPU time.** One 5080 with its cache adapted spends 16.3 ms waiting for its GPU and
5.5 ms in the CPU pool per window. The split removes the pool (0.8-1.0 ms) but adds the slower card's layers:
7.9 + 12.1 ms at K=24. The per-layer GPU half (kernels plus the host round trip that publishes each layer's
expert plan) is the next thing to cut, for one GPU as well (perf-review-plan E-6).
- **A third, slower card costs more than it holds.** The 2080 Ti's one-layer stage took 3.1 ms per window.
For a model that already fits two cards, leave it out. It pays only when the two faster cards cannot hold most
of the routed experts (a bigger model; not testable on this rig's 62 GB of RAM).
- **Prompt chunk:** 1024 instead of 2048 frees ~0.7 GB per card, but prompts read 7-12% slower.
## Auto placement
`--layer-split auto` predicts one decode window's time for every placement (every placement up to three GPUs) and
keeps the fastest:
- **per-layer time** from SMs x clock (0.33 ms for an RTX 5080);
- **cache contents:** each card's layers' profiled pairs, hottest first, as the fill does;
- **routed mass** of rank r taken as (r+1)^-1.2 (fits the sweep's hit rates within 0.1-1%);
- **missed mass** costing 190 ms per unit (fitted to the sweep's best K; `STRATA_SPLIT_MISS_MS` overrides).
It picked K=22 on the 5080 + 3090: within 0-12% of the best fixed split on prompts and on par on decode. It gives
a slow third card a single layer.
## Reproduce
On a rig: the `strata-remote` tools, then `rib_matrix.bat`, `rib_ksweep.bat`, `rib_validate.bat` (in the dev
notes, not in this repository).
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 193,
"tok_s": 74.6,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, rough as sandpaper, gripped the iron railing as he ascended the final flight of steps to the lantern room.\n\nBelow him, the Atlantic churned in a frenzy, whitecaps exploding against the black rocks like fireworks. Above, the sky was a bruised purple, heavy with rain that hadn\u2019t yet fallen.\n\nElias checked the clock. 11:42 PM. The light was due.\n\nHe didn\u2019t flip a switch. He didn\u2019t press a button. He reached for the heavy brass lever that had worn smooth by his palm over the years. With a grunt that matched the strain of his aging muscles, he pulled it down.\n\n*Clunk.*\n\nThe"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 181,
"tok_s": 105.5,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 85.8,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 94.3,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 118,
"repeats": 0,
"drafts_accepted": 37,
"drafts_offered": 105,
"tok_s": 67.9,
"text": "Aardvark \nAlbatross \nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird of Paradise \nBuffalo \nCamel \nCheetah \nChimpanzee \nCrane \nCrocodile \nDeer \nDolphin \nDonkey \nElephant \nFlamingo \nFox \nGiraffe \nGoat \nHippopotamus \nIguana \nJaguar \nKangaroo \nKoala \nLion \nLeopard \nMonkey \nMoose \nOstrich \nOtter\nPanda<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 147,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 103,
"tok_s": 63.2,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral hall outside my window at midnight on November third this year\u2014so I pulled up to that date and time for context because you asked me about a lighthouse keeper but gave no specifics beyond \u201cshort story,\u201d so let\u2019s set it: isolated coastal station off the Oregon coast during winter storm season, 1987.\n\nMy name is Elias Vane (I made this fictional; real keepers often kept journals signed with initials only), and I\u2019m writing from memory because my logbook got waterlogged when a rogue wave breached lower storage room at dawn\u2014so here\u2019s what happened:<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 109,
"repeats": 0,
"drafts_accepted": 13,
"drafts_offered": 56,
"tok_s": 54.1,
"text": "The wind didn\u2019t just blow at Cape Hollow; it screamed through the gaps in my cottage\u2019s shutters, rattling them like loose teeth until they settled into silence with every storm break out of three hundred days here being storms that broke something else again but mostly kept me warm because I burned coal and wood whenever we had any left over from last winter when even then if you\u2019re lucky enough to get through it all without freezing yourself so hard your own thoughts become ice too\n\nBut Elias knew better than anyone: fear was useful only until dawn<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 92,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 48,
"tok_s": 64.7,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKoala \nKangaroo \nHippopotamus \nRhinoceros \nCamel \nCheetah \nLeopard \nPanther \nJaguar \nBison \nElk \nDeer \nFox \nWolf \nBear \nLlama \nDonkey \nGoat \nSheep \nCow \nPig \nRabbit \nCat \nDog<|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 14,
"tok_s": 71.7,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 53,
"drafts_offered": 134,
"tok_s": 66.1,
"text": "The ocean is not merely water; it has been, and continues to be one with humanity\u2019s most profound narrative arc in human civilization: **the story we tell ourselves through our relationship** between landmasses separated by vast expansiveness that defines both isolation from other worlds (and thus their own unique evolutionary paths) as well than connection across them via currents wind patterns tides depth pressure temperature salinity biodiversity geology climate change economic trade routes ecological systems cultural exchange political boundaries scientific discovery technological advancement environmental degradation conservation efforts maritime archaeography historical cartographic evolution legal frameworks international law enforcement mechanisms insurance liability regimes regulatory compliance standards safety protocols navigation aids communication networks emergency response strategies search-and-rescue operations salvage rights ownership disputes heritage protection zones sustainable harvesting practices aquaculture development infrastructure engineering projects coastal management policies urban planning regulations zoning ordinances building codes architectural design guidelines aesthetic considerations functional requirements accessibility provisions energy efficiency measures thermal insulation techniques moisture control methods ventilation optimization lighting solutions acoustic soundproofing structural integrity assessments seismic resistance evaluations flood mitigation plans storm surge barriers levee construction seawall reinforcement breakwater installation harbor dredging channel maintenance buoy placement lighthouse automation radar monitoring sonar mapping bathymetric surveys hydrographic charts nautical almanacs tide tables current predictions weather forecasts marine meteorological models satellite imagery remote sensing data assimilation numerical simulations computational fluid dynamics finite element analysis partial differential equations tensor calculus linear algebra probability theory statistics stochastic processes Markov chains Bayesian inference machine learning artificial neural network deep convolutional recurrent transformer attention heads generative adversarial diffusion transformers large language model tokenization"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 193,
"tok_s": 68.2,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, rough as sandpaper, gripped the iron railing as he ascended the final flight of steps to the lantern room.\n\nBelow him, the Atlantic churned in a frenzy, whitecaps exploding against the black rocks like fireworks. Above, the sky was a bruised purple, heavy with rain that hadn\u2019t yet fallen.\n\nElias checked the clock. 11:42 PM. The light was due.\n\nHe didn\u2019t flip a switch. He didn\u2019t press a button. He reached for the heavy brass lever that had worn smooth by his palm over the years. With a grunt that matched the strain of his aging muscles, he pulled it down.\n\n*Clunk.*\n\nThe"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 136,
"drafts_offered": 181,
"tok_s": 99.4,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 74.5,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 76.3,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 118,
"repeats": 0,
"drafts_accepted": 37,
"drafts_offered": 105,
"tok_s": 65.2,
"text": "Aardvark \nAlbatross \nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird of Paradise \nBuffalo \nCamel \nCheetah \nChimpanzee \nCrane \nCrocodile \nDeer \nDolphin \nDonkey \nElephant \nFlamingo \nFox \nGiraffe \nGoat \nHippopotamus \nIguana \nJaguar \nKangaroo \nKoala \nLion \nLeopard \nMonkey \nMoose \nOstrich \nOtter\nPanda<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 147,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 103,
"tok_s": 45.7,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral hall outside my window at midnight on November third this year\u2014so I pulled up to that date and time for context because you asked me about a lighthouse keeper but gave no specifics beyond \u201cshort story,\u201d so let\u2019s set it: isolated coastal station off the Oregon coast during winter storm season, 1987.\n\nMy name is Elias Vane (I made this fictional; real keepers often kept journals signed with initials only), and I\u2019m writing from memory because my logbook got waterlogged when a rogue wave breached lower storage room at dawn\u2014so here\u2019s what happened:<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 109,
"repeats": 0,
"drafts_accepted": 13,
"drafts_offered": 56,
"tok_s": 42.1,
"text": "The wind didn\u2019t just blow at Cape Hollow; it screamed through the gaps in my cottage\u2019s shutters, rattling them like loose teeth until they settled into silence with every storm break out of three hundred days here being storms that broke something else again but mostly kept me warm because I burned coal and wood whenever we had any left over from last winter when even then if you\u2019re lucky enough to get through it all without freezing yourself so hard your own thoughts become ice too\n\nBut Elias knew better than anyone: fear was useful only until dawn<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 92,
"repeats": 0,
"drafts_accepted": 26,
"drafts_offered": 48,
"tok_s": 43.9,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKoala \nKangaroo \nHippopotamus \nRhinoceros \nCamel \nCheetah \nLeopard \nPanther \nJaguar \nBison \nElk \nDeer \nFox \nWolf \nBear \nLlama \nDonkey \nGoat \nSheep \nCow \nPig \nRabbit \nCat \nDog<|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 14,
"tok_s": 61.4,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 53,
"drafts_offered": 134,
"tok_s": 51.9,
"text": "The ocean is not merely water; it has been, and continues to be one with humanity\u2019s most profound narrative arc in human civilization: **the story we tell ourselves through our relationship** between landmasses separated by vast expansiveness that defines both isolation from other worlds (and thus their own unique evolutionary paths) as well than connection across them via currents wind patterns tides depth pressure temperature salinity biodiversity geology climate change economic trade routes ecological systems cultural exchange political boundaries scientific discovery technological advancement environmental degradation conservation efforts maritime archaeography historical cartographic evolution legal frameworks international law enforcement mechanisms insurance liability regimes regulatory compliance standards safety protocols navigation aids communication networks emergency response strategies search-and-rescue operations salvage rights ownership disputes heritage protection zones sustainable harvesting practices aquaculture development infrastructure engineering projects coastal management policies urban planning regulations zoning ordinances building codes architectural design guidelines aesthetic considerations functional requirements accessibility provisions energy efficiency measures thermal insulation techniques moisture control methods ventilation optimization lighting solutions acoustic soundproofing structural integrity assessments seismic resistance evaluations flood mitigation plans storm surge barriers levee construction seawall reinforcement breakwater installation harbor dredging channel maintenance buoy placement lighthouse automation radar monitoring sonar mapping bathymetric surveys hydrographic charts nautical almanacs tide tables current predictions weather forecasts marine meteorological models satellite imagery remote sensing data assimilation numerical simulations computational fluid dynamics finite element analysis partial differential equations tensor calculus linear algebra probability theory statistics stochastic processes Markov chains Bayesian inference machine learning artificial neural network deep convolutional recurrent transformer attention heads generative adversarial diffusion transformers large language model tokenization"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 91,
"drafts_offered": 190,
"tok_s": 74.0,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, rough as sandpaper, knew every bolt, every lever, and every creak in the iron bones of the tower.\n\nFor forty years, Elias had been the guardian of the Point. Not just of the light, but of the silence between the waves.\n\nIt was November. The sky was a bruised purple, heavy with rain that hadn\u2019t yet fallen. The sea was a churning cauldron of grey and white, throwing itself against the granite base with a violence that shook the floorboards beneath Elias\u2019s boots.\n\nHe climbed the stairs. One hundred and twelve steps. He counted them by feel, his left hand trailing along the cold iron railing. At the top, the air was"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 137,
"drafts_offered": 178,
"tok_s": 103.4,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 91.1,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 70.7,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 118,
"repeats": 0,
"drafts_accepted": 38,
"drafts_offered": 108,
"tok_s": 65.4,
"text": "Aardvark \nAlbatross \nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird of Paradise \nBuffalo \nCamel \nCheetah \nChimpanzee \nCrane \nCrocodile \nDeer \nDolphin \nDonkey \nElephant \nFlamingo \nFox \nGiraffe \nGoat \nHippopotamus \nIguana \nJaguar \nKangaroo \nKoala \nLion \nLeopard \nMonkey \nMoose \nOstrich \nOtter\nPanda<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 57,
"repeats": 0,
"drafts_accepted": 11,
"drafts_offered": 61,
"tok_s": 57.1,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral vaults below Elias\u2019s boots were heavy with salt and exhaustion as he climbed one hundred twelve steps to reach top room where his world began again.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 58,
"repeats": 0,
"drafts_accepted": 14,
"drafts_offered": 54,
"tok_s": 52.9,
"text": "The wind didn\u2019t just blow at Cape Harrow; it screamed through the gaps in stone and steel, carrying with them spray from three miles out into churning black water that no longer resembled an ocean so much as abyssal chaos waiting for its prey to surface beneath waves of salt.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 59,
"repeats": 0,
"drafts_accepted": 11,
"drafts_offered": 47,
"tok_s": 51.7,
"text": "Lion \nTiger \n\nElephant\\nGiraffe\\Zebra\\rHippopotamus\\tRhinoceros\\vCrocodile\\fAlligator\\bSnake\\aSpider\\u2615CoffeeCup\u2615\ufe0fTeaTime\ud83d\udc09DragonFishWhaleDolph<|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 12,
"tok_s": 73.5,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 43,
"drafts_offered": 126,
"tok_s": 63.4,
"text": "The ocean is not merely water; it has been, and continues to be one with humanity\u2019s most profound narrative arc in human civilization: **the story told by waves**. It serves as both witness (through sedimentary layers)and participant( through currents that carry messages across continents). Its \"history\" spans geological epochs where life first emerged from primordial pools into complex ecosystems today\u2014and cultural histories shaped entirely around its roleas connector between distant lands or barrier separating them forever depending on perspective taken at different times throughout millennia since ancient civilizations began building boats outof logs carved using stone tools until modern container ships carrying billions tons cargo daily cross Atlantic routes once navigated onlyby stars alone under moonlight during nights when sailors feared monsters lurking beneath hulls made fragile against storms they could barely predict even now despite advanced meteorology systems developed over past century after industrial revolution transformed maritime trade patterns globally due economic pressures driving demand for faster logistics chains linking factories producing electronics manufactured inside cities located far inland near rivers feeding estuaries opening directly onto open seas connected via canals engineered centuries ago like Suez Canal built originallyfor military purposes later adapted commercial use post-World War II era reshaping global supply networks dramatically because geopolitical shifts altered which ports remained accessible politically versus physically blocked temporarily then reopened permanently following treaty agreements signed among nations agreeing upon shared responsibilities regarding environmental stewardship including coral reef restoration efforts funded jointly international organizations coordinating scientific research conducted aboard vessels specifically designed deep-sea exploration equipped robotic submersibles capable descending trenches deeper than any previous technology allowed"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 98,
"drafts_offered": 166,
"tok_s": 54.4,
"text": "The wind didn\u2019t just blow; it screamed. It tore through the iron gallery of the Blackwood Point Lighthouse, rattling the glass panes like a prisoner shaking its bars.\n\nElias Thorne was seventy years old, and he had been the keeper for forty of them. He knew every creak in the spiral staircase, every draft that slipped under the door, and every shift in the weather that the barometer couldn\u2019t quite predict. He knew that when the fog rolled in thick enough to swallow the horizon, the world ceased to exist. There was only the light, the stone, and him.\n\nIt was November, the month of storms. The sea was a churning cauldron of grey and white, throwing itself against the granite base with a violence that made the floorboards tremble. Elias stood by the lantern room, his hands wrapped around a mug of coffee that had gone cold hours ago. He wasn\u2019t drinking it. He was holding it for warmth."
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 137,
"drafts_offered": 163,
"tok_s": 70.7,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv(file_path: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys being the header values\n and values being the corresponding cell values.\n \n Args:\n file_path: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(file_path, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_basic():\n \"\"\"Test basic CSV parsing with simple data.\"\"\"\n import tempfile\n import os\n \n # Create a temporary CSV"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 56.7,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 57.6,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 59,
"repeats": 0,
"drafts_accepted": 13,
"drafts_offered": 47,
"tok_s": 39.7,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBuffalo \n\nCat \nCheetah \n\nChimpanzee \nClam \n\n\nCrocodile \n\n\n\nDolphin \n\n\n\n\nDonkey <|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 57,
"repeats": 0,
"drafts_accepted": 12,
"drafts_offered": 58,
"tok_s": 39.9,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral vaults below Elias\u2019s boots were heavy with salt and exhaustion as he climbed one hundred twelve steps to reach topmost gallery where his world began.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 30,
"drafts_offered": 111,
"tok_s": 34.7,
"text": "The wind didn\u2019t just blow at Blackwood Rock; it screamed through the gaps in his stone cottage like an angry ghost trying to get inside, yet finding no door left unlocked because Elias never let one stay that way for more than three seconds after entering or leaving during storm season. He had been doing this since 1987\u2014since before he even knew why\u2014and now it was muscle memory so deep into his bones and fingers\u2019 calloused skin texture patterns worn thin by years of gripping the same iron railing at midnight watch that if you asked him what time exactly, down to quarter-hour increments when fog rolled in thick enough where light beams bent around itself like old rope coiling tight on a winch drum spooling out slowly from below deck floor level storage locker number seven under stairs leading"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 96,
"repeats": 0,
"drafts_accepted": 31,
"drafts_offered": 48,
"tok_s": 42.3,
"text": "Lion \nTiger \nElephant \nGiraffe \nZebra \nPanda \nKoala \nKangaroo \nCheetah \nHippopotamus \nRhinoceros \nLeopard \nJaguar \nWolf \nFox \nBear \nDeer \nOtter \nBeaver \nBadger \nMongoose \nMeerkat \nChimpanzee \nGorilla \nOrangetutan \nAntelope \nBison \nElk <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 43.0,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 29,
"drafts_offered": 127,
"tok_s": 37.6,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\u201d \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \u201cthe Sea\u201d or \"Ocean\" (terms often used interchangeably but distinct geographically) was never just an element within our planet\u2019s geography\u2014it became its engine itself\u2014a living system whose existence predates humanity by billions years yet continues shaping us today through climate regulation oxygen production food supply trade routes cultural identity ecological resilience economic development political boundaries scientific discovery technological innovation environmental crisis conservation efforts historical memory philosophical reflection poetic metaphor musical composition cinematic storytelling literary narrative visual art architectural inspiration culinary tradition spiritual symbolism psychological influence sociological transformation educational foundation medical necessity agricultural dependency industrial revolution urban planning infrastructural engineering legal framework regulatory policy international cooperation global governance planetary stewardship existential risk mitigation sustainable practice ethical consideration moral imperative social justice democratic participation civic engagement public health community welfare individual responsibility personal accountability collective action shared experience common understanding mutual respect reciprocal trust collaborative effort cooperative endeavor joint venture unified mission integrated strategy coordinated plan synchronized schedule aligned timeline concurrent execution simultaneous operation parallel processing sequential progression iterative refinement recursive improvement continuous adaptation dynamic adjustment flexible response adaptive behavior responsive conduct proactive initiative anticipatory foresight predictive modeling probabilistic assessment statistical analysis empirical observation experimental verification theoretical hypothesis conceptual abstraction logical deduction rational inference critical evaluation analytical reasoning systematic investigation methodical inquiry structured exploration comprehensive examination detailed scrutiny meticulous inspection thorough review exhaustive audit complete survey full census total enumeration absolute"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 187,
"tok_s": 59.2,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, mapped with veins like the roots of an ancient oak, gripped the cold iron railing.\n\nFor forty years, Elias had tended the light at the end of the world. Not the end of the map, but the end of the land. Here, the Atlantic threw itself against the granite cliffs with such violence that the spray reached the gallery, thirty feet above the rocks.\n\nHe checked the clock. 11:42 PM. The fog was thickening, a gray soup that swallowed the horizon. In the old days, he would have climbed the stairs to the lantern room, polished the Fresnel lens, and wound the clockwork mechanism that turned the great beam. But the automation had changed"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 135,
"drafts_offered": 179,
"tok_s": 81.6,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 74.9,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 74.3,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 61,
"repeats": 0,
"drafts_accepted": 19,
"drafts_offered": 49,
"tok_s": 49.6,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBoar \nBuffalo \nButterfly \n\nCamel \nCat \nCheetah \n\nDolphin \n\n\nElephant \n\n\n\nFlamingo<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 80,
"repeats": 0,
"drafts_accepted": 6,
"drafts_offered": 52,
"tok_s": 44.9,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral hall outside my window at midnight on November third this year\u2014so I pulled up to check what was happening with that storm and why they were calling me out there when all we had left for tonight\u2019s shift is one last pass through our logs before dawn breaks.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 17,
"drafts_offered": 83,
"tok_s": 44.1,
"text": "The wind didn\u2019t just blow at Cape Harrow; it screamed through the gaps in its teeth, hungry and relentless for three centuries of stone salt-spray history that had long since forgotten to be polite or gentle with itself against anything smaller than God Himself was willing acknowledge as divine protection from drowning souls who were already lost before they ever touched water.\n\nElias knew this rhythm better he himself could hear it sometimes; his boots worn smooth by pacing the spiral stair\u2019s center-post groove where thousands upon countless steps had rubbed away texture until all that remained beneath sole leather now felt like polished bone carved out slowly but surely over time into something more hollow than wood ever really should be allowed become when used so often enough within such confined circular spaces meant only for vertical ascent down always descending again back upward never truly leaving"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 62,
"repeats": 0,
"drafts_accepted": 18,
"drafts_offered": 31,
"tok_s": 49.5,
"text": "Lion \nTiger \n\nElephant \n \nGiraffe \n\n\n\nHippopotamus \nZebra \nRhinoceros \n\n\nPanda Bear \n\n\n\n\nKangaroo \nKoala \nSloth \n\n\n\n\n\nCheetah \nLeopard \nPanther \n\n\n\n\n\n\nWolf \nFox \nBear <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 65.2,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 47,
"drafts_offered": 153,
"tok_s": 52.5,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element within our planet\u2019s geography but rather its most fundamental biological engine\u2014a living system whose existence predates humanity by billions years yet continues shaping every aspect from climate regulation through evolutionary biology all way down modern industrial supply chains today's global shipping lanes carry cargo across vast distances because they were once crossed only slowly via coastal hopping routes used since antiquity until steamships revolutionized travel during late nineteenth century when diesel engines replaced wind power entirely allowing container ships carrying thousands TEUs (Twenty-foot Equivalent Units) per voyage crossing entire Pacific basins without stopping at intermediate ports anymore unlike earlier sailing vessels needing favorable winds currents seasonal patterns dictating departure windows based upon monsoon calendars established centuries ago before technological advances changed those constraints fundamentally altering logistics networks worldwide thereby transforming economic structures globally while simultaneously creating ecological disruptions due invasive species transported unintentionally alongside legitimate goods causing cascading ecosystem collapses locally where non-native organisms outcompete native fauna disrupting food webs leading population declines affecting biodiversity indices measured scientifically using standardized metrics like Shannon-Wiener diversity index calculations applied consistently over decades revealing trends showing significant decreases post-introduction events correl strongly with introduction dates matching historical records documenting first sightings confirmed taxonomically classified formally according International Code Nomenclature rules"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 187,
"tok_s": 58.4,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, mapped with veins like the roots of an ancient oak, gripped the cold iron railing.\n\nFor forty years, Elias had tended the light at the end of the world. Not the end of the map, but the end of the land. Here, the Atlantic threw itself against the granite cliffs with such violence that the spray reached the gallery, thirty feet above the rocks.\n\nHe checked the clock. 11:42 PM. The fog was thickening, a gray soup that swallowed the horizon. In the old days, he would have climbed the stairs to the lantern room, polished the Fresnel lens, and wound the clockwork mechanism that turned the great beam. But the automation had changed"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 135,
"drafts_offered": 179,
"tok_s": 80.6,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 75.2,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 74.2,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 61,
"repeats": 0,
"drafts_accepted": 19,
"drafts_offered": 49,
"tok_s": 48.5,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBoar \nBuffalo \nButterfly \n\nCamel \nCat \nCheetah \n\nDolphin \n\n\nElephant \n\n\n\nFlamingo<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 80,
"repeats": 0,
"drafts_accepted": 6,
"drafts_offered": 52,
"tok_s": 46.4,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral hall outside my window at midnight on November third this year\u2014so I pulled up to check what was happening with that storm and why they were calling me out there when all we had left for tonight\u2019s shift is one last pass through our logs before dawn breaks.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 17,
"drafts_offered": 83,
"tok_s": 43.3,
"text": "The wind didn\u2019t just blow at Cape Harrow; it screamed through the gaps in its teeth, hungry and relentless for three centuries of stone salt-spray history that had long since forgotten to be polite or gentle with itself against anything smaller than God Himself was willing acknowledge as divine protection from drowning souls who were already lost before they ever touched water.\n\nElias knew this rhythm better he himself could hear it sometimes; his boots worn smooth by pacing the spiral stair\u2019s center-post groove where thousands upon countless steps had rubbed away texture until all that remained beneath sole leather now felt like polished bone carved out slowly but surely over time into something more hollow than wood ever really should be allowed become when used so often enough within such confined circular spaces meant only for vertical ascent down always descending again back upward never truly leaving"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 62,
"repeats": 0,
"drafts_accepted": 18,
"drafts_offered": 31,
"tok_s": 49.8,
"text": "Lion \nTiger \n\nElephant \n \nGiraffe \n\n\n\nHippopotamus \nZebra \nRhinoceros \n\n\nPanda Bear \n\n\n\n\nKangaroo \nKoala \nSloth \n\n\n\n\n\nCheetah \nLeopard \nPanther \n\n\n\n\n\n\nWolf \nFox \nBear <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 62.5,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 47,
"drafts_offered": 153,
"tok_s": 52.6,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element within our planet\u2019s geography but rather its most fundamental biological engine\u2014a living system whose existence predates humanity by billions years yet continues shaping every aspect from climate regulation through evolutionary biology all way down modern industrial supply chains today's global shipping lanes carry cargo across vast distances because they were once crossed only slowly via coastal hopping routes used since antiquity until steamships revolutionized travel during late nineteenth century when diesel engines replaced wind power entirely allowing container ships carrying thousands TEUs (Twenty-foot Equivalent Units) per voyage crossing entire Pacific basins without stopping at intermediate ports anymore unlike earlier sailing vessels needing favorable winds currents seasonal patterns dictating departure windows based upon monsoon calendars established centuries ago before technological advances changed those constraints fundamentally altering logistics networks worldwide thereby transforming economic structures globally while simultaneously creating ecological disruptions due invasive species transported unintentionally alongside legitimate goods causing cascading ecosystem collapses locally where non-native organisms outcompete native fauna disrupting food webs leading population declines affecting biodiversity indices measured scientifically using standardized metrics like Shannon-Wiener diversity index calculations applied consistently over decades revealing trends showing significant decreases post-introduction events correl strongly with introduction dates matching historical records documenting first sightings confirmed taxonomically classified formally according International Code Nomenclature rules"
}
]
@@ -0,0 +1,92 @@
[
{
"name": "greedy story 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 88,
"drafts_offered": 187,
"tok_s": 58.4,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, mapped with veins like the roots of an ancient oak, gripped the cold iron railing.\n\nFor forty years, Elias had tended the light at the end of the world. Not the end of the map, but the end of the land. Here, the Atlantic threw itself against the granite cliffs with such violence that the spray reached the gallery, thirty feet above the rocks.\n\nHe checked the clock. 11:42 PM. The fog was thickening, a gray soup that swallowed the horizon. In the old days, he would have climbed the stairs to the lantern room, polished the Fresnel lens, and wound the clockwork mechanism that turned the great beam. But the automation had changed"
},
{
"name": "greedy code 200",
"tokens": 200,
"repeats": 0,
"drafts_accepted": 135,
"drafts_offered": 179,
"tok_s": 80.5,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
},
{
"name": "greedy sanity",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 74.5,
"text": "144<|im_end|>"
},
{
"name": "greedy, no penalty (sanity)",
"tokens": 4,
"repeats": 0,
"drafts_accepted": 3,
"drafts_offered": 3,
"tok_s": 76.8,
"text": "144<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64",
"tokens": 61,
"repeats": 0,
"drafts_accepted": 19,
"drafts_offered": 49,
"tok_s": 49.6,
"text": "Aardvark \nAlbatross \n\nAlligator \nAntelope \nBaboon \nBadger \nBeaver \nBear \nBird \nBoar \nBuffalo \nButterfly \n\nCamel \nCat \nCheetah \n\nDolphin \n\n\nElephant \n\n\n\nFlamingo<|im_end|>"
},
{
"name": "greedy presence=100 last_n=64 story",
"tokens": 80,
"repeats": 0,
"drafts_accepted": 6,
"drafts_offered": 52,
"tok_s": 46.7,
"text": "The wind didn\u2019t just blow; it screamed against the glass of Lantern House 42, rattling like chains in an empty cathedral hall outside my window at midnight on November third this year\u2014so I pulled up to check what was happening with that storm and why they were calling me out there when all we had left for tonight\u2019s shift is one last pass through our logs before dawn breaks.<|im_end|>"
},
{
"name": "sampled presence=100 last_n=64 seed 3",
"tokens": 160,
"repeats": 0,
"drafts_accepted": 17,
"drafts_offered": 83,
"tok_s": 44.3,
"text": "The wind didn\u2019t just blow at Cape Harrow; it screamed through the gaps in its teeth, hungry and relentless for three centuries of stone salt-spray history that had long since forgotten to be polite or gentle with itself against anything smaller than God Himself was willing acknowledge as divine protection from drowning souls who were already lost before they ever touched water.\n\nElias knew this rhythm better he himself could hear it sometimes; his boots worn smooth by pacing the spiral stair\u2019s center-post groove where thousands upon countless steps had rubbed away texture until all that remained beneath sole leather now felt like polished bone carved out slowly but surely over time into something more hollow than wood ever really should be allowed become when used so often enough within such confined circular spaces meant only for vertical ascent down always descending again back upward never truly leaving"
},
{
"name": "sampled presence=100 last_n=64 seed 11",
"tokens": 62,
"repeats": 0,
"drafts_accepted": 18,
"drafts_offered": 31,
"tok_s": 50.0,
"text": "Lion \nTiger \n\nElephant \n \nGiraffe \n\n\n\nHippopotamus \nZebra \nRhinoceros \n\n\nPanda Bear \n\n\n\n\nKangaroo \nKoala \nSloth \n\n\n\n\n\nCheetah \nLeopard \nPanther \n\n\n\n\n\n\nWolf \nFox \nBear <|im_end|>"
},
{
"name": "greedy frequency=100 last_n=16",
"tokens": 11,
"repeats": 0,
"drafts_accepted": 5,
"drafts_offered": 13,
"tok_s": 62.0,
"text": "1 to 50, separated by commas:<|im_end|>"
},
{
"name": "greedy presence=100 last_n=4096 (8-row windows)",
"tokens": 300,
"repeats": 0,
"drafts_accepted": 47,
"drafts_offered": 153,
"tok_s": 52.1,
"text": "The ocean is not merely water; it has been, and remains to this day one giant machine for making life possible on Earth.\" \u2014 *David Attenborough* \n\nThis quote captures perhaps more than any other single sentence in human language: that which we call \"the Sea\" or \u201cOcean\u201d was never just an element within our planet\u2019s geography but rather its most fundamental biological engine\u2014a living system whose existence predates humanity by billions years yet continues shaping every aspect from climate regulation through evolutionary biology all way down modern industrial supply chains today's global shipping lanes carry cargo across vast distances because they were once crossed only slowly via coastal hopping routes used since antiquity until steamships revolutionized travel during late nineteenth century when diesel engines replaced wind power entirely allowing container ships carrying thousands TEUs (Twenty-foot Equivalent Units) per voyage crossing entire Pacific basins without stopping at intermediate ports anymore unlike earlier sailing vessels needing favorable winds currents seasonal patterns dictating departure windows based upon monsoon calendars established centuries ago before technological advances changed those constraints fundamentally altering logistics networks worldwide thereby transforming economic structures globally while simultaneously creating ecological disruptions due invasive species transported unintentionally alongside legitimate goods causing cascading ecosystem collapses locally where non-native organisms outcompete native fauna disrupting food webs leading population declines affecting biodiversity indices measured scientifically using standardized metrics like Shannon-Wiener diversity index calculations applied consistently over decades revealing trends showing significant decreases post-introduction events correl strongly with introduction dates matching historical records documenting first sightings confirmed taxonomically classified formally according International Code Nomenclature rules"
}
]
@@ -0,0 +1,72 @@
{
"label": "dev-3090",
"extra": [],
"gpus": "2",
"needles": [
{
"tokens": 2726,
"prompt_ms": 3424.8,
"pp_tok_s": 796.0,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 8617.9,
"pp_tok_s": 972.3,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 12820.1,
"pp_tok_s": 1253.2,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 22901.1,
"pp_tok_s": 1211.4,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 5657.8,
"turn2_tokens": 6660,
"reused": 6631,
"turn2_ms": 238.7,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 71.7,
"hit_rate": 0.9266798230257504,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning black void, punctuated by whitecaps that looked like teeth.\n\nHe climbed the spiral stairs, his knees protesting with every step. The iron rungs were cold, slick with condensation that had leaked through the hairline cracks in the masonry. At the top, the lantern room hummed with the low, steady thrum of the rotating lens. It was a sound Elias had learned to sleep with, a heartbeat of the house.\n\nHe checked the clock. 11:42 PM. The light swept across the water, a slow, hypnotic arc. *Flash. Darkness. Flash. Darkness.*\n\nFor a moment, the beam caught something that wasn\u2019t water. A shape"
},
{
"name": "code",
"tok_s": 92.9,
"hit_rate": 0.8636411400016346,
"text": "```python\nimport csv\nfrom typing import List, Dict\n\n\ndef parse_csv(filepath: str) -> List[Dict[str, str]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header row\n and values from the corresponding row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(filepath, 'r', newline='') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n```<|im_end|>"
}
],
"engine_lines": [
"strata generate: expert cache auto: 17.31 GiB free, 700 MiB reserved -> 6704 slots",
"strata generate: expert cache 8730 slots, 16.62 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 2315 cache slots (4.42 GiB)",
"strata serve: decode expert cache hit rate: 86.6% (4986 hits / 5756 lookups)",
"strata serve: decode expert cache hit rate: 87.1% (3757 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 88.7% (3827 hits / 4314 lookups)",
"strata serve: decode expert cache hit rate: 92.1% (3975 hits / 4318 lookups)",
"strata serve: decode expert cache hit rate: 95.5% (4124 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 94.3% (25793 hits / 27360 lookups)",
"strata serve: decode expert cache hit rate: 94.6% (4088 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 92.7% (116454 hits / 125668 lookups)",
"strata serve: decode expert cache hit rate: 86.4% (73970 hits / 85649 lookups)"
]
}
@@ -0,0 +1,72 @@
{
"label": "dev-5080",
"extra": [],
"gpus": "0",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2129.8,
"pp_tok_s": 1279.9,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5057.2,
"pp_tok_s": 1656.8,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 7964.7,
"pp_tok_s": 2017.2,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 14053.6,
"pp_tok_s": 1974.1,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3366.4,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 197.3,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.1,
"hit_rate": 0.8418683557684931,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning cauldron of black ink and white foam, throwing itself against the granite base with the force of a battering ram.\n\nInside the lantern room, the air was thick with the smell of kerosene and old brass. Elias wiped his hands on his woolen trousers, leaving faint, oily smears on the fabric. He checked the clock. 11:42 PM. The rotation was due.\n\nHe reached for the crank handle, a heavy iron wheel worn smooth by decades of his own palms. With a grunt that matched the strain of his shoulders, he began to turn it. *Click. Click. Click.* The gears engaged, and the massive Fresnel lens began its slow"
},
{
"name": "code",
"tok_s": 88.0,
"hit_rate": 0.7011385968111186,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"engine_lines": [
"strata generate: expert cache auto: 9.15 GiB free, 700 MiB reserved -> 3415 slots",
"strata generate: expert cache 4293 slots, 8.17 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 2343 cache slots (4.42 GiB)",
"strata serve: decode expert cache hit rate: 59.6% (2749 hits / 4610 lookups)",
"strata serve: decode expert cache hit rate: 64.2% (2136 hits / 3329 lookups)",
"strata serve: decode expert cache hit rate: 67.3% (2309 hits / 3432 lookups)",
"strata serve: decode expert cache hit rate: 72.5% (2539 hits / 3504 lookups)",
"strata serve: decode expert cache hit rate: 77.3% (2817 hits / 3642 lookups)",
"strata serve: decode expert cache hit rate: 75.3% (16500 hits / 21916 lookups)",
"strata serve: decode expert cache hit rate: 80.0% (2978 hits / 3723 lookups)",
"strata serve: decode expert cache hit rate: 84.2% (98752 hits / 117301 lookups)",
"strata serve: decode expert cache hit rate: 70.1% (69215 hits / 98718 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k18",
"extra": [
"--layer-split",
"18"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 10383.7,
"pp_tok_s": 1547.2,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 16371.9,
"pp_tok_s": 1694.5,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4308.0,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 239.8,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 77.2,
"reps": [
74.6,
78.1,
79.0
],
"hit_rate": 0.9985378787878788,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, the glass was vibrating like a plucked cello string. The sea, usually a rhythmic pulse against the granite base, was now a chaotic, frothing beast, throwing itself against the rocks with the force of a battering ram.\n\nElias climbed the spiral stairs, his hand gripping the cold iron railing. His knees ached, not from age, but from the constant vibration of the floor beneath his feet. He reached the lantern room, the heart of the lighthouse, and checked the clockwork mechanism. The great lens, a prism of Fresnel glass, rotated with a steady, hypnotic *whirrr-click, whirrr-click*. It was the only sound that mattered. The light swept out into the black void, cutting through"
},
{
"name": "code",
"tok_s": 103.8,
"reps": [
97.6,
106.4,
107.3
],
"hit_rate": 0.9984863945578232,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 524 windows; per window: wait for the GPU 6.456 ms, pool + plan 0.181 ms, host staging 0.581 ms, commit 0.206 ms",
"strata serve: stage 1: 524 windows; per window: wait for the GPU 14.872 ms, pool + plan 0.501 ms, host staging 0.000 ms, commit 0.387 ms",
"strata serve: stage 0: 593 windows; per window: wait for the GPU 6.479 ms, pool + plan 0.185 ms, host staging 0.515 ms, commit 0.207 ms",
"strata serve: stage 1: 593 windows; per window: wait for the GPU 15.053 ms, pool + plan 0.496 ms, host staging 0.000 ms, commit 0.390 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 18)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4608 slots, 8.24 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 18-47, expert cache 7152 slots (14.14 GiB), 7152 of its 7680 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-17",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-17 (CUDA0), 18-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 98.7% (6159 hits / 6240 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (3796 hits / 3840 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (3811 hits / 3840 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (27723 hits / 27840 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (3807 hits / 3840 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (142002 hits / 143040 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (139882 hits / 140160 lookups)",
"strata serve: decode expert cache hit rate: 99.9% (131807 hits / 132000 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (119278 hits / 120480 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (117410 hits / 117600 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (117422 hits / 117600 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k19",
"extra": [
"--layer-split",
"19"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 9875.2,
"pp_tok_s": 1626.9,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 15498.0,
"pp_tok_s": 1790.1,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4085.5,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 238.7,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 79.3,
"reps": [
74.8,
85.6,
77.4
],
"hit_rate": 0.9977757017660928,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, the glass was vibrating like a plucked cello string. The sea, usually a rhythmic pulse against the granite base, was now a chaotic, frothing beast, throwing itself against the rocks with the force of a battering ram.\n\nElias stood at the top of the spiral staircase, his hand resting on the cold iron railing. He was not afraid. Fear was for the tourists who came in summer, wide-eyed and clutching their cameras. Elias was a keeper. He was a guardian.\n\nHe checked the logbook. The entry for the night was simple: *Wind, 60 knots. Visibility, zero. Light, operational.*\n\nHe walked down the stairs, his boots heavy on the metal grating. The smell of salt"
},
{
"name": "code",
"tok_s": 107.4,
"reps": [
98.9,
111.3,
112.1
],
"hit_rate": 0.9986286196529353,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header row\n and values from the corresponding row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with"
}
],
"stage_timing": [
"strata serve: stage 0: 533 windows; per window: wait for the GPU 6.226 ms, pool + plan 0.294 ms, host staging 0.561 ms, commit 0.225 ms",
"strata serve: stage 1: 533 windows; per window: wait for the GPU 14.156 ms, pool + plan 0.373 ms, host staging 0.000 ms, commit 0.371 ms",
"strata serve: stage 0: 599 windows; per window: wait for the GPU 6.314 ms, pool + plan 0.292 ms, host staging 0.501 ms, commit 0.225 ms",
"strata serve: stage 1: 599 windows; per window: wait for the GPU 14.366 ms, pool + plan 0.375 ms, host staging 0.000 ms, commit 0.374 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 19)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4606 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 19-47, expert cache 7160 slots (14.14 GiB), 7160 of its 7424 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-18",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-18 (CUDA0), 19-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 98.5% (6141 hits / 6234 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (3797 hits / 3836 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (3814 hits / 3837 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (28659 hits / 28795 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (3814 hits / 3839 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (141378 hits / 142508 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (124963 hits / 125273 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (134574 hits / 134874 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (121132 hits / 122322 lookups)",
"strata serve: decode expert cache hit rate: 99.9% (114073 hits / 114235 lookups)",
"strata serve: decode expert cache hit rate: 99.9% (113598 hits / 113754 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k20",
"extra": [
"--layer-split",
"20"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 9044.3,
"pp_tok_s": 1776.4,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13928.7,
"pp_tok_s": 1991.8,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3743.5,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 236.8,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 76.4,
"reps": [
74.2,
78.4,
76.7
],
"hit_rate": 0.9979911611088791,
"text": "The wind didn\u2019t just blow; it screamed. It clawed at the granite base of the tower, trying to pry it from the bedrock, and it howled through the narrow slits of the lantern room glass.\n\nElias Thorne was seventy years old, but he moved with the grace of a man who had spent fifty years listening to the sea. He knew the rhythm of the waves against the rocks below\u2014the *thud, thud, crash*\u2014and he knew when that rhythm changed. When the sea grew too quiet, or too violent, he knew a storm was coming, or a ship was in trouble.\n\nTonight, the storm was a Category 4. The rain lashed against the windows like gravel thrown by a giant. Elias stood in the center of the lantern room, his hand resting on the cold brass railing. He wasn\u2019t afraid. He was a guardian.\n\nHe checked the clockwork mechanism. It was old, a relic from 1"
},
{
"name": "code",
"tok_s": 105.9,
"reps": [
103.2,
106.8,
107.7
],
"hit_rate": 0.9981849807154201,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 537 windows; per window: wait for the GPU 6.685 ms, pool + plan 0.403 ms, host staging 0.593 ms, commit 0.215 ms",
"strata serve: stage 1: 537 windows; per window: wait for the GPU 13.928 ms, pool + plan 0.282 ms, host staging 0.000 ms, commit 0.359 ms",
"strata serve: stage 0: 606 windows; per window: wait for the GPU 6.764 ms, pool + plan 0.399 ms, host staging 0.528 ms, commit 0.216 ms",
"strata serve: stage 1: 606 windows; per window: wait for the GPU 14.108 ms, pool + plan 0.288 ms, host staging 0.000 ms, commit 0.362 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 20)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4603 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 20-47, expert cache 7142 slots (14.14 GiB), 7142 of its 7168 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-19",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-19 (CUDA0), 20-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 98.9% (6162 hits / 6230 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (3803 hits / 3835 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (3810 hits / 3836 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (27185 hits / 27330 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (3807 hits / 3835 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (139825 hits / 140938 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (142122 hits / 142524 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (146556 hits / 146851 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (118026 hits / 119233 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (118729 hits / 118997 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (118791 hits / 119007 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k21",
"extra": [
"--layer-split",
"21"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 8880.5,
"pp_tok_s": 1809.1,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13654.6,
"pp_tok_s": 2031.8,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3680.6,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 235.4,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 77.4,
"reps": [
80.2,
80.8,
71.2
],
"hit_rate": 0.9973622550922132,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and shadow.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined his life for thirty years.\n\nElias was not a man of many words. He was a man of many tasks. His hands, mapped with scars from rope burns and broken glass, moved with a rhythm that predated his memory. He checked the clockwork mechanism of the Fresnel lens, ensuring the rotation was smooth, silent, and steady. *Tick. Tick. Tick.* The sound was the heartbeat of the coast.\n\n"
},
{
"name": "code",
"tok_s": 106.7,
"reps": [
100.6,
110.5,
109.0
],
"hit_rate": 0.998218711927068,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 527 windows; per window: wait for the GPU 6.934 ms, pool + plan 0.620 ms, host staging 0.530 ms, commit 0.245 ms",
"strata serve: stage 1: 527 windows; per window: wait for the GPU 13.912 ms, pool + plan 0.365 ms, host staging 0.000 ms, commit 0.359 ms",
"strata serve: stage 0: 595 windows; per window: wait for the GPU 7.031 ms, pool + plan 0.595 ms, host staging 0.472 ms, commit 0.245 ms",
"strata serve: stage 1: 595 windows; per window: wait for the GPU 14.051 ms, pool + plan 0.361 ms, host staging 0.000 ms, commit 0.361 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 21)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4627 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 21-47, expert cache 6912 slots (13.78 GiB), 6912 of its 6912 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-20",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-20 (CUDA0), 21-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 98.7% (5672 hits / 5748 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (4287 hits / 4314 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (4287 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (29024 hits / 29229 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (4278 hits / 4316 lookups)",
"strata serve: decode expert cache hit rate: 98.8% (131499 hits / 133085 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (130974 hits / 131451 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (138767 hits / 139134 lookups)",
"strata serve: decode expert cache hit rate: 98.8% (121454 hits / 122932 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (116809 hits / 117079 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (118803 hits / 119015 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k22",
"extra": [
"--layer-split",
"22"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 8730.9,
"pp_tok_s": 1840.1,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13375.4,
"pp_tok_s": 2074.2,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3611.4,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 234.8,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 80.2,
"reps": [
78.4,
80.9,
81.5
],
"hit_rate": 0.9977764197412695,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy-three years old, and he had been the keeper of the Blackwood Point Light for forty of those years. The other thirty-three had been spent in a small, drafty cottage on the mainland, waiting for the day when the sea would finally claim him.\n\nTonight, the storm was a Category 4. The waves weren\u2019t just crashing; they were climbing. They reached the gallery railing, ten feet above the base of the tower, spraying a fine mist of brine that coated Elias\u2019s glasses.\n\nHe climbed the spiral stairs, his knees protesting with every step. The iron steps were cold, slick with condensation. At the top, in the lantern room, the air was thin and sharp. The great Fresnel lens, a cathedral of glass and brass, stood silent. It was time to wake it.\n\n"
},
{
"name": "code",
"tok_s": 106.4,
"reps": [
100.7,
110.3,
108.1
],
"hit_rate": 0.9971798370756316,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 529 windows; per window: wait for the GPU 7.229 ms, pool + plan 0.561 ms, host staging 0.529 ms, commit 0.233 ms",
"strata serve: stage 1: 529 windows; per window: wait for the GPU 13.058 ms, pool + plan 0.253 ms, host staging 0.000 ms, commit 0.334 ms",
"strata serve: stage 0: 598 windows; per window: wait for the GPU 7.325 ms, pool + plan 0.552 ms, host staging 0.469 ms, commit 0.235 ms",
"strata serve: stage 1: 598 windows; per window: wait for the GPU 13.238 ms, pool + plan 0.260 ms, host staging 0.000 ms, commit 0.337 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 22)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4640 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 22-47, expert cache 6656 slots (13.34 GiB), 6656 of its 6656 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-21",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-21 (CUDA0), 22-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 97.7% (5609 hits / 5739 lookups)",
"strata serve: decode expert cache hit rate: 98.7% (4256 hits / 4310 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (4278 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (27008 hits / 27289 lookups)",
"strata serve: decode expert cache hit rate: 98.8% (4260 hits / 4313 lookups)",
"strata serve: decode expert cache hit rate: 98.5% (135183 hits / 137274 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (136132 hits / 136708 lookups)",
"strata serve: decode expert cache hit rate: 99.8% (135515 hits / 135817 lookups)",
"strata serve: decode expert cache hit rate: 98.4% (121287 hits / 123280 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (117658 hits / 118026 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (120574 hits / 120915 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k23",
"extra": [
"--layer-split",
"23"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 8742.6,
"pp_tok_s": 1837.7,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13084.2,
"pp_tok_s": 2120.3,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3566.1,
"turn2_tokens": 6660,
"reused": 6631,
"turn2_ms": 238.0,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 81.0,
"reps": [
76.8,
85.2,
81.1
],
"hit_rate": 0.9961302681992337,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning black void, swallowing the horizon, throwing white foam against the granite base like a tantrum.\n\nInside the lantern room, the air was thick with the smell of kerosene and old brass. Elias wiped his hands on his wool trousers, the fabric stiff with salt and grease. He checked the clockwork mechanism that turned the lens. It was a beast of a thing, a Fresnel lens of eight feet in diameter, a cathedral of glass and crystal that focused the beam of the lamp into a single, piercing needle of light.\n\nHe wound the weights. *Click, click, click.* The gears engaged. The massive lens began its slow, hypnotic rotation.\n\nFor a moment, the"
},
{
"name": "code",
"tok_s": 108.4,
"reps": [
105.1,
109.0,
111.1
],
"hit_rate": 0.9961698810290479,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 537 windows; per window: wait for the GPU 7.423 ms, pool + plan 0.953 ms, host staging 0.542 ms, commit 0.237 ms",
"strata serve: stage 1: 537 windows; per window: wait for the GPU 12.223 ms, pool + plan 0.236 ms, host staging 0.000 ms, commit 0.324 ms",
"strata serve: stage 0: 606 windows; per window: wait for the GPU 7.519 ms, pool + plan 0.904 ms, host staging 0.483 ms, commit 0.239 ms",
"strata serve: stage 1: 606 windows; per window: wait for the GPU 12.395 ms, pool + plan 0.242 ms, host staging 0.000 ms, commit 0.326 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 23)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4609 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 23-47, expert cache 6400 slots (12.82 GiB), 6400 of its 6400 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-22",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-22 (CUDA0), 23-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 96.8% (5528 hits / 5709 lookups)",
"strata serve: decode expert cache hit rate: 98.4% (4235 hits / 4303 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (4260 hits / 4308 lookups)",
"strata serve: decode expert cache hit rate: 98.6% (30643 hits / 31089 lookups)",
"strata serve: decode expert cache hit rate: 98.6% (4248 hits / 4309 lookups)",
"strata serve: decode expert cache hit rate: 98.3% (134776 hits / 137143 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (133024 hits / 133766 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (129995 hits / 130500 lookups)",
"strata serve: decode expert cache hit rate: 97.9% (115782 hits / 118287 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (117953 hits / 118464 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (117560 hits / 118012 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k24",
"extra": [
"--layer-split",
"24"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 8122.5,
"pp_tok_s": 1978.0,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 12285.8,
"pp_tok_s": 2258.1,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3407.0,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 234.0,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.5,
"reps": [
84.9,
83.3,
82.3
],
"hit_rate": 0.9949381327334084,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and shadow.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined Elias\u2019s life for thirty years. He was not merely a keeper; he was a guardian of silence.\n\nHe checked the clock. 11:42 PM. The rotation of the great Fresnel lens was due. With a practiced hand, he engaged the brake on the clockwork mechanism. The heavy brass gears groaned, then began their slow, rhythmic turn. *Tick. Tick. Tick.* It was a heartbeat"
},
{
"name": "code",
"tok_s": 109.5,
"reps": [
104.9,
111.0,
112.6
],
"hit_rate": 0.9953796671318199,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 519 windows; per window: wait for the GPU 7.835 ms, pool + plan 0.767 ms, host staging 0.557 ms, commit 0.244 ms",
"strata serve: stage 1: 519 windows; per window: wait for the GPU 11.922 ms, pool + plan 0.232 ms, host staging 0.000 ms, commit 0.325 ms",
"strata serve: stage 0: 587 windows; per window: wait for the GPU 7.943 ms, pool + plan 0.744 ms, host staging 0.497 ms, commit 0.246 ms",
"strata serve: stage 1: 587 windows; per window: wait for the GPU 12.073 ms, pool + plan 0.238 ms, host staging 0.000 ms, commit 0.326 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 24)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4603 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 24-47, expert cache 6144 slots (12.35 GiB), 6144 of its 6144 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-23",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-23 (CUDA0), 24-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 95.9% (5916 hits / 6171 lookups)",
"strata serve: decode expert cache hit rate: 97.4% (3717 hits / 3815 lookups)",
"strata serve: decode expert cache hit rate: 98.4% (3761 hits / 3823 lookups)",
"strata serve: decode expert cache hit rate: 98.1% (27138 hits / 27664 lookups)",
"strata serve: decode expert cache hit rate: 97.8% (3742 hits / 3826 lookups)",
"strata serve: decode expert cache hit rate: 97.9% (126114 hits / 128875 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (133626 hits / 134658 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (132675 hits / 133350 lookups)",
"strata serve: decode expert cache hit rate: 96.8% (113593 hits / 117399 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (118372 hits / 118949 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (116981 hits / 117524 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k26",
"extra": [
"--layer-split",
"26"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 7880.9,
"pp_tok_s": 2038.6,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 11771.9,
"pp_tok_s": 2356.7,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3323.9,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 231.5,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.8,
"reps": [
85.9,
83.1,
82.4
],
"hit_rate": 0.9933966961830739,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and green.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined Elias\u2019s life for thirty years. He was a keeper of the light, but more importantly, he was a keeper of time.\n\nHe checked his pocket watch. 11:42 PM. The rotation of the great Fresnel lens was due. With a practiced, rhythmic *clunk-clunk-clunk*, he wound the clockwork mechanism. The massive lens began its slow, hypnotic turn, sweeping its beam across"
},
{
"name": "code",
"tok_s": 109.7,
"reps": [
104.4,
112.7,
111.9
],
"hit_rate": 0.9938670271352759,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 519 windows; per window: wait for the GPU 8.376 ms, pool + plan 1.030 ms, host staging 0.566 ms, commit 0.273 ms",
"strata serve: stage 1: 519 windows; per window: wait for the GPU 10.886 ms, pool + plan 0.214 ms, host staging 0.000 ms, commit 0.297 ms",
"strata serve: stage 0: 588 windows; per window: wait for the GPU 8.506 ms, pool + plan 0.985 ms, host staging 0.505 ms, commit 0.274 ms",
"strata serve: stage 1: 588 windows; per window: wait for the GPU 11.036 ms, pool + plan 0.219 ms, host staging 0.000 ms, commit 0.298 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 26)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4568 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 26-47, expert cache 5632 slots (11.32 GiB), 5632 of its 5632 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-25",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-25 (CUDA0), 26-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 92.8% (5259 hits / 5665 lookups)",
"strata serve: decode expert cache hit rate: 95.2% (4056 hits / 4259 lookups)",
"strata serve: decode expert cache hit rate: 97.2% (4164 hits / 4285 lookups)",
"strata serve: decode expert cache hit rate: 97.1% (27690 hits / 28510 lookups)",
"strata serve: decode expert cache hit rate: 97.5% (4184 hits / 4293 lookups)",
"strata serve: decode expert cache hit rate: 96.9% (121839 hits / 125724 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (132165 hits / 133648 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (137652 hits / 138567 lookups)",
"strata serve: decode expert cache hit rate: 95.3% (111307 hits / 116822 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (118436 hits / 119329 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (119109 hits / 119844 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k28",
"extra": [
"--layer-split",
"28"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 7989.3,
"pp_tok_s": 2010.9,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 12132.9,
"pp_tok_s": 2286.6,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3433.5,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 228.0,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.5,
"reps": [
83.7,
82.4,
84.5
],
"hit_rate": 0.9900441970361987,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, the glass was vibrating like a plucked cello string. The sea, usually a rhythmic pulse against the granite base, was now a chaotic, frothing beast, throwing itself against the rocks with the force of a battering ram.\n\nElias climbed the spiral stairs, his hand gripping the cold iron railing. His knees ached, not from age, but from the constant vibration of the floor beneath his feet. He reached the lantern room, the heart of the lighthouse, and checked the clockwork mechanism. The great lens, a prism of Fresnel glass, rotated with a steady, hypnotic *whirrr-click, whirrr-click*. It was the only sound that mattered. The light swept out into the black void, cutting through"
},
{
"name": "code",
"tok_s": 112.5,
"reps": [
101.4,
112.1,
124.1
],
"hit_rate": 0.9896636587366694,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv(file_path: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys being the header values\n and values being the corresponding cell values.\n \n Args:\n file_path: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(file_path, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n```<|im_end|>"
}
],
"stage_timing": [
"strata serve: stage 0: 527 windows; per window: wait for the GPU 8.765 ms, pool + plan 1.401 ms, host staging 0.593 ms, commit 0.281 ms",
"strata serve: stage 1: 527 windows; per window: wait for the GPU 9.893 ms, pool + plan 0.195 ms, host staging 0.000 ms, commit 0.276 ms",
"strata serve: stage 0: 578 windows; per window: wait for the GPU 8.908 ms, pool + plan 1.359 ms, host staging 0.559 ms, commit 0.284 ms",
"strata serve: stage 1: 578 windows; per window: wait for the GPU 10.025 ms, pool + plan 0.200 ms, host staging 0.000 ms, commit 0.278 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 28)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4537 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 28-47, expert cache 5120 slots (10.30 GiB), 5120 of its 5120 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-27",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-27 (CUDA0), 28-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 88.8% (5405 hits / 6086 lookups)",
"strata serve: decode expert cache hit rate: 93.8% (3539 hits / 3773 lookups)",
"strata serve: decode expert cache hit rate: 94.2% (3560 hits / 3781 lookups)",
"strata serve: decode expert cache hit rate: 95.7% (26702 hits / 27899 lookups)",
"strata serve: decode expert cache hit rate: 95.9% (3647 hits / 3804 lookups)",
"strata serve: decode expert cache hit rate: 95.7% (122618 hits / 128160 lookups)",
"strata serve: decode expert cache hit rate: 98.6% (135035 hits / 136884 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (129476 hits / 130778 lookups)",
"strata serve: decode expert cache hit rate: 93.9% (111037 hits / 118290 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (118829 hits / 120140 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (90480 hits / 91425 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k30",
"extra": [
"--layer-split",
"30"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 8301.3,
"pp_tok_s": 1935.4,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 12507.6,
"pp_tok_s": 2218.1,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3448.7,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 229.4,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 81.1,
"reps": [
78.0,
84.4,
81.0
],
"hit_rate": 0.9798126997277784,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, the glass was vibrating like a plucked cello string. The sea, usually a rhythmic pulse against the granite base, was now a chaotic, frothing beast, throwing itself against the rocks with the force of a battering ram.\n\nElias stood at the top of the spiral staircase, his hand resting on the cold iron railing. He was not afraid. Fear was for the tourists who came in summer, wide-eyed and clutching their cameras. Elias was a keeper. He was a guardian.\n\nHe checked the logbook. The entry for the night was simple: *Wind, 60 knots. Visibility, zero. Light, operational.*\n\nHe walked down the stairs, his boots heavy on the metal grating. The smell of salt"
},
{
"name": "code",
"tok_s": 109.6,
"reps": [
100.7,
114.1,
114.1
],
"hit_rate": 0.9825370343977666,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 540 windows; per window: wait for the GPU 9.206 ms, pool + plan 1.794 ms, host staging 0.525 ms, commit 0.298 ms",
"strata serve: stage 1: 540 windows; per window: wait for the GPU 8.767 ms, pool + plan 0.173 ms, host staging 0.000 ms, commit 0.242 ms",
"strata serve: stage 0: 608 windows; per window: wait for the GPU 9.390 ms, pool + plan 1.733 ms, host staging 0.471 ms, commit 0.301 ms",
"strata serve: stage 1: 608 windows; per window: wait for the GPU 8.910 ms, pool + plan 0.178 ms, host staging 0.000 ms, commit 0.244 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 30)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4479 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 30-47, expert cache 4608 slots (9.19 GiB), 4608 of its 4608 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-29",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-29 (CUDA0), 30-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 84.6% (5099 hits / 6028 lookups)",
"strata serve: decode expert cache hit rate: 90.7% (3403 hits / 3751 lookups)",
"strata serve: decode expert cache hit rate: 91.1% (3415 hits / 3747 lookups)",
"strata serve: decode expert cache hit rate: 94.1% (26108 hits / 27737 lookups)",
"strata serve: decode expert cache hit rate: 93.4% (3534 hits / 3784 lookups)",
"strata serve: decode expert cache hit rate: 94.6% (127532 hits / 134866 lookups)",
"strata serve: decode expert cache hit rate: 98.6% (135828 hits / 137786 lookups)",
"strata serve: decode expert cache hit rate: 98.0% (132455 hits / 135184 lookups)",
"strata serve: decode expert cache hit rate: 91.0% (106351 hits / 116917 lookups)",
"strata serve: decode expert cache hit rate: 97.9% (115791 hits / 118246 lookups)",
"strata serve: decode expert cache hit rate: 98.3% (117198 hits / 119281 lookups)"
]
}
@@ -0,0 +1,85 @@
{
"label": "ks-k32",
"extra": [
"--layer-split",
"32"
],
"gpus": "0,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 8837.1,
"pp_tok_s": 1818.0,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13449.8,
"pp_tok_s": 2062.7,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3565.7,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 227.0,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.0,
"reps": [
75.0,
86.1,
87.8
],
"hit_rate": 0.9796934433020751,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning cauldron of black ink and white foam, throwing itself against the granite base with the force of a battering ram.\n\nInside the lantern room, the air was thick with the smell of kerosene and old brass. Elias wiped his hands on his woolen trousers, leaving faint, oily smears on the fabric. He checked the clock. 11:42 PM. The rotation was due.\n\nHe reached for the crank handle, a heavy iron wheel worn smooth by decades of his own palms. With a grunt that matched the strain of his shoulders, he began to turn it. *Click. Click. Click.* The gears engaged, and the massive Fresnel lens began its slow"
},
{
"name": "code",
"tok_s": 108.7,
"reps": [
98.8,
112.2,
115.1
],
"hit_rate": 0.9777186846317358,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 533 windows; per window: wait for the GPU 9.839 ms, pool + plan 2.192 ms, host staging 0.585 ms, commit 0.318 ms",
"strata serve: stage 1: 533 windows; per window: wait for the GPU 8.014 ms, pool + plan 0.156 ms, host staging 0.000 ms, commit 0.231 ms",
"strata serve: stage 0: 602 windows; per window: wait for the GPU 10.014 ms, pool + plan 2.111 ms, host staging 0.520 ms, commit 0.321 ms",
"strata serve: stage 1: 602 windows; per window: wait for the GPU 8.094 ms, pool + plan 0.160 ms, host staging 0.000 ms, commit 0.232 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 32)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4488 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 32-47, expert cache 4096 slots (8.26 GiB), 4096 of its 4096 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-31",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-31 (CUDA0), 32-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 81.3% (4889 hits / 6010 lookups)",
"strata serve: decode expert cache hit rate: 87.7% (3272 hits / 3733 lookups)",
"strata serve: decode expert cache hit rate: 87.9% (3280 hits / 3733 lookups)",
"strata serve: decode expert cache hit rate: 92.3% (24238 hits / 26254 lookups)",
"strata serve: decode expert cache hit rate: 91.5% (3457 hits / 3778 lookups)",
"strata serve: decode expert cache hit rate: 93.7% (133254 hits / 142240 lookups)",
"strata serve: decode expert cache hit rate: 98.0% (130112 hits / 132812 lookups)",
"strata serve: decode expert cache hit rate: 98.0% (130069 hits / 132765 lookups)",
"strata serve: decode expert cache hit rate: 89.1% (105263 hits / 118190 lookups)",
"strata serve: decode expert cache hit rate: 97.2% (116970 hits / 120309 lookups)",
"strata serve: decode expert cache hit rate: 97.8% (115450 hits / 118081 lookups)"
]
}
@@ -0,0 +1,76 @@
{
"label": "ks-single",
"extra": [],
"gpus": "0",
"needles": [
{
"tokens": 16066,
"prompt_ms": 9310.4,
"pp_tok_s": 1725.6,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 14085.4,
"pp_tok_s": 1969.6,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3388.7,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 200.7,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 86.7,
"reps": [
78.2,
91.4,
90.4
],
"hit_rate": 0.921882148773369,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy-three years old, and his hands were a map of the coastline he had guarded for fifty years. The knuckles were swollen, the skin cracked like dry earth, but they moved with a precision that no machine could replicate.\n\nHe stood on the gallery of the *Harrow Point* lighthouse, the iron railing cold against his palms. Below, the Atlantic churned in a frenzy, whitecaps exploding against the black rocks like fireworks. Above, the sky was a bruised purple, heavy with rain that hadn\u2019t yet fallen.\n\nElias checked the clock. 11:42 PM. The light was due.\n\nHe didn\u2019t look at the gauge. He didn\u2019t check the fuel levels. He knew. He always knew. The rhythm of the ocean, the pressure of the air, the subtle vibration"
},
{
"name": "code",
"tok_s": 105.2,
"reps": [
87.8,
113.5,
114.2
],
"hit_rate": 0.9109811738648947,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 382 windows; per window: wait for the GPU 15.155 ms, pool + plan 4.331 ms, host staging 0.656 ms, commit 0.458 ms",
"strata serve: stage 0: 455 windows; per window: wait for the GPU 15.790 ms, pool + plan 5.552 ms, host staging 0.688 ms, commit 0.467 ms",
"strata serve: stage 0: 524 windows; per window: wait for the GPU 16.064 ms, pool + plan 5.529 ms, host staging 0.606 ms, commit 0.472 ms",
"strata serve: stage 0: 593 windows; per window: wait for the GPU 16.305 ms, pool + plan 5.462 ms, host staging 0.540 ms, commit 0.475 ms"
],
"engine_lines": [
"strata generate: expert cache auto: 9.15 GiB free, 700 MiB reserved -> 3415 slots",
"strata generate: expert cache 4293 slots, 8.17 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 2343 cache slots (4.42 GiB)",
"strata serve: decode expert cache hit rate: 59.6% (2749 hits / 4610 lookups)",
"strata serve: decode expert cache hit rate: 67.8% (2299 hits / 3391 lookups)",
"strata serve: decode expert cache hit rate: 68.5% (2345 hits / 3424 lookups)",
"strata serve: decode expert cache hit rate: 75.6% (16605 hits / 21959 lookups)",
"strata serve: decode expert cache hit rate: 73.3% (2608 hits / 3558 lookups)",
"strata serve: decode expert cache hit rate: 83.7% (104878 hits / 125346 lookups)",
"strata serve: decode expert cache hit rate: 92.7% (119739 hits / 129184 lookups)",
"strata serve: decode expert cache hit rate: 92.2% (116867 hits / 126770 lookups)",
"strata serve: decode expert cache hit rate: 68.1% (64111 hits / 94116 lookups)",
"strata serve: decode expert cache hit rate: 89.0% (96370 hits / 108259 lookups)",
"strata serve: decode expert cache hit rate: 91.1% (102827 hits / 112875 lookups)"
]
}
@@ -0,0 +1,72 @@
{
"label": "rel-5080",
"extra": [],
"gpus": "0",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2132.8,
"pp_tok_s": 1278.1,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5008.9,
"pp_tok_s": 1672.8,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 7855.4,
"pp_tok_s": 2045.2,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13833.9,
"pp_tok_s": 2005.4,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3298.2,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 197.9,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.2,
"hit_rate": 0.8418683557684931,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning cauldron of black ink and white foam, throwing itself against the granite base with the force of a battering ram.\n\nInside the lantern room, the air was thick with the smell of kerosene and old brass. Elias wiped his hands on his woolen trousers, leaving faint, oily smears on the fabric. He checked the clock. 11:42 PM. The rotation was due.\n\nHe reached for the crank handle, a heavy iron wheel worn smooth by decades of his own palms. With a grunt that matched the strain of his shoulders, he began to turn it. *Click. Click. Click.* The gears engaged, and the massive Fresnel lens began its slow"
},
{
"name": "code",
"tok_s": 88.2,
"hit_rate": 0.7011385968111186,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"engine_lines": [
"strata generate: expert cache auto: 9.15 GiB free, 700 MiB reserved -> 3415 slots",
"strata generate: expert cache 4293 slots, 8.17 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 2343 cache slots (4.42 GiB)",
"strata serve: decode expert cache hit rate: 59.6% (2749 hits / 4610 lookups)",
"strata serve: decode expert cache hit rate: 64.2% (2136 hits / 3329 lookups)",
"strata serve: decode expert cache hit rate: 67.3% (2309 hits / 3432 lookups)",
"strata serve: decode expert cache hit rate: 72.5% (2539 hits / 3504 lookups)",
"strata serve: decode expert cache hit rate: 77.3% (2817 hits / 3642 lookups)",
"strata serve: decode expert cache hit rate: 75.3% (16500 hits / 21916 lookups)",
"strata serve: decode expert cache hit rate: 80.0% (2978 hits / 3723 lookups)",
"strata serve: decode expert cache hit rate: 84.2% (98752 hits / 117301 lookups)",
"strata serve: decode expert cache hit rate: 70.1% (69215 hits / 98718 lookups)"
]
}
@@ -0,0 +1,78 @@
{
"label": "t75-5080",
"extra": [],
"gpus": "0",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2158.8,
"pp_tok_s": 1262.7,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5060.0,
"pp_tok_s": 1655.9,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3389.7,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 213.5,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 80.4,
"reps": [
80.4
],
"hit_rate": 0.855538332528651,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning black void, punctuated by whitecaps that looked like teeth.\n\nHe climbed the spiral stairs, his boots heavy with salt and exhaustion. The iron steps groaned under his weight, a sound that had become so familiar to him that he could sleep to it. But tonight, he wasn\u2019t sleeping. He was working.\n\nThe lens was the heart of the lighthouse. A massive, rotating Fresnel lens, a cathedral of glass and brass that turned with a mechanical hum. Elias checked the clockwork mechanism. It was smooth, rhythmic, a heartbeat in the dark. But the light itself was failing.\n\nHe climbed higher, into the lantern room. The wind battered the glass panes,"
},
{
"name": "code",
"tok_s": 87.5,
"reps": [
87.5
],
"hit_rate": 0.7075461363025227,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 39 windows; per window: wait for the GPU 17.252 ms, pool + plan 8.473 ms, host staging 0.611 ms, commit 0.525 ms",
"strata serve: stage 0: 47 windows; per window: wait for the GPU 18.018 ms, pool + plan 8.616 ms, host staging 0.607 ms, commit 0.534 ms",
"strata serve: stage 0: 169 windows; per window: wait for the GPU 15.223 ms, pool + plan 5.717 ms, host staging 0.688 ms, commit 0.467 ms",
"strata serve: stage 0: 243 windows; per window: wait for the GPU 16.500 ms, pool + plan 7.316 ms, host staging 0.742 ms, commit 0.481 ms"
],
"engine_lines": [
"strata generate: expert cache auto: 9.15 GiB free, 700 MiB reserved -> 3415 slots",
"strata generate: expert cache 4294 slots, 8.18 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 2343 cache slots (4.42 GiB)",
"strata serve: decode expert cache hit rate: 59.6% (2749 hits / 4610 lookups)",
"strata serve: decode expert cache hit rate: 64.2% (2136 hits / 3329 lookups)",
"strata serve: decode expert cache hit rate: 67.3% (2309 hits / 3432 lookups)",
"strata serve: decode expert cache hit rate: 75.5% (16316 hits / 21602 lookups)",
"strata serve: decode expert cache hit rate: 74.4% (2660 hits / 3573 lookups)",
"strata serve: decode expert cache hit rate: 83.4% (92664 hits / 111086 lookups)",
"strata serve: decode expert cache hit rate: 72.0% (70946 hits / 98601 lookups)",
"strata generate: expert cache auto: 9.15 GiB free, 700 MiB reserved -> 3415 slots",
"strata generate: expert cache 4293 slots, 8.17 GiB of VRAM; policy is",
"strata serve: the prompt path borrows 2343 cache slots (4.42 GiB)",
"strata serve: decode expert cache hit rate: 59.6% (2749 hits / 4610 lookups)",
"strata serve: decode expert cache hit rate: 64.2% (2136 hits / 3329 lookups)",
"strata serve: decode expert cache hit rate: 67.3% (2309 hits / 3432 lookups)",
"strata serve: decode expert cache hit rate: 75.5% (16316 hits / 21602 lookups)",
"strata serve: decode expert cache hit rate: 74.4% (2659 hits / 3572 lookups)",
"strata serve: decode expert cache hit rate: 85.6% (101004 hits / 118059 lookups)",
"strata serve: decode expert cache hit rate: 70.8% (71044 hits / 100409 lookups)"
]
}
@@ -0,0 +1,103 @@
{
"label": "v-x2-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "0,2",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2577.0,
"pp_tok_s": 1057.8,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 4433.5,
"pp_tok_s": 1889.9,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 7885.8,
"pp_tok_s": 2037.3,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13384.6,
"pp_tok_s": 2072.8,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3667.5,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 253.8,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 79.5,
"reps": [
78.9,
79.0,
80.7
],
"hit_rate": 0.9969903443250137,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the rough fabric scratching against his stubble. Outside the thick, salt-crusted glass of the lantern room, the Atlantic was a churning cauldron of black and white foam. Rain lashed against the panes in horizontal sheets, blurring the world into a smear of gray and shadow.\n\nInside, however, it was warm. The smell of kerosene and old wood hung heavy in the air, a scent that had defined his life for thirty years.\n\nElias was not a man of many words. He was a man of many tasks. His hands, mapped with scars from rope burns and broken glass, moved with a rhythm that predated his memory. He checked the clockwork mechanism of the Fresnel lens, listening for the faint, rhythmic *tick-tick-tick* that signaled the rotation was true. If the rhythm faltered, ships"
},
{
"name": "code",
"tok_s": 108.9,
"reps": [
103.3,
113.7,
109.5
],
"hit_rate": 0.9971795386830036,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 542 windows; per window: wait for the GPU 7.167 ms, pool + plan 0.572 ms, host staging 0.550 ms, commit 0.232 ms",
"strata serve: stage 1: 542 windows; per window: wait for the GPU 12.855 ms, pool + plan 0.250 ms, host staging 0.000 ms, commit 0.334 ms",
"strata serve: stage 0: 610 windows; per window: wait for the GPU 7.276 ms, pool + plan 0.559 ms, host staging 0.493 ms, commit 0.234 ms",
"strata serve: stage 1: 610 windows; per window: wait for the GPU 13.048 ms, pool + plan 0.258 ms, host staging 0.000 ms, commit 0.337 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: layer split auto: CUDA0 84 SMs at 2.81 GHz -> 0.31 ms per layer, 8.30 GiB for experts",
"strata generate: layer split auto: CUDA1 82 SMs at 1.86 GHz -> 0.48 ms per layer, 14.14 GiB for experts",
"strata generate: layer split auto: K=22 - predicted 19.7 ms per decode window; the caches hold 11298 of 12288 profiled pairs (~99.7% of the routed mass)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4640 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 22-47, expert cache 6656 slots (13.34 GiB), 6656 of its 6656 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-21",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-21 (CUDA0), 22-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 97.7% (5609 hits / 5739 lookups)",
"strata serve: decode expert cache hit rate: 98.5% (4241 hits / 4306 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (4262 hits / 4309 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (4290 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (4303 hits / 4319 lookups)",
"strata serve: decode expert cache hit rate: 98.8% (28379 hits / 28721 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (4285 hits / 4314 lookups)",
"strata serve: decode expert cache hit rate: 98.5% (136232 hits / 138256 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (135540 hits / 136216 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (136812 hits / 137225 lookups)",
"strata serve: decode expert cache hit rate: 98.4% (118437 hits / 120406 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (112836 hits / 113230 lookups)"
]
}
@@ -0,0 +1,88 @@
{
"label": "v-x2r-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "2,0",
"needles": [
{
"tokens": 16066,
"prompt_ms": 9610.0,
"pp_tok_s": 1671.8,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 14786.1,
"pp_tok_s": 1876.3,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3850.8,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 245.0,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 90.3,
"reps": [
91.0,
97.0,
82.8
],
"hit_rate": 0.993905472636816,
"text": "The wind didn\u2019t just blow; it screamed. It tore through the iron gallery of the Blackwood Point Lighthouse, rattling the glass panes like a prisoner shaking its bars.\n\nElias Thorne was seventy years old, and he had been the keeper for forty of them. He knew every creak of the spiral staircase, every draft that slipped under the door, and every shift in the barometric pressure that preceded a storm. He was not merely a worker; he was the heartbeat of the coast.\n\nIt was November, the month of grey seas and shorter days. The fog had rolled in thick, a wall of white so dense that even the beam of the Fresnel lens seemed to struggle against it. Elias stood in the lantern room, his hands roughened by salt and rope, adjusting the rotation mechanism. The great lens, a cathedral of glass and brass, began its slow, rhythmic sweep. *Flash. Dark. Flash. Dark.*\n\nHe checked the log"
},
{
"name": "code",
"tok_s": 105.1,
"reps": [
98.4,
109.9,
107.0
],
"hit_rate": 0.9949900793650793,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 492 windows; per window: wait for the GPU 15.016 ms, pool + plan 0.297 ms, host staging 0.517 ms, commit 0.404 ms",
"strata serve: stage 1: 492 windows; per window: wait for the GPU 5.775 ms, pool + plan 0.854 ms, host staging 0.001 ms, commit 0.210 ms",
"strata serve: stage 0: 561 windows; per window: wait for the GPU 15.257 ms, pool + plan 0.306 ms, host staging 0.455 ms, commit 0.408 ms",
"strata serve: stage 1: 561 windows; per window: wait for the GPU 5.852 ms, pool + plan 0.820 ms, host staging 0.001 ms, commit 0.210 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 57.7 GB/s -> pcie_frac 0.55",
"strata generate: layer split: CUDA1 holds its weights, session and head; 9.98 GiB free",
"strata generate: layer split auto: CUDA0 82 SMs at 1.86 GHz -> 0.48 ms per layer, 16.45 GiB for experts",
"strata generate: layer split auto: CUDA1 84 SMs at 2.81 GHz -> 0.31 ms per layer, 5.99 GiB for experts",
"strata generate: layer split auto: K=30 - predicted 20.8 ms per decode window; the caches hold 10684 of 12288 profiled pairs (~99.5% of the routed mass)",
"strata generate: expert cache auto: 18.62 GiB free, 700 MiB reserved -> 6634 slots",
"strata generate: expert cache 7680 slots, 14.23 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 30-47, expert cache 3004 slots (5.98 GiB), 3004 of its 4608 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-29",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-29 (CUDA0), 30-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 93.2% (5367 hits / 5760 lookups)",
"strata serve: decode expert cache hit rate: 93.7% (4049 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 96.7% (4178 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 97.7% (26272 hits / 26880 lookups)",
"strata serve: decode expert cache hit rate: 97.1% (4194 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 97.8% (110748 hits / 113280 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (113787 hits / 114240 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (127856 hits / 128640 lookups)",
"strata serve: decode expert cache hit rate: 96.3% (116528 hits / 120960 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (116423 hits / 117120 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (120354 hits / 120960 lookups)"
]
}
@@ -0,0 +1,89 @@
{
"label": "v-x3-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "0,1,2",
"needles": [
{
"tokens": 16066,
"prompt_ms": 9525.4,
"pp_tok_s": 1686.6,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 15017.1,
"pp_tok_s": 1847.4,
"found": true,
"answer": "juniper-766<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4039.6,
"turn2_tokens": 6660,
"reused": 6631,
"turn2_ms": 360.2,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 67.6,
"reps": [
59.8,
83.1,
59.9
],
"hit_rate": 0.9967581085879247,
"text": "The wind didn\u2019t just blow; it screamed. It tore through the iron gallery of the Blackwood Point Lighthouse, rattling the glass panes like a prisoner shaking its bars.\n\nElias Thorne was seventy years old, and he had been the keeper for forty of them. He knew every creak in the spiral staircase, every draft that slipped under the door, and every shift in the weather that the barometer couldn\u2019t quite predict. He knew that when the fog rolled in thick enough to swallow the horizon, the world ceased to exist. There was only the light, the stone, and him.\n\nIt was November, the month of storms. The sea was a churning cauldron of grey and white, throwing itself against the granite base with the force of a battering ram. Elias stood in the lantern room, his hands rough as sandpaper, adjusting the massive Fresnel lens. The rotation mechanism groaned, a sound like a ship\u2019s anchor chain being h"
},
{
"name": "code",
"tok_s": 89.6,
"reps": [
81.0,
104.8,
83.0
],
"hit_rate": 0.9975041387598007,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 2: 522 windows; per window: wait for the GPU 12.405 ms, pool + plan 0.242 ms, host staging 0.006 ms, commit 0.375 ms",
"strata serve: stage 0: 590 windows; per window: wait for the GPU 7.387 ms, pool + plan 0.549 ms, host staging 0.517 ms, commit 0.248 ms",
"strata serve: stage 1: 590 windows; per window: wait for the GPU 3.071 ms, pool + plan 0.011 ms, host staging 0.000 ms, commit 0.606 ms",
"strata serve: stage 2: 590 windows; per window: wait for the GPU 12.592 ms, pool + plan 0.250 ms, host staging 0.006 ms, commit 0.377 ms"
],
"engine_lines": [
"strata generate: layer split across 3 GPUs: CUDA0, then CUDA1,2 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 3.2 GB/s -> pcie_frac 0.00",
"strata generate: layer split: CUDA1 holds its weights, session; 16.67 GiB free",
"strata generate: layer split: CUDA2 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA2 holds its weights, session and head; 18.13 GiB free",
"strata generate: layer split auto: CUDA0 84 SMs at 2.81 GHz -> 0.31 ms per layer, 8.30 GiB for experts",
"strata generate: layer split auto: CUDA1 68 SMs at 1.54 GHz -> 0.69 ms per layer, 13.50 GiB for experts",
"strata generate: layer split auto: CUDA2 82 SMs at 1.86 GHz -> 0.48 ms per layer, 14.14 GiB for experts",
"strata generate: layer split auto: K=22,23 - predicted 19.9 ms per decode window; the caches hold 11298 of 12288 profiled pairs (~99.7% of the routed mass)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4640 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 22-22, expert cache 256 slots (0.52 GiB), 256 of its 256 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA2 runs layers 23-47, expert cache 6400 slots (12.82 GiB), 6400 of its 6400 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-21",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-21 (CUDA0), 22-22 (CUDA1), 23-47 (CUDA2), one hand-off per window",
"strata serve: decode expert cache hit rate: 97.7% (5609 hits / 5739 lookups)",
"strata serve: decode expert cache hit rate: 98.7% (4256 hits / 4310 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (4278 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (29862 hits / 30170 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (4264 hits / 4313 lookups)",
"strata serve: decode expert cache hit rate: 98.6% (134418 hits / 136339 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (116986 hits / 117525 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (129134 hits / 129554 lookups)"
]
}
@@ -0,0 +1,84 @@
{
"label": "x2-auto-c1024",
"extra": [
"--layer-split",
"auto",
"--prefill",
"1024"
],
"gpus": "0,2",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2648.7,
"pp_tok_s": 1029.2,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5547.0,
"pp_tok_s": 1510.5,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 10123.3,
"pp_tok_s": 1587.0,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 17652.0,
"pp_tok_s": 1571.7,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4379.9,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 235.5,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 82.7,
"hit_rate": 1.0,
"text": "The wind didn\u2019t just blow; it screamed. It tore through the iron gallery of the Blackwood Point Lighthouse, rattling the glass panes like a prisoner shaking its bars.\n\nElias Thorne was seventy years old, and he had been the keeper for forty of them. He knew every creak in the spiral staircase, every draft that slipped under the door, and every shift in the weather that the barometer couldn\u2019t quite predict. He knew that when the fog rolled in thick enough to swallow the horizon, the world ceased to exist. There was only the light, the stone, and him.\n\nIt was November, the month of storms. The sea was a churning cauldron of grey and white, throwing itself against the granite base with a violence that made the floorboards tremble. Elias stood by the lantern room, his hands wrapped around a mug of coffee that had gone cold hours ago. He wasn\u2019t drinking it. He was holding it for warmth."
},
{
"name": "code",
"tok_s": 111.1,
"hit_rate": 1.0,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv(file_path: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys being the header values\n and values being the corresponding cell values.\n \n Args:\n file_path: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(file_path, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_basic():\n \"\"\"Test basic CSV parsing with simple data.\"\"\"\n import tempfile\n import os\n \n # Create a temporary CSV"
}
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: layer split auto: K=19 - the caches hold 12288 of 12288 profiled pairs (fullest device 99%)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3613 slots",
"strata generate: expert cache 4864 slots, 8.76 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 19-47, expert cache 7424 slots (14.66 GiB), 7424 of its 7424 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-18",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-18 (CUDA0), 19-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 100.0% (5760 hits / 5760 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (4320 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (4320 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (4320 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (4320 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (26400 hits / 26400 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (4320 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (130080 hits / 130080 lookups)",
"strata serve: decode expert cache hit rate: 100.0% (110400 hits / 110400 lookups)"
]
}
@@ -0,0 +1,82 @@
{
"label": "x2-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "0,2",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2711.7,
"pp_tok_s": 1005.3,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5010.3,
"pp_tok_s": 1672.4,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 8994.8,
"pp_tok_s": 1786.1,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 15497.2,
"pp_tok_s": 1790.2,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4050.3,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 249.7,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 83.0,
"hit_rate": 0.9920220447608565,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias had lived in the tower for thirty years, and he knew the difference between a gale and a hurricane by the way the glass rattled in its frame. Tonight, it was the latter. The sea was a churning cauldron of black ink and white foam, throwing itself against the granite base with the force of a battering ram.\n\nInside the lantern room, the air was thick with the smell of kerosene and old brass. Elias wiped his hands on his woolen trousers, leaving faint, oily smears on the fabric. He checked the clock. 11:42 PM. The rotation was due.\n\nHe reached for the crank handle, a heavy iron wheel worn smooth by decades of his own palms. With a grunt that matched the strain of his shoulders, he began to turn it. The massive Fresnel lens, a cathedral of glass and crystal, began its slow, hypnotic sweep"
},
{
"name": "code",
"tok_s": 101.8,
"hit_rate": 0.991767530342175,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: layer split auto: K=19 - the caches hold 11767 of 12288 profiled pairs (fullest device 100%)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4606 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 19-47, expert cache 7160 slots (14.14 GiB), 7160 of its 7424 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-18",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-18 (CUDA0), 19-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 98.5% (6141 hits / 6234 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (3797 hits / 3838 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (3804 hits / 3838 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (3816 hits / 3839 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (3825 hits / 3839 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (27693 hits / 27832 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (3815 hits / 3839 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (127081 hits / 128103 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (119386 hits / 120377 lookups)"
]
}
@@ -0,0 +1,81 @@
{
"label": "x2-k16",
"extra": [
"--layer-split",
"16"
],
"gpus": "0,2",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2987.1,
"pp_tok_s": 912.6,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5908.6,
"pp_tok_s": 1418.1,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 10655.8,
"pp_tok_s": 1507.7,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 18484.6,
"pp_tok_s": 1500.9,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4790.8,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 243.3,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 71.4,
"hit_rate": 0.9814261642156863,
"text": "The wind didn\u2019t just blow; it screamed. It clawed at the granite base of the tower, trying to pry the lighthouse from its bedrock. Inside, however, the air was still, smelling faintly of kerosene and old paper.\n\nElias had been the keeper of the Point for thirty-two years. He knew every creak in the floorboards, every draft that slipped through the window seals, and exactly how much oil the great Fresnel lens needed to turn its slow, hypnotic rotation.\n\nIt was 2:00 AM. The storm was at its peak. The sea was a chaotic black mirror, whipped into whitecaps that looked like jagged teeth.\n\nElias climbed the spiral stairs, his hand gripping the cold iron railing. His boots, heavy with salt and wear, thumped a steady rhythm against the stone. *Thump. Thump. Thump.*\n\nAt the top, the lantern room hummed with the vibration of"
},
{
"name": "code",
"tok_s": 86.4,
"hit_rate": 0.9798276439089693,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 16)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4096 slots, 7.17 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 16-47, expert cache 7124 slots (14.14 GiB), 7124 of its 8192 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-15",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-15 (CUDA0), 16-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 97.7% (5626 hits / 5760 lookups)",
"strata serve: decode expert cache hit rate: 98.1% (4237 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 98.8% (4269 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (4279 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (4298 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (28471 hits / 28800 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (4282 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 98.1% (128135 hits / 130560 lookups)",
"strata serve: decode expert cache hit rate: 98.0% (117109 hits / 119520 lookups)"
]
}
@@ -0,0 +1,81 @@
{
"label": "x2-k22",
"extra": [
"--layer-split",
"22"
],
"gpus": "0,2",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2537.7,
"pp_tok_s": 1074.2,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 4440.0,
"pp_tok_s": 1887.2,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 7872.5,
"pp_tok_s": 2040.8,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 13405.0,
"pp_tok_s": 2069.6,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 3610.4,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 236.0,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 78.1,
"hit_rate": 0.9851122208995093,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy-three years old, and he had been the keeper of the Blackwood Point Light for forty of those years. The other thirty-three had been spent in a hospital bed, waiting for the end.\n\nEvery night, Elias climbed the one hundred and twelve steps of the spiral staircase. His knees, worn smooth by the friction of his own boots over decades, protested with a sharp ache. He counted them. *One, two, three\u2026* The rhythm was his heartbeat. If he lost the count, he lost the light.\n\nAt the top, the lantern room hummed with the low, electric thrum of the Fresnel lens. It was a cathedral of glass and brass, a machine that had never failed him. Not once. Not in four decades.\n\nHe polished the brass railing with a rag that smelled of lemon and"
},
{
"name": "code",
"tok_s": 104.3,
"hit_rate": 0.9842543225870746,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split 22)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session and head; 18.13 GiB free",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4640 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 22-47, expert cache 6656 slots (13.34 GiB), 6656 of its 6656 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-21",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-21 (CUDA0), 22-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 97.7% (5609 hits / 5739 lookups)",
"strata serve: decode expert cache hit rate: 98.5% (4241 hits / 4306 lookups)",
"strata serve: decode expert cache hit rate: 98.9% (4262 hits / 4309 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (4290 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (4303 hits / 4319 lookups)",
"strata serve: decode expert cache hit rate: 98.8% (28379 hits / 28721 lookups)",
"strata serve: decode expert cache hit rate: 99.3% (4285 hits / 4314 lookups)",
"strata serve: decode expert cache hit rate: 98.5% (135713 hits / 137764 lookups)",
"strata serve: decode expert cache hit rate: 98.4% (118518 hits / 120414 lookups)"
]
}
@@ -0,0 +1,82 @@
{
"label": "x2r-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "2,0",
"needles": [
{
"tokens": 2726,
"prompt_ms": 2804.2,
"pp_tok_s": 972.1,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 5641.7,
"pp_tok_s": 1485.2,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 10643.4,
"pp_tok_s": 1509.5,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 18363.5,
"pp_tok_s": 1510.8,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 4517.2,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 245.6,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 78.9,
"hit_rate": 0.9913117283950618,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his woolen collar up to his chin, the fabric stiff with salt and ice. Outside the thick glass of the lantern room, the Atlantic was a churning cauldron of black and white, the waves crashing against the granite base with a violence that shook the very foundations of the tower. It was November, 1924, and the storm had been raging for three days.\n\nElias was not a man who feared the dark. He was a man who feared the silence.\n\nHe checked the clock on the wall. 11:42 PM. The rotation of the great Fresnel lens was steady, a rhythmic *whirrr-click, whirrr-click* that seemed to sync with his own heartbeat. He had been doing this for forty years. His hands were mapped with scars from rope burns and broken glass, his eyes permanently squinted against the glare of the bulb."
},
{
"name": "code",
"tok_s": 93.4,
"hit_rate": 0.990286044973545,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 57.7 GB/s -> pcie_frac 0.55",
"strata generate: layer split: CUDA1 holds its weights, session and head; 9.98 GiB free",
"strata generate: layer split auto: K=36 - the caches hold 11775 of 12288 profiled pairs (fullest device 100%)",
"strata generate: expert cache auto: 18.62 GiB free, 700 MiB reserved -> 6634 slots",
"strata generate: expert cache 8828 slots, 16.45 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 36-47, expert cache 2945 slots (5.99 GiB), 2945 of its 3072 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-35",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-35 (CUDA0), 36-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 98.6% (5678 hits / 5760 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (4283 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.2% (4285 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.6% (4303 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.7% (4306 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.5% (27700 hits / 27840 lookups)",
"strata serve: decode expert cache hit rate: 99.4% (4295 hits / 4320 lookups)",
"strata serve: decode expert cache hit rate: 99.1% (128474 hits / 129600 lookups)",
"strata serve: decode expert cache hit rate: 99.0% (119785 hits / 120960 lookups)"
]
}
@@ -0,0 +1,99 @@
{
"label": "x2t-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "0,1",
"needles": [
{
"tokens": 2726,
"prompt_ms": 4201.1,
"pp_tok_s": 648.9,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 9655.6,
"pp_tok_s": 867.8,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 17573.8,
"pp_tok_s": 914.2,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 30615.6,
"pp_tok_s": 906.2,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 7864.7,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 366.9,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 45.5,
"reps": [
45.5
],
"hit_rate": 0.9703345151405784,
"text": "The wind didn\u2019t just blow; it screamed.\n\nElias pulled his wool coat tighter around his shoulders, the fabric stiff with salt and age. He was seventy years old, but he moved with the grace of a man who had spent decades on a narrow spiral staircase. His hands, rough as sandpaper, knew every bolt, every lever, and every creak in the iron bones of the tower.\n\nFor forty years, Elias had been the guardian of the Point. Not just of the light, but of the silence between the waves.\n\nIt was November. The sky was a bruised purple, heavy with rain that hadn\u2019t yet fallen. The sea was a churning cauldron of grey and white, throwing itself against the granite base with a violence that shook the floorboards beneath Elias\u2019s boots.\n\nHe climbed the stairs. One hundred and twelve steps. He counted them by feel, his left hand trailing along the cold iron railing. At the top, the air was"
},
{
"name": "code",
"tok_s": 58.0,
"reps": [
58.0
],
"hit_rate": 0.9700542735219787,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv_to_dicts(filepath: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys from the header and values from the row.\n \n Args:\n filepath: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result: List[Dict[str, Any]] = []\n with open(filepath, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_to_dicts():\n import tempfile\n import os\n \n # Create a temporary CSV file\n with tempfile.NamedTemporaryFile"
}
],
"stage_timing": [
"strata serve: stage 0: 168 windows; per window: wait for the GPU 9.683 ms, pool + plan 0.509 ms, host staging 0.714 ms, commit 0.320 ms",
"strata serve: stage 1: 168 windows; per window: wait for the GPU 25.253 ms, pool + plan 0.881 ms, host staging 0.000 ms, commit 0.559 ms",
"strata serve: stage 0: 242 windows; per window: wait for the GPU 9.860 ms, pool + plan 0.550 ms, host staging 0.750 ms, commit 0.328 ms",
"strata serve: stage 1: 242 windows; per window: wait for the GPU 26.029 ms, pool + plan 0.980 ms, host staging 0.000 ms, commit 0.571 ms"
],
"engine_lines": [
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 3.2 GB/s -> pcie_frac 0.00",
"strata generate: layer split: CUDA1 holds its weights, session and head; 16.18 GiB free",
"strata generate: layer split auto: K=20 - the caches hold 10761 of 12288 profiled pairs (fullest device 100%)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4603 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 20-47, expert cache 6157 slots (12.19 GiB), 6157 of its 7168 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-19",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-19 (CUDA0), 20-47 (CUDA1), one hand-off per window",
"strata generate: layer split across 2 GPUs: CUDA0, then CUDA1 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 3.2 GB/s -> pcie_frac 0.00",
"strata generate: layer split: CUDA1 holds its weights, session and head; 16.18 GiB free",
"strata generate: layer split auto: K=20 - the caches hold 10761 of 12288 profiled pairs (fullest device 100%)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3345 slots",
"strata generate: expert cache 4603 slots, 8.29 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 20-47, expert cache 6157 slots (12.19 GiB), 6157 of its 7168 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-19",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-19 (CUDA0), 20-47 (CUDA1), one hand-off per window",
"strata serve: decode expert cache hit rate: 96.6% (5559 hits / 5754 lookups)",
"strata serve: decode expert cache hit rate: 97.5% (4205 hits / 4315 lookups)",
"strata serve: decode expert cache hit rate: 97.8% (4217 hits / 4312 lookups)",
"strata serve: decode expert cache hit rate: 98.6% (4257 hits / 4318 lookups)"
]
}
@@ -0,0 +1,99 @@
{
"label": "x3-auto",
"extra": [
"--layer-split",
"auto"
],
"gpus": "0,2,1",
"needles": [
{
"tokens": 2726,
"prompt_ms": 3657.4,
"pp_tok_s": 745.3,
"found": true,
"answer": "meadow-254<|im_end|>"
},
{
"tokens": 8379,
"prompt_ms": 7594.1,
"pp_tok_s": 1103.4,
"found": true,
"answer": "juniper-766<|im_end|>"
},
{
"tokens": 16066,
"prompt_ms": 13996.4,
"pp_tok_s": 1147.9,
"found": true,
"answer": "falcon-174<|im_end|>"
},
{
"tokens": 27743,
"prompt_ms": 24255.2,
"pp_tok_s": 1143.8,
"found": true,
"answer": "willow-474<|im_end|>"
}
],
"chat": {
"turn1_tokens": 6584,
"turn1_ms": 6104.9,
"turn2_tokens": 6660,
"reused": 6632,
"turn2_ms": 393.4,
"answer": "lantern-417<|im_end|>",
"ok": true
},
"decode": [
{
"name": "story",
"tok_s": 51.9,
"reps": [
51.9
],
"hit_rate": 1.0,
"text": "The wind didn\u2019t just blow; it screamed. It tore through the iron gallery of the Blackwood Point Lighthouse, rattling the glass panes like a prisoner shaking its bars.\n\nElias Thorne was seventy years old, and he had been the keeper for forty of them. He knew every creak in the spiral staircase, every draft that slipped under the door, and every shift in the weather that the barometer couldn\u2019t quite predict. He knew that when the fog rolled in thick enough to swallow the horizon, the world ceased to exist. There was only the light, the stone, and him.\n\nIt was November, the month of storms. The sea was a churning cauldron of grey and white, throwing itself against the granite base with a violence that made the floorboards tremble. Elias stood by the lantern room, his hands wrapped around a mug of coffee that had gone cold hours ago. He wasn\u2019t drinking it. He was holding it for warmth."
},
{
"name": "code",
"tok_s": 69.7,
"reps": [
69.7
],
"hit_rate": 1.0,
"text": "```python\nimport csv\nfrom typing import List, Dict, Any\n\n\ndef parse_csv(file_path: str) -> List[Dict[str, Any]]:\n \"\"\"\n Parse a CSV file into a list of dictionaries.\n \n Each dictionary represents a row, with keys being the header values\n and values being the corresponding cell values.\n \n Args:\n file_path: Path to the CSV file.\n \n Returns:\n A list of dictionaries, one per row (excluding the header).\n \"\"\"\n result = []\n with open(file_path, 'r', newline='', encoding='utf-8') as f:\n reader = csv.DictReader(f)\n for row in reader:\n result.append(dict(row))\n return result\n\n\n# Tests\ndef test_parse_csv_basic():\n \"\"\"Test basic CSV parsing with simple data.\"\"\"\n import tempfile\n import os\n \n # Create a temporary CSV"
}
],
"stage_timing": [
"strata serve: stage 2: 165 windows; per window: wait for the GPU 13.452 ms, pool + plan 0.162 ms, host staging 0.000 ms, commit 0.328 ms",
"strata serve: stage 0: 234 windows; per window: wait for the GPU 6.235 ms, pool + plan 0.145 ms, host staging 0.704 ms, commit 0.214 ms",
"strata serve: stage 1: 234 windows; per window: wait for the GPU 15.592 ms, pool + plan 0.246 ms, host staging 0.000 ms, commit 0.657 ms",
"strata serve: stage 2: 234 windows; per window: wait for the GPU 13.916 ms, pool + plan 0.177 ms, host staging 0.000 ms, commit 0.335 ms"
],
"engine_lines": [
"strata generate: layer split across 3 GPUs: CUDA0, then CUDA1,2 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session; 18.62 GiB free",
"strata generate: layer split: CUDA2 PCIe probe 3.2 GB/s -> pcie_frac 0.00",
"strata generate: layer split: CUDA2 holds its weights, session and head; 16.18 GiB free",
"strata generate: layer split auto: K=12,33 - the caches hold 12288 of 12288 profiled pairs (fullest device 66%)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3072 slots",
"strata generate: expert cache 3072 slots, 5.41 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 12-32, expert cache 5376 slots (10.22 GiB), 5376 of its 5376 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA2 runs layers 33-47, expert cache 3840 slots (7.79 GiB), 3840 of its 3840 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-11",
"strata serve: the prompt path allocates its own buffers (too few cache slots to borrow)",
"strata serve: layer split: layers 0-11 (CUDA0), 12-32 (CUDA1), 33-47 (CUDA2), one hand-off per window",
"strata generate: layer split across 3 GPUs: CUDA0, then CUDA1,2 (split auto)",
"strata generate: layer split: CUDA1 PCIe probe 6.4 GB/s -> pcie_frac 0.14",
"strata generate: layer split: CUDA1 holds its weights, session; 18.62 GiB free",
"strata generate: layer split: CUDA2 PCIe probe 3.2 GB/s -> pcie_frac 0.00",
"strata generate: layer split: CUDA2 holds its weights, session and head; 16.18 GiB free",
"strata generate: layer split auto: K=12,33 - the caches hold 12288 of 12288 profiled pairs (fullest device 66%)",
"strata generate: expert cache auto: 10.46 GiB free, 700 MiB reserved -> 3072 slots",
"strata generate: expert cache 3072 slots, 5.41 GiB of VRAM; policy is",
"strata generate: layer split: CUDA1 runs layers 12-32, expert cache 5376 slots (10.22 GiB), 5376 of its 5376 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA2 runs layers 33-47, expert cache 3840 slots (7.79 GiB), 3840 of its 3840 profiled pairs; slot 0 verified",
"strata generate: layer split: CUDA0 runs layers 0-11"
]
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,46 @@
# Preliminary native RTX 3090 / EPYC 7453 results
Issue: [#165](https://github.com/Niko1221/Strata/issues/165)
Engine: Strata v0.1.26, source build at `ac8b251`
Status: preliminary; prompt set and harness guidance requested upstream.
## Hardware
| Component | Guest-visible details |
|---|---|
| CPU | AMD EPYC 7453, 20 vCPU, AVX2; AVX-512 not exposed |
| RAM | 165 GiB visible to Ubuntu 24.04.5 KVM guest; physical DIMM layout and bandwidth not verified |
| GPUs | 2x RTX 3090 24 GB; PCIe Gen4 x16 maximum reported per card; driver 595.91.07 |
| Power limit | 280 W configured on each GPU; defaults are 350 W / 420 W, max-adjustable limits 366 W / 450 W |
| Storage | 541 GiB ext4 guest data disk; host storage layout not verified |
The power values above are configured caps, not measured workload draw. Idle draw during the hardware capture was approximately 35 W / 29 W.
## Method
Native Linux setup, local build from v0.1.26, `--prefill auto`, shipped expert profile, automatic expert cache, int8 KV, MTP on, images off, experimental speed projection off. The GPU power limit was 280 W per card. Native runs used a systemd service with `LimitMEMLOCK=infinity`; other llama-swap model containers were stopped.
The local API harness used the public Strata source tree as its prompt corpus, with generated tasks, `temperature: 0`, `reasoning_effort: none`, and `max_tokens: 256`. It is not the upstream code-agent prompt set or harness. Runs use unique request IDs and no conversation reuse in speed mode. Three runs per speed cell are summarized by medians; actual `usage.prompt_tokens` are recorded because nominal context labels can drift. The first decode run is also shown. Several requests ended early on EOS, so actual generated counts are in the raw JSON.
No calibration comparison, upstream prompt comparison, `STRATA_SPLIT_TIMING` stage timing, or under-load power-draw capture has been done. Treat all prompt/decode comparisons with upstream as indicative only until the requested upstream method is available.
## Results
See [`matrix.md`](matrix.md) for summary tables and [`matrix.json`](matrix.json) for all successful run records. Per-run source JSON, logs, the local harness, and scrubbed hardware facts are in [`data/`](data/).
Highlights:
- IQ3_XXS on one 3090: 1K–262K cells, three runs each; 262K prompts were 232,183 / 259,589 / 261,068 tokens and fit within the 262,144 context.
- Coder IQ1_M at 32K: single-GPU median decode 66.8 tok/s; dual-GPU auto (K=19) median 98.6 tok/s.
- Fixed Coder K sweep: K=22 was the highest observed three-run median (103.3 tok/s); independently generated prompts mean this is not a paired ranking.
- IQ3_XXS and IQ3_S dual-GPU auto both selected K=24 at 32K.
- IQ3_XXS and IQ3_S needles passed 3/3 at 8K / 16K / 28K on the tested single/dual configurations; two-turn checkpoint reuse passed for both.
## Limits and follow-up
- Upstream prompts/harness are not yet available; all current speed data uses a local source-tree corpus.
- The guest exposes one NUMA node and 165 GiB RAM, but physical DIMM topology and memory bandwidth are unknown.
- PCIe x16 is the reported maximum link width; the engine probes were about 23–26 GB/s. Verify PCIe generation/width under sustained load before claiming a direct x16-vs-x4 comparison.
- Coder dual-GPU needle/two-turn checks and dual-GPU timing breakdowns are not run yet.
- K=18 has one successful run at 83.1 tok/s; a repeated startup failed while uploading a dense weight with an out-of-memory error, so it is not a three-run median.
- Calibration/default comparisons and IQ3_S single-GPU results remain outstanding.
@@ -0,0 +1,360 @@
{
"label": "native-v0.1.26-Coder-1x3090",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 1,
"run_id": "6c1252339f83",
"corpus_repeated": false,
"time": "2026-09-29T21:11:07",
"prompt_tokens": 28317,
"completion_tokens": 48,
"ttft_s": 12.868,
"e2e_s": 13.739,
"api_prompt_tps": 2200.5,
"api_decode_tps": 54.0,
"engine_prompt_tps": 2229.2,
"engine_decode_tps": 52.7,
"log_lines": [
"strata serve: prompt 28317 tokens = 0 reused + 28317 read in 12703 ms (2229.2 tok/s), 48 generated in 911 ms (52.7 tok/s), drafts accepted 31 of 38, 2 checkpoints",
"strata serve: decode expert cache hit rate: 96.3% (25001 hits / 25956 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what's there and identify the last file.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 8973,
"expert_cache_mib": 17503,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 369,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790716268.10927,
"duration_s": 13.6,
"finish": "stop",
"prompt_tokens": 28317,
"reused": 0,
"output_tokens": 48,
"engine_generated": 48,
"prompt_ms": 12702.6,
"decode_ms": 910.6,
"decode_tok_s": 52.7,
"hit_rate": 0.963
}
],
"requests_kept": 1,
"totals": {
"since": 1790716256.7391467,
"requests": 1,
"prompt_tokens": 28317,
"reused": 0,
"output_tokens": 48,
"prompt_ms": 12702.6,
"decode_ms": 910.6
},
"hardware": {
"gpu_util": 88,
"gpu_mem_used": 25531711488,
"gpu_mem_total": 25769803776,
"gpu_temp": 46,
"gpu_power": 220.362,
"gpu_power_limit": 280.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 0.28515625,
"gpu_pcie_tx_mb": 0.4287109375,
"cpu": 81.3,
"ram_used": 43964985344,
"ram_total": 177175990272,
"disk_read_mb": 2.9148478336133063,
"disk_write_mb": 0.0,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090",
"gpu_count": 1,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
99,
100,
100,
96,
100,
99,
100,
99,
99,
100,
100,
88
],
"gpu_mem_used": [
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25407979520,
25418465280,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25531711488
],
"gpu_temp": [
43,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
46,
47,
47,
47,
47,
47,
47,
47,
47,
47,
47,
46
],
"gpu_power": [
148.8,
140.48,
132.59,
119.03,
118.99,
119.03,
119.04,
119.03,
119.12,
119.17,
119.16,
119.15,
119.26,
191.57,
276.49,
276.88,
275.95,
277.04,
277.35,
267.91,
275.37,
276.16,
278.28,
279.07,
220.36
],
"gpu_pcie_rx_mb": [
0.33,
0.33,
0.29,
0.29,
0.33,
0.33,
0.33,
0.29,
0.33,
0.29,
0.33,
0.29,
385.14,
19.88,
1892.23,
13280.39,
85.45,
16838.55,
680.06,
1060.72,
13.45,
15.74,
5013.75,
1194.52,
0.29
],
"cpu": [
0.0,
0.7,
7.3,
15.7,
7.1,
2.0,
1.2,
2.0,
2.1,
1.6,
1.9,
7.4,
13.9,
18.3,
6.1,
6.0,
16.7,
24.9,
13.2,
12.0,
7.1,
6.2,
6.0,
8.8,
81.3
],
"ram_used": [
43381886976,
43381694464,
43410796544,
43434872832,
43483377664,
43485900800,
43484897280,
43481210880,
43469135872,
43462295552,
43458363392,
43496140800,
43624353792,
43648593920,
43633405952,
43641139200,
43635249152,
43689267200,
43846070272,
43924541440,
43924467712,
43918565376,
43883311104,
44002877440,
43964985344
],
"disk_read_mb": [
null,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.01,
0.0,
0.0,
0.0,
25.43,
204.93,
214.14,
7.41,
0.22,
184.19,
135.03,
0.0,
101.27,
0.0,
0.0,
0.0,
0.78,
2.91
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790716282.234284
}
}
@@ -0,0 +1,491 @@
{
"label": "native-v0.1.26-Coder-1x3090",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 2,
"run_id": "894af0ad69b7",
"corpus_repeated": false,
"time": "2026-09-29T21:11:22",
"prompt_tokens": 31630,
"completion_tokens": 26,
"ttft_s": 12.792,
"e2e_s": 13.164,
"api_prompt_tps": 2472.7,
"api_decode_tps": 67.3,
"engine_prompt_tps": 2506.6,
"engine_decode_tps": 66.8,
"log_lines": [
"strata serve: prompt 31630 tokens = 0 reused + 31630 read in 12619 ms (2506.6 tok/s), 26 generated in 389 ms (66.8 tok/s), drafts accepted 12 of 21, 2 checkpoints",
"strata serve: decode expert cache hit rate: 99.1% (16646 hits / 16791 lookups)"
],
"answer_head": "I don't see any file content in the conversation above. Could you please share the file you'd like me to analyze?",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 8973,
"expert_cache_mib": 17503,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 369,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790716282.3818104,
"duration_s": 13.0,
"finish": "stop",
"prompt_tokens": 31630,
"reused": 0,
"output_tokens": 26,
"engine_generated": 26,
"prompt_ms": 12618.8,
"decode_ms": 389.0,
"decode_tok_s": 66.8,
"hit_rate": 0.991
},
{
"projection": null,
"time": 1790716268.10927,
"duration_s": 13.6,
"finish": "stop",
"prompt_tokens": 28317,
"reused": 0,
"output_tokens": 48,
"engine_generated": 48,
"prompt_ms": 12702.6,
"decode_ms": 910.6,
"decode_tok_s": 52.7,
"hit_rate": 0.963
}
],
"requests_kept": 2,
"totals": {
"since": 1790716256.7391467,
"requests": 2,
"prompt_tokens": 59947,
"reused": 0,
"output_tokens": 74,
"prompt_ms": 25321.4,
"decode_ms": 1299.6
},
"hardware": {
"gpu_util": 81,
"gpu_mem_used": 25531711488,
"gpu_mem_total": 25769803776,
"gpu_temp": 45,
"gpu_power": 256.109,
"gpu_power_limit": 280.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 9.7744140625,
"gpu_pcie_tx_mb": 25.1767578125,
"cpu": 46.1,
"ram_used": 43872071680,
"ram_total": 177175990272,
"disk_read_mb": 1.6222807345356267,
"disk_write_mb": 0.018689870213544086,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090",
"gpu_count": 1,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
99,
100,
100,
96,
100,
99,
100,
99,
99,
100,
100,
88,
0,
100,
99,
100,
99,
100,
98,
99,
99,
100,
99,
100,
81
],
"gpu_mem_used": [
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25407979520,
25418465280,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488
],
"gpu_temp": [
43,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
46,
47,
47,
47,
47,
47,
47,
47,
47,
47,
47,
46,
44,
47,
47,
47,
47,
47,
47,
47,
47,
47,
48,
47,
45
],
"gpu_power": [
148.8,
140.48,
132.59,
119.03,
118.99,
119.03,
119.04,
119.03,
119.12,
119.17,
119.16,
119.15,
119.26,
191.57,
276.49,
276.88,
275.95,
277.04,
277.35,
267.91,
275.37,
276.16,
278.28,
279.07,
220.36,
189.89,
258.72,
276.75,
276.67,
277.52,
275.34,
268.93,
277.69,
277.77,
276.91,
278.53,
278.56,
256.11
],
"gpu_pcie_rx_mb": [
0.33,
0.33,
0.29,
0.29,
0.33,
0.33,
0.33,
0.29,
0.33,
0.29,
0.33,
0.29,
385.14,
19.88,
1892.23,
13280.39,
85.45,
16838.55,
680.06,
1060.72,
13.45,
15.74,
5013.75,
1194.52,
0.29,
2531.19,
14.02,
3715.09,
10.68,
13440.37,
8568.48,
14.92,
15.31,
15.69,
6486.56,
17.31,
9674.45,
9.77
],
"cpu": [
0.0,
0.7,
7.3,
15.7,
7.1,
2.0,
1.2,
2.0,
2.1,
1.6,
1.9,
7.4,
13.9,
18.3,
6.1,
6.0,
16.7,
24.9,
13.2,
12.0,
7.1,
6.2,
6.0,
8.8,
81.3,
4.2,
6.8,
6.4,
7.6,
5.5,
6.0,
20.6,
12.9,
6.2,
7.0,
5.7,
5.9,
46.1
],
"ram_used": [
43381886976,
43381694464,
43410796544,
43434872832,
43483377664,
43485900800,
43484897280,
43481210880,
43469135872,
43462295552,
43458363392,
43496140800,
43624353792,
43648593920,
43633405952,
43641139200,
43635249152,
43689267200,
43846070272,
43924541440,
43924467712,
43918565376,
43883311104,
44002877440,
43964985344,
43959267328,
43943862272,
43937861632,
43930320896,
43922284544,
43915001856,
43817238528,
43819147264,
43818729472,
43828027392,
43824201728,
43825434624,
43872071680
],
"disk_read_mb": [
null,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.01,
0.0,
0.0,
0.0,
25.43,
204.93,
214.14,
7.41,
0.22,
184.19,
135.03,
0.0,
101.27,
0.0,
0.0,
0.0,
0.78,
2.91,
0.61,
0.0,
0.0,
0.02,
0.0,
0.0,
79.64,
41.92,
0.0,
0.0,
0.0,
0.0,
1.62
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790716295.9009006
}
}
@@ -0,0 +1,622 @@
{
"label": "native-v0.1.26-Coder-1x3090",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 3,
"run_id": "a4c63bd599ab",
"corpus_repeated": false,
"time": "2026-09-29T21:11:35",
"prompt_tokens": 31601,
"completion_tokens": 45,
"ttft_s": 12.796,
"e2e_s": 13.243,
"api_prompt_tps": 2469.6,
"api_decode_tps": 98.5,
"engine_prompt_tps": 2502.4,
"engine_decode_tps": 97.4,
"log_lines": [
"strata serve: prompt 31601 tokens = 0 reused + 31601 read in 12628 ms (2502.4 tok/s), 45 generated in 462 ms (97.4 tok/s), drafts accepted 33 of 37, 2 checkpoints",
"strata serve: decode expert cache hit rate: 98.6% (23985 hits / 24333 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what we're working with.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 8973,
"expert_cache_mib": 17503,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 369,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790716296.0458817,
"duration_s": 13.1,
"finish": "stop",
"prompt_tokens": 31601,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 12628.3,
"decode_ms": 461.9,
"decode_tok_s": 97.4,
"hit_rate": 0.986
},
{
"projection": null,
"time": 1790716282.3818104,
"duration_s": 13.0,
"finish": "stop",
"prompt_tokens": 31630,
"reused": 0,
"output_tokens": 26,
"engine_generated": 26,
"prompt_ms": 12618.8,
"decode_ms": 389.0,
"decode_tok_s": 66.8,
"hit_rate": 0.991
},
{
"projection": null,
"time": 1790716268.10927,
"duration_s": 13.6,
"finish": "stop",
"prompt_tokens": 28317,
"reused": 0,
"output_tokens": 48,
"engine_generated": 48,
"prompt_ms": 12702.6,
"decode_ms": 910.6,
"decode_tok_s": 52.7,
"hit_rate": 0.963
}
],
"requests_kept": 3,
"totals": {
"since": 1790716256.7391467,
"requests": 3,
"prompt_tokens": 91548,
"reused": 0,
"output_tokens": 119,
"prompt_ms": 37949.7,
"decode_ms": 1761.5
},
"hardware": {
"gpu_util": 96,
"gpu_mem_used": 25531711488,
"gpu_mem_total": 25769803776,
"gpu_temp": 47,
"gpu_power": 245.399,
"gpu_power_limit": 280.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 414.037109375,
"gpu_pcie_tx_mb": 303.36328125,
"cpu": 35.1,
"ram_used": 43772702720,
"ram_total": 177175990272,
"disk_read_mb": 0.6087260569540994,
"disk_write_mb": 0.20539836277592313,
"tok_s": 97.29124144311484,
"tok_s_mean": 96.62332449971758
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090",
"gpu_count": 1,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
99,
100,
100,
96,
100,
99,
100,
99,
99,
100,
100,
88,
0,
100,
99,
100,
99,
100,
98,
99,
99,
100,
99,
100,
81,
99,
100,
100,
99,
100,
99,
17,
99,
100,
99,
99,
99,
96
],
"gpu_mem_used": [
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25382813696,
25407979520,
25418465280,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25424756736,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488,
25531711488
],
"gpu_temp": [
43,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
46,
47,
47,
47,
47,
47,
47,
47,
47,
47,
47,
46,
44,
47,
47,
47,
47,
47,
47,
47,
47,
47,
48,
47,
45,
47,
47,
47,
48,
48,
48,
46,
47,
48,
48,
48,
48,
47
],
"gpu_power": [
148.8,
140.48,
132.59,
119.03,
118.99,
119.03,
119.04,
119.03,
119.12,
119.17,
119.16,
119.15,
119.26,
191.57,
276.49,
276.88,
275.95,
277.04,
277.35,
267.91,
275.37,
276.16,
278.28,
279.07,
220.36,
189.89,
258.72,
276.75,
276.67,
277.52,
275.34,
268.93,
277.69,
277.77,
276.91,
278.53,
278.56,
256.11,
199.6,
275.35,
275.49,
276.31,
275.46,
277.78,
266.35,
267.73,
276.71,
276.41,
275.34,
278.17,
245.4
],
"gpu_pcie_rx_mb": [
0.33,
0.33,
0.29,
0.29,
0.33,
0.33,
0.33,
0.29,
0.33,
0.29,
0.33,
0.29,
385.14,
19.88,
1892.23,
13280.39,
85.45,
16838.55,
680.06,
1060.72,
13.45,
15.74,
5013.75,
1194.52,
0.29,
2531.19,
14.02,
3715.09,
10.68,
13440.37,
8568.48,
14.92,
15.31,
15.69,
6486.56,
17.31,
9674.45,
9.77,
2795.22,
4473.69,
14.5,
12.97,
14.92,
7716.66,
2819.78,
4278.33,
12009.48,
17.07,
9916.78,
8518.17,
414.04
],
"cpu": [
0.0,
0.7,
7.3,
15.7,
7.1,
2.0,
1.2,
2.0,
2.1,
1.6,
1.9,
7.4,
13.9,
18.3,
6.1,
6.0,
16.7,
24.9,
13.2,
12.0,
7.1,
6.2,
6.0,
8.8,
81.3,
4.2,
6.8,
6.4,
7.6,
5.5,
6.0,
20.6,
12.9,
6.2,
7.0,
5.7,
5.9,
46.1,
4.0,
6.9,
5.7,
6.2,
7.1,
5.5,
5.6,
9.9,
13.7,
8.4,
7.7,
6.4,
35.1
],
"ram_used": [
43381886976,
43381694464,
43410796544,
43434872832,
43483377664,
43485900800,
43484897280,
43481210880,
43469135872,
43462295552,
43458363392,
43496140800,
43624353792,
43648593920,
43633405952,
43641139200,
43635249152,
43689267200,
43846070272,
43924541440,
43924467712,
43918565376,
43883311104,
44002877440,
43964985344,
43959267328,
43943862272,
43937861632,
43930320896,
43922284544,
43915001856,
43817238528,
43819147264,
43818729472,
43828027392,
43824201728,
43825434624,
43872071680,
43734614016,
43728027648,
43719413760,
43703148544,
43695366144,
43687342080,
43744829440,
43791024128,
43789787136,
43766198272,
43761831936,
43750072320,
43772702720
],
"disk_read_mb": [
null,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.01,
0.0,
0.0,
0.0,
25.43,
204.93,
214.14,
7.41,
0.22,
184.19,
135.03,
0.0,
101.27,
0.0,
0.0,
0.0,
0.78,
2.91,
0.61,
0.0,
0.0,
0.02,
0.0,
0.0,
79.64,
41.92,
0.0,
0.0,
0.0,
0.0,
1.62,
0.72,
0.0,
0.0,
0.05,
0.0,
0.0,
0.18,
0.0,
0.0,
0.0,
0.0,
0.0,
0.61
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
97.29
]
},
"time": 1790716309.6464093
}
}
@@ -0,0 +1,423 @@
{
"label": "native-v0.1.26-Coder-2x3090-K18",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 1,
"run_id": "052cbcbb7af7",
"corpus_repeated": false,
"time": "2026-09-29T21:21:48",
"prompt_tokens": 28313,
"completion_tokens": 57,
"ttft_s": 12.346,
"e2e_s": 12.991,
"api_prompt_tps": 2293.3,
"api_decode_tps": 86.9,
"engine_prompt_tps": 2324.6,
"engine_decode_tps": 83.1,
"log_lines": [
"strata serve: prompt 28313 tokens = 0 reused + 28313 read in 12180 ms (2324.6 tok/s), 57 generated in 686 ms (83.1 tok/s), drafts accepted 39 of 41, 2 checkpoints",
"strata serve: decode expert cache hit rate: 99.7% (28717 hits / 28800 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what's there and identify the last file.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 4608,
"expert_cache_mib": 8437,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 9557,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790716909.0337873,
"duration_s": 12.9,
"finish": "stop",
"prompt_tokens": 28313,
"reused": 0,
"output_tokens": 57,
"engine_generated": 57,
"prompt_ms": 12179.9,
"decode_ms": 685.7,
"decode_tok_s": 83.1,
"hit_rate": 0.997
}
],
"requests_kept": 1,
"totals": {
"since": 1790716889.8200681,
"requests": 1,
"prompt_tokens": 28313,
"reused": 0,
"output_tokens": 57,
"prompt_ms": 12179.9,
"decode_ms": 685.7
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 0,
"mem_used": 15823994880,
"mem_total": 25769803776,
"temp": 44,
"power": 161.267
},
{
"index": 1,
"util": 90,
"mem_used": 24208408576,
"mem_total": 25769803776,
"temp": 51,
"power": 278.413
}
],
"gpu_util": 45.0,
"gpu_mem_used": 40032403456,
"gpu_mem_total": 51539607552,
"gpu_temp": 51,
"gpu_power": 439.68,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 5.33984375,
"gpu_pcie_tx_mb": 18.5009765625,
"cpu": 16.1,
"ram_used": 44574941184,
"ram_total": 177175990272,
"disk_read_mb": 0.9737281407762519,
"disk_write_mb": 0.11497896865254635,
"tok_s": 36.0,
"tok_s_mean": 64.01736582189179
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
0.5,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
41.5,
68.0,
50.0,
73.0,
87.5,
41.5,
47.0,
50.0,
78.0,
50.5,
45.0
],
"gpu_mem_used": [
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39866728448,
39965294592,
39965294592,
39965294592,
39965294592,
39965294592,
39965294592,
39965294592,
39965294592,
39965294592,
39965294592,
40032403456
],
"gpu_temp": [
45,
44,
44,
44,
44,
44,
45,
44,
44,
45,
45,
45,
45,
45,
45,
45,
42,
41,
41,
47,
50,
51,
51,
51,
52,
51,
52,
51,
52,
51
],
"gpu_power": [
255.0,
240.14,
239.41,
239.38,
239.55,
239.75,
239.74,
239.65,
239.56,
239.63,
239.71,
239.58,
239.49,
239.62,
239.83,
239.9,
200.58,
138.26,
69.98,
230.69,
516.11,
485.77,
500.33,
518.82,
491.98,
467.22,
490.75,
506.65,
510.68,
439.68
],
"gpu_pcie_rx_mb": [
0.71,
0.62,
0.67,
0.67,
0.67,
0.57,
0.71,
0.62,
0.62,
0.57,
0.67,
0.62,
0.71,
0.67,
0.67,
0.62,
0.76,
0.86,
0.71,
1243.59,
45.3,
2454.38,
77.06,
3382.78,
1099.87,
47.06,
2677.87,
79.25,
1143.26,
5.34
],
"cpu": [
0.0,
1.0,
2.8,
3.4,
1.0,
1.6,
1.6,
1.0,
1.4,
0.7,
3.6,
8.3,
1.0,
8.2,
5.7,
2.6,
3.4,
1.4,
10.1,
12.9,
8.2,
10.3,
10.7,
11.6,
27.0,
14.9,
11.2,
18.3,
15.0,
16.1
],
"ram_used": [
43962929152,
43965493248,
43981484032,
43968327680,
43968602112,
43968954368,
43964538880,
43964022784,
43964801024,
43964657664,
43961843712,
43958075392,
43958878208,
43967004672,
43967807488,
43951247360,
43945017344,
43945320448,
43971616768,
44103409664,
44112924672,
44112818176,
43979321344,
43978510336,
44145692672,
44276580352,
44275351552,
44327723008,
44296556544,
44574941184
],
"disk_read_mb": [
null,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
64.21,
105.78,
24.45,
89.11,
67.32,
92.34,
145.4,
79.43,
98.92,
75.23,
0.0,
0.97
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
36.0
]
},
"time": 1790716922.4098496
}
}
@@ -0,0 +1,270 @@
{
"label": "native-v0.1.26-Coder-2x3090-K22",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 1,
"run_id": "69d919ce1297",
"corpus_repeated": false,
"time": "2026-09-29T21:32:53",
"prompt_tokens": 28316,
"completion_tokens": 57,
"ttft_s": 10.575,
"e2e_s": 11.256,
"api_prompt_tps": 2677.7,
"api_decode_tps": 82.2,
"engine_prompt_tps": 2718.9,
"engine_decode_tps": 79.8,
"log_lines": [
"strata serve: prompt 28316 tokens = 0 reused + 28316 read in 10414 ms (2718.9 tok/s), 57 generated in 715 ms (79.8 tok/s), drafts accepted 37 of 48, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (34080 hits / 34080 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what's there and identify the last file.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 5632,
"expert_cache_mib": 10325,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 7669,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717573.1737459,
"duration_s": 11.1,
"finish": "stop",
"prompt_tokens": 28316,
"reused": 0,
"output_tokens": 57,
"engine_generated": 57,
"prompt_ms": 10414.4,
"decode_ms": 714.5,
"decode_tok_s": 79.8,
"hit_rate": 1.0
}
],
"requests_kept": 1,
"totals": {
"since": 1790717571.262619,
"requests": 1,
"prompt_tokens": 28316,
"reused": 0,
"output_tokens": 57,
"prompt_ms": 10414.4,
"decode_ms": 714.5
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 34,
"mem_used": 17822580736,
"mem_total": 25769803776,
"temp": 49,
"power": 184.581
},
{
"index": 1,
"util": 54,
"mem_used": 22828482560,
"mem_total": 25769803776,
"temp": 52,
"power": 232.993
}
],
"gpu_util": 44.0,
"gpu_mem_used": 40651063296,
"gpu_mem_total": 51539607552,
"gpu_temp": 52,
"gpu_power": 417.57399999999996,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 0.666015625,
"gpu_pcie_tx_mb": 0.953125,
"cpu": 6.2,
"ram_used": 44281556992,
"ram_total": 177175990272,
"disk_read_mb": 3.421090719626586,
"disk_write_mb": 0.0,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1.0,
0.0,
0.0,
73.5,
58.5,
96.0,
57.5,
95.5,
47.5,
93.0,
58.5,
50.0,
44.0
],
"gpu_mem_used": [
40449736704,
40449736704,
40449736704,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40651063296
],
"gpu_temp": [
48,
47,
47,
50,
53,
53,
54,
53,
54,
54,
54,
54,
52
],
"gpu_power": [
256.98,
244.54,
244.03,
340.49,
532.78,
528.1,
525.31,
536.01,
504.78,
526.85,
529.86,
484.8,
417.57
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
0.67,
78.72,
82.54,
76.1,
85.97,
55.17,
62.23,
87.12,
83.54,
85.83,
0.67
],
"cpu": [
0.0,
1.2,
5.4,
10.7,
10.0,
9.8,
14.0,
18.3,
17.8,
18.6,
9.0,
5.8,
6.2
],
"ram_used": [
43564908544,
43556737024,
43564126208,
43739512832,
43734302720,
43734560768,
43735560192,
43898122240,
44013469696,
43991674880,
43978436608,
43974189056,
44281556992
],
"disk_read_mb": [
null,
0.0,
73.94,
96.25,
39.8,
73.77,
144.08,
110.85,
127.27,
131.68,
42.81,
0.0,
3.42
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790717584.813652
}
}
@@ -0,0 +1,383 @@
{
"label": "native-v0.1.26-Coder-2x3090-K22",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 2,
"run_id": "8506420b91e1",
"corpus_repeated": false,
"time": "2026-09-29T21:33:04",
"prompt_tokens": 31633,
"completion_tokens": 45,
"ttft_s": 10.85,
"e2e_s": 11.271,
"api_prompt_tps": 2915.4,
"api_decode_tps": 104.6,
"engine_prompt_tps": 2962.7,
"engine_decode_tps": 103.3,
"log_lines": [
"strata serve: prompt 31633 tokens = 0 reused + 31633 read in 10677 ms (2962.7 tok/s), 45 generated in 436 ms (103.3 tok/s), drafts accepted 32 of 37, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (24960 hits / 24960 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what the last file does.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 5632,
"expert_cache_mib": 10325,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 7669,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717584.9637764,
"duration_s": 11.1,
"finish": "stop",
"prompt_tokens": 31633,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 10677.2,
"decode_ms": 435.6,
"decode_tok_s": 103.3,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717573.1737459,
"duration_s": 11.1,
"finish": "stop",
"prompt_tokens": 28316,
"reused": 0,
"output_tokens": 57,
"engine_generated": 57,
"prompt_ms": 10414.4,
"decode_ms": 714.5,
"decode_tok_s": 79.8,
"hit_rate": 1.0
}
],
"requests_kept": 2,
"totals": {
"since": 1790717571.262619,
"requests": 2,
"prompt_tokens": 59949,
"reused": 0,
"output_tokens": 102,
"prompt_ms": 21091.6,
"decode_ms": 1150.1
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 37,
"mem_used": 17822580736,
"mem_total": 25769803776,
"temp": 49,
"power": 187.064
},
{
"index": 1,
"util": 60,
"mem_used": 22828482560,
"mem_total": 25769803776,
"temp": 53,
"power": 243.674
}
],
"gpu_util": 48.5,
"gpu_mem_used": 40651063296,
"gpu_mem_total": 51539607552,
"gpu_temp": 53,
"gpu_power": 430.738,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 0.6181640625,
"gpu_pcie_tx_mb": 1.0,
"cpu": 6.1,
"ram_used": 44308209664,
"ram_total": 177175990272,
"disk_read_mb": 0.7959400039611647,
"disk_write_mb": 0.24130307812397303,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1.0,
0.0,
0.0,
73.5,
58.5,
96.0,
57.5,
95.5,
47.5,
93.0,
58.5,
50.0,
44.0,
18.0,
52.0,
95.5,
50.0,
97.0,
24.5,
95.5,
82.0,
96.5,
50.0,
48.5
],
"gpu_mem_used": [
40449736704,
40449736704,
40449736704,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296
],
"gpu_temp": [
48,
47,
47,
50,
53,
53,
54,
53,
54,
54,
54,
54,
52,
49,
54,
54,
54,
54,
52,
54,
54,
54,
54,
53
],
"gpu_power": [
256.98,
244.54,
244.03,
340.49,
532.78,
528.1,
525.31,
536.01,
504.78,
526.85,
529.86,
484.8,
417.57,
353.2,
539.11,
526.73,
525.83,
531.55,
507.76,
523.24,
527.24,
526.07,
501.32,
430.74
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
0.67,
78.72,
82.54,
76.1,
85.97,
55.17,
62.23,
87.12,
83.54,
85.83,
0.67,
1765.25,
83.88,
85.4,
85.07,
88.59,
82.35,
78.3,
85.16,
61.94,
46.3,
0.62
],
"cpu": [
0.0,
1.2,
5.4,
10.7,
10.0,
9.8,
14.0,
18.3,
17.8,
18.6,
9.0,
5.8,
6.2,
4.4,
6.6,
6.4,
5.8,
6.1,
7.4,
5.9,
6.8,
19.8,
14.2,
6.1
],
"ram_used": [
43564908544,
43556737024,
43564126208,
43739512832,
43734302720,
43734560768,
43735560192,
43898122240,
44013469696,
43991674880,
43978436608,
43974189056,
44281556992,
44278607872,
44263776256,
44251099136,
44242841600,
44235354112,
44232339456,
44218642432,
44210057216,
44216344576,
44288196608,
44308209664
],
"disk_read_mb": [
null,
0.0,
73.94,
96.25,
39.8,
73.77,
144.08,
110.85,
127.27,
131.68,
42.81,
0.0,
3.42,
0.48,
0.01,
0.04,
0.06,
0.02,
0.02,
0.02,
12.53,
104.78,
0.0,
0.8
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790717596.5877995
}
}
@@ -0,0 +1,496 @@
{
"label": "native-v0.1.26-Coder-2x3090-K22",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 3,
"run_id": "831d87a1dacc",
"corpus_repeated": false,
"time": "2026-09-29T21:33:16",
"prompt_tokens": 31601,
"completion_tokens": 60,
"ttft_s": 10.877,
"e2e_s": 11.43,
"api_prompt_tps": 2905.4,
"api_decode_tps": 106.7,
"engine_prompt_tps": 2951.6,
"engine_decode_tps": 105.7,
"log_lines": [
"strata serve: prompt 31601 tokens = 0 reused + 31601 read in 10706 ms (2951.6 tok/s), 60 generated in 568 ms (105.7 tok/s), drafts accepted 41 of 46, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (31680 hits / 31680 lookups)"
],
"answer_head": "I'll start by examining the repository structure to understand what the last file does.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 5632,
"expert_cache_mib": 10325,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 7669,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717596.735188,
"duration_s": 11.3,
"finish": "stop",
"prompt_tokens": 31601,
"reused": 0,
"output_tokens": 60,
"engine_generated": 60,
"prompt_ms": 10706.3,
"decode_ms": 567.6,
"decode_tok_s": 105.7,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717584.9637764,
"duration_s": 11.1,
"finish": "stop",
"prompt_tokens": 31633,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 10677.2,
"decode_ms": 435.6,
"decode_tok_s": 103.3,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717573.1737459,
"duration_s": 11.1,
"finish": "stop",
"prompt_tokens": 28316,
"reused": 0,
"output_tokens": 57,
"engine_generated": 57,
"prompt_ms": 10414.4,
"decode_ms": 714.5,
"decode_tok_s": 79.8,
"hit_rate": 1.0
}
],
"requests_kept": 3,
"totals": {
"since": 1790717571.262619,
"requests": 3,
"prompt_tokens": 91550,
"reused": 0,
"output_tokens": 162,
"prompt_ms": 31797.899999999998,
"decode_ms": 1717.6999999999998
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 25,
"mem_used": 17822580736,
"mem_total": 25769803776,
"temp": 50,
"power": 201.214
},
{
"index": 1,
"util": 61,
"mem_used": 22828482560,
"mem_total": 25769803776,
"temp": 53,
"power": 238.993
}
],
"gpu_util": 43.0,
"gpu_mem_used": 40651063296,
"gpu_mem_total": 51539607552,
"gpu_temp": 53,
"gpu_power": 440.207,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 0.666015625,
"gpu_pcie_tx_mb": 0.9052734375,
"cpu": 4.4,
"ram_used": 44291919872,
"ram_total": 177175990272,
"disk_read_mb": 0.7281688058408962,
"disk_write_mb": 0.01802398034259644,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1.0,
0.0,
0.0,
73.5,
58.5,
96.0,
57.5,
95.5,
47.5,
93.0,
58.5,
50.0,
44.0,
18.0,
52.0,
95.5,
50.0,
97.0,
24.5,
95.5,
82.0,
96.5,
50.0,
48.5,
49.5,
96.0,
48.0,
96.0,
47.5,
94.5,
61.0,
97.5,
47.5,
49.5,
43.0
],
"gpu_mem_used": [
40449736704,
40449736704,
40449736704,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296,
40651063296
],
"gpu_temp": [
48,
47,
47,
50,
53,
53,
54,
53,
54,
54,
54,
54,
52,
49,
54,
54,
54,
54,
52,
54,
54,
54,
54,
53,
50,
54,
54,
54,
55,
54,
54,
54,
55,
55,
53
],
"gpu_power": [
256.98,
244.54,
244.03,
340.49,
532.78,
528.1,
525.31,
536.01,
504.78,
526.85,
529.86,
484.8,
417.57,
353.2,
539.11,
526.73,
525.83,
531.55,
507.76,
523.24,
527.24,
526.07,
501.32,
430.74,
359.38,
510.67,
527.45,
526.85,
532.56,
501.47,
534.58,
526.16,
522.92,
475.95,
440.21
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
0.67,
78.72,
82.54,
76.1,
85.97,
55.17,
62.23,
87.12,
83.54,
85.83,
0.67,
1765.25,
83.88,
85.4,
85.07,
88.59,
82.35,
78.3,
85.16,
61.94,
46.3,
0.62,
87.45,
41.53,
89.07,
84.21,
76.25,
52.17,
78.29,
83.59,
51.45,
47.92,
0.67
],
"cpu": [
0.0,
1.2,
5.4,
10.7,
10.0,
9.8,
14.0,
18.3,
17.8,
18.6,
9.0,
5.8,
6.2,
4.4,
6.6,
6.4,
5.8,
6.1,
7.4,
5.9,
6.8,
19.8,
14.2,
6.1,
7.3,
6.6,
6.3,
5.7,
6.0,
6.2,
6.3,
6.1,
7.3,
6.4,
4.4
],
"ram_used": [
43564908544,
43556737024,
43564126208,
43739512832,
43734302720,
43734560768,
43735560192,
43898122240,
44013469696,
43991674880,
43978436608,
43974189056,
44281556992,
44278607872,
44263776256,
44251099136,
44242841600,
44235354112,
44232339456,
44218642432,
44210057216,
44216344576,
44288196608,
44308209664,
44011671552,
43989819392,
43978940416,
43970682880,
43962933248,
44045533184,
44039798784,
44039798784,
44047568896,
44044705792,
44291919872
],
"disk_read_mb": [
null,
0.0,
73.94,
96.25,
39.8,
73.77,
144.08,
110.85,
127.27,
131.68,
42.81,
0.0,
3.42,
0.48,
0.01,
0.04,
0.06,
0.02,
0.02,
0.02,
12.53,
104.78,
0.0,
0.8,
0.61,
0.05,
0.03,
0.09,
0.08,
0.04,
0.05,
0.04,
0.15,
0.0,
0.73
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790717608.5200934
}
}
@@ -0,0 +1,279 @@
{
"label": "native-v0.1.26-Coder-2x3090-K24",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 1,
"run_id": "87af67edfd2d",
"corpus_repeated": false,
"time": "2026-09-29T21:34:02",
"prompt_tokens": 28314,
"completion_tokens": 256,
"ttft_s": 10.029,
"e2e_s": 12.977,
"api_prompt_tps": 2823.1,
"api_decode_tps": 86.5,
"engine_prompt_tps": 2871.1,
"engine_decode_tps": 85.9,
"log_lines": [
"strata serve: prompt 28314 tokens = 0 reused + 28314 read in 9862 ms (2871.1 tok/s), 256 generated in 2981 ms (85.9 tok/s), drafts accepted 143 of 193, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (147360 hits / 147360 lookups)"
],
"answer_head": "Looking at the last file above, `docs/AMD_HIP_PERFORMANCE.md`, it documents the performance evidence and correctness limits for the experimental AMD HIP backend targeting gfx1100 (RX 7900 XTX). It records:\n\n- The exact build/runtime configuration used for the benchmark (env vars, CLI flags, model pa",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 6144,
"expert_cache_mib": 11337,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 6657,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717642.434618,
"duration_s": 12.9,
"finish": "length",
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 256,
"engine_generated": 256,
"prompt_ms": 9861.8,
"decode_ms": 2981.5,
"decode_tok_s": 85.9,
"hit_rate": 1.0
}
],
"requests_kept": 1,
"totals": {
"since": 1790717640.7776525,
"requests": 1,
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 256,
"prompt_ms": 9861.8,
"decode_ms": 2981.5
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 43,
"mem_used": 18898419712,
"mem_total": 25769803776,
"temp": 50,
"power": 244.874
},
{
"index": 1,
"util": 57,
"mem_used": 21758935040,
"mem_total": 25769803776,
"temp": 54,
"power": 254.71
}
],
"gpu_util": 50.0,
"gpu_mem_used": 40657354752,
"gpu_mem_total": 51539607552,
"gpu_temp": 54,
"gpu_power": 499.584,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 5.38671875,
"gpu_pcie_tx_mb": 87.7373046875,
"cpu": 11.7,
"ram_used": 44619116544,
"ram_total": 177175990272,
"disk_read_mb": 4.515214018548113,
"disk_write_mb": 0.17657261525048487,
"tok_s": 107.09643367230112,
"tok_s_mean": 83.26979350665239
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1.0,
0.0,
1.0,
93.0,
94.0,
64.0,
94.5,
88.5,
95.5,
92.5,
48.5,
32.0,
48.0,
50.0
],
"gpu_mem_used": [
40447639552,
40447639552,
40476999680,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40657354752,
40657354752,
40657354752
],
"gpu_temp": [
49,
49,
49,
53,
54,
54,
55,
54,
55,
55,
55,
52,
54,
54
],
"gpu_power": [
259.59,
247.18,
245.88,
414.53,
546.71,
547.76,
546.95,
533.57,
531.41,
545.46,
539.14,
402.04,
486.67,
499.58
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
247.86,
81.11,
78.2,
80.92,
83.88,
57.6,
82.87,
79.92,
40.29,
5.39,
9.77,
5.39
],
"cpu": [
0.0,
1.9,
9.1,
8.8,
9.5,
10.7,
13.9,
10.5,
13.9,
11.8,
6.0,
6.3,
12.4,
11.7
],
"ram_used": [
43887411200,
43859918848,
43956232192,
44034678784,
44030210048,
44028076032,
44015280128,
44252610560,
44247789568,
44243210240,
44230340608,
44558966784,
44619108352,
44619116544
],
"disk_read_mb": [
null,
0.0,
126.09,
44.05,
80.0,
93.94,
138.6,
104.01,
156.43,
97.38,
0.0,
1.76,
4.66,
4.52
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
78.74,
81.4,
107.1
]
},
"time": 1790717655.7892108
}
}
@@ -0,0 +1,392 @@
{
"label": "native-v0.1.26-Coder-2x3090-K24",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 2,
"run_id": "b38394f620b3",
"corpus_repeated": false,
"time": "2026-09-29T21:34:15",
"prompt_tokens": 31635,
"completion_tokens": 45,
"ttft_s": 10.269,
"e2e_s": 10.686,
"api_prompt_tps": 3080.5,
"api_decode_tps": 105.5,
"engine_prompt_tps": 3130.4,
"engine_decode_tps": 104.2,
"log_lines": [
"strata serve: prompt 31635 tokens = 0 reused + 31635 read in 10106 ms (3130.4 tok/s), 45 generated in 432 ms (104.2 tok/s), drafts accepted 33 of 37, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (24480 hits / 24480 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what we're working with.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 6144,
"expert_cache_mib": 11337,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 6657,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717655.9301767,
"duration_s": 10.5,
"finish": "stop",
"prompt_tokens": 31635,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 10105.9,
"decode_ms": 431.8,
"decode_tok_s": 104.2,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717642.434618,
"duration_s": 12.9,
"finish": "length",
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 256,
"engine_generated": 256,
"prompt_ms": 9861.8,
"decode_ms": 2981.5,
"decode_tok_s": 85.9,
"hit_rate": 1.0
}
],
"requests_kept": 2,
"totals": {
"since": 1790717640.7776525,
"requests": 2,
"prompt_tokens": 59949,
"reused": 0,
"output_tokens": 301,
"prompt_ms": 19967.699999999997,
"decode_ms": 3413.3
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 20,
"mem_used": 18898419712,
"mem_total": 25769803776,
"temp": 50,
"power": 191.174
},
{
"index": 1,
"util": 53,
"mem_used": 21758935040,
"mem_total": 25769803776,
"temp": 54,
"power": 238.032
}
],
"gpu_util": 36.5,
"gpu_mem_used": 40657354752,
"gpu_mem_total": 51539607552,
"gpu_temp": 54,
"gpu_power": 429.206,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 0.6181640625,
"gpu_pcie_tx_mb": 0.857421875,
"cpu": 5.3,
"ram_used": 44575621120,
"ram_total": 177175990272,
"disk_read_mb": 2.4688909410966033,
"disk_write_mb": 0.23427432287777988,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1.0,
0.0,
1.0,
93.0,
94.0,
64.0,
94.5,
88.5,
95.5,
92.5,
48.5,
32.0,
48.0,
50.0,
0.0,
84.5,
96.0,
95.5,
79.0,
97.5,
97.0,
95.5,
96.0,
50.0,
36.5
],
"gpu_mem_used": [
40447639552,
40447639552,
40476999680,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752
],
"gpu_temp": [
49,
49,
49,
53,
54,
54,
55,
54,
55,
55,
55,
52,
54,
54,
50,
54,
55,
55,
55,
55,
55,
55,
55,
55,
54
],
"gpu_power": [
259.59,
247.18,
245.88,
414.53,
546.71,
547.76,
546.95,
533.57,
531.41,
545.46,
539.14,
402.04,
486.67,
499.58,
409.59,
480.21,
551.08,
548.0,
547.82,
539.97,
537.72,
548.44,
548.33,
513.55,
429.21
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
247.86,
81.11,
78.2,
80.92,
83.88,
57.6,
82.87,
79.92,
40.29,
5.39,
9.77,
5.39,
45.78,
64.8,
1284.55,
129.94,
83.06,
82.63,
84.78,
73.15,
82.44,
0.57,
0.62
],
"cpu": [
0.0,
1.9,
9.1,
8.8,
9.5,
10.7,
13.9,
10.5,
13.9,
11.8,
6.0,
6.3,
12.4,
11.7,
6.7,
8.3,
6.5,
7.2,
6.1,
6.0,
6.1,
6.5,
11.2,
8.0,
5.3
],
"ram_used": [
43887411200,
43859918848,
43956232192,
44034678784,
44030210048,
44028076032,
44015280128,
44252610560,
44247789568,
44243210240,
44230340608,
44558966784,
44619108352,
44619116544,
44154675200,
44152926208,
44151242752,
44149854208,
44145594368,
44375150592,
44366180352,
44365656064,
44362940416,
44400578560,
44575621120
],
"disk_read_mb": [
null,
0.0,
126.09,
44.05,
80.0,
93.94,
138.6,
104.01,
156.43,
97.38,
0.0,
1.76,
4.66,
4.52,
0.63,
0.02,
0.04,
0.03,
0.05,
0.03,
0.02,
13.87,
103.6,
0.0,
2.47
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
78.74,
81.4,
107.1,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790717666.9788535
}
}
@@ -0,0 +1,496 @@
{
"label": "native-v0.1.26-Coder-2x3090-K24",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 3,
"run_id": "3bee1c1e482a",
"corpus_repeated": false,
"time": "2026-09-29T21:34:26",
"prompt_tokens": 31602,
"completion_tokens": 60,
"ttft_s": 10.281,
"e2e_s": 10.865,
"api_prompt_tps": 3073.8,
"api_decode_tps": 101.0,
"engine_prompt_tps": 3123.2,
"engine_decode_tps": 100.2,
"log_lines": [
"strata serve: prompt 31602 tokens = 0 reused + 31602 read in 10118 ms (3123.2 tok/s), 60 generated in 599 ms (100.2 tok/s), drafts accepted 40 of 52, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (34560 hits / 34560 lookups)"
],
"answer_head": "I'll start by examining the repository structure to understand what the last file does.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 6144,
"expert_cache_mib": 11337,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 6657,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717667.1193056,
"duration_s": 10.7,
"finish": "stop",
"prompt_tokens": 31602,
"reused": 0,
"output_tokens": 60,
"engine_generated": 60,
"prompt_ms": 10118.5,
"decode_ms": 599.0,
"decode_tok_s": 100.2,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717655.9301767,
"duration_s": 10.5,
"finish": "stop",
"prompt_tokens": 31635,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 10105.9,
"decode_ms": 431.8,
"decode_tok_s": 104.2,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717642.434618,
"duration_s": 12.9,
"finish": "length",
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 256,
"engine_generated": 256,
"prompt_ms": 9861.8,
"decode_ms": 2981.5,
"decode_tok_s": 85.9,
"hit_rate": 1.0
}
],
"requests_kept": 3,
"totals": {
"since": 1790717640.7776525,
"requests": 3,
"prompt_tokens": 91551,
"reused": 0,
"output_tokens": 361,
"prompt_ms": 30086.199999999997,
"decode_ms": 4012.3
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 43,
"mem_used": 18898419712,
"mem_total": 25769803776,
"temp": 51,
"power": 178.691
},
{
"index": 1,
"util": 51,
"mem_used": 21758935040,
"mem_total": 25769803776,
"temp": 54,
"power": 244.501
}
],
"gpu_util": 47.0,
"gpu_mem_used": 40657354752,
"gpu_mem_total": 51539607552,
"gpu_temp": 54,
"gpu_power": 423.192,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 5.1962890625,
"gpu_pcie_tx_mb": 103.138671875,
"cpu": 6.6,
"ram_used": 44596383744,
"ram_total": 177175990272,
"disk_read_mb": 1.1207609014346964,
"disk_write_mb": 0.0288298624163266,
"tok_s": 110.47676922161895,
"tok_s_mean": 99.0096070780873
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
1.0,
0.0,
1.0,
93.0,
94.0,
64.0,
94.5,
88.5,
95.5,
92.5,
48.5,
32.0,
48.0,
50.0,
0.0,
84.5,
96.0,
95.5,
79.0,
97.5,
97.0,
95.5,
96.0,
50.0,
36.5,
49.5,
96.5,
61.0,
94.5,
91.0,
97.0,
95.5,
98.5,
47.5,
47.0
],
"gpu_mem_used": [
40447639552,
40447639552,
40476999680,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40546205696,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752,
40657354752
],
"gpu_temp": [
49,
49,
49,
53,
54,
54,
55,
54,
55,
55,
55,
52,
54,
54,
50,
54,
55,
55,
55,
55,
55,
55,
55,
55,
54,
52,
55,
55,
55,
55,
56,
56,
55,
56,
54
],
"gpu_power": [
259.59,
247.18,
245.88,
414.53,
546.71,
547.76,
546.95,
533.57,
531.41,
545.46,
539.14,
402.04,
486.67,
499.58,
409.59,
480.21,
551.08,
548.0,
547.82,
539.97,
537.72,
548.44,
548.33,
513.55,
429.21,
354.61,
553.75,
546.57,
547.59,
532.21,
535.5,
547.28,
548.49,
534.02,
423.19
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
247.86,
81.11,
78.2,
80.92,
83.88,
57.6,
82.87,
79.92,
40.29,
5.39,
9.77,
5.39,
45.78,
64.8,
1284.55,
129.94,
83.06,
82.63,
84.78,
73.15,
82.44,
0.57,
0.62,
81.82,
83.45,
75.34,
84.16,
82.11,
81.49,
70.95,
73.91,
52.55,
5.2
],
"cpu": [
0.0,
1.9,
9.1,
8.8,
9.5,
10.7,
13.9,
10.5,
13.9,
11.8,
6.0,
6.3,
12.4,
11.7,
6.7,
8.3,
6.5,
7.2,
6.1,
6.0,
6.1,
6.5,
11.2,
8.0,
5.3,
6.7,
12.7,
10.0,
5.9,
6.4,
6.0,
7.1,
5.7,
6.8,
6.6
],
"ram_used": [
43887411200,
43859918848,
43956232192,
44034678784,
44030210048,
44028076032,
44015280128,
44252610560,
44247789568,
44243210240,
44230340608,
44558966784,
44619108352,
44619116544,
44154675200,
44152926208,
44151242752,
44149854208,
44145594368,
44375150592,
44366180352,
44365656064,
44362940416,
44400578560,
44575621120,
44112637952,
44134821888,
44114948096,
44112486400,
44240445440,
44343197696,
44341800960,
44344549376,
44344029184,
44596383744
],
"disk_read_mb": [
null,
0.0,
126.09,
44.05,
80.0,
93.94,
138.6,
104.01,
156.43,
97.38,
0.0,
1.76,
4.66,
4.52,
0.63,
0.02,
0.04,
0.03,
0.05,
0.03,
0.02,
13.87,
103.6,
0.0,
2.47,
0.63,
0.05,
0.08,
0.08,
0.04,
0.07,
0.04,
0.17,
0.0,
1.12
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
78.74,
81.4,
107.1,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
110.48
]
},
"time": 1790717678.3471994
}
}
@@ -0,0 +1,261 @@
{
"label": "native-v0.1.26-Coder-2x3090-K26",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 1,
"run_id": "5ca39dba7489",
"corpus_repeated": false,
"time": "2026-09-29T21:35:22",
"prompt_tokens": 28314,
"completion_tokens": 48,
"ttft_s": 9.79,
"e2e_s": 10.297,
"api_prompt_tps": 2892.2,
"api_decode_tps": 92.7,
"engine_prompt_tps": 2940.2,
"engine_decode_tps": 88.8,
"log_lines": [
"strata serve: prompt 28314 tokens = 0 reused + 28314 read in 9630 ms (2940.2 tok/s), 48 generated in 540 ms (88.8 tok/s), drafts accepted 33 of 37, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (25920 hits / 25920 lookups)"
],
"answer_head": "I'll first look at the repository structure to understand what files exist and identify the last file.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 6656,
"expert_cache_mib": 12387,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 5607,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717722.129571,
"duration_s": 10.2,
"finish": "stop",
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 48,
"engine_generated": 48,
"prompt_ms": 9630.1,
"decode_ms": 540.5,
"decode_tok_s": 88.8,
"hit_rate": 1.0
}
],
"requests_kept": 1,
"totals": {
"since": 1790717720.412441,
"requests": 1,
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 48,
"prompt_ms": 9630.1,
"decode_ms": 540.5
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 47,
"mem_used": 19980550144,
"mem_total": 25769803776,
"temp": 51,
"power": 179.665
},
{
"index": 1,
"util": 28,
"mem_used": 20641153024,
"mem_total": 25769803776,
"temp": 53,
"power": 236.004
}
],
"gpu_util": 37.5,
"gpu_mem_used": 40621703168,
"gpu_mem_total": 51539607552,
"gpu_temp": 53,
"gpu_power": 415.669,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 0.666015625,
"gpu_pcie_tx_mb": 0.9052734375,
"cpu": 6.0,
"ram_used": 44539072512,
"ram_total": 177175990272,
"disk_read_mb": 2.97776661958663,
"disk_write_mb": 0.0,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
0.5,
0.0,
0.5,
71.0,
95.5,
96.0,
91.5,
66.5,
95.5,
90.5,
84.5,
37.5
],
"gpu_mem_used": [
40447639552,
40447639552,
40472805376,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40621703168
],
"gpu_temp": [
50,
50,
50,
52,
55,
55,
56,
56,
56,
55,
56,
53
],
"gpu_power": [
261.03,
248.02,
247.5,
351.95,
548.48,
553.77,
555.05,
536.77,
531.38,
552.37,
551.57,
415.67
],
"gpu_pcie_rx_mb": [
0.76,
0.62,
224.64,
86.02,
83.16,
75.62,
86.69,
102.66,
65.66,
83.97,
38.86,
0.67
],
"cpu": [
0.0,
1.1,
8.1,
9.1,
9.3,
12.2,
12.6,
12.6,
23.5,
14.7,
5.9,
6.0
],
"ram_used": [
43871281152,
43867545600,
43958726656,
44040531968,
44041007104,
44033200128,
44032602112,
44244361216,
44250234880,
44236812288,
44233678848,
44539072512
],
"disk_read_mb": [
null,
0.0,
122.8,
47.19,
79.99,
100.77,
127.09,
108.72,
156.54,
97.45,
0.0,
2.98
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790717732.8116624
}
}
@@ -0,0 +1,365 @@
{
"label": "native-v0.1.26-Coder-2x3090-K26",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 2,
"run_id": "14c7210ce0e9",
"corpus_repeated": false,
"time": "2026-09-29T21:35:32",
"prompt_tokens": 31635,
"completion_tokens": 45,
"ttft_s": 10.029,
"e2e_s": 10.496,
"api_prompt_tps": 3154.3,
"api_decode_tps": 94.3,
"engine_prompt_tps": 3205.2,
"engine_decode_tps": 93.5,
"log_lines": [
"strata serve: prompt 31635 tokens = 0 reused + 31635 read in 9870 ms (3205.2 tok/s), 45 generated in 481 ms (93.5 tok/s), drafts accepted 31 of 36, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (25440 hits / 25440 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what the last file does.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 6656,
"expert_cache_mib": 12387,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 5607,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717732.947479,
"duration_s": 10.4,
"finish": "stop",
"prompt_tokens": 31635,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 9870.0,
"decode_ms": 481.3,
"decode_tok_s": 93.5,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717722.129571,
"duration_s": 10.2,
"finish": "stop",
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 48,
"engine_generated": 48,
"prompt_ms": 9630.1,
"decode_ms": 540.5,
"decode_tok_s": 88.8,
"hit_rate": 1.0
}
],
"requests_kept": 2,
"totals": {
"since": 1790717720.412441,
"requests": 2,
"prompt_tokens": 59949,
"reused": 0,
"output_tokens": 93,
"prompt_ms": 19500.1,
"decode_ms": 1021.8
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 51,
"mem_used": 19991035904,
"mem_total": 25769803776,
"temp": 51,
"power": 190.866
},
{
"index": 1,
"util": 56,
"mem_used": 20653735936,
"mem_total": 25769803776,
"temp": 55,
"power": 249.813
}
],
"gpu_util": 53.5,
"gpu_mem_used": 40644771840,
"gpu_mem_total": 51539607552,
"gpu_temp": 55,
"gpu_power": 440.679,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 16.44921875,
"gpu_pcie_tx_mb": 82.0625,
"cpu": 6.0,
"ram_used": 44553875456,
"ram_total": 177175990272,
"disk_read_mb": 0.6053434938228842,
"disk_write_mb": 0.0,
"tok_s": 0.0,
"tok_s_mean": 0.0
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
0.5,
0.0,
0.5,
71.0,
95.5,
96.0,
91.5,
66.5,
95.5,
90.5,
84.5,
37.5,
1.5,
97.0,
96.0,
81.0,
86.5,
95.5,
95.0,
96.5,
95.0,
53.5
],
"gpu_mem_used": [
40447639552,
40447639552,
40472805376,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40644771840
],
"gpu_temp": [
50,
50,
50,
52,
55,
55,
56,
56,
56,
55,
56,
53,
51,
56,
56,
56,
56,
56,
56,
56,
56,
55
],
"gpu_power": [
261.03,
248.02,
247.5,
351.95,
548.48,
553.77,
555.05,
536.77,
531.38,
552.37,
551.57,
415.67,
326.37,
532.86,
555.24,
552.15,
555.12,
507.8,
554.85,
554.0,
554.13,
440.68
],
"gpu_pcie_rx_mb": [
0.76,
0.62,
224.64,
86.02,
83.16,
75.62,
86.69,
102.66,
65.66,
83.97,
38.86,
0.67,
3698.35,
132.46,
131.89,
82.06,
85.92,
83.02,
1279.88,
50.4,
86.21,
16.45
],
"cpu": [
0.0,
1.1,
8.1,
9.1,
9.3,
12.2,
12.6,
12.6,
23.5,
14.7,
5.9,
6.0,
4.5,
6.1,
6.0,
6.4,
6.7,
5.9,
6.7,
9.9,
7.4,
6.0
],
"ram_used": [
43871281152,
43867545600,
43958726656,
44040531968,
44041007104,
44033200128,
44032602112,
44244361216,
44250234880,
44236812288,
44233678848,
44539072512,
44064968704,
44058497024,
44058497024,
44058501120,
44175929344,
44294356992,
44294352896,
44295569408,
44291391488,
44553875456
],
"disk_read_mb": [
null,
0.0,
122.8,
47.19,
79.99,
100.77,
127.09,
108.72,
156.54,
97.45,
0.0,
2.98,
0.47,
0.01,
0.04,
0.06,
0.03,
0.03,
12.57,
71.85,
33.12,
0.61
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"time": 1790717743.8106396
}
}
@@ -0,0 +1,469 @@
{
"label": "native-v0.1.26-Coder-2x3090-K26",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 3,
"run_id": "3f243fdb63ee",
"corpus_repeated": false,
"time": "2026-09-29T21:35:43",
"prompt_tokens": 31601,
"completion_tokens": 60,
"ttft_s": 10.026,
"e2e_s": 10.61,
"api_prompt_tps": 3151.9,
"api_decode_tps": 101.0,
"engine_prompt_tps": 3208.3,
"engine_decode_tps": 100.2,
"log_lines": [
"strata serve: prompt 31601 tokens = 0 reused + 31601 read in 9850 ms (3208.3 tok/s), 60 generated in 599 ms (100.2 tok/s), drafts accepted 40 of 50, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (34080 hits / 34080 lookups)"
],
"answer_head": "I'll start by examining the repository structure to understand what the last file is.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 6656,
"expert_cache_mib": 12387,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 5607,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717743.964174,
"duration_s": 10.5,
"finish": "stop",
"prompt_tokens": 31601,
"reused": 0,
"output_tokens": 60,
"engine_generated": 60,
"prompt_ms": 9849.9,
"decode_ms": 598.9,
"decode_tok_s": 100.2,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717732.947479,
"duration_s": 10.4,
"finish": "stop",
"prompt_tokens": 31635,
"reused": 0,
"output_tokens": 45,
"engine_generated": 45,
"prompt_ms": 9870.0,
"decode_ms": 481.3,
"decode_tok_s": 93.5,
"hit_rate": 1.0
},
{
"projection": null,
"time": 1790717722.129571,
"duration_s": 10.2,
"finish": "stop",
"prompt_tokens": 28314,
"reused": 0,
"output_tokens": 48,
"engine_generated": 48,
"prompt_ms": 9630.1,
"decode_ms": 540.5,
"decode_tok_s": 88.8,
"hit_rate": 1.0
}
],
"requests_kept": 3,
"totals": {
"since": 1790717720.412441,
"requests": 3,
"prompt_tokens": 91550,
"reused": 0,
"output_tokens": 153,
"prompt_ms": 29350.0,
"decode_ms": 1620.6999999999998
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 46,
"mem_used": 19991035904,
"mem_total": 25769803776,
"temp": 52,
"power": 189.922
},
{
"index": 1,
"util": 52,
"mem_used": 20653735936,
"mem_total": 25769803776,
"temp": 55,
"power": 246.398
}
],
"gpu_util": 49.0,
"gpu_mem_used": 40644771840,
"gpu_mem_total": 51539607552,
"gpu_temp": 55,
"gpu_power": 436.32,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 17.8330078125,
"gpu_pcie_tx_mb": 43.1533203125,
"cpu": 6.0,
"ram_used": 44614168576,
"ram_total": 177175990272,
"disk_read_mb": 0.4973320821900814,
"disk_write_mb": 0.05405783502066102,
"tok_s": 99.26679787882165,
"tok_s_mean": 97.997900113756
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
0.5,
0.0,
0.5,
71.0,
95.5,
96.0,
91.5,
66.5,
95.5,
90.5,
84.5,
37.5,
1.5,
97.0,
96.0,
81.0,
86.5,
95.5,
95.0,
96.5,
95.0,
53.5,
0.0,
95.0,
95.5,
82.0,
94.5,
93.0,
88.5,
96.0,
97.0,
49.0
],
"gpu_mem_used": [
40447639552,
40447639552,
40472805376,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40535719936,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40621703168,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840,
40644771840
],
"gpu_temp": [
50,
50,
50,
52,
55,
55,
56,
56,
56,
55,
56,
53,
51,
56,
56,
56,
56,
56,
56,
56,
56,
55,
52,
55,
56,
56,
56,
56,
56,
56,
56,
55
],
"gpu_power": [
261.03,
248.02,
247.5,
351.95,
548.48,
553.77,
555.05,
536.77,
531.38,
552.37,
551.57,
415.67,
326.37,
532.86,
555.24,
552.15,
555.12,
507.8,
554.85,
554.0,
554.13,
440.68,
363.75,
474.54,
552.72,
554.16,
554.3,
509.24,
555.11,
555.88,
555.36,
436.32
],
"gpu_pcie_rx_mb": [
0.76,
0.62,
224.64,
86.02,
83.16,
75.62,
86.69,
102.66,
65.66,
83.97,
38.86,
0.67,
3698.35,
132.46,
131.89,
82.06,
85.92,
83.02,
1279.88,
50.4,
86.21,
16.45,
38.96,
81.63,
83.25,
84.4,
72.53,
127.22,
48.4,
96.51,
100.71,
17.83
],
"cpu": [
0.0,
1.1,
8.1,
9.1,
9.3,
12.2,
12.6,
12.6,
23.5,
14.7,
5.9,
6.0,
4.5,
6.1,
6.0,
6.4,
6.7,
5.9,
6.7,
9.9,
7.4,
6.0,
11.7,
10.8,
6.2,
11.4,
7.0,
6.7,
5.6,
5.4,
6.0,
6.0
],
"ram_used": [
43871281152,
43867545600,
43958726656,
44040531968,
44041007104,
44033200128,
44032602112,
44244361216,
44250234880,
44236812288,
44233678848,
44539072512,
44064968704,
44058497024,
44058497024,
44058501120,
44175929344,
44294356992,
44294352896,
44295569408,
44291391488,
44553875456,
44129808384,
44180946944,
44184666112,
44182659072,
44296114176,
44404658176,
44394999808,
44386783232,
44374441984,
44614168576
],
"disk_read_mb": [
null,
0.0,
122.8,
47.19,
79.99,
100.77,
127.09,
108.72,
156.54,
97.45,
0.0,
2.98,
0.47,
0.01,
0.04,
0.06,
0.03,
0.03,
12.57,
71.85,
33.12,
0.61,
0.53,
0.06,
0.06,
0.09,
0.08,
0.04,
0.04,
0.04,
0.15,
0.5
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
99.27
]
},
"time": 1790717754.9239163
}
}
@@ -0,0 +1,261 @@
{
"label": "native-v0.1.26-Coder-2x3090-K28",
"mode": "speed",
"ctx": "32k",
"target_tokens": 31488,
"run": 1,
"run_id": "0796a1ce12e4",
"corpus_repeated": false,
"time": "2026-09-29T21:36:29",
"prompt_tokens": 28316,
"completion_tokens": 57,
"ttft_s": 10.285,
"e2e_s": 10.86,
"api_prompt_tps": 2753.1,
"api_decode_tps": 97.4,
"engine_prompt_tps": 2798.5,
"engine_decode_tps": 93.7,
"log_lines": [
"strata serve: prompt 28316 tokens = 0 reused + 28316 read in 10118 ms (2798.5 tok/s), 57 generated in 608 ms (93.7 tok/s), drafts accepted 40 of 42, 2 checkpoints",
"strata serve: decode expert cache hit rate: 100.0% (28800 hits / 28800 lookups)"
],
"answer_head": "I'll start by exploring the repository structure to understand what's there and identify the last file.",
"metrics_after": {
"engine": {
"model": "qwen3.8-flash-next-coder-iq1_m",
"max_context": 32768,
"images": false,
"version": "0.1.26",
"context": 32768,
"kv": "int8",
"kv_resident": 0,
"expert_slots": 7168,
"expert_cache_mib": 13437,
"spec": 6,
"mtp_max": 4,
"lookup": 3,
"vram_free_mib": 4557,
"cvec": 0,
"arena_mib": 23981,
"pool_workers": 19,
"pcie_frac": "0.55",
"spec_min_p": "0.50",
"engine": "0.1.26"
},
"live": {
"state": "idle",
"queued": 0,
"phase": null,
"prompt_tokens": null,
"prompt_read": null,
"prompt_total": null,
"generated": null,
"max_tokens": null,
"elapsed_s": null,
"tok_s": null,
"tok_s_mean": null,
"tok_s_window_s": null
},
"requests": [
{
"projection": null,
"time": 1790717789.1846194,
"duration_s": 10.7,
"finish": "stop",
"prompt_tokens": 28316,
"reused": 0,
"output_tokens": 57,
"engine_generated": 57,
"prompt_ms": 10118.2,
"decode_ms": 608.0,
"decode_tok_s": 93.8,
"hit_rate": 1.0
}
],
"requests_kept": 1,
"totals": {
"since": 1790717787.625935,
"requests": 1,
"prompt_tokens": 28316,
"reused": 0,
"output_tokens": 57,
"prompt_ms": 10118.2,
"decode_ms": 608.0
},
"hardware": {
"gpus": [
{
"index": 0,
"util": 41,
"mem_used": 21104623616,
"mem_total": 25769803776,
"temp": 52,
"power": 189.832
},
{
"index": 1,
"util": 42,
"mem_used": 19548536832,
"mem_total": 25769803776,
"temp": 54,
"power": 230.563
}
],
"gpu_util": 41.5,
"gpu_mem_used": 40653160448,
"gpu_mem_total": 51539607552,
"gpu_temp": 54,
"gpu_power": 420.395,
"gpu_power_limit": 560.0,
"gpu_pcie_gen": 4,
"gpu_pcie_gen_max": 4,
"gpu_pcie_width": 16,
"gpu_pcie_rx_mb": 17.1650390625,
"gpu_pcie_tx_mb": 53.119140625,
"cpu": 6.1,
"ram_used": 44503613440,
"ram_total": 177175990272,
"disk_read_mb": 1.946188226381293,
"disk_write_mb": 0.0036040522710764685,
"tok_s": 81.42212134334822,
"tok_s_mean": 78.79383849818437
},
"hardware_static": {
"gpu_name": "NVIDIA GeForce RTX 3090 + NVIDIA GeForce RTX 3090",
"gpu_count": 2,
"cpu_name": "AMD EPYC 7453 28-Core Processor",
"cores": 20,
"threads": 20,
"psutil": true
},
"history": {
"gpu_util": [
0.5,
0.0,
0.0,
46.5,
96.0,
72.5,
93.0,
90.0,
95.5,
93.5,
97.5,
41.5
],
"gpu_mem_used": [
40447639552,
40447639552,
40495874048,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40542011392,
40653160448
],
"gpu_temp": [
51,
50,
50,
52,
55,
54,
56,
54,
56,
56,
57,
54
],
"gpu_power": [
261.46,
249.04,
248.59,
342.07,
531.07,
533.47,
545.68,
511.29,
533.34,
534.58,
541.51,
420.39
],
"gpu_pcie_rx_mb": [
0.67,
0.62,
182.48,
55.88,
87.02,
80.58,
57.5,
78.87,
83.88,
1294.42,
46.78,
17.17
],
"cpu": [
0.0,
6.6,
18.0,
11.3,
10.5,
9.8,
11.5,
13.9,
11.4,
12.9,
7.4,
6.1
],
"ram_used": [
43799613440,
43777974272,
43950305280,
43962880000,
43939622912,
43930345472,
43932086272,
44071780352,
44183257088,
44182880256,
44179062784,
44503613440
],
"disk_read_mb": [
null,
0.0,
129.59,
40.83,
79.94,
84.92,
115.02,
136.42,
117.79,
106.04,
29.9,
1.95
],
"tok_s": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
81.42
]
},
"time": 1790717800.421838
}
}
Loaded 100 of 480 files, more files were not shown because too many files have changed in this diff. Show more