Skip to content

kolibri1: Kolibri-1 on CUDA through the lane host, drafts off (#481) - #991

Closed
jschmied wants to merge 47 commits into
ashhart:mainfrom
jschmied:kolibri1-family
Closed

jschmied wants to merge 47 commits into
ashhart:mainfrom
jschmied:kolibri1-family

Conversation

@jschmied

@jschmied jschmied commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Kolibri-1, a research checkpoint, on native CUDA as a plain family behind the lane host (#481). It is registered in the CUDA family registry as kolibri1 (fp8-block) and serves through tensorfold-native with shared rounds, copy drafts from the prompt's text and the prompt cache. The EAGLE-3 drafter is not in this PR: head drafts and --drafter are refused by name.

The family brings no kernels of its own. Every launch is a shared operator: block-FP8 lane projections through qlinear (cuda/fp8.zig), grouped fp8g experts with the shared expert stacked last, qk_rows, norms, embed_rows, moe_glue, dense_rows, window_attention (prompt rows of every layer, sliding decode rows) and tree_attention (full layers' decode rows).

Stacked on #798 (cuda-moe-dense-window at 74b749fa, which carries #746's fixes; merged as d2f62303), #746 (cuda-attn-shared at ee81000d, merged; #798 carries its later commits up to b502747c) and #797 (cuda-glue-rows at 31d3baf4). Not on #734: windows are chains, so no tree rounds are needed. Base: main 6ee828c2 (1.0.12 plus docs). Only the commits below are this PR's.

  • Family, zig/src/families/kolibri1/:
    • config checks, the tensor map (116,303 tensors, none left over) and the loader on the shared block-FP8 layouts (3c7a1860);
    • the layer graph: sliding layers with RoPE, every fifth layer full without positions, sigmoid top-6 of 384 with the expert bias plus the shared expert at weight 1; one-row step graphs a stream (de56c8dc);
    • the lane backend: prefill and prefill_step, shared verify windows of up to 16 rows a stream and 64 a round, keep, keyed draws; prompt-cache states at any row (full layers' keys, each sliding layer's last 512) (0a267b0b);
    • prompt passes of 8,192 rows, as the Python engine ran them; each sliding layer's ring holds a pass and its window (8,768 keys) (7633646a);
    • admission (17964843): the open reads the room the weights leave (Options.room, as GLM does) and plans the streams before allocating caches or scratch. Model.openBytes counts init's allocations by the same path, allocating nothing. Auto allocates the most streams that fit, up to 8. A fixed --parallel that does not fit, or is above 64 (a round's rows), is refused at open, the latter before the weights load.
  • Native, small shared changes (6498344d): kolibri1 in native/cuda.zig's registry; a family with an Options.lanes field gets --parallel (Kolibri allocates every stream's caches at open); cuda_refusal.zig takes a family's own drafter_refusal text; cuda_snaps.zig takes a family's snap_lookahead (Kolibri's states need no token past their row). In 17964843, such a family also gets --parallel's fixedness, and cuda_memory.growRoom gives it no room to grow into: the host admits only the streams it allocated, never a lane without a slot. Each has a host test.
  • Tests (0101e1ff, 60d5bdd8, 7633646a): tf-cuda-test kolibri-reference, kolibri-layers, kolibri-full, kolibri-greedy, kolibri-logits on the checkpoint; no fixtures.
  • Recipe: docs/recipes/kolibri-1.md (57755519, 7633646a).

Refusals, each worded: other quantizations, norm_topk_prob, other layer kinds, a config with a vision tower, shapes the shared kernels do not take, --vision and --precision (no such option for this family), --drafter, head drafts, structured output, a captured kernel set.

Receipt

  • Environment: this branch at 7633646a on main 6ee828c2 unless a receipt names another commit (the admission receipts and the re-checks under them are at 17964843), one GB10 (sm_121), CUDA 13.0, Zig 0.17.0, ReleaseFast. Checkpoint pin: Aleph-Alpha/Kolibri-1 at e52eb4627d11516b0c01de49210ab5a4e4061444, 74 GB block FP8; publisher sha256 of model.safetensors.index.json c6769a34820c9057b404b2a5cae1ac24fd7b4a9ca08b84eb0a06e7b5bad3005e, first shard a7a98d7a62ea98d5ea087803ce018f51a15e442bc9a621617c68df7582947542. The shards were not re-hashed for this receipt.

  • Plain reference: the Python engine on our fork (kolibri1/cuda, Triton glue), the one the family was ported from.

    • Greedy through serving, /v1/completions with fixed token ids (6 chat prompts rendered by the checkpoint's template, 54 to 71 tokens, and 2,000 tokens of real text), 160 tokens, "draft": false. The Python engine's own greedy loop gives its server's SHAs on all 7.

      Prompt Python Native served Native eager rows First difference
      code 34e0f8a1b2c0 34e0f8a1b2c0 34e0f8a1b2c0 none, 160 of 160
      prose 7a8162e41fb8 c1657fc2f1b6 c1657fc2f1b6 token 141, Python margin 0.109
      German 42e2eebedcb2 3b3596014050 3b3596014050 token 96, Python margin 0.003
      arithmetic f47ad4367586 cc206f8c50ad cc206f8c50ad token 32, Python margin 0.137
      story 8a51bc7eeb55 24865c85fd4c 24865c85fd4c token 69, Python margin 0.111
      table bbc25f7ae2ca 947b0722b165 947b0722b165 token 40, Python margin 0.024
      2,000-token text 76a017be68d4 e619651b8e23 e619651b8e23 token 17, Python margin 0.025

      Served replies equal the family's own one-row reference on all 7. Both native columns re-run at 7633646a: the same SHAs, and kolibri-greedy's output (ids and margins) is byte-identical to the 2,048-row build's. Against Python, 1 of 7 is equal over 160 tokens; the other six part at near-ties (top-2 margins 0.003 to 0.137 logits on Python's side, 0.006 to 0.189 on ours).

    • Logits at prompt rows are not byte-equal (measured at 57755519; the 8,192-row passes leave every row's bits as they were, see the cuts below). Last rows of the 7 prompts: top-1 equal 7 of 7, max |difference| 0.11 to 1.50. Teacher-forced over 2,048 rows of real text: top-1 agrees on 92.3 % of rows, KL mean 0.060 (p99 0.63). Why: the Python engine's glue is Triton (approximate exp, its own sum orders); these operators sum in other orders with IEEE division and square root, so outputs move by an ulp or so; Kolibri-1's top-6-of-384 routing turns that into other experts on near-tied rows, and later layers carry it. On cuda: tree attention and DFlash block attention for every family, checked against float64 #746/cuda: SwiGLU, output gates, Gated DeltaNet inputs, QK norm and RoPE for every family, checked against float64 #797/cuda: sliding-window attention, sigmoid top-k routing and combine, dense rows for every family, checked against float64 #798 alone, each glue change by itself gave KL 0.04 to 0.06 against Triton, the same as all of them.

    • Independent check, kolibri-reference: layer 0 and the head against float64 computed from the checkpoint's tensors (block-FP8 dequantised in float64, rounded where the engine stores bf16): max |logit difference| 0.0045 to 0.0073 of a 6.6 to 7.8 logit scale (0.11 %), top-1 equal, prompt and decode rows, 3 cases.

  • Quality on real text, at 57755519 (kolibri-logits + nll.py, 2,047 rows of our notes text, one 2,048-row pass either way): native NLL 3.5737, top-1 35.96 %; Python engine 3.5784, 35.47 %; earlier Triton-free run 3.586, 35.52 %. Mean entropy 2.354 against 2.358: not softer.

  • Exactness, served (tensorfold-native, --parallel 8, --context 32768):

    • drafted == "draft": false: 7 of 7 completions (no copy matched there); every turn of the 36-request agent run below drafted and plain alike (19 of 19 pairs), with copies landing; bench_concurrent.py --serial 54 of 54;
    • concurrent == solo: bench_concurrent.py --alone --serial --levels 1,4,8 --temperatures 0,1.0 --tokens 256 --reps 3, 156 of 156 equal, 0 failed;
    • resumed == fresh: tools/zig/reuse_served.py record (prompt cache 8 GiB, --min-prompt 0 --turns 5), 36 requests all cached exactly as planned (history marks, shared system cuts at 4,955 / 6,491 / 7,003, a 1,566-token history, a resume after a cancel), then replay on --prompt-cache-gib 0 and compare: 35 of 35 replies equal. Resumes start inside passes (8,246, 12,729, 17,210 ...). All 36 replies equal the 2,048-row build's record;
    • cut prompt == uncut (kolibri-full): a 5,000- and a 20,000-token prompt's last logits in 1,000-, 1,536- and 2,048-row chunks equal the 8,192-row passes byte for byte, 6 of 6.
  • Exactness through the lane rounds (kolibri-full, 128 tokens): eager one-row forwards == step graphs == lane rounds == copy-drafted rounds (77 drafts landed) on a short and a long prompt; sampled drafted == plain; four streams in shared rounds each equal alone; states kept at 2,048, 3,001 and 4,096 by one stream and resumed by another equal fresh. Same hashes before and after merging the cuda: tree attention and DFlash block attention for every family, checked against float64 #746/cuda: sliding-window attention, sigmoid top-k routing and combine, dense rows for every family, checked against float64 #798 NaN fixes, after cuda: sliding-window attention, sigmoid top-k routing and combine, dense rows for every family, checked against float64 #798's flag-free tensor-core decode (7d599181, merged as 3dd1d1a1), and with 8,192-row passes (7633646a); kolibri-layers and kolibri-reference print the same lines too.

  • Decode, bench_openai.py --tokens 256 --reps 3 (one stream, median, --context 65536 --parallel 8): code 52.0 tok/s greedy, 50.4 sampled; chat 50.3 greedy, 50.3 sampled (2,048-row build 3dd1d1a1, same server: 51.7 / 49.9 and 49.9 / 49.9, so unchanged). Aggregate, bench_concurrent.py (--context 32768): 4 streams 124.6 (code) / 126.5 (chat), 8 streams 176.2 / 184.2 greedy; sampled 111.2 / 115.1 and 149.1 / 159.5. No before against another engine: a new family.

  • Cold prompt, bench_cache.py --require-cold --repeats 3, --prompt-cache-gib 0, --context 65536 --parallel 8, real text, cached 0 on every request, ttft_s the median of the 4 cold requests. Before: the 2,048-row build of 3dd1d1a1, same flags, same idle GPU, measured just before:

    Prompt tokens 2,048-row passes 8,192-row passes Change
    2,048 0.97 s 0.97 s none
    8,192 4.01 s 3.72 s (2,205 tok/s) -7.3 %
    32,768 17.98 s 16.82 s (1,949 tok/s) -6.5 %
    64,000 40.40 s 37.84 s (1,691 tok/s) -6.3 %

    Same token SHA on every repeat and on both builds. The 1.00 / 5.04 / 21.5 s in this PR's earlier receipt were measured at 57755519. Expected before the run: 14 to 16.5 s at 32k (first written against the 21.5 s above, restated against the 17.98 s baseline before building); it is 16.82 s, just outside. Each 2,048-row pass dropped saves about 0.1 s (0.29 s for 3 at 8k, 1.17 s for 12 at 32k), so reading the experts once more costs a tenth of a 1 s pass: the passes were already bound by the expert and attention math, not by reading the weights. A full pass now holds other streams' rounds for about 3.7 s instead of 1 s.

  • Admission, at 17964843, on the checkpoint (--context 32768):

    • --parallel 8: the same load as at 7633646a, model 85.23 GiB, 8 streams at 1.29 GiB; planned bytes equal allocated bytes, 12,578,056,556 (checked once with a temporary log line, not committed).
    • TENSORFOLD_CUDA_MEMORY_LIMIT_GB=79.4, auto: 3 streams, model 78.76 GiB, 0.6 GiB left for the prompt cache.
    • The same limit with --parallel 8: refused at open, --parallel 8 needs 11.71 GiB at a 32768-token window and the CUDA memory budget leaves 5.89 GiB after the weights: 3 streams fit. The 7633646a build admitted 8 there, with 0.0 GiB left.
    • --parallel 65: refused in 2 s, before the load.
    • Unchanged at 17964843: kolibri-full and kolibri-layers print the same lines as at 7633646a; the 7 served greedy SHAs are equal; bench_concurrent.py --alone --serial --levels 1,4 --temperatures 0,1.0 --tokens 256 --reps 3 gives 80 of 80 equal at the same speed (4 streams 124.3 / 126.2 greedy).
  • Memory: weights 73.4 GiB; 1.29 GiB a stream at 32k, 1.92 GiB at 64k (2,048-row passes: 0.83 / 1.45): each sliding layer's ring grows from 2,624 to 8,768 keys. Forward scratch about 1.2 GiB (0.3 GiB at 2,048 rows). At 8 streams and 32k the load reports 85.23 GiB (80.61 with 2,048-row passes). Peak device memory 91.8 GiB (8 streams at 32k, 8 GiB prompt cache in use, after the concurrent and agent runs; 87.2 GiB with 2,048-row passes at 57755519), 128 GB unified.

  • Tests:

  • Mutations at 60d5bdd8, each applied alone, the runner rebuilt, then restored:

    Mutation Caught by
    routing bias dropped kolibri-reference
    window one key wider kolibri-layers
    window one key narrower kolibri-layers
    full layers one off (i % 5 == 0) kolibri-reference, kolibri-layers
    shared expert left out kolibri-reference
    projection FP8 block scales read as 1 kolibri-reference
    expert gate and up scales swapped kolibri-reference

    The first run used a 2 % bound and missed the bias and the swapped scales (0.75 to 1.7 % of the scale); the bound is now 0.3 % (60d5bdd8), clean rows sit at 0.11 %.

  • After merging cuda: sliding-window attention, sigmoid top-k routing and combine, dense rows for every family, checked against float64 #798's 74b749fa (d2f62303: cuda: tree attention and DFlash block attention for every family, checked against float64 #746's and cuda: sliding-window attention, sigmoid top-k routing and combine, dense rows for every family, checked against float64 #798's review fixes, the float64-weight replay in block, tree and window attention), on the checkpoint: kolibri-reference and kolibri-layers print the same lines; kolibri-full gives the same hashes (eager, steps, drafted, sampled, four shared streams each alone, the six cuts byte-equal, kept states at 2,048, 3,001 and 4,096); served (--parallel 8 --context 32768): the 7 greedy SHAs are equal, drafted == "draft": false 7 of 7, bench_concurrent.py --alone --serial --levels 1,4 --temperatures 0,1.0 --tokens 256 --reps 3 80 of 80 at the same speed (4 streams 125.0 / 126.9 greedy), reuse_served.py record with the same 36 SHAs and replay/compare 35 of 35; admission: planned bytes equal allocated bytes at 8 streams, 12,578,056,556 (temporary log line, not committed), auto under TENSORFOLD_CUDA_MEMORY_LIMIT_GB=79.4 takes 3 streams, a fixed 8 there is refused with the same message, --parallel 65 is refused in 3 s. zig build kernel-contract: SUMMARY 60 kernels: 60 pass gates 1-4, 0 fail, 0 known findings; 5 controls: 5 red as planted, 0 missed; 0 limit problems. zig build test from an empty cache 88/88 steps, 1,399/1,407 tests (8 skipped); test-cuda-host with TF_KOLIBRI_DIR 43/43 steps; zig fmt --check clean, lean_check 517.

  • Not run, and why:

    • Byte equality with the Python engine: not met, see above.
    • Other GPUs (sm_80/86/89/120) and kernel-contract-limits on multi-SM fatbins: only a GB10 here, and this PR adds no kernels.
    • Metal: no Metal path.
    • The 128k/256k windows: the serving receipts ran at 32k and 64k windows.
    • The EAGLE-3 drafter, logprobs and structured output: not built for this family yet; refused by name.
    • A served side-by-side speed run against the Python engine: no before for a new family.

Checklist

  • Every token still goes through the lane rounds. No serial path, and nothing that needs drafts off.
  • Drafted output equals "draft": false, a resumed prompt equals a fresh one, and concurrent equals solo.
  • No precision traded for speed. If the bits change, the description says which sums change and why.
  • A new or changed GPU kernel is registered with the kernel contract (docs/kernel-contract.md); each backend's SUMMARY line is in the receipt. (No new kernels; CUDA SUMMARY above.)
  • Prompt processing is no slower than the last release. (A new family: no release to compare; numbers above.)
  • The description says which platforms I ran, Metal M1 to M5 and CUDA, and which I could not.
  • New tests fail before the change, pass after it, and skip cleanly without their dependency.
  • Comments and docstrings are one line. No measurements or history in the source.
  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.

…e_attention.cu, block_attention.cu), checked against core/attention_ref.zig
… merge combines them in order; a slice takes its own one-warp block where four-warp blocks would leave SMs idle (TreeAttention.sms), the same bits
…lves again, only one-warp blocks leave them to the merge (a small fixed slice area), so the scratch stays at one partial a group
…async stages), the same arithmetic in the same order
…w), partials sized to the query heads, one-warp blocks with 3 tile stages where they fit one wave of the SMs, else 2
…ts) before folding them, the same arithmetic in the same order
… gates, DFlash2's conv, Gated DeltaNet inputs, QK norm and RoPE), checked against float64
…iton's tuned sizes): swiglu 128 and gate 64 threads, gdn_prep a warp a block pair; the same bits at any row count
…, and dense rows for every family, checked against float64
# Conflicts:
#	zig/src/core/root.zig
#	zig/tests/cuda/main.zig
…te value there stays out of its sum

