Repository navigation
XPU Qwen3.8-27B: a lone DFlash2 stream draws its tree's width from the lane rule's lone pricing - #3052
XPU Qwen3.8-27B: a lone DFlash2 stream draws its tree's width from the lane rule's lone pricing#3052Mirrdhyn wants to merge 4 commits into
Conversation
|
Thanks @Mirrdhyn. This is what #2897 asked for: the lone tree's width now comes from We also checked who else the core change reaches. Before it can land
|
|
Measurements for items 1, 3 and 4, and where item 1 stands. Nothing is pushed yet. I don't have a fix for item 1 (details below), and the 34-token figure in the changelog waits on it. The rest does not: the rebase after #3051 (the All runs: one Arc Pro B70, this branch's parent Item 1: the 34-token cellGreedy, 5 fresh servers a side, 4 timed requests each after one warmup.
The receipt's −3.6 % (136.14 against 141.23) is the same card at
So I don't have a rule that meets #2897's bar yet. Forcing 15 removes the served loss at 34 tokens, and 15 sits on that plateau (0.4 % under the derived best), but it is far from the best at 7,588, and nothing is measured between those two depths. Sampled, the simulation puts the rule above the cap at 34 tokens (161.7 against 159.2, best fixed 160.2 at 14 nodes) while the served item 3 cell is 0.991, so a cutoff would need its own sampled cells too. Would you review a cutoff in Item 3: sampledTemperature 1.0,
Item 4: requests the lone driver refuses, and more streamsThe lone driver refuses
At both batch sizes each request's hash equals the parent's and equals the same request sent alone. |
|
Thanks @Mirrdhyn. The new runs isolate the overhead, add sampled and engine-round coverage, and keep the hashes equal. The short-prompt gate is still open. These items remain:
|
Refs #2897.
A stream served alone with the DFlash2 drafter (
--parallel 1) went throughLaneHost.lonetoXpuLanes.alone, whichverified a fixed 15-node tree every round. At long context 16 verified rows each read the whole context, so a smaller
tree is faster with the same tokens (#2897). This takes that choice through
lone_costs, as CUDA's lone round does:alonegrows the tree tocap = min(XPU_TREE, 15)on the host (drafter_tree.grow: lattice, noise, best-firsttree, now also each draft's own log-chance), prices it with the calls
windows.zigmakes for a lone head window (prices,allocateDrafts, thenprobe/trial), exceptleast_rows2 wherewindows.ziguses 0, and verifies only the first w drafts (settle). A treeof w nodes is the first w pops of the 15-node tree and a child pops after its parent, so the prefix keeps every
parent and the accepted path is the wide path cut at w.
Landing.chance(sampled, own) x parent, ascuda_draft.zigdoes (xpu_dflash2.chances), updatedwith
observeTreeon the verified prefix. They are not on the Drafter vtable:Draftedreadsvtable.chancesonly whenfacts.chancesortree_nodes > 0, and XPU sets neither.stream's
depth.msthroughRule.observeCost, thenlone_costs.observe. The first round (prefill-to-decodeswitch) and the first round after a cold drafter-context restart in
resumeLoneare not samples.keepleaves theband's calibration in
Engine.lone_kepton exit, at the end or at the hand-over to the engine;seedstarts from it.XPU_TREEis a cap. Below 2 nodes the width is fixed and nothing is learned (as before).lone_cost_tile = 4; probes every 4 rows from row 2 (1, 5, 9, 13, 15 drafts).What the core lacked, and what this extends (small, and CUDA's behaviour is unchanged):
lone_costs.enabledrequiredplain_guard, andRule.costswrote the stream's own table only under it. XPU leavesplain_guardfalse so that a shared round is not held at 0 drafts. AWho.lonemark (set bylone_costs.marked) now makesRule.costsuse the stream's own table for a lone round priced bylone_costs, andenabledno longer requiresplain_guard.enabledstill requirese.alone, a timedlone_cost_tile, no draftconfidence, a head plan and
p.lanes == null(lone_costs.zig); the caller'slive(windows.zig) makes it oneplan. Shared rounds do not change.
probestarted at 1 row, i.e. a draft-less window. Withoutplain_guardno round is drafted with nothing, so itstarts at 2 rows (CUDA's schedule with
plain_guard, tile 16, is unchanged:lone_costs_test.zigstill passes).lanes.lone_costsis exported so a native backend can call it.Two things the lone pricing changes beyond the lone driver, with the tokens unchanged:
lane_host_lone.takes: logprobs, prompt logprobs, grammar, think budget, loop guard, a tool call, drafts off) is now priced from the stream's own times withleast_rows0(
windows.zig, thelivebranch), so it can pick a round with no draft on the strength of the start-up price; around with 0 drafts is not recorded as a sample (
Rule.observeCost).width, verify and the drafter's taps (
XpuLanes.alone); the engine'sfamilyRoundtimes flush, verify and the latedraft.
keep/seedshareEngine.lone_keptby context band, so a calibration kept by one is read by the other.I did not measure the offset between the two.
Exactness: the width only sets how many drafts are verified; the walk keeps the target's own pick (greedy ties to the
lower id, sampled picks keyed by
(seed, position, id)).Measured on an Arc Pro B70 (main and the branch were built with the same local, uncommitted VRAM-guard patch to
zig/src/xpu/rt.zig, and the runs also set that patch's card variable, which the scripts leave out; receiptdocs/receipts/xpu-qwen27-lone-tree-width): the same hash per prompt across thefixed trees of 15, 7 and 3 nodes, the branch and plain decode (greedy, 34 to 121,022 tokens) and across main and branch
(seeded, 34 and 121,022 tokens). Decode tok/s, branch default against main with
XPU_TREE15 / 7 / 3:Branch over the best fixed tree: 0.964 at 34 tokens, 1.027 at 28,946, 1.015 at 58,543 and 1.056 at 121,022. At 34
tokens it is 3.6 % below the best fixed tree (15 nodes): it verifies about 11.7 nodes a round and accepts 4.36 drafts
a round where 15 accept 4.67. I looked for the cause on the same card with a per-round log of the price table, the
choice and the measured time (34 tokens, 4 requests, 190 rounds):
single price is a single timing, 3 % noisy, 5 % for the difference of two, and the table keeps stale prices of
widths it no longer picks (44.6 to 44.9 ms at 11 and 12 nodes against 39.8 at 14).
I tried three rules that take the widest width priced within a band of the best one (receipt, "Why 34 tokens stays
below", with the raw runs): a band of 5 % of tokens a ms, and bands of 2 and 4 scatters of the stream's own times. The
5 % and the 4 sigma are bands of the rule, not losses. Greedy tok/s at 34 / 28,946 tokens, against 135.82 / 46.61 for
the branch's rule in the same session (141.23 for 15 fixed nodes at 34): 5 %: 138.05 / 41.28; 2 sigma: 135.11 / 47.06;
4 sigma: 137.92 / 42.42. The two wide bands lose 11 % and 9 % at 28,946 tokens (41.28 / 46.61, 42.42 / 46.61) and none
reaches the fixed tree at 34 tokens, so none is in this change. The first request of each band (the warm-up) also pays
the width trials (121,022 tokens: 18.0 tok/s against 21.4 to 22.4 afterwards); the per-request table is in the receipt.
Tests:
zig build testexited 0 on the code commit (the host tests drivewidth/measureddirectly: none callsXpuLanes.alone, so theloop's wiring is covered by the device runs and the tables above only); new host tests (width follows the band's measured rounds;
XPU_TREEcaps; w-node treeis the prefix of the 15-node one and tokens are equal at widths 1 to 15, greedy and seeded; lone pricing without
plain_guard). Device testsxpu-qwen27-tests3 of 3 and the kernel contract (9 pass, 5 controls red) on the B70.