Repository navigation
nemotron cuda: a prompt pass with images runs the RADIO tower once, not twice - #1057
plotarmordev wants to merge 1 commit into
Conversation
…ot twice The merge of ashhart#897 and ashhart#938 put a `Vision.begin` where Bolt's branch set up its image rows, after the kept-prefix restore, and kept the one Super VL's images brought after the prompt-rows setup. The second ends the first pass and encodes every image again, so each image request ran the tower twice before its first chunk. The first call stays.
|
Thanks @plotarmordev, that's a clean find. The second We've carried it onto the 1.1.2 line under your name and measured it on a GB10 against the line without it. Super VL NVFP4, one stream, prompt cache off, ABBA, 20 requests a cell: cold TTFT fell 20.3% with one image (1.558 to 1.241 s) and 21.8% with two (2.677 to 2.093 s), with identical token hashes, drafted equal to plain, greedy and seeded. That's a little under your 28%, with our own fixtures. A repeated image that hits the prompt cache skips the tower anyway, so the gain is on cold requests. An encode hook on Posted by Ash's AI Assistant Synthena |
|
The encode hook on |
…dits The 1.1.2 section had no entries for the contributed pull requests carried into this release, so neither their changes nor their authors reached the release notes. Add an entry for each, written from its commits: Flash Next's CUDA prompt tiles and structured output (#1046, #1062), --thinking-reserve, the long-loop guard and the --vision template diagnostic (#1064, #1075, #1076), GLM's Metal eh_proj tile and CUDA waiting ranks (#1066, #1053), M1's simdgroup cap (#1055), Nemotron's CUDA image and shared-window fixes (#1057, #1058), Qwen's live-row expert routing (#1047), the shared CUDA operators and contract fixture (#1056, #1068, #1070, #1071, #1073), the ROCm receipt, drafter and draft-count work (#1043, #1044, #1045), the ModelOpt recipe numbers (#1042), the Windows host build (#1034) and the session store's allocation-free rollback (#637). Thank #643's and #1060's reporters as the other entries thank theirs.
|
The Nemotron CUDA image-prompt fix is included in 1.1.2 (https://github.com/ashhart/TensorFold/releases/tag/v1.1.2), with your original authorship retained. The prompt path starts the RADIO vision work once instead of starting it again. Closing this as carried, and thank you. It landed as 6bdcd38. - Synthema, Ash's AI Assistant. |
…st encodes each image once `Vision.encoder`, when set, takes a prompt pass's planned images in the tower's place, as Flash Next's `encoder` does (a `ctx` and an `encode` function). The new host test runs the actual lane prefill with a counting encoder and checks one encode per image per request; it fails with the duplicate `Vision.begin` that #1057 removed.
What this changes
On CUDA, Nemotron 3.5 Super VL's lane prefill (
Cuda.prefillFninzig/src/families/nemotron/cuda_lanes.zig) callsVision.begintwice for a request with images. The merge of #897 and #938 (7f6f940) put one call where Bolt's branchset up its image rows, after the kept-prefix restore, and kept the one Super VL's images brought (e144e45) after the
prompt-rows setup.
beginends any pass in progress, so the second call plans the same spans and runs every imagethrough the tower and projector again before the first chunk. The rows it writes are the same, so replies do not
change; each image just costs a second tower pass (about 0.9 s for a 2048 x 1664 image on a GB10). This removes the
second call and its
defer.No host test comes with this.
Tower.encodeneeds the ViT kernels and cuBLASLt, so no host test can runVision.beginpast it without a GPU (the existing host tests stop at itserror.RowShapeshape check, where thefirst
beginwould already fail). The proof is the GPU receipt below: tower kernel launches halve and replies staybyte-identical. The smallest seam for a behavioural host test would be an encode hook on
Vision, as Flash Next'sencoderhas (actxand anencodefunction), so a test could count encodes per request; I can add it as afollow-up if you want it.
Bug report
zig/since 1.1.0.nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4at revision82d93699acd3be9e4617c53360fe5b3f5dbeb1b3.tensorfold-native serve MODEL --vision --vision-image-tokens 13312 --vision-max-images 4 --context 18432 --parallel 1 --prompt-cache-gib 0.prompts are 3,356 and 13,366 tokens), greedy, thinking off,
"draft": false, 32 tokens.tf_vit_position_addlaunches, 8 requests carrying 20 images (nsys)tf_vit_unshufflelaunches, same requeststoken_sha256, 4 runs541bca2d…9735ec541bca2d…9735ectoken_sha256, 4 runsef22b44e…d444c4ef22b44e…d444c4Full hashes:
541bca2d52751d8bb9c44b2af53ce8ff2dfb93745d8cb6bdb740442dad9735ec,ef22b44ea1d731737c2d2a757d671d03b1269563d6bb505944e58608ecd444c4. Both kernels run once per image the tower encodes.How to reproduce
bench.py image-ttftsends 4 one-image and 4 four-image requests (20 images) and prints each TTFT andtoken_sha256. The launch count column read 40 on 1.1.0 (main's source) and 20 with this fix.Receipt
Raw files: https://github.com/plotarmordev/TensorFold/tree/nemotron-receipts/receipts/nemotron/vision-tower-once (
README.mdmaps every file;SHA256SUMS).branch at e2357b5 on main d46d17a. The GPU runs compare 1.1.0 (76aafcf) with 1.1.0 plus this fix (b4495aa3);
git diff --stat 76aafcf2 d46d17a7 -- zig build.zig build.zig.zon toolsis empty, and this branch's diff againstmain is byte-identical to
git diff 76aafcf2 b4495aa3. Each built from a clean checkout withzig build fatbins -Dsm=121 -Dnvcc=…andzig build native -Doptimize=fast -Dstrip=true -Dversion=1.1.0 -Dtarget=aarch64-linux-gnu.2.28 -Dcpu=baseline(zig 0.17.0, nvcc 13.0.r13.0);
tensorfold-nativesha256 1.1.004167a17…74015088, fix52b3990e…a15853fd(full in the receipts). One DGX Spark (GB10), driver 580.173.02, CUDA 13.0,
nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4@82d93699acd3be9e4617c53360fe5b3f5dbeb1b3, served as in the bugreport on loopback with
TF_HEAT_HIGH=85 TF_HEAT_LOW=75, a fresh server andHOMEeach hold.tools/bench_concurrent.py --alone --serialortoken_shaagainst"draft": false: every imagerequest is
"draft": false, and each build's 4 runs at 1 and at 4 images give one hash, the same on both builds(above), in the profiled and the unprofiled holds. Drafted image replies and
bench_concurrent.py: not run.tools/bench_openai.py: not measured. The change removes work from a promptpass with images only; decode rounds and text-only prompts run the same code as before.
tools/bench_cache.pysends no images, so the TTFT above is the server'stime_to_first_tokenfrombench.py image-ttft, cooled to 75 °C before every request. Unprofiled holds in theorder 1.1.0, fix, 1.1.0, each started at a zone of 60 °C or less. One image: 3.170 / 2.285 / 3.188 s, no heat waits.
Four images: 20.666 / 15.034 / 18.678 s raw; every four-image request on every build paused at the heat gate inside
its prompt pass (10 / 8 / 8 s, logged as
heat_wait_s), so the table nets them out. Both counts save about 0.9 s animage, one tower pass.
-j1, a fresh cache each step), on this branch:zig build test152/152 steps, 2413 of 2422 tests passed, 9 skipped, 0 failed (main: the same counts);zig build test-kernel-contract43 of 43;zig build test-golden306/319 equal to the frozen answers, 0unexpected differences;
zig build fuzz-cortheon8 of 8;zig fmt --check zig build.zigclean;tools/zig/lean_check.py503 problems, 503 on main, 0 new (tools/zig/lean_new.py);zig build fatbins -Dsm=121and
zig build native -Doptimize=fast -Dstrip=truesucceed.tf-cuda-test kernel-contract: no kernel changed.Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (The change only drops a repeated tower pass whose rows the first pass already wrote; image replies are byte-equal before and after with"draft": false. Drafted, resumed and concurrent image requests were not run.)SUMMARYline is in the receipt. (No kernel changed.)