Repository navigation
serve: the Monitor's Workers card - one widget per batch slot, each with its own tok/s (#465) - #2858
street-tripy wants to merge 3 commits into
Conversation
…ith its own tok/s (Niko1221#465) /metrics has reported live.slots (per-slot state, tokens, tok/s) since Niko1221#465 and nothing on the page consumed it, so with "parallel": N there was no way to see whether the requests were decoding together or one after the other. What changed: - serve/web/index.html: a Workers card after Model state, one widget per slot. Hidden unless /metrics reports live.parallel. - serve/web/app.js: renderWorkers()/workerWidgetsFor() fill it from live.slots (state badge, live tok/s, tokens and seconds in the slot, that slot's context fill) and update the widgets in place, so the state and progress transitions animate. The header pill now names every decoding stream ("2 decoding · 80.4 + 76.1 tok/s") when more than one is decoding. - serve/web/app.css: the card's styles, from the existing design tokens only. - serve/test_monitor.py: the page test of test_the_card_is_on_the_page, plus that the card is in the markup hidden and gated on live.parallel. Left alone: every server file, the engine, the Chat and About tabs, and the Monitor's hardware cards; no backend was touched, so there is nothing to build. How the default was checked: against this branch with a payload without "parallel" the card is hidden, no widget is built and the pill reads "Generating · 79.8 tok/s" as before; with "parallel": 2 both slots render with their own rate. `python -m unittest serve.test_monitor serve.test_runconfig`: 20 tests, all pass. No JavaScript errors in either state.
…token live.slots[].tok_s is generated / seconds since the first token, so a slot opens at the burst rate and only decays to the truth over minutes: 300 falling to 35 on a 4090 with two coding agents, which is what the Workers card showed. The card and the header pill now take the server's own window rule (RATE_WINDOW_S / RATE_MIN_SPAN_S, server.py:220) from consecutive samples, so the number is the rate the slot is writing at now. The first sample of a slot keeps the mean, as the server's rule does; a slot that goes idle, or takes a new request whose counter starts over, starts its ring over. Nothing outside serve/web and this test: live.slots[].tok_s is left as it is, so the server, its payload and Prometheus are untouched. Checked: node --check serve/web/app.js clean. The new test feeds 30 tokens in the first 0.1 s and 35/s after, and pins 300.0, 35.0, 35.0, 35.0 where the mean over the same samples is 300.0, 59.1, 47.6, 43.5. python -m unittest serve.test_monitor serve.test_runconfig serve.test_prometheus -> 27 tests, all pass. In a browser against a /metrics payload with two slots writing at 35 and 30 t/s: the widgets read 35.0 and 30.0 while the published means drift at 35.6 and 30.5, the pill reads "2 decoding · 35.0 + 30.0 tok/s", no console errors. Without live.parallel the page is unchanged: card hidden, 0 widgets, 8 tiles, pill "Generating · 65.0 tok/s".
|
Found a bug in this card while running it on my own box (RTX 4090, Swift 1.5 IQ3_S,
So the card and the pill now take that window rule from consecutive samples — 2.0 s window, 0.25 s minimum Measured against a The mean is still what other readers of |
…ets a row of its own The widgets were boxes in an auto-fit grid, so two slots sat side by side and anything extra had nowhere tidy to go. Each slot is now a row that stretches the card, stacked in slot order: name, state, rate, what it has written, its context fill, and the context bar under it. Two numbers the card was not showing: - a slot that is reading. It showed a dash, on the reasoning that the batch slot reader handles BADM/BDONE/BGEN/BT only (server.py:1683-1830) and so never sees a prompt-progress line. That is true of its own loop, but the reader delegates to _control() (server.py:1704), and _control does parse the engine's PP lines and set engine.prefill_tok_s_mean (server.py:1283-1288) - the same figure the Speed card's right-hand number reads. So the reading row shows that: one engine-wide rate, belonging to the slot that is reading, which is the only one, because prompts are admitted one at a time. The cached prefix is not read, so it is the rate over the new tail tokens. prompt_tokens / elapsed_s is not that rate: prompt_tokens is the whole prompt, known at admission, so the ratio opens at 50,000 and only lands on the truth as the read ends. - the requests in flight that are not in a slot (live.outside_slots, server.py:3775): parallel requests waiting for a slot, being admitted, or running on their own. The row is labelled Solo, sits first in the card, and is always there, so a queued request has a place to appear. The aggregate live block is the newest parallel request's status, so its numbers belong to the request outside a slot only when that is the one and only request in flight - the common case, and what the box showed while writing this: both slots idle holding their conversations, one request running on its own. In that case the row shows its rate, tokens, seconds and context fill; with a slotted request alongside it shows the count and the queue state and no rate, because putting the aggregate's speed on that row would attribute one request's numbers to a different request. Checked in a browser against a /metrics payload. Reading, two slots reading and one request outside a slot: Solo first, "Waiting - 1 request in flight outside a slot - 1 waiting"; Worker 1 and 2 1,320 t/s "reading 8,128 prompt tokens - engine prefill". One request in flight outside a slot, both slots idle: Solo "Decoding 69.6 t/s - 1,200 tokens - 17.4 s outside a slot - 9.3k / 256K". Both slots decoding, nothing outside: Solo "Free - nothing outside a slot", workers 35.0 and 30.0 t/s. On a server running with "parallel": 2, two agents decoding at once: Solo first "Free - nothing outside a slot", Worker 1 28.2 t/s, Worker 2 32.5 t/s, rows 695 px wide in one column. No console errors. python -m unittest serve.test_monitor serve.test_runconfig serve.test_prometheus -> 28 tests, all pass; node --check serve/web/app.js clean. Without live.parallel the card stays away.
|
Rows, not boxes. Each slot stretches the card, stacked in slot order: name, state, rate, what it has written, context fill, bar. Measured 895 px in a 937 px card, one column, 100 px tall (the boxes were 440 px wide, 157 px tall). A slot that is reading now shows a rate. It had a dash, on the reasoning that the batch slot reader handles
The queue row. Requests in flight that are not in a slot ( Checked in a browser against a |
|
Thanks for the pull request. We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected. To reopen it takes about two minutes:
The rules are in CONTRIBUTING.md. |
Title
Issue: Resolves #2857
Summary
With
"parallel": Nthe server runs several requests at once, but the Monitor still shows onerequest's numbers: the header pill reads the newest request's rate and the Model state card
shows one request's progress. There is no way to see on the page whether the requests are
decoding together or one after the other.
This adds a Workers card to the Monitor — one widget per batch slot with its own state, live
tok/s, tokens written and context fill — and makes the header pill name every decoding stream
(
2 decoding · 80.4 + 76.1 tok/s). It is opt-in by construction: the card is in the markuphidden and is shown only when
/metricsreportslive.parallel, which the server sets only fora run config with
"parallel": N.What changed
serve/web/index.html(+7): theworkers-cardmarkup after Model state,hiddenbydefault, with
workers-sum,workersandworkers-note.serve/web/app.js(+63 / -1):workerWidgetsFor()builds one widget per slot the first timeand reuses it, so state and progress transitions animate instead of being replaced every
second;
renderWorkers()fills it from the existinglive.slotsand is called fromrenderMonitor(). The pill line gained a branch: when two or more slots are decoding it liststheir rates; otherwise it is the old text.
serve/web/app.css(+17): the card's styles, from existing design tokens only (no newcolors, fonts or selectors outside
.worker*).serve/test_monitor.py(+11):test_the_workers_card_is_on_the_page_and_opt_in— the samepage assertion as
test_the_card_is_on_the_page, plus that the card shipshiddenand isgated on
live.parallel.Extra Notes
serve/weband this test. No server, setup or engine file changed, sothere is nothing to build: no CUDA, HIP or SYCL file is touched.
/metricspayload withoutparallel: the card stays hidden, no widget is built, and the pill readsGenerating · 79.8 tok/sas before. Withparallel: 2both slots render with their own rate.No console errors in either state.
python -m unittest serve.test_monitor serve.test_runconfig→ 20 tests, all pass(19 on
main+ the new one).node --check serve/web/app.jsis clean.live.slotsasslots_view()inserve/server.pyalreadypublishes it; the server and its tests are untouched.
poll(), the way the requesttable already is; each widget keeps its own context bar against
engine.max_context.serve/web/monitor.html), the Chatand About tabs, and every hardware card.
per card on a layer split); no functional overlap — happy to rebase if either lands first.