Repository navigation
Pipelined batch: two requests run at half speed (pad rows in group windows, unbalanced slot choice) - #1249
Pipelined batch: two requests run at half speed (pad rows in group windows, unbalanced slot choice)#1249crazyaimachine wants to merge 2 commits into
Conversation
…ctive one With --batch-groups, every group window ran all GS slots, and an idle slot ran a pad row through every layer of every stage. The server spreads requests over the groups first, so with --batch 4 --batch-groups 2 and two requests each window was half pad rows. Now PGroup::S is the group's last active slot + 1, and the window, its picks and batch_launch() cover only those slots. This was in the PR Niko1221#559 series (ca74f92 there) and did not come along into 0.1.39. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s worth keeping pick_slot() took the least recently used free slot when no slot held the prompt's start. With --batch-groups that could put two requests into one group while the other group's stage sat idle. A held prefix of at least 512 tokens still wins (reading a conversation again costs more); otherwise the free slot in the group with the fewest busy slots is taken, then an empty one, then the least recently used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This overlaps with #1179 (opened a few hours earlier, the replacement of #793 after the history rewrite), so here are notes to line the two up. Commit 1 (group window up to the last active slot) is the same change as #1179's first commit (your 8119a7f / ca74f92 from #559). One of the two PRs can carry it. Commit 2 (slot choice) solves the same problem with a different rule. #1179's
It ignores held prefixes there, because a pipelined slot is never kept as a conversation cache (BDONE clears Measured on 4× RTX 5080, On your "not solved" case (two requests in slots 0 and 2, still ~19 tok/s each): I have not reproduced it on 4 groups of 2. One thing to rule out is a stale |
…e group, and idle slots no longer cost rows in a group's window (#1249) On a layer split with --batch-groups, two concurrent requests ran at about half speed: the slot choice could put both in one group, and a group's window carried a row for every slot, active or not. Slot choice now prefers the group with the fewest busy slots (a held prefix of 512 tokens or more still wins), and a window covers the slots up to its last active one. Without --batch-groups the slot choice is exactly as before. Co-authored-by: crazyaimachine <209005491+crazyaimachine@users.noreply.github.com>
|
Thank you, crazyaimachine. The batch-group fixes are in 0.1.40.2 (Co-authored-by credit). One change: the slot choice and the 512-token prefix threshold apply only with --batch-groups, so the default slot choice is exactly as before. |
Upstream now carries our picks of Niko1221#851 (AVX2 Q8_K quantizer), Niko1221#789 (expert uploads beside the shared expert), Niko1221#813 (--ple-io ram on Windows), Niko1221#1185 (window graphs free older batch layouts), Niko1221#1201/Niko1221#1118 (doorbell per window), Niko1221#1101/Niko1221#1057 (Stager sleep: now the default on Linux only, STRATA_STAGER_SLEEP=1 forces it) and Niko1221#1063 (the shared draft head's type); their versions are kept. Conflicts resolved: - prefill: upstream's own CPU share (STRATA_PREFILL_CPU_SHARE, opt-in) sits beside CPU assist. Its setter is set_cpu_share_pool; when it is asked for, it replaces CPU assist for the chunks it covers, otherwise CPU assist runs as before (on by default). The debug NaN check skips both kinds of CPU rows. - conversation cache: a pool-borrowed entry and upstream's pinned shared prefix (pin=N) are both kept from eviction; the borrow (Niko1221#1164) and pin=N's sibling path do not park at once. - generate: pool batch groups launch only up to their last active slot (Niko1221#1249's G.S), also on the workers; --adapt-async's poll between verdicts runs beside the pool's verified-path-first loop. - server: upstream's chunked bodies (Niko1221#893) and the pool's read-once body are one helper; do_POST's BadBody wrapper around the pool's routed-request check. - native dense: the pool's served_bytes beside upstream's weight_bytes_for (Niko1221#1238). - expert source: the commit-limit retry (Niko1221#1064) beside upstream's paced page-locking (Niko1221#1250). - setup: no ready-made engine (StrataPool compiles its own); REPO names StrataPool for the SHA-256 lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
Merged 23 upstream commits, from the previous merge base e8ca9af (v0.1.40.2) to upstream tag v0.1.40.3 (d5ea713). Upstream did not rewrite history: e8ca9af is still an ancestor of upstream/main. The llama.cpp/ggml pin is unchanged at 3cf03257f219afbe7334045ff7c6a06ac68c627d. One file conflicted (1 hunk). The resolution keeps this fork's gfx906 paths: - setup.py: the AMD GPU listing keeps the fork's two-argument amd_problem(g, a.experimental_gfx906) calls; upstream's new Linux amd_device_access_problem() /dev/kfd warning block is adopted below them. CMakeLists.txt, src/program/generate.cpp and sycl/setup_intel.py auto-merged and were verified line-by-line afterwards: all fork lines (STRATA_EXPERIMENTAL_GFX906, the gfx906 deprecation warning, STRATA_HIP_MULTIGPU_TESTS, the vmm.cpp STRATA_USE_HIP=1 source property, hip_layer_handoff, hip_wave64_probe, the gfx906 oracle tests, the frozen PGroup::rows window, the mapped-host handoff bridge, the fail-closed model pin) are present, and the diff of the four shared files against upstream/main contains only fork gfx906 content. The fork's frozen active-slot mapping (74583c6) stays in every hunk; upstream's Niko1221#1249 pad-row PGroup::S variant did not return. sycl/setup_intel.py's stubs still match the merged signatures (amd_problem(g, experimental_gfx906=False), hip_vision(asked, *, experimental_gfx906=False, archs=())); the new amd_device_access_problem() call site is safe there because it returns None without /dev/kfd. The Niko1221#1357 router guard (n_expert == 512 && K == 10) keeps the native router, which reads exactly 512 floats per row, for 512-expert models such as the acceptance model GSQ-RCO IQ2_XS, and falls back to the generic router_top10 otherwise; the gfx906 build compiles the same native_router.cu through the compat layer. The new /dev/kfd permission message in generate.cpp uses ::access/F_OK/R_OK|W_OK via <unistd.h>, already included in its !defined(_WIN32) branch. The STRATA_MMVQ_IL opt-in change was reverted upstream before release: zero occurrences on both sides of the merge. GPU-independent tests on this Mac: py_compile of setup.py, sycl/setup_intel.py and tools/hip/dll_closure.py passes. tools 'test_setup_*.py' discovery: 412 tests, 2 failures, 2 errors, 1 skipped; full tools 'test_*.py' discovery: 582 tests, 2 failures, 2 errors, 2 skipped; tests.test_gfx906_upstream plus tests.test_gfx906_setup: 29 tests OK, 5 skipped (the CmakeArchitectureGate class needs cmake, absent on this Mac; no GPU test was skipped, the modules are CPU-only by design). Upstream d5ea713 alone, in a throwaway /tmp worktree (removed afterwards), runs the same 'test_setup_*.py' discovery with 412 tests, 2 failures and 4 errors: every merged-tree failure is also a baseline failure, so the merge adds no test regression; the merged tree additionally passes the two HipVision tests that error on pure upstream (upstream's rocm_root fails on a missing compiler before the test's cmake stub; the fork's wheel-fallback path tolerates it). tests/test_gfx906_upstream.py now pins source_version() == 0.1.40.3 and MIN_ENGINE == (0, 1, 40, 3); the remaining 0.1.40.2 literals in tools/ are version parsing test data, a "supported since" table string and a historical fixture identity, none of which assert the current source version. README.md and docs/GFX906.md now state source version v0.1.40.3 / d5ea713; no workload numbers were moved or relabelled (no new workload observation in this range). The running T5810 service was not touched: this merge was prepared and tested on the maintainer's Mac only.
…e group, and idle slots no longer cost rows in a group's window (Niko1221#1249) On a layer split with --batch-groups, two concurrent requests ran at about half speed: the slot choice could put both in one group, and a group's window carried a row for every slot, active or not. Slot choice now prefers the group with the fewest busy slots (a held prefix of 512 tokens or more still wins), and a window covers the slots up to its last active one. Without --batch-groups the slot choice is exactly as before. Co-authored-by: crazyaimachine <209005491+crazyaimachine@users.noreply.github.com>
Two small fixes for
--batch N --batch-groups Gon a layer split. Two concurrent requests ran at about half the speed of the 4-request case per request, sometimes worse.1. A group window holds only the slots up to its last active one (
generate.cpp)Every pipelined group window ran all
GSslots of its group. An idle slot ran a pad row through every layer of every stage. The server spreads requests over the groups first (slot_order). So with--batch 4 --batch-groups 2and two requests, every window was half pad rows. NowPGroup::Sis the group's last active slot + 1, and the window, its picks andbatch_launch()cover only those slots.This fix was in the #559 series (ca74f92 there, "pipelined group windows only up to the last active slot"). It did not come along into 0.1.39, where the non-pipelined path got the same idea.
2.
pick_slot()spreads requests over the groups (serve/server.py)When no slot held the prompt's start,
pick_slot()took the least recently used free slot. Once every slot held an old conversation, two new requests could land in one group while the other group's stage sat idle. Now the rule is:Measured
Setup:
--batch 4 --batch-groups 2 --trim-stage-weights, layer split 22, images on.Without
--batch-groups(one group), 0.1.40.1 gave 50 / 68 / 94 on the same machine.Not solved
Even with both fixes, a round of two requests now and then still runs at ~19 tok/s each, with the requests in slots 0 and 2 (different groups). In six back-to-back rounds the worst was 52 tok/s total, but it does happen. I have not found the cause yet. If you have an idea where to look (doorbells? the stage hand-off?), I am happy to test.
The two commits are on 0.1.40.1 and apply to current
main. Written by my agent, measured on my machine.