Repository navigation
Make the server random by default - #488
Merged
Merged
Conversation
…be random by default
timethink
pushed a commit
to timethink/sglang
that referenced
this pull request
Mar 9, 2025
EdwardXuy
pushed a commit
to shun8686/sglang
that referenced
this pull request
May 9, 2026
Update full-test-npu.yml
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 3, 2026
…the vLLM route revoked The GPU window stood the TTS stage up and tested the whole doc-derived contract against a real server. DESIGN §9 predicted that a documentation error would pass every desk test and fail on first contact; it did, in nine distinct places, because the stub was built from the same documentation as the adapter. Partway through, a standing user order struck the serving choice: no vLLM anywhere, ever -- everything serves inside htsglang, because cross-asset spill/offload/eviction arbitration needs one runtime with one ledger and a foreign serving process is invisible to that register. The server was killed, all three cards verified back to 0 MiB. What survives the reversal is the contract itself, observed rather than read. The scripts are corrected here because their previous content was simply wrong, and the corrections carry the evidence in comments for the native bring-up (sgl-project#488) that replaces this path: - vllm-omni does not depend on vllm; it is a plugin, and the core has to be installed explicitly, which also pins torch back down (2.13.0 -> 2.11.0). - `--omni` is a sys.argv sentinel, not an argparse flag: invisible to --help, and omitting it does not fail -- it silently serves a plain LLM whose only symptom is a missing /v1/audio/*. The design doc records the remaining seven deltas, the latency result, and what is still owed. Headline: warm streaming first-audio measured 71 ms on a 3080 against a 150-300 ms budget, RTF 0.16 -- the TTS stage is not the risk. The 36 preset clips remain undone; the render mechanism was proven on one clip minutes before the reversal. Full evidence, corridor rows and artifacts: /spinning/gpu-battery-results/2026-08-03_w3_t0_translator/RESULTS.md
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 3, 2026
…roject#488) Desk investigation against the runtime, ahead of any Phase 3 building. The blocker is not what it looked like. "The decode loop assumes one token per step" is wrong in both directions: the append side is already N-per-step because speculative decoding put it there, and the KV/position side is strictly one-per-step -- which is exactly right, because sixteen codebook entries are one audio frame at one sequence position, not sixteen positions. The residual codes must never enter the KV sequence. The real blocker is a single line that clears input_embeds on entry to decode, while the talker's next input is a summed embedding vector rather than a token id. The CUDA-graph side already carries embeds, so this is a scheduler unblock rather than a graph rewrite. The speculative stack is the wrong vehicle and is rejected on the record: a draft head is the right shape and the wrong contract -- nothing to verify, no target distribution, no accept length, and spec exists to ADD sequence positions. Smuggling a depth transformer through CustomSpecAlgo would poison the spec metrics, the adaptive-draft ladder and the cross-algo bandit for the rest of the fork's life. The precedent that fits is the dLLM lane: a non-speculative custom decode regime, 935 lines plus wiring, with full_logits as the precedent for a non-standard logits shape and customized_info as a wired-but-unused transport for the extra codes. No host exists. sgl-project#274 is a dual lane within one model's world, sgl-project#305 is design-only, DESIGN_333 M5 is unformulated, and ANALYSE_334 already priced this family HARD/L. Effort against the only complete implementation (5192 lines plus a 2102-line bespoke runner, whose authors also could not host it in a stock decode loop): ~16 days. Seven buys a boot-proven model file, desk parity for the trunk, a harness-driven code predictor and a written hook design -- not streaming decode, graph coverage, the vocoder, or the native speech endpoint. The translator cannot move to the native lane before 08-10, so the ladder's rung B ships. Three traps pre-registered, the sharpest being that the checkpoint writes rope_scaling "interleaved" while the factory reads "mrope_interleaved": passed through, the model loads, runs, emits plausible codec tokens and sounds wrong. Also preserves the vLLM-Omni 0.24.0 reference outside the venv being deleted. It is the only source of truth for the 1596-line prompt builder, the predictor loop and the vocoder, and losing it would have cost more than any other artifact here.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 3, 2026
…block to sgl-project#488 Two operator rulings. The translator is published as /translate/ under an existing server_name with the existing certificate, instead of as its own subdomain. That removes both remaining user actions -- no DNS record, no certbot --expand against a certificate that fronts a dozen other services -- and with them the last risk to the user's certificate setup. The host already path-mounts /go2rtc/, /llama/ and /plex/, so the pattern is idiomatic there and /translate/ is collision-free. The client had to learn where it lives: every URL is now built relative to location.pathname, and the manifest's start_url and scope are "./" so the installed PWA scope follows the mount point without the server being told about it. The same file works at the site root and under any prefix, which is what keeps the standalone-subdomain template valid too. Verified through the real front door: health and the PWA serve under the prefix, the manifest carries the relative scope, and the WebSocket handshake returns 101. A diff of the live config against the pre-change backup shows 29 added lines and zero deletions or modifications, so the neighbouring locations are provably untouched. The scheduler unblock (the input_embeds clear in prepare_for_decode) is NOT built. Rung B runs its own generation loop and never enters the scheduler, so a gated patch would have no caller and no falsifier -- unvalidated ballast of exactly the kind the desk-written-never-executed rule exists to prevent. It is recorded as a named sgl-project#488 prerequisite in 11.2 instead.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 3, 2026
… the ledger gap it hid
The first warm GPU number came back at RTF 24.6 on a 5090 for a 0.6 B talker.
Two orders of magnitude from anything the architecture predicts is a symptom,
not a limit, so it got opened rather than reported. `probe_stage_timing.py`
(new) times the three links of one call with the device drained around each:
prompt 5.3s | talker 4.3s | codec decode 85.2s <- 90% of the call
The codec hangs off `model.speech_tokenizer`, and `Qwen3TTSTokenizer` is a
PLAIN class, not an nn.Module (`inference/qwen3_tts_tokenizer.py:44`). It is
therefore not in `model._modules`, `model.to("cuda:0")` never reached it, and
it carries its own construction-time device snapshot that its encode/decode
use to place inputs. Talker on the card, the entire 12 Hz codec left on the
CPU in bfloat16. Nothing failed; bfloat16 convolutions are simply that slow.
Same shape as the device fault fixed in the previous commit, one level deeper,
which is why the fix is one function: it now finds holders STRUCTURALLY -- a
plain attribute owning an nn.Module and caching a device -- rather than by
name, so a second such holder cannot be stranded silently the way this one
was. And it VERIFIES: after the moves it walks every parameter and buffer and
refuses if any is off-target, because "we called .to()" is a success claim and
a stranded module is silent by construction.
Found while reading that code: `codec_decoder` was registered against
`inner.code2wav`, which does not exist on this checkpoint. It resolved to None
on every load, logged a warning nobody read, and the largest single audio
asset (229 MB) was never in the sgl-project#286 register at all -- a registration with
reach zero. Now `speech_tokenizer.model`, and a missing module is a hard
failure: an unledgered module is memory the register cannot see, park or
evict, and the one-runtime law rests on that register being complete.
Falsifiers (hermetic, `meta` for `cuda`): the detached holder is NOT moved by
the model's own `.to()` (the bug), is moved after retargeting (the fix), a
`.to()` that silently does nothing is caught by the state probe, and an
explicit device the model is not on is refused rather than recorded.
Tests: 295 hermetic pass under CUDA_VISIBLE_DEVICES=99 (was 291).
Measured on the 5090, same command before and after:
before: prompt 5.3s talker 4.3s codec 85.2s RTF 24.18
after: prompt 0.02s talker 5.2s codec 0.04s RTF 1.15
codec per frame 1739.6 ms -> 0.7 ms
`latency_window.py`, warm, n=10, A-vs-A floor 245 ms:
TTS whole utterance 4216 ms median for 3.68 s of audio, RTF 1.146
ASR (faster-whisper large-v3-turbo, int8_float16) 96.9 ms for 3.11 s
MT still unmeasured -- no endpoint up in this window.
The talker is now 99% of the call at ~90 ms/frame. That is the rung-B
limitation DESIGN_466 §12 already names (no CUDA-graph capture over the nested
code predictor), not a new defect, and it is what sgl-project#488 exists to remove.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 3, 2026
…r instrument that can refute it Three things, all desk-executable, ahead of the GPU window. The TP=3 rejection is now a register row (planner/rejected.py, tts_talker_tensor_parallel) rather than only a paragraph, so the wizard cannot propose what the arithmetic already settled. NOT_DEFAULT, not BLOCKED: the shard is representable and correct, it simply loses -- so it stays offerable with the counter-number attached, unlock line included. Scope is "rig", because the collective term is a rig property while the 192-steps-per-audio-second shape is architectural; the verdict is the two together. The register's own brevity contract caught the first draft (gain/cost <= 120 chars) -- the row was shortened, not the contract widened. The precursor instrument (scripts/dev/488_talker_profile/) decomposes a talker forward into wall / kernel-covered / gap / kernels-per-iteration / syncs, and projects the RTF that would remain if the gap went to zero. It runs against an ALREADY LOADED module inside the tenant process -- no second load, no server boot, ~6 minutes, every loop bounded by both an iteration count and a wall deadline. It is built so it can tell us we are wrong: at >= 70 % kernel coverage it prints PREMISE FALSIFIED and says ANALYSE_488's redirect needs revisiting. And it refuses to testify at all unless two known-answer calibration arms separate by >= 0.40 gap fraction AND the known GPU-bound arm stays under a 0.35 ceiling. The second condition is the one that matters here: the card is currently held by a resident serving instance, and on a saturated box EVERY arm looks overhead-bound -- a separation-only gate would have let contention manufacture the answer we expect. Both refusals are executed as tests, as is the falsified verdict wording. Reach correction, cited rather than assumed: "sgl-project#274 lanes carry multi-instance naturally" does not hold at the code level. dual_group_lane.py:35 says the scheduler holds a list of lanes and "Slice B instantiates exactly one lane"; :16-25 make a lane a full-width shell tree over the SERVING model's own parts, not a slot a different model occupies; server_args.py:9524 requires an explicit --rank-tp-ratio because "the lane shares the plan's rank-0 segment". One talker instance per card is sgl-project#305 shaped, and sgl-project#305 is design-only. What slice 1 can honestly claim is that the model file holds no process-global state -- every geometry decision comes from the parallel context at construction, the same axis sgl-project#274's overlays already substitute -- so N instances stay a host problem rather than a model-file rewrite. Written into ANALYSE_488 §6a. Tests: 25 hermetic across the two sgl-project#488 files (13 lane + 12 instrument), plus 190 green on the register's consumers (wizard, wizard_v2, planner generality, rig coupling). ruff + codespell clean on every touched file. No GPU touched: the arb request is rewritten to the one short in-process arm and the TP=3 arm is withdrawn from it.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 3, 2026
… all falsified Slot 1 of the field-report queue, run against the live build 9f60fbb742. The probe captures whole 20 ms wire buffers at `playback.schedule`, together with the clock each was scheduled against, and reads them numerically. seams : max step 0.19052, mean 0.01661 inner : max step 0.25858, p99 0.12994 onset : 1%/10%/50% of local peak at samples 173/316/358, lead-in 320 schedule: 268 calls, 1 re-anchored, 1 started in the past, min lead -20.1s (a) The talker's waveform is NOT the source. The onset rises from near silence to half amplitude over ~185 samples, 11.6 ms -- a natural speech onset, not a step. The very first buffer of a turn starts at exactly 0.0 with a DC offset of 0.0. Nothing here belongs to the sgl-project#488 profile mandate. (b) The framing seams are NOT the source. Buffer boundaries are not the outlier: the largest step across a seam is SMALLER than the largest step inside a buffer, and the mean seam step is an order below the inner p99. (c) Scheduling underrun is NOT the source. Of 268 buffer schedules exactly one started in the past, and it is the benign initialization: `cursor` is 0 while the context clock is at 20.1 s, which is precisely the case the re-anchor exists for. No genuine late start occurred. The rise metric was wrong in the first pass and is fixed here: slicing the stream AT the threshold crossing and then asking how long the rise took reported 0 samples, which was true by construction. The window now starts 320 samples before the crossing. What this leaves, and it is not in the measured set: the client builds one `AudioBufferSource` per 20 ms frame at the WIRE rate -- `createBuffer(1, float32.length, rate)` with rate 16000 -- while the output context runs at 44100 (measured, `playback.diagnostics`) or 48000 on a phone. Every 320-sample frame is therefore resampled INDEPENDENTLY by the browser, with no continuity across frames. This probe reads the input samples and by construction cannot see an artefact created in the resampled output, which is exactly where that one would live. Not fixed here: nothing is faded blind, per the standing order.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…ed by no kernel Ran on the 5090 with coordinator GO, user not testing. The in-process entry point cannot be injected into a live PID, so this used the standalone one: a short-lived second copy beside rank 0 and the tenant. NO tenant restart and no tenant code change -- pid 3954713 verified alive and HTTP 200 afterwards, and the card's free VRAM returned to exactly its pre-run 3605 MiB. The instrument passed its own gate before testifying: the known GPU-bound arm sat at a 0.000 gap fraction against a 0.35 contention ceiling, the known launch-bound arm at 0.851, separation 0.851 against a required 0.40. The GPU-bound reading doubles as lever 5's answer -- the card was not SM-contended during the run. Numbers. Trunk step 38.34 ms wall / 5.64 ms kernel (85.3 % gap). Predictor step 6.07 / 0.98 (83.8 %). The HF generate() envelope 104.44 / 16.21 with 602 SYNCS per frame. Derived frame 142.78 / 21.85 -> RTF 1.713 measured, 0.262 kernel-only, recoverable factor 6.53x. Against the live 5511 ms first-audio that is 38.6 frames, i.e. ~3.2 s of audio: the token loop is essentially the whole TTS latency, and agent 8's tenant-side split will say the rest. Three caveats are in RESULTS.md rather than buried: the event COUNT is inflated because it sums non-leaf profiler events (the TIME uses self-device-time and is not), RTF 1.713 is this configuration and not the tenant's 1.23, and the gpu-bound arm proves "not launch-starved" rather than measuring a ratio. The measurement re-ranks two things. TRT gains a numeric argument rather than deference: kernel time is 21.85 ms against a 2.2 ms bandwidth floor, a 10x that is kernel SHAPE, so graphs remove the 84.7 % gap and only fusion touches the rest -- ladder 1.71 -> ~0.26 -> ~0.05-0.10, crossover measured either way. And fp8 stops being a speed lever: the run measured a bf16 talker instance at 2678 MiB (free went 3605 -> 903, minus the 24 MiB calibration transient), while a 3080 has 2551 MiB usable above the corridor. bf16 misses by 127 MiB on both 3080s, so fp8 is the GATE on the talker pool existing at all, not an optimisation after it. DESIGN_488_talker_pool_and_revision_guard.md carries the rest: TRT engine discipline (I/O signature, dynamic-shape profiles, sha256 + consumer matrix, separate sm120/sm86 engines), the pool with in-order assembly and the head-biased dispatcher a straggler makes necessary, the always-eager chunk policy with the lag-driven context-adaptive clause rule written as an explicit rule rather than hoped for as a side effect, the two-stage revision guard (deterministic prefix check first, Qwen only on divergence, unparseable verdict treated as no-break because a false repair is worse than a missed one), the RAM-cached correction prefix keyed (voice, language) and invalidated on roster events, and the four server interfaces agent 8's client halves attach to -- of which only the per-turn abort channel is genuinely new protocol. Tests: 29 hermetic green (16 instrument + 13 lane). The headroom fixture is now derived from the constants after the calibration transient was resized 96 -> 24 MiB and a hardcoded fixture stopped exercising its branch. ruff + codespell clean.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…rtion says why The 2x PASS was on 55ec9fe802. The ?raw=1 capture switch landed after it, so the served build is ac5163c54a and it was re-gated: 2 PASS / 3 FAIL. Every failure is one assertion, and it is not in this cut: turn 2: 1 of 2 streamed clauses arrived AFTER the first audio frame (19.13: text must not wait for audio); margins [2.2] It fires when synthesis is unusually FAST -- the failing turns recorded tts_first_audio 1.04s against a 5.02-6.62s median in the same run, so the second streamed clause lost a race it normally wins by seconds. tts_first_audio spans 1.04-6.62s for one identical clip across five runs, which is not a property of a client that counts frames. The sgl-project#488 talker agent is running a ~6 min TTS profiling call inside this tenant. This is 17.8.13 repeating verbatim: gating in that state measures the neighbour. The delta between the green build and the flaking one is two lines (?raw=1 plus one diagnostics field, default behaviour unchanged) and cannot reorder clauses against audio. ac5163c54a is therefore NOT claimed as gated; the live 2x is owed once the tenant is quiet. The hermetic arms are unaffected because they never touch the GPU: mic recovery, hidden gate, roster touch, roster and auto-scroll are green on exactly this file with their sabotage controls still red. Also recorded rather than left to be discovered: client_gate never taps a roster speaker button, so pinnedSpeaker stays null and the re-arm is a no-op for the whole run. The mitigation is covered by reading the code, not by a measurement, and an arm that taps a speaker button and counts speaker.arm frames per turn is owed with it.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…ections that move the plan SLICE 1, BUILT. models/qwen3_tts_fast_predictor.py replaces the single call that the precursor measured at 104.44 ms of a 142.78 ms frame -- 73 % of it -- with a raw 15-step loop. It is a drop-in: the reference reads .sequences off code_predictor.generate (modeling_qwen3_tts.py:1671) and gets the same shape, so the 1596-line prompt builder, the trunk loop and the codec are untouched. install()/uninstall() keep the reference reachable in the same process, which the identity gate needs and an operator needs as a way out that is not a restart. It holds no weights, so it costs no VRAM. Sampled tokens never leave the device -- every .item() there is one of the 602 syncs per frame this removes, and a host round trip would also make the loop uncapturable in slice 2. The step schedule is data, not a comment: prefill takes lm_head[0] and no codebook embedding, decode step g takes codec_embedding[g-1] with lm_head[g]. Off-by-one there does not crash, it picks a neighbouring codebook and degrades timbre, so step_schedule() is pinned by five assertions. The sampler is the main correctness surface and is driven against transformers' OWN TemperatureLogitsWarper / TopKLogitsWarper / TopPLogitsWarper on fixed logits, mask and values separately, including the production defaults (top_k=50, top_p=1.0, temperature=0.9) and all three no-op cases -- a skip transformers takes and we do not is a silent divergence on a DEFAULT config. One test was wrong and is corrected rather than deleted: temperature and top_k genuinely COMMUTE (monotonic scaling cannot change which tokens top-k keeps), so an ordering bug between those two is unobservable; the detectability arm now uses top_p, which does not commute with temperature. Both facts are pinned. 14 hermetic, 215 green across the models suite. End-to-end greedy code-token identity needs the real checkpoint and the tenant's import shims, so it runs with the GPU measurement rather than here. The gap is named in the test file, not only in this message. CORRECTION 1 -- the 127 MiB is a rebooking and fp8 is NOT the pool gate. My "fp8 is the gate" is withdrawn. But two named levers do not reach this boot, read at source: sgl-project#330's dial needs --enable-vram-dial, which is a BOOT flag the deployed launch line does not carry (/proc/3953200/cmdline), so "no restart" does not apply to this instance; and it additionally requires weighted uneven DCP (vram_dial.py:1110) while the boot has no --rank-kv-ratio at all. The knob that DOES reach it is already in the launch line: --rank-auto-reserve-mib 13000,3800,3800, whose two 3080 entries go 3800 -> 4200 for ~400 MiB each at the next restart, with no new flag and no dial precondition. Priced honestly: 16 full-attention layers x 4 kv heads x 256 x 2 x 1 B fp8 = 32 KiB/token, so 400 MiB costs 12,800 tokens on that rank, ~4.9 % of one 262144 context. bf16 instances then fit and the pool does not wait for fp8. Still to check before any quantisation effort: an sm86 bf16 engine may already fit under 2551 MiB by itself, which is a measurement on the built engine, not a prediction. CORRECTION 2 -- TRT is cut as fork lane infrastructure (sgl-project#337), not a talker side path. The discipline I specified already EXISTS in video_enhance/engine_cache.py: EngineKey over nvml_uuid|device|driver|trt| onnx_sha256|precision|shape triplet|flags, provenance manifests, and "no manifest means absent, not usable". So the plan is promote, not duplicate: lift it to srt/trt_lane/ with video_enhance as its first existing consumer and byte-identical video behaviour as the acceptance condition, then add the sgl-project#337 rung registry, sgl-project#286 ledger registration so a loaded engine is parkable like any other VRAM tenant, sgl-project#407 tier placement, per-regime crossover as a reported output, and sm86 refusals by name. The talker's two engines are the first production consumer; RIFE/SR (sgl-project#469) attach afterwards. Ordering unchanged: graphs first -- the 6.53x needs none of this.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…t-order finding that is not where it was reported The standalone profile path puts a ~2.6 GiB second copy of the talker on a card a live tenant and a serving rank already use, and on 2026-08-04 that cost minutes of VRAM triage on an unattributed pid -- this run. It now renames itself sglang::488-talker-profile-GUEST in both /proc/<pid>/comm and /proc/<pid>/cmdline, so ps, top, py-spy and nvidia-smi attribute it on sight. GUEST is deliberate: it answers the second triage question, which is not whose process this is but whether it belongs on that card. Best-effort by two mechanisms (setproctitle for argv, prctl for comm) because a diagnostic aid must never kill the run it is diagnosing. Pinned as a BINDS-PROOF rather than "the mechanism exists" (sgl-project#493): the test calls the tagger and reads the result back out of /proc, and separately asserts the ticket number survives the kernel's 15-byte comm truncation -- outside that window `top` would show nothing useful. 20 hermetic, green. The in-process path needs no marker; it runs inside the tenant's own pid.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…a gate that had to be rewritten SLICE 2, BUILT AND MEASURED. models/qwen3_tts_graph_driver.py captures the talker's token loop: fifteen predictor step graphs over a 16-slot static scratch (one per residual group, because a graph cannot switch which lm_head runs) and the 28-layer trunk decode step as one graph. Frame time 142.18 -> 14.64 ms, RTF 1.706 -> 0.176, 9.71x; the 84.7 % launch gap the precursor measured is now 4.6 % on the trunk and 5.9 % on the predictor. Artefacts: /spinning/gpu-battery-results/2026-08-04_488_slice2_graphs/. Two independent runs; the graphed rung was 14.64 ms both times while the eager reference moved +/-4 %, which is what an overhead-bound path inheriting host jitter looks like. No transformers patching was needed, which is worth stating because the alternative was reimplementing the layer stack. _preprocess_mask_arguments:840 returns an already-4-D attention_mask AS-IS, so a caller-owned static mask buffer reaches sdpa untouched through both the trunk's single-mask path and the predictor's dict path; passing cache_position and position_ids explicitly removes the two host-side aranges; and the one real sync on the decode path (cache_position[0] == 0, modeling_qwen3_tts.py:1696) lives in the OUTER forward, which we never capture. StaticLayer.update already keeps its write pointer as a device tensor advanced in place, so a replayed graph walks forward through the cache by itself -- refuse_unless_graph_safe pins that instead of trusting the version, since a Python-int spelling would silently rewrite one slot forever. The two graphs need OPPOSITE discipline and getting it backwards is silent. A predictor step owns one slot for the life of the process, so its write-slot pin is captured INSIDE the graph, which makes each step idempotent and deletes the between-frame rewind. The trunk's slot advances every frame, so its pin must sit OUTSIDE the capture -- captured, it would freeze the trunk on one slot for the whole utterance. Found by reading before running, not by debugging audio. GATE 1 AS SPECIFIED IS NOT ACHIEVABLE, and the cut is still correct. ANALYSE_488 7.5 asked for identical codec tokens against the eager reference; measured, 23.3 % differ. Decomposed, the design had conflated two changes: eager-static vs GRAPHED is bit-exact (0.000000), and the whole divergence comes from the StaticCache + explicit mask that capture requires. Rerun in float32 that same pair agrees to 1.4e-6..2.4e-6 relative against fp32 eps 1.2e-7 -- reduction order, not semantics: an explicit mask makes sdpa pick a different backend, and at bf16 a few ulp flips the predictor's frequently near-tied argmax. So the gate is split -- capture gated bit-exactly against the same static cache (stronger than the original for what capture can break), semantics gated in fp32 where the question is answerable, and the 23.3 % reported up front so the listening arm is told rather than surprised. check_static_cache_semantics.py is that falsifier, kept executable rather than quoted. Slice 1 is corrected on the record too: the raw loop alone bought 1.04x, not the estimated ~1.3 RTF. The 602 syncs were not the cost. Its value was enabling capture. CORRIDOR: the FIRST run peaked ~5090 MiB against a 3000 MiB estimate and left the 5090 at 312 MiB free, under the 400 MiB floor -- transient, no serving process affected, and it should not have happened. The line items were right and the total was wrong: each phase's caches, graph pools and cuBLAS workspaces were still resident when the next allocated. Phases now release before the next claims, and the corridor is re-checked BETWEEN phases and aborts, not only at the ends where a mid-run peak is invisible. Re-proven: minimum 1382 MiB free, all three cards ok at every phase. Also recorded, contradicting a claim carried into this session as fact: the reserve rebooking to 4200 shipped, but the 3080s' free VRAM is 2957/2959 MiB -- 6-8 MiB above baseline, not 400. Usable 2557 against a 2678 MiB bf16 talker, so the pool gate is NOT open and item (3) is blocked on 121 MiB. 19 hermetic tests for the geometry, the mask, and four refusals that each corrupt audio without raising (int write position, sliding-window layer, shared-pool replay out of capture order, replay before capture) -- each exercised until it fires. 66 green across the 488 suite; ruff and codespell clean.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…, the baseline did not
MECHANISM, traced to the code that actually ran. First correction: that code is
/spinning/wt-530-serving/python, reached via PYTHONPATH in /proc/1236/environ --
NOT the htsglang-gpu tree the venv's editable install points at, which is a
detached HEAD from 2026-07-13 predating --rank-tp-ratio auto-performance and
unable to parse this cmdline. Reading the wrong tree would have produced a
confident wrong answer.
The reserve entered the arithmetic exactly as designed; the boot logged
"derived memory budgets [19607, 16280, 16280] MiB ... (reserve per GPU:
{0: 13000, 1: 4200, 2: 4200})". budget = NVML total - reserve, converted once to
a per-rank fraction and consumed as the KV-pool ceiling. What the knob does NOT
do is cap allocation, and every rank overshoots it: +686 and +684 MiB on the two
3080s, +2963 MiB on the 5090 (CUDA context, NCCL buffers, flashinfer workspace,
graph capture, prefill scratch). That was already on record here before the
prediction was written -- server_args.py:10758-10763 "It does NOT cap any
runtime allocation", and NOTE_493 section 3 measuring the same class on another
model.
But the overshoot is a CONSTANT, not a function of the reserve, so free VRAM
moves 1:1 with it. Reconstructed with the same 686 MiB: reserve 3800 -> free
2557, reserve 4200 -> free 2957, and 2957 is what the card shows. The 400 MiB
DID materialise. The false premise was the BASELINE: the "2951 MiB free at
3800" the prediction reasoned from sits within 6 MiB of the post-change
reading, which is what a number taken on a boot that already carried 4200 looks
like. Stated with its limit: the pre-reboot log did not survive the container
restart, so "the old boot's overshoot happened to be 400 MiB larger" cannot be
excluded -- and does not matter, because both readings give the same present
state and the same lever.
The lever is priced rather than argued: free and KV budget move 1:1 against each
other, so 4400 buys a 79 MiB margin for ~6,400 tokens and 4600 buys 279 MiB for
~12,800, against a 333254-token pool -- if the 3080s are the min-reducing ranks,
which is NOT established and is flagged as such.
CONSEQUENCE, and it is the part that should change the plan: slice 2 devalued
this pool by ~10x. Its justification was head-of-line blocking of a median 4.8 s
synthesis (inprocess_tts.py:124-134); at RTF 0.176 that median is ~0.5 s and one
instance sustains ~5.7x real-time. The pool's remaining value is multi-user
concurrency, not single-user latency, and the live case driving this work is one
user. Recommendation recorded: do not spend the KV tokens, leave the reserve at
4200, leave the pool unbuilt, revisit if concurrent speakers become real. The
121 MiB is not a blocker to route around -- it is a price signal on something
that stopped being worth buying.
Docs only; 66 green, codespell clean.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
…arger than estimated STEP A BUILT AND SMOKE-GREEN. GraphedPredictorFrame.install() swaps code_predictor.generate for fifteen captured graphs and nothing else changes -- worth 2.98x on its own (frame 142.18 -> 47.64 ms, RTF 1.706 -> 0.572, a 5511 ms clause becomes ~1850 ms), with the remaining 3.3x waiting on Step B. Handoff is documented in DESIGN_488 4b: sampling arguments, refusal/fallback table, the measured VRAM cost, and gate_loaded_model() to run in-process against the live tenant. The tenant is NOT touched by this commit. A replayed graph cannot draw randomness, so entropy became an input: UniformPool keeps pre-drawn uniforms on the device and advances its cursor INSIDE the last step's graph, which costs the host nothing per frame. Explicit uniforms are still accepted, written under the cursor, so one captured body serves both production and the identity gate. The sampling arguments are required keyword arguments with no defaults, because a graph bakes its warpers -- and because of a trap this deployment invites: the tenant passes temperature=0.9, top_p=0.9, and those are the TRUNK's knobs. The code predictor runs on subtalker_* (modeling_qwen3_tts.py:2036-2039), which the tenant never passes, so top_k=50, top_p=1.0, temperature=0.9. Capturing the tenant's 0.9 would apply a warper the reference never applies -- permanent, silent, audible only as timbre. install() reads the reference's own signature and refuses at load time; the refusal has a can-fail test. A runtime mismatch FALLS BACK to the reference instead of raising, a deliberate exception to fail-fast: the reference output is correct, just slower, so raising would break a live turn to prevent what the fallback already prevents. It is counted, so telemetry can say the speedup is not being delivered. GRAPH OVERHEAD MEASURED, and ANALYSE_488 7.4 understated it by 4.9x: 1184 MiB, not 242 -- predictor 183.5 against ~60, trunk 1000.5 against ~181. Identical on sm86 and sm120, so it is allocator- and shape-driven, not architectural. The discriminator is complete: the trunk term is LINEAR in cache slots (1024/256/64 -> 1000.5/262.3/77.8 MiB), fitting 0.96 MiB per slot + 16 MiB against a static cache of 0.109 MiB per slot. Capture retains every per-layer expanded-KV intermediate rather than freeing it, so the cut costs ~8.8x the cache it captures. Slot count is thus the dominant VRAM knob of the whole feature, and 1024 is the wrong default for a ~3.2 s clause; right-sizing to 256 is worth 738 MiB, six times the pool's entire original 121 MiB shortfall. The default stays 1024 only until generate_icl_prompt's real prompt length is measured. POOL VERDICT RETRACTED, kept in place rather than deleted because the way it was wrong is the lesson. It priced the pool on single-synthesis latency, found graphs had taken that win, and called the pool devalued ~10x. The axis it never checked: a turn is not one synthesis -- session.py:1852-1871 already splits it into clauses, so the pool parallelises WITHIN a turn and composes multiplicatively with graphs. Rule recorded: a building block is devalued only after checking every axis it serves, not the first one. The honest re-pricing (2.2f/2.2g) is worse for the pool than the retracted verdict was: a fully graphed instance misses a 3080 by 567 MiB at 256 slots, not 121, because the graphs cut is itself what put it out of reach. Three unblock paths priced; recommendation is to shrink the instance (quantise + right-sized cache), not to tax serving KV. Dispatcher gains a HARD acceptance gate (2.3): turn-scoped cancel across running, queued and already-delivered clauses, with four observable conditions, plus the arithmetic for why drop-and-refill beats salvage at RTF 0.176. 80 green, ruff and codespell clean.
efschu
added a commit
to efschu/htsglang
that referenced
this pull request
Aug 4, 2026
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.
Do not set the default value of the global random seed, so the server can be random by default