Skip to content

fix(generate): guard per-sequence logits_processors against None in batched generation - #1657

Open
chronoshift wants to merge 1 commit into
ml-explore:mainfrom
chronoshift:fix-batched-logits-processors-none
Open

fix(generate): guard per-sequence logits_processors against None in batched generation#1657
chronoshift wants to merge 1 commit into
ml-explore:mainfrom
chronoshift:fix-batched-logits-processors-none

Conversation

@chronoshift

Copy link
Copy Markdown

Problem

In a mixed batch — some sequences carrying logits_processors (e.g. repetition or frequency penalties), others not — the absent entries are None. The per-sequence step loop in GenerationBatch then does:

for processor in self.logits_processors[e]:

which raises TypeError: 'NoneType' object is not iterable and kills the generation thread. The HTTP thread keeps answering GET /health with 200 while every completion hangs indefinitely, so the failure is invisible to a health check and the server has to be restarted.

The practical effect is that --decode-concurrency above 1 is unsafe whenever concurrent requests use different sampling settings, which is the normal case for a shared server.

Reproduction

Start mlx_lm.server --decode-concurrency 2 and send two concurrent chat completions where one sets repetition_penalty (or frequency_penalty) and the other does not, so they land in the same batch.

Fix

One line, mirroring the guard the sibling samplers path already uses twelve lines below in the same method:

sample_sampler = self.samplers[e] or self.fallback_sampler
-                for processor in self.logits_processors[e]:
+                for processor in (self.logits_processors[e] or []):

Relationship to existing issues and PRs

This patch is deliberately the reader-side guard instead. It is one line, it cannot regress a caller that legitimately passes None, it matches the established pattern immediately below it, and it is orthogonal to #1225 — if that lands, this becomes redundant rather than conflicting. It is offered as a minimal stop-gap for a crash that has been reported repeatedly and is still reproducible on main.

In a mixed batch where some sequences carry logits_processors and others do
not, the absent entries are None, so the per-sequence step loop raises
`TypeError: 'NoneType' object is not iterable` and kills the generation
thread. The HTTP thread keeps answering /health with 200 while every
completion hangs forever.

Mirrors the guard the sibling samplers path already uses twelve lines below
(`self.samplers[e] or self.fallback_sampler`).

Refs ml-explore#1472, ml-explore#1505.

Co-Authored-By: Claude Opus 5 <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