Skip to content

fix(moa): show real tok/s on mesh by reporting effective completion_tokens - #638

Merged
michaelneale merged 4 commits into
mainfrom
micn/moa-effective-tokps
May 22, 2026
Merged

fix(moa): show real tok/s on mesh by reporting effective completion_tokens#638
michaelneale merged 4 commits into
mainfrom
micn/moa-effective-tokps

Conversation

@michaelneale

Copy link
Copy Markdown
Collaborator

What

The chat UI's response stats bar always read 0.0 tok/s when using the mesh virtual model (the default since #619 flipped AUTO_BACKEND_MODEL = 'mesh'). After this PR, mesh responses show real, honest effective throughput.

Why "0.0 tok/s"?

The UI's mesh-connection.ts already measures decode_time_ms client-side (from first delta to completion). It then divides usage.output_tokens / decode_time_ms to compute tok/s. But MoA's three response builders \u2014 chat_response, tool_call_response, error_response \u2014 all hardcoded usage: { 0, 0, 0 }, so the dividend was always zero. The downstream Responses-API translation chain (moa_gateway.rs \u2192 chat_usage_to_responses_usage \u2192 responses_stream_completed_event) faithfully forwarded the zeros to the wire.

Why not report the winning worker's measured tokens?

Considered and rejected. MoA fans out to multiple workers and may run a reducer; reporting any single worker's measured rate would lie about the user's actual wait. A worker that finishes at 80 tok/s while the reducer deliberates for another 2s does not mean the user got an 80 tok/s response.

The honest number for a chat UI is "how many tokens did the user actually receive over the wall time they waited." That's effective throughput across the whole MoA pipeline \u2014 fan-out, grace window, reducer, the lot.

Approach

Estimate completion_tokens from the final user-visible content using a chars / 4 heuristic (OpenAI's documented rule-of-thumb for English token density). Combined with the UI's existing client-side decode_time_ms, this yields an honest effective tok/s for the whole MoA response.

Plumbed via one helper:

fn estimate_completion_tokens(content: &str) -> u64 {
    if content.is_empty() {
        return 0;
    }
    let chars = content.chars().count() as u64;
    chars.div_ceil(4).max(1)
}
  • chat_response uses the final answer text.
  • tool_call_response uses the tool-args JSON.
  • error_response uses the error message text.

Tradeoffs

  • Estimate, not a measurement. For English the chars / 4 rule is stable at ~3-5% of true token count; close enough for a UI stats bar.
  • Does not break wire compat: usage is already a field everyone expects; just goes from "always zero" to "always reasonable."
  • Non-English content (CJK, emoji-heavy) will under-estimate slightly, but the UX failure mode here is "slightly off number" \u2014 strictly better than "always 0.0 tok/s."

Tests

New tests in response_builder_tests:

  • estimate_completion_tokens_returns_zero_for_empty_content
  • estimate_completion_tokens_returns_at_least_one_for_non_empty
  • estimate_completion_tokens_is_roughly_chars_over_four
  • chat_response_reports_non_zero_completion_tokens
  • tool_call_response_reports_non_zero_completion_tokens
  • error_response_reports_message_based_completion_tokens

cargo test -p mesh-mixture-of-agents --lib: 107 pass, 0 fail.

Refs

Closes #637.

Validation plan post-merge

  • Visit mesh-llm-console.fly.dev after the next deploy.
  • Send any prompt via the chat page (defaults to mesh).
  • Confirm the response stats bar shows a real X.Y tok/s not 0.0 tok/s.
  • For sanity, switch the model to a direct one and confirm the number is broadly in the same ballpark.

Screenshot

n/a (UI behavior change only; the value displayed in the existing stats bar goes from "0.0" to a real number).

The chat UI's response stats bar always read '0.0 tok/s' for the mesh
virtual model. MoA's response builders hardcoded
`usage: { 0, 0, 0 }`, so even though the UI computes decode_time_ms
from client-side first-delta-to-completion (mesh-connection.ts:243),
it had no token count to divide.

Reporting the winning worker's measured tokens would be misleading:
MoA fans out to multiple workers and may run a reducer, so any single
worker's rate lies about the user's actual wait. The honest number is
'how many tokens did the user receive over the wall time they waited'.

Use a `chars / 4` estimate (OpenAI's documented rule-of-thumb for
English) of the final user-visible content. The UI's client-side
decode interval then yields an honest effective tok/s for the whole
MoA pipeline.

Wires through chat_response, tool_call_response, and error_response.
Tests pin both the estimator and each builder's non-zero output.

Refs #637
Copilot AI review requested due to automatic review settings May 22, 2026 06:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves the MoA (mesh virtual model) response usage reporting so the chat UI can compute a non-zero effective tok/s by providing a best-effort completion_tokens estimate derived from the final user-visible content.

Changes:

  • Replaced hardcoded zero usage values in MoA response builders with a chars / 4-based completion_tokens estimate (mirrored into total_tokens).
  • Added helper functions (estimate_completion_tokens, usage_for_content) to centralize the estimation logic.
  • Added unit tests covering the estimator and ensuring chat_response, tool_call_response, and error_response report non-zero completion tokens for non-empty content.
Comments suppressed due to low confidence (1)

crates/mesh-mixture-of-agents/src/lib.rs:763

  • This test comment mentions clients "dividing by completion_tokens", but the tok/s computation uses completion_tokens as the token count and divides by elapsed time. Recommend updating the wording to match the actual usage (avoid confusion for future readers).
        assert_eq!(estimate_completion_tokens(&"x".repeat(40)), 10);
    }

Comment thread crates/mesh-mixture-of-agents/src/lib.rs Outdated
… too small

MoA blocks until a worker wins, then dumps the full reduced answer in
a single SSE event. decode_time_ms (firstDelta -> completed) is ~0 in
that case, so tokens / decode_time produces absurd numbers (we saw
236,000 tok/s locally).

When the streaming gap is unrealistically small (<50ms), use the total
wall time as the denominator instead. That matches what the user
actually waited and yields honest single/low-double-digit tok/s for
MoA responses (verified ~13 tok/s vs the old ~100,000).

Refs #637
Copilot AI review requested due to automatic review settings May 22, 2026 06:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


const decodeOk =
typeof decodeTimeMs === 'number' && Number.isFinite(decodeTimeMs) && decodeTimeMs >= MIN_DECODE_INTERVAL_MS
const totalOk = typeof totalTimeMs === 'number' && Number.isFinite(totalTimeMs) && totalTimeMs > 0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what a waste of tokens copilot - doing what a deterministic tool can do. You may as well pretend to compile it 👎

@michaelneale
michaelneale merged commit 2c5dc6d into main May 22, 2026
22 checks passed
@michaelneale
michaelneale deleted the micn/moa-effective-tokps branch May 22, 2026 07:38
michaelneale added a commit that referenced this pull request May 22, 2026
…back

* origin/main:
  fix(moa): show real tok/s on mesh by reporting effective completion_tokens (#638)
michaelneale added a commit that referenced this pull request May 24, 2026
* origin/main:
  Ship real client and serving SDKs across Swift, Kotlin, and Node.js (#634)
  task: Add tok/s and model name to nightly workflow summary (#654)
  fix(docs): restore docs.anarchai.org as Pages custom domain
  Update llama.cpp upstream pin
  Fix skippy smoke llama build directory (#655)
  Update pinned llama.cpp revision (#646)
  Normalize non-stream chat tool call IDs
  chore(github): Add lightweight issue templates to repo
  Add nightly mesh stability harness (#631)
  fix(moa): show real tok/s on mesh by reporting effective completion_tokens (#638)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console shows 0.0 tok/s for mesh (MoA) — usage hardcoded to zero, timings never emitted

2 participants