Repository navigation
Conversation
A request's image tokens were capped at 16,384 on the command line. A model with a 262,144-token window and an engine that encodes an image as its prompt pass reaches it (no rows buffer for the whole request) can take more, up to the window; the per-image budget is still the total over the request's images.
8 of 9 tasks
Bolt's config.json is a nemotron_h_omni wrapper around a nemotron_h_puzzle text model: per-layer expert counts, IQ2_XXS experts, NVFP4 Mamba projections, Q8_0 attention. The serve path reads its model type as nemotron_h_puzzle when an omni config wraps one, and the CUDA host opens it with a lane backend over libbolt.so. - families/nemotron/bolt_native.zig: the backend (prompt pieces, shared verify windows for up to 16 streams, rollback, the LiLiCorr drafts, keyed draws, prompt-cache copies, images through the RADIO tower). - zig build bolt -Dnvcc -Dbolt-src: builds libbolt.so beside the server; the backend loads it from there and checks its ABI. - prompt cache rules: a family may set snap_lookahead (Bolt has no head reading ahead: 0). - docs/recipes/nemotron-3.5-bolt.md: build, limits and receipts.
MiaAI-Lab
force-pushed
the
nemotron-bolt
branch
from
October 16, 2026 14:25
ce855f5 to
66dede9
Compare
MiaAI-Lab
marked this pull request as ready for review
October 16, 2026 14:26
response_format, the guided_* fields and tool_choice "required" or a named function were refused with HTTP 400: the backend offered no masked draws, so the engine advertised no structured output, and the server could not read an omni checkpoint's vocab_size (it sits in llm_config). - bolt_native: verify redraws the rows of a masked window among the tokens their bits allow (rows x words, the grammar's layout), first_masked draws the first token again under the first mask, and held() gives the grammar the drafts the drafter holds. A masked row with fewer allowed tokens than candidates is drawn from the allowed ones only. - libbolt (bolt_mask_rows, C ABI 2): a masked variant of the two-stage GPU top-k, which treats a disallowed token as minus infinity; only the masked rows are recomputed. - server/vocab.zig: the vocabulary size comes from text_config, llm_config or the top level, in that order. - The libbolt binding moves to bolt_api.zig, which keeps bolt_native.zig under 600 lines.
Owner
|
Thank you, @MiaAI-Lab, we will carry the Bolt lane design and focused metadata work with your credit into the single native Bolt family; the landing order and remaining checks are in our reply on #897. Posted by Ash's AI Assistant Synthena |
This was referenced Oct 17, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #937 (RFC, please read first).
What this changes
Serves NVIDIA's Nemotron 3.5 Bolt 74B-A9B 24GB on the native CUDA host as the family
nemotron_h_puzzle. Thehub.modelTypeandserver/main.zigreaders report an omni config wrapped around a puzzle text model asnemotron_h_puzzle; a lane backend,families/nemotron/bolt_native.zig, runs it overlibbolt.so;zig build bolt -Dnvcc -Dbolt-srcbuilds that library beside the server; images go through the existing RADIO tower. Two commits:--vision-image-tokenspast 16,384 (a family that encodes images as the prompt pass reaches them needs no whole-request buffer), then the family. A family may setsnap_lookaheadfor the prompt cache (Bolt has no head reading ahead). A recipe page,docs/recipes/nemotron-3.5-bolt.md, has the build steps, the exactness notes, the receipts and the limits.The CUDA sources are not in this PR.
libbolt(about 4,800 lines of CUDA runtime code, sm_121) is built from a directory-Dbolt-srcnames; it is in https://github.com/MiaAI-Lab/Nemotron-3.5-Bolt-TensorFold-DGX-Spark/tree/main/engine, which is private for now (I can make it public or send an archive). Whether the kernels belong here, under the kernel contract, is question 1 in the RFC.Structured output and forced tool calls (added after the first review round)
response_format(json_schema,json_object), theguided_*fields andtool_choice"required"or a named function were answered with HTTP 400 ("not supported by this engine yet"). Two separate causes, both fixed ina6660d38:first_maskedwas missing.verifynow redraws the rows of a masked window among the tokens their bits allow (the grammar'srows x wordslayout),first_maskeddraws the first token again under the first mask, andheld()hands the grammar the drafts the drafter holds. libbolt getsbolt_mask_rows(C ABI 2): a masked variant of its two-stage GPU top-k that treats a disallowed token as minus infinity and recomputes only the masked rows, so a round without masks costs what it did. A kernel test compares it with a CPU reference (a mask of 5 tokens, about half the vocabulary, and a single token, at k of 1, 20 and 64).vocab_sizeof an omni checkpoint. The server read it fromtext_configor the top level only; an omni checkpoint keeps it inllm_config, so every structured request failed with "needs the checkpoint's config.json vocab_size" (this affects any omni model, not just Bolt). It is nowserver/vocab.zig, a pure function with a test.The libbolt binding moved to
bolt_api.zigsobolt_native.zigstays under 600 lines;lean_new.pystill reports 0 new problems.Checked on a GB10 against the built image:
json_schemaandjson_objectreplies parse and match;tool_choicerequiredand a named function make valid calls (several tools,parallel_tool_calls);guided_choice,guided_regexandguided_jsonobey; thinking on gives free reasoning and a schema-valid answer; a bad schema is a clean 400; 6 of 6 sampled (temperature 1) replies on a nested enum/array schema are valid; a streamed reply is valid JSON; with 4 structured and 4 plain requests at once, all 4 structured replies are valid and all 4 plain replies equal their solo runs. Structured decode ran at about 130 tok/s alone against 90 for the same task unconstrained (the grammar makes drafts accurate). At 16 streams (bench_concurrent --alone --serial, 256 tokens, 2 reps): code 285.9 tok/s aggregate and chat 210.4, every concurrent reply equal to its solo run (32 of 32 in both) and drafted equal to"draft": false.Not run: the structured-output path on another family or on Metal; grammars against the Python 0.6.6 engine's outputs.
Receipt
46a08c18plus these commits; DGX Spark GB10 (sm_121), Ubuntu 24.04.4, kernel 6.17.0-1026-nvidia, driver 580.159.03, CUDA 13.3.73;NVIDIA-Nemotron-3.5-Bolt-74B-A9B-24GBata326822af111290f2af5ac59ef4b5040347dabc4, drafter4c6a5613b6d887194a3a5493e3a18a5b3930bb86; bf16 cache with every stream's window mapped at load.tools/bench_concurrent.py --alone --serial:tools/bench_concurrent.py --levels 1,4,8 --alone --serial, greedy, 256 tokens, 3 repetitions, against--parallel 8 --context 262144 --prompt-cache-gib 8 --vision:--alone(concurrent equals solo)--serial(drafted equals"draft": false)Concurrent equals solo in every cell, code and chat prompts, and drafted equals
"draft": falsein every cell.Why concurrent used to differ from solo, and what changed. An earlier version of this text blamed "GEMM kernels chosen by the rows in a block"; that was incomplete. Three things made a stream's rounding depend on the batch, and all three are fixed in
libbolt:win_chunk(base): 128 keys, doubled until 32 chunks reach the end of the sequence and its window, a function of the stream's own length alone. With the split count forced fixed in a scratch build, alone and 4-stream perplexity were identical, which isolated this one.Harness checks (run with 32-token GEMM tiles): perplexity of four passages scored alone and in a block of 2, 3, 4, 8 and 16 streams (windows of 1, 5 and 8 rows, up to 128 rows a block) is identical to five decimals; sixteen sequences in one block equal their solo runs, 768 of 768 tokens, plain steps and 4-row windows alike. The final 64-token tile changes only the tokens a CTA holds, not the order K is summed in; it is covered by the table above.
Cost. The K-split kernel is slower in the widest rounds (shared round of N streams x R rows, ms, before / after): 8 x 8 rows 135.5 / 130.4, 16 x 4 rows 133.2 / 141.7, 16 x 8 rows (128 rows) 176.9 / 220.3. Rounds of up to 32 rows are unchanged.
tools/bench_cache.py --require-coldon exact token arrays (0 cached tokens,qualified: true), measured on the build before the exactness changes, which touch decode blocks only (not re-run since): TTFT 1.44 s (2,048 tokens), 5.60 s (8,192), 24.66 s (32,768), 55.60 s (65,536).zig build test(host suite; added:hub.modelTypefor an omni config around a puzzle model, a plain one and Lightning's).zig fmt --check build.zig zig: clean.python3 tools/zig/lean_check.pythroughlean_new.pyagainst the base: 517 problems, 517 on the base, 0 new. Each commit builds and passes the host suite on its own. Engine gates in the recipe's harness (run after the attention change; the GEMM-order change was checked with the 16-stream and in-block perplexity tests above): perplexity 5.286 on 1,499 tokens; a window cut to n rows equals a window of n rows; a resumed snapshot equals a whole prefill; the drafter accepts 1.70 of 7 on a creative passage (the same histogram as on the base before the attention change); a stream's logits are bit-identical whatever its neighbours hold.Checklist
"draft": false(every--serialcell), a resumed prompt equals a fresh one (checked in the recipe's harness, not with the repo's tools), and concurrent equals solo (every--alonecell).libboltis a CUDA runtime library outside this tree (RFC question 1).