perf(qwen35): add opt-in F32 rollback checkpoints for single-GPU inference#515
perf(qwen35): add opt-in F32 rollback checkpoints for single-GPU inference#515Amidwestnoob wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
1 issue found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="server/src/qwen35/qwen35_backend.cpp">
<violation number="1" location="server/src/qwen35/qwen35_backend.cpp:1932">
P2: The rollback diagnostic counters and print logic are duplicated across both decode loops (`qwen35_backend.cpp` and `dflash_spec_decode.cpp`): five counter variables, the accept_n histogram indexing, three counter-update sites, and the diagnostics print block. Approximately 26 lines are verbatim copies. This makes the two paths prone to drift when someone updates one side but forgets the other. Consider extracting the counters and the print block into a small RAII accumulator struct (e.g. `RollbackDiag`) shared from `chain_rollback_policy.h`, then use it from both decode loops. The struct could hold the counters, expose `record_fast_rollback(accept_n)`, `record_legacy_replay()`, `record_fallback()`, and a `print()` method — keeping the two decode loops focused on decode logic rather than diagnostic bookkeeping.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| int n_hint_accepted = 0; | ||
| int target_forwards = 0; | ||
| const ChainRollbackPolicy rollback_policy = resolve_chain_rollback_policy(); | ||
| int rollback_accept_hist[17] = {}; |
There was a problem hiding this comment.
P2: The rollback diagnostic counters and print logic are duplicated across both decode loops (qwen35_backend.cpp and dflash_spec_decode.cpp): five counter variables, the accept_n histogram indexing, three counter-update sites, and the diagnostics print block. Approximately 26 lines are verbatim copies. This makes the two paths prone to drift when someone updates one side but forgets the other. Consider extracting the counters and the print block into a small RAII accumulator struct (e.g. RollbackDiag) shared from chain_rollback_policy.h, then use it from both decode loops. The struct could hold the counters, expose record_fast_rollback(accept_n), record_legacy_replay(), record_fallback(), and a print() method — keeping the two decode loops focused on decode logic rather than diagnostic bookkeeping.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/qwen35/qwen35_backend.cpp, line 1932:
<comment>The rollback diagnostic counters and print logic are duplicated across both decode loops (`qwen35_backend.cpp` and `dflash_spec_decode.cpp`): five counter variables, the accept_n histogram indexing, three counter-update sites, and the diagnostics print block. Approximately 26 lines are verbatim copies. This makes the two paths prone to drift when someone updates one side but forgets the other. Consider extracting the counters and the print block into a small RAII accumulator struct (e.g. `RollbackDiag`) shared from `chain_rollback_policy.h`, then use it from both decode loops. The struct could hold the counters, expose `record_fast_rollback(accept_n)`, `record_legacy_replay()`, `record_fallback()`, and a `print()` method — keeping the two decode loops focused on decode logic rather than diagnostic bookkeeping.</comment>
<file context>
@@ -1927,12 +1928,33 @@ bool Qwen35Backend::do_spec_decode(int committed, int n_gen,
int n_hint_accepted = 0;
int target_forwards = 0;
+ const ChainRollbackPolicy rollback_policy = resolve_chain_rollback_policy();
+ int rollback_accept_hist[17] = {};
+ int rollback_fast_low = 0;
+ int rollback_fast_high = 0;
</file context>
Summary
Adds an experimental, default-off F32 checkpoint mode for single-chain speculative rollback on memory-constrained single-GPU Qwen deployments.
The existing behavior remains unchanged unless explicitly enabled:
DFLASH_SINGLE_CHAIN_CHECKPOINT_F32=1 DFLASH_FAST_ROLLBACK_THRESHOLD=1 # valid range: 1-5Optional diagnostics:
Without the F32 opt-in, checkpoints remain F16 and the rollback threshold remains 5.
Motivation
On a single RTX 3090 running Qwen3.6-27B Q4, replaying rejected speculative tokens can cost more than restoring a saved recurrent-state checkpoint. F32 checkpoints permit direct restoration and avoid the conversion/replay path for sufficiently deep rollbacks.
This is intentionally separate from the split/layer-sharded work in #506. It targets a different deployment: one 24 GiB GPU, with no layer split.
Implementation
Validation
Final standalone branch, based on current
main:check: 15/15 tests passedFresh A/B/A validation on the standalone build, six prompt families and three repetitions per arm:
The broader development campaign previously found threshold 2 to be the best pooled policy, but this PR deliberately uses the 6.46% threshold-1 result from the exact standalone candidate as its directly reproducible performance claim.
Limitations
Suggested test configuration