A block whose output holds a NaN or infinity runs again with the values of the tiles some row cannot see all of taken out of the mma; each row adds back the ones it sees. On finite values the first pass is the kernel as it was, byte for byte.
…te value there stays out of its sum

A block whose output holds a NaN or infinity runs again with the values of the tiles some row cannot see all of taken out of the mma; each row adds back the ones it sees. On finite values the first pass is the kernel as it was, byte for byte.
…ax flag, so a NaN head leaves the other heads' keys
…ld zeros, not a copy of the window's first key, so an infinite value there reaches no sum
…, so a later row's infinite value stays out of earlier rows' sums

A block whose output holds a NaN or infinity runs again, out of line, with the values of the tiles some row cannot see all of taken out of the mma; each row adds back the ones it sees. On finite values the first pass is the kernel as it was, byte for byte.
…block-FP8 layouts (cuda/fp8.zig, qlinear, fp8g experts)
…rows, norms, sigmoid routing, grouped FP8 experts), one-row step graphs
…--parallel streams at open, no-lookahead states
…w edge, full layers, steps, drafted, shared rounds, cuts, kept states
…scale (a dropped routing bias or swapped expert scales pass 0.7 %)
…i1-family

# Conflicts:
#	zig/src/core/contract/registry.zig
#	zig/tests/cuda/contract_tests.zig
…mily

# Conflicts:
#	zig/src/core/contract/registry.zig
#	zig/tests/cuda/contract_tests.zig
…sion, the shared operators it runs on, checks, speed
… flag (a head with no key keeps m, exp2 of +0 is exactly 1), the old kernel's speed
…(sliding rings hold a pass and the window, 8,768 keys); cuts of 5,000 and 20,000 rows check against them

@cwschroeder cwschroeder left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both admission findings from this historical review are addressed at 179648432b561e986a2ec83951360b49749e114a, verified in a source/CPU follow-up: #991 (review) . No material admission finding remains in that scope. Device/model qualification remains separate.

Original review scope:

Two admission issues remain in the Kolibri/native integration below. I checked the shared registry/options, refusal and snapshot hooks, the configuration checks and the memory-admission call path, with an independent Astra source review.

The CPU probe imports the actual cuda_memory.Pool and admit; its memory sizes are synthetic examples. It demonstrates the admission arithmetic, while the connection to Kolibri's allocations and slot ownership comes from source inspection. The three configuration tests pass in Debug and ReleaseFast. No device allocation, kernel, model-quality or performance validation was performed here. The four current-head CI checks are green.

errdefer own.s.deinit();
const context = positions(o.context);
const n = streams(o.lanes);
own.m = try fwd.Model.init(gpa, ctx.d, own.s, &own.w, &own.k, context, n, be.prompt_rows);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Budget the prepaid caches and scratch before allocating them

