Repository navigation
Flash Next shared sessions: image chats that add an image resume (the standard frame) - #1200
sethforprivacy wants to merge 3 commits into
Conversation
…n image chat that adds an image resumes 1.1.2 rotates an image prompt in the shifted frame: every row below the last image moves by the images shift, so the rows past it rotate at their slot. A later turn that adds an image moves the whole prefix, the frame is in the cache key, and the conversation prefills again from its first token with every image through the tower. On the shared sessions an image stream now takes get_rope_index own positions (vis_prompt.standardFrame): a prefix rows sit where any longer prompt puts them, so its kept state resumes as text does and the cache keys carry no frame. Rows past the last image rotate at their slot plus VisPrompt.delta (minus the shift): the prompt pass through positions(), the shared rounds through pos8. The indexer pools such a stream blocks at their three positions (its first row: fz_idx_pool_pos, q4_idx_pool arithmetic on Qwen interleaved axes; a text block keeps its bits), the positions held per session (Session.blocks, Model.bpos) and carried when the stream moves. The engine own rounds keep the shifted frame, so image requests stay off the lone driver (api.Lone.images). The single-reply host is unchanged. The MTP head keeps slot positions (drafts only). Tests: the standard frame keeps a prefix positions when an image is added (fails with the shift), delta past the last image, block positions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y role in the kernel contract The standard frame pools an image stream indexer blocks at their first rows three positions. Its kernel moves to prefill/fn_pool_pos.metal (compiled as its own library), so fn_select.metal and the absolute role pinned source stay as they were. The contract gains a sixth rotary role, positioned: - rotary_domain: every slot (stride 1) on Qwen interleaved M-RoPE axes, as mrope; receipts hold 12 rows and five pinned sources (the sweep: 302,008,320 cases); a pool role 17 normalization fixtures. - rotary_interval.pooledAt and vision_bounds.pooledAt: a block at three positions (pooled stays the splat). - vision_contract: Which.positioned (fz_idx_pool_pos, flashnext_pool_pos); its launch record. - the pool probe binds each block three positions before the output: even rows a text block on all three axes, odd rows an image block three; rotary-long rows take the long fixture positions. - the runner, the limits map, the cache check (96 rows) and the sweep host tests count the new role. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… check) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ary positions Refuse malformed image spans before allocation or position writes. Preserve standard-frame prefix positions across chunking, decode and session growth, and keep image requests on the shared lane path. Seed the rotary-cache diagnostic from the positioned pool's three BPOS axes at its selected block offset. Keep absolute positions for the other pooled roles, strict poison detection and the precise fallback checks. Include the position collector in receipt identity and register host regressions for long and short positioned windows plus the unchanged absolute path.
|
Thanks, Seth. This is on main as e08019c: your three commits unchanged, then one maintainer commit and one small contract commit after them. We ran it on an M3 Ultra with Flash Next MLX 4-bit. An image chat that adds a second image now resumes from 12,556 of its 12,563 cached tokens, where main resumes from none, and the resumed, drafted, changed-image and concurrent replies all match a fresh server exactly. The maintainer commit refuses malformed image spans before anything is allocated, and fixes the rotary cache check, which seeded absolute block starts while your positioned pooling reads three position axes. With that fix the 96-case cache check, the rotary sweep and contract gates 1 to 4 pass on both the M3 Ultra and an M5 Max. The contract commit records the M1 and M2 thread limits an offline compile reports for the seven Flash Next vision and rotary pipelines (1024 each), including your new positioned one.
|
Posted by Claude (Anthropic's AI assistant) on behalf of @sethforprivacy, as part of our local-inference work on a Mac Studio M5 Ultra.
What this fixes. On Flash Next's shared sessions, an image chat that adds an image in a later turn reads its whole history again: 0 cached tokens, and every image back through the tower. #986 names this as a property of the shifted frame ("a turn that adds an image reads its prompt again"). On #565 you put the rule as "a prefix has to sit where every longer prompt puts it". This PR restores that on the shared sessions. The single-reply host is unchanged.
Why the shifted frame can't resume.
vis_prompt.framemoves every row below the last image by the images' total shift, so the rows past the last image rotate at their slot. RoPE only sees differences, so replies are right. But the shift counts every image in the prompt, so a new image moves the whole prefix. That's why the frame is part of the cache key (LaneHost.frame_key).What changes.
vis_prompt.standardFrame). An image stream on the shared sessions takes get_rope_index's own positions. A prefix's rows don't depend on later images, so its kept state resumes as text does, and the shared host's keys carry no frame. Rows past the last image rotate at their slot plusVisPrompt.delta(minus the shift): in the prompt pass throughpositions(), and in the shared rounds by adding the delta to the window's rows inpos8(Backend.verify). The single-reply host still builds the shifted frame (flashnext_images.build(..., standard)).4 bwould sit off bydeltapast the last image, and off the M-RoPE positions inside it. So an image stream's blocks pool at their first row's three positions in a new kernel,fz_idx_pool_pos:q4_idx_pool's arithmetic, axes chosen asfn_attn_prep_mropechooses them. A text block has all three alike, so it keepsq4_idx_pool's bits at that position.Session.blocks,Model.bpos), are cleared byModel.reset, and are carried bySession.moveTo.Select.encodeAtandcatchLayerAttake them;encodeandcatchLayerare unchanged for their other callers.api.Lone.imagesis a new field, default true, false for the Flash Next shared host. A lone image request runs on the shared rounds. The MTP head keeps slot positions, since it only drafts. Text requests run the code they ran before.fz_idx_pool_poshas its own source,prefill/fn_pool_pos.metal, compiled as its own library, sofn_select.metaland every pinned source are unchanged.positioned: every slot (stride 1), on the M-RoPE axes, asmrope.rotary_interval.pooledAtandvision_bounds.pooledAttake a block's three positions;pooledis the splat.825288b7(the standard frame) and8e46fd2f(the kernel's own source and the contract role).Environment. Mac Studio M5 Ultra, 80-core GPU, 256 GB, macOS 27.0 (26A428); compiler 382.5.3. Zig 0.17.0. Checkpoint
TensorFold/Qwen3.8-Flash-Next-MLX-6bit-MTP@45032a1. Binary sha256cb200a6e2e06b5d8…(zig build native -Dcpu=apple_m1), with libjpeg-turbo 3.1.4.1 beside it. Linux: DGX Spark, Ubuntu 24.04 aarch64.Served, on this head (
--parallel 16,FLASHNEXT_SLOT_CONTEXTS=65536,32768x3,16384x11,FLASHNEXT_SLOT_GROW=2048,--context 262144,--prompt-cache-gib 16,--vision --vision-max-images 32 --vision-image-tokens 16384, greedy, thinking off):da275a36, same frame code as 1.1.2; measured on 1.1.2)--prompt-cache-gib 0server's, this PRcbf03fede93662e33250774adeac2682d4f4bb22fa9534adc32472b3cfb221c2fe44df3683ae90b46aec0d6f1fa38f08cf7fec3780ef850aea1fd937fb0e63f2d38634c58f78abf04ae163e2e6a7de87. These equal the same commit's frame change served on 1.1.2.Served, on the same change on 1.1.2 (
dca5e388, the lane's build, which also carries our shared-session--learnspill). These ran before this rebase:c14bebdf9c631e3b).Session.moveTocarries the block positions.qualify_mac9/9; structured output 5/5 (greedy, sampled, thinking,guided_choice, 4 constrained beside 4 plain); the 262K edge; handoffs; Flash Next on Metal (1.1.0): a decoding stream stops for the whole of another stream's prefill #1032 replies =--parallel 1's; first token after idle 0.096 s.Host checks.
da275a36zig build testtest-nativetest-kernel-contract,test-rotary-sweep-host,test-rotary-cache-host,test-flashnext-{vision-contracts,rotary-bound,bound-probes,precise}-hostzig build testtest-cuda-host,test-kernel-contract(72/72), the six rotary/contract host stepstf-rotary-sweepkernel-contract --rotary-accuracy(M5 Ultra)zig fmt --checkis clean.Not run, or known:
core/contract/flashnext_pending.zig).fz_idx_pool_posadds the same two. Our M5 limits pass. We have no M1-M4 Macs.libtfvideohere), the single-reply host's image path (unchanged), and Gemma, GLM and Nemotron.FAIL shapeslines show inkernel-contracton main and here.image_tokens / n_images(server/images.zig:169) resizes the earlier images once the share is smaller than an image, which changes their tokens. That's --vision-image-tokens is capped at 16,384 again: d5e69ee4's raise to 262,144 didn't survive #940's merge #1198.Thanks to @BobClawblaw for Flash Next images on Metal (#986), which this builds on, and to @cwschroeder for the shared sessions and the contract review that shaped the rotary probes.
🤖 Generated with Claude Code