Modal: decode a batch's prompts together on the GPU
Stacked on #1233: the base is codex/batch-full-report-moments, so only this PR's commit shows. Retarget to main after #1233 merges.
What this changes
generate_batch answered its prompts one at a time, which leaves an A100 mostly idle on an 8B model. After #1233, full_report runs its 13 sections in one batch, so the full report's total time is the sum of its sections.
Now prompts that share temperature, top_p and do_sample are decoded together, up to 8 per generate() call:
- The processor pads on the left, so every row's new tokens start at the same place.
- Each answer is cut to its own
max_new_tokens.
Billing stays honest
- Each answer's
elapsed_secondsis its share of its group's time. The answers still add up to the time actually spent, which is what the API bills.
Fails safe
- A single prompt takes the same path as
/generate. - If the GPU runs out of memory, the group is split in half and retried, down to one prompt.
- Any other error in a grouped call falls back to answering one at a time.
- The optional request field
group_size(1–8, default 8) lets a caller force one-at-a-time decoding (1).
Tests
modal/test_serve_music_flamingo.py adds:
- grouping by settings and size,
- grouped decoding with separate clip files that are deleted afterwards,
- equal time shares,
- cleanup when the model fails.
35 tests pass locally. A bench entrypoint (modal run modal/serve_music_flamingo.py::bench ...) compares one-at-a-time and grouped decoding on a real song.
Not verified on a GPU
The local Modal workspace is paused, so this hasn't run on an A100. Verify after deploy:
- Run
full_reporton the same song before and after. - Answers from the greedy presets should match or nearly match. Batched bf16 maths can shift a few tokens.
- Time and billed seconds should drop.
If answers degrade, any caller can send group_size: 1, or revert this PR.
🤖 Generated with Claude Code
Summary by cubic
Speeds up /generate_batch on GPUs by decoding prompts that share temperature, top_p, and do_sample together, up to 8 per generate() call, instead of one at a time.
Behavior
- Each answer's
elapsed_secondsis its share of its group's time, so the answers still add up to the time billed. - Prompts pad on the left so every answer starts at the same place; each is cut to its own
max_new_tokens. - A single prompt takes the same path as
/generate; an out-of-memory error splits the group in half and retries, and any other grouped error falls back to one at a time. - The optional request field
group_size(1–8, default 8) lets callers force one-at-a-time decoding with1. - Adds a
benchentrypoint that compares one-at-a-time and grouped decoding on a real song; this hasn't been verified on a GPU yet.