Optimize iLLaDA end-of-thinking logit boost - #138
Open
mercurystraw wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors and optimizes the </think> logit-boosting path used during (variable-length) iLLaDA generation to avoid per-position Python loops and reduce CUDA host-device synchronization overhead.
Changes:
- Replaces per-row positional scanning (
torch.equalin a Python loop) with batched sequence detection viaunfold+ reductions. - Vectorizes “next token in end-sequence” selection across the batch while preserving context boundary handling (
context_start) and longest-prefix precedence. - Avoids cloning the full logits tensor when no rows are eligible for boosting.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Author
|
The change improved throughput from 11.1 to 17.8 tokens/s (~1.6×) and reduced total runtime from 26h to 15.5h on one H100 GPU in a Math500 local comparison, but it was not fully controlled. For reference only. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR optimizes the
</think>logit-boosting logic used by variable-length iLLaDA generation.The existing implementation scans generated tokens one position at a time with
torch.equal. On CUDA, every comparison returns a Python boolean and therefore introduces a device synchronization. This becomes increasingly expensive forlong reasoning outputs.
The new implementation performs sequence detection and per-row token selection with batched tensor operations while preserving the existing boost behavior.
Existing behavior
During each diffusion decoding step,
apply_end_think_logit_boostencourages the model to emit the token sequence corresponding to</think>.The original implementation performs the following operations for each row:
[batch, sequence, vocabulary]logits tensor.torch.equalfor each candidate window to search for</think>.</think>shouldbe boosted next.
The positional scan is implemented as a Python loop:
for start in range(sequence_length): torch.equal(...)When the tensors are on CUDA, each
torch.equalresult must be synchronized back to Python. Since this check runs at every diffusion step, the number of host-device synchronizations grows quickly for long generations.With
steps=32andblock_length=32, a generation that does not emit</think>can perform approximately:gen_length=2048gen_length=4096The original implementation also clones the full logits tensor before checking whether any row still needs a boost. For iLLaDA's vocabulary size, a batch-1 BF16 logits clone is approximately 1.2 GiB at a sequence length of 4096, excluding the additional prompt length.
Changes
Vectorized end-sequence detection
The positional Python loop is replaced with a batched tensor operation:
tokens.unfold(1, sequence_length, 1) == sequenceThe resulting windows are reduced with
allandanyto determine whether each row already contains the complete</think>sequence.This keeps the comparisons on the GPU instead of synchronizing once per candidate position.
Batched candidate and prefix handling
The implementation now computes the following values for all rows together:
For example, if
</think>tokenizes into three tokens, the logic remains:The longest matching prefix continues to take precedence, matching the original implementation.
Preserve the generation-context boundary
Complete-sequence detection is restricted to:
tokens[:, context_start:]Partial-prefix matching also checks that the prefix does not cross
context_start.This prevents a sequence present in the prompt, or one spanning the prompt/completion boundary, from being treated as a generated
</think>.Avoid unnecessary logits cloning
Rows are marked active only when they:
If no row is active, the function returns the original logits immediately. The full logits tensor is cloned only when a boost actually needs to be applied.
Preserve numerical behavior
Boost progress is computed in
float64before being converted to the logits dtype. This matches the previous Python floating-point calculation and avoids small rounding differences that could affect low-confidence token ordering.Only active rows are updated through indexed tensor assignment. The input logits remain unmodified whenever a boost is required, preserving the previous non-in-place behavior.
Expected impact
This change is expected to:
</think>has already been generated;The model forward pass, softmax, remasking strategy, generation schedule, end-think boost strength, and EOS handling are unchanged.