openWith checks only weight bytes before F.open. Kolibri has no Options.room hook and allocates all requested stream caches plus scratch here, then returns them as free_streams. If those allocations cross TENSORFOLD_CUDA_MEMORY_LIMIT_GB or the reserve, the later after.room(model) just becomes zero; budget.admit(0, stream_bytes, 8, 8, true) still accepts all eight prepaid slots. For example, the CPU probe accepts 70 GiB of weights under a 72 GiB cap, then admits eight slots after synthetic held bytes reach 78 GiB. A budget/size plan is needed before cache and scratch allocation; auto parallelism should allocate only what fits, and a fixed oversized count should fail before consuming the protected memory. The shared admit function itself is behaving according to its prepaid-slot contract.

Comment thread zig/src/families/kolibri1/native.zig Outdated

/// Streams whose caches the open allocates: the server's --parallel, at most a round's rows.
fn streams(lanes_asked: u32) usize {
return std.math.clamp(@as(usize, lanes_asked), 1, be.round_rows);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Refuse a fixed parallel count above the backend's slot capacity

The server CLI accepts --parallel 65, but this clamp creates only 64 slots. If room remains for one stream, generic admission returns 65 (admit(gib, gib, 64, 65, true) in the CPU probe), and the host advertises/adopts 65 lanes. facts.max_streams=64 limits a shared forward, not the host's admitted request count. Once all 64 slots are occupied, backend.opening returns NoFreeSlot for the next admitted request and the host ends it; only LaneMemory is requeued there. Reject the fixed count at open or carry the backend's hard capacity into admission, rather than treating unallocated slots as growable.

…llocating them; refuse a fixed --parallel past the memory or a round's 64 rows; admission adds no lane to a family that allocated its streams at open

Model.openBytes counts init's allocations by the same path without allocating; the open reads the server's room,
allocates as many streams as asked when they fit, the most that fit under auto, and refuses a fixed count that
does not fit or exceeds 64 before any cache is allocated. cuda_memory.growRoom gives a family whose Options
carry `lanes` no room to grow into, so the host admits only the streams it holds.
@jschmied

Copy link
Copy Markdown
Contributor Author

@cwschroeder, thank you. Both findings were right. Fixed in 17964843.

native.zig:70, budget before allocating. Kolibri now has the Options.room hook GLM uses. Its open reads the room the weights leave and plans the streams before any cache or scratch is allocated. fwd.Model.openBytes counts what init allocates by running the same allocation path with nothing allocated, plus the backend's buffers. With that plan:

  • auto allocates the most streams that fit, up to the 8 asked;
  • a fixed --parallel that does not fit is refused before the caches are allocated;
  • no stream fitting at all is refused too.

The planned bytes equal the bytes allocated (12,578,056,556 at 8 streams and 32k).

native.zig:48, more than 64. A fixed --parallel above 64 is refused at the top of open, before the weights load. Admission also changed, in a small shared step: cuda_memory.growRoom gives a family whose Options carries lanes no room to grow into. So the host admits only the streams it holds. Under auto, leftover room can no longer add a lane that has no slot.

Tests. Your probe's cases, with synthetic sizes:

  • kolibri1/native.zig:
    • 70 GiB under a 72 GiB cap: auto gives 2, fixed 8 refuses;
    • the same through the reserve;
    • scratch counted;
    • no room left;
    • --parallel 65 refused, 64 taken.
  • native/cuda_memory.zig:
    • 64 held plus room for one more: a fixed 65 refuses;
    • auto with 3 held keeps 3;
    • 8 prepaid stay admitted at zero room.

Mutation: I put back the old clamp in fit and passed the room through growRoom. Three tests fail, with your probe's numbers: expected 2, found 8; ParallelPastRound expected, found 64; TooMany expected, found 65.

GB10, real checkpoint:

  • --parallel 8 --context 32768: the same load as before, model 85.23 GiB, 8 streams at 1.29 GiB.
  • TENSORFOLD_CUDA_MEMORY_LIMIT_GB=79.4, auto: 3 streams, model 78.76 GiB, 0.6 GiB left for the prompt cache.
  • The same limit, --parallel 8: refused at open. The log says --parallel 8 needs 11.71 GiB at a 32768-token window and the CUDA memory budget leaves 5.89 GiB after the weights: 3 streams fit.
  • The build before the fix, with that limit, admitted 8 with 0.0 GiB left.
  • --parallel 65: refused in 2 s, before the load.

Exactness is unchanged:

  • kolibri-full and kolibri-layers print the same lines;
  • the 7 served greedy SHAs are equal;
  • bench_concurrent.py --alone --serial --levels 1,4 (temperatures 0 and 1.0): 80 of 80 equal, at the same speed.

@cwschroeder cwschroeder left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both admission findings are addressed in 17964843. I rechecked the hook before cache/scratch allocation, fixed-count refusals and the shared host's zero-growth admission for this preallocated family. A fixed 65 refuses before weight loading, and auto cannot add an unowned slot later.

Local Zig 0.17.0 checks: whole-tree formatting passes; test-cuda-host passes 872 tests with 3 skipped (41/41 steps). Focused native tests pass 9/9; shared memory tests pass 6 with one Linux-only skip.

The additional CPU allocation probe and receipts call the actual forward/backend initializers with a fake driver. For 12 synthetic slot/context/cluster combinations, planning makes zero driver calls and its count equals the requested allocation bytes. This covers the accounting path, not device residency or tensor contents.

All four current-head CI checks are green. My two source/admission requests are closed. I did not rerun the author-reported GB10 model/exactness results, so this remains a scoped source/CPU follow-up.

@ashhart

ashhart commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Thanks @jschmied for the budget-before-allocation and fixed-slot controls, including the real refusal at the tighter cap. We reviewed 1796484: the source admission findings are addressed, and the text-family increment is suitable for integration.

Keep the shared operators, lanes and prefix snapshots, with copy drafting available and EAGLE-3/external head drafting explicitly refused. The unchanged seven served hashes and 80/80 checks do not establish general equality with Python; the disclosed reference differences remain. The final merged source still needs its numerical/resource, copy-draft, cache, cancellation and concurrent/solo device gates. There is no new source correction to send back from this review.

Posted by Ash's AI Assistant Synthena

…ook when a later allocation fails, with a host test that fails each allocation in turn
…ut of the mma and adds it back at its key's float64 weight, so an infinite value gives NaN where float64 does
… value out of the mma and adds it back at its key's float64 weight (attn_odd), so an infinite value gives NaN where float64 does
…ded back before it

The replay adds the context's NaN and infinite values back before the block's own tile, whose rescale is
exp(m - m') in float32. When a block key holds the row's max and the context's scores sit more than ~103
below it, that factor is 0 and turns an added-back infinity into NaN, where float64 (attention_ref) still
weighs it above 0 and gives inf. The replay's block tile now leaves an infinite o as it is (attn_odd::keep);
the common path is unchanged.

Gate 1 plants the case on stream 1, key head 7: block key 0 all 8, every other key all -8 with +inf at
element 23, queries all 2 (inf) and all 8 (float64 weights of 0: NaN).
…values out of the mma, added back at float64's weights

A visible infinite value whose weight underflows in float32 (more than ~103 below the max) but not in float64
(745) went through the mma as 0 x inf, or reached a merge at a factor of 0: NaN, where attention_ref gives
inf. As in block attention, a block whose partial holds a NaN or infinite value now runs again (ODD, out
of line): its V tiles' NaN and infinite values leave the mma (attn_odd::clean_rows), and each pair adds
them back after its last tile at the key's float64 weight (attn_odd::add_back), so weight 0 gives NaN.
The four-warp combine and the merge fold as before; only a NaN result folds again with the infinities
unscaled (attn_odd::fold, keep). tf_tree_shared takes the window's keys (kn) for the path keys' weights.

Finite outputs keep their bits. Gate 1 plants key head 1, row 0: committed key p0 - 1 all 8, every other
key (committed and its path) all -8 with +inf at element 21, queries all 2 (inf) and all 8 (NaN).
…again with those values out of the sums, added back at float64's weights

A visible infinite value whose weight underflows in float32 (more than ~103 below the max) but not in float64
(745) went into a sum as 0 x inf, or reached a fold of partials at a factor of 0: NaN, where attention_ref
gives inf. The prompt rows had the replay (462d952); the decode kernels now have it too, out of line:

- tf_window_decode_tc: a NaN output (the one wrong class: an infinite output holds a weight above float32's
  least, so above float64's) runs the block, or the one warp that saw keys, again as ODD. Its V tiles' NaN
  and infinite values leave the mma (attn_odd::clean_rows), each head adds them back after its last tile at
  the key's float64 weight (attn_odd::add_back), and the warps' fold keeps them (attn_odd::fold).
- tf_window_part: a tile with a NaN or infinite output recomputes those outputs from the finite values, then
  adds each NaN or infinite value at its key's float64 weight against the row's float64 max over its window
  (attn_odd::score64 with a run-time head dim). tf_window_merge folds a NaN result again with the infinities
  unscaled.

Finite outputs keep their bits. Gate 1 plants the case in each decode form: a row's key p all 8, its window's
other keys all -8 with +inf at element 21, one head querying all 2 (inf), the next all 8 (NaN).
…split.cuh (the file was past 600 lines), comments on one line
…i1-family

# Conflicts:
#	zig/src/core/contract/registry.zig
@jschmied

Copy link
Copy Markdown
Contributor Author

I merged #798's 74b749fa in as d2f62303, which brings in #746's and #798's review fixes (the float64-weight replay in block, tree and window attention). These are the gates on d2f62303, run on the checkpoint on GB10:

  • Engine:
    • kolibri-reference and kolibri-layers print the same lines as before.
    • kolibri-full gives the same hashes for eager rows, steps, copy-drafted rounds and sampled drafted == plain.
    • Each of the four shared streams equals its run alone, the six cuts are byte-equal, and the kept states at 2,048, 3,001 and 4,096 rows equal fresh.
  • Served (--parallel 8 --context 32768):
    • The 7 greedy SHAs are unchanged, and drafted == "draft": false on 7 of 7.
    • bench_concurrent.py --alone --serial --levels 1,4 --temperatures 0,1.0 gives 80 of 80 equal, at the same speed.
    • reuse_served.py records the same 36 SHAs (cuts, history marks, a resume after a cancel), and replay/compare gives 35 of 35.
  • Admission:
    • At 8 streams, planned bytes equal allocated bytes: 12,578,056,556 (checked with a temporary log line, not committed).
    • Under the 79.4 GB cap, auto takes 3 streams and a fixed 8 is refused with the same message. --parallel 65 is refused in 3 s.
  • Kernel contract: SUMMARY 60 kernels: 60 pass gates 1-4, 0 fail, 0 known findings; 5 controls: 5 red as planted, 0 missed; 0 limit problems.
  • Tests: zig build test from an empty cache passes 88/88 steps (1,399 of 1,407 tests, 8 skipped), and test-cuda-host with the checkpoint passes 43/43 steps.

The Python reference differences are unchanged and still disclosed. The description has these results.

@ashhart ashhart mentioned this pull request Oct 18, 2026
@ashhart

ashhart commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Thank you, @jschmied, for Kolibri-1 and the concurrent/solo and resumed/fresh comparisons.

Kolibri moves with its shared CUDA family dependencies to 1.1.1, the next patch release. The reviewed implementation uses the common lanes, prompt cache and corrected memory planning. Shared copy drafting is available; MTP, external drafting and vision remain explicitly refused.

It needs the integrated checkpoint's device and resource gates, cache resumes and concurrent copy drafting before release. The supplied comparisons should stay separate from general Python/native equality, which was not established.

On the RTX PRO 6000 host, the Kolibri checkpoint verified 40/40 files, while final device exactness, resource, cache and concurrency checks remain owed.

Posted by Ash's AI Assistant Synthena

@ashhart

ashhart commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Thanks @jschmied. 1.1.1 ships Kolibri-1's native CUDA text-family port through the common lane scheduler, prompt cache, shared rounds and verified copy drafts. Closing that scoped family contribution; EAGLE-3, a neural drafter and vision remain unsupported. The later d2f6230 merge leaves Kolibri's family files unchanged and brings in #746/#798's shared attention repairs. We'll carry those once into 1.1.2 with your credit and their final-image plus real Kolibri exactness, timing and memory checks. General Python/native token identity is not claimed.

Posted by Ash's AI Assistant Synthena

@ashhart ashhart closed this Oct 19, 2026
@jschmied
jschmied deleted the kolibri1-family branch October 23, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants