Skip to content

Fix progressive slowdown in text streaming across generations - #86

Open
auroter wants to merge 3 commits into
Tencent-Hunyuan:mainfrom
auroter:fix/streaming-slowdown
Open

Fix progressive slowdown in text streaming across generations#86
auroter wants to merge 3 commits into
Tencent-Hunyuan:mainfrom
auroter:fix/streaming-slowdown

Conversation

@auroter

@auroter auroter commented Feb 16, 2026

Copy link
Copy Markdown

Summary

Consecutive generations in the Gradio web UI get progressively slower in the text streaming (thinking/recaption) phase. The model generates tokens at a constant rate (~24 tok/s), but each yield history sends the full chatbot history to Gradio for serialization. Two compounding issues cause this:

  • ~200 yields per generation: Every single text token triggers a yield history, and serialization cost grows with history size (O(tokens × images))
  • PIL re-encoding on every yield: gr.Image(pil_image, type="pil") objects in history get re-encoded to PNG by Gradio on every yield — even for unchanged old messages (Gradio creates a new component instance each time in chatbot.py:557). This affects both generated images and user-uploaded images.

A 1024px PNG encode takes ~100ms. With N prior images and ~200 yields, this adds N×20 seconds of overhead per generation, making text streaming visibly degrade from fast to ~1 word/second after just 2-3 generations.

Changes (single file: app/run_chatbot.py)

  • Throttle yields to 50ms intervals (~20-40 per generation instead of ~200). Text is still buffered and flushed before image events.
  • Store file paths in gr.Image instead of PIL objects. Generated images are saved to temp files first; user-uploaded images are already file paths from Gradio's MultimodalTextbox, so dropping type="pil" avoids an unnecessary PIL round-trip. File path serialization is nearly free vs. re-encoding.
  • Close the file descriptor from tempfile.mkstemp() to prevent fd exhaustion after many generations.

Together these reduce per-generation overhead from O(tokens × images) to roughly O(1).

Measured results

Generation Model tok/s Avg yield time (before fix) Avg yield time (after fix)
1st 24.0 2ms 2ms
2nd (1 image in history) 24.6 ~500ms+ (unthrottled) 72ms
3rd (2 images in history) 24.5 ~1000ms+ (unthrottled) 129ms

Model speed is constant — the slowdown was entirely in the Gradio serialization layer.

Test plan

  • Run 3+ consecutive text-to-image generations with single_round + think mode
  • Confirm text streaming speed stays consistent across all generations
  • Confirm images still display correctly in the chatbot
  • Confirm --image-cache-dir still saves images correctly
  • Test with unlimited context mode to verify full history still works
  • Test image-to-image (user uploads an image) to confirm uploaded images display correctly

🤖 Generated with Claude Code

auroter and others added 3 commits February 15, 2026 20:59
Consecutive generations in the Gradio web UI get progressively slower in
the text streaming phase. The root cause is twofold:

1. Every text token yields the full chatbot history to Gradio for
   serialization (~200 yields per generation). As images accumulate in
   history, serialization gets more expensive per yield, so the consumer
   falls behind the producer. Fix: throttle yields to at most once per
   50ms (~20-40 per generation instead of ~200).

2. PIL Image objects stored in history via gr.Image(pil, type="pil") get
   re-encoded to PNG by Gradio on every single yield — even for unchanged
   old messages. A 1024px PNG encode takes ~100ms, so with N prior images
   and 20 yields, that adds N*2 seconds of overhead per generation. Fix:
   save images to /tmp files immediately and store the file path in
   gr.Image instead. File path serialization is nearly free.

Together these reduce per-generation overhead from O(tokens * images) to
O(1), keeping text streaming speed consistent regardless of history size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Close the file descriptor from mkstemp() to prevent fd exhaustion
  after many generations (EMFILE after ~1000 images)
- Use gr.Image(filepath) for user-uploaded images too, not just
  generated ones — same re-encoding problem on every yield
- Remove print(tmp_path) when no cache dir is set (undocumented
  behavioral change from original)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant