Skip to content

feat(deepseek): support arbitrary MTP draft depth - #120

Open
hashiqiqixian wants to merge 12 commits into
hw-native-sys:mainfrom
hashiqiqixian:feat/deepseek-v4-arbitrary-mtp
Open

feat(deepseek): support arbitrary MTP draft depth#120
hashiqiqixian wants to merge 12 commits into
hw-native-sys:mainfrom
hashiqiqixian:feat/deepseek-v4-arbitrary-mtp

Conversation

@hashiqiqixian

@hashiqiqixian hashiqiqixian commented Jul 28, 2026

Copy link
Copy Markdown

Summary

  • Add vLLM-style --num-speculative-tokens K configuration for DeepSeek V4; any positive K enables MTP and legacy --enable-mtp remains the K=1 alias.
  • Generate arbitrary draft depth recurrently and verify it in fixed eight-row target chunks.
  • Select B4S2 for K=1, B2S4 for K=2-3, and B1S8 for K>=4; K>7 uses repeated verification chunks.
  • Keep MTP draft, cache, acceptance, and recurrent hidden state request-local, including multi-request row packing.
  • Reserve all speculative positions in the scheduler and remove the former fixed two-token IPC context.

Repository responsibility

pypto-serving owns the CLI/configuration, scheduler reservations, request/worker state, target verification, cache metadata, acceptance accounting, tests, and documentation in this PR.

PyPTO-lib revision

This PR pins official hw-native-sys/pypto-lib main revision 186e1d85911f37ead07b8e74fd12734f23c947f0, which contains the DeepSeek V4 MTP projection updates used by the serving path.

The PR no longer depends on hw-native-sys/pypto-lib#852. That PR was closed because there was no A/B reproduction showing its decode output-return cleanup was required by arbitrary-depth serving; its simulator stall reproduced on both attempted revisions and did not establish that dependency.

Boundaries

This PR does:

  • Support arbitrary non-negative MTP depth for greedy DeepSeek V4 Flash W8A8 serving.
  • Preserve non-MTP K=0 behavior and legacy K=1 CLI compatibility.

This PR does not:

  • Enable MTP for non-DeepSeek-V4 model families.
  • Add stochastic speculative sampling; the current device acceptance path remains greedy-only.
  • Claim a throughput target; performance benchmarking is separate from functional support.
  • Modify pypto-lib kernel output contracts.

Validation

Current revisions:

  • pypto-serving: affe1b4f10fb2e94b5d39b706cda8f77a933cd9a
  • pypto-lib: 186e1d85911f37ead07b8e74fd12734f23c947f0

Focused CPU coverage previously passed with 97 passed, 4 warnings; the warnings were CANN installation ownership notices.

Prior 8-NPU DeepSeek V4 Flash W8A8 HTTP evidence was collected before the pypto-lib dependency was removed, on serving 4ad65da806c3c42d0055dae3db91119be39b72d3 with pypto-lib 864b05396ec72e2bb42f5f77032a17f83e1794f8:

  • K=1 via legacy --enable-mtp: HTTP 200, 4 completion tokens.
  • K=3 (B2S4): HTTP 200, 4 completion tokens.
  • K=4 (B1S8 boundary): HTTP 200, 4 completion tokens.
  • K=9 (repeated S=8 target chunks): HTTP 200, 10 completion tokens.

These prior hardware results demonstrate the serving implementation but are not claimed as exact-revision validation for the current pypto-lib pin. A current-hash NPU A/B can be run separately if required.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

DeepSeek V4 speculative decoding now accepts configurable speculative-token depth, selects depth-specific layouts, verifies drafts in fixed-width chunks, and removes previous-token plumbing from async decode requests and batches.

Changes

DeepSeek V4 speculative serving

Layer / File(s) Summary
Numeric speculative-depth configuration
docs/dev/model/deepseek-v4.md, pypto_serving/cli/main.py, pypto_serving/model/deepseek/npu_executor.py, pypto_serving/model/deepseek/npu_runner.py
CLI and runtime configuration resolve num_speculative_tokens, derive decode layouts, and propagate the value through executor and compiled-kernel metadata.
Chunked MTP proposal and verification
pypto_serving/model/deepseek/npu_runner.py
Draft state, recurrent proposal, token acceptance, target preparation, padding slot mapping, and iterative fixed-width verification are reworked for arbitrary speculative depths.
Decode request and runtime state contracts
pypto_serving/config/types.py, pypto_serving/serving/engine/async_engine.py, pypto_serving/serving/server/*, pypto_serving/serving/sched/scheduler.py
Decode requests and batches no longer carry previous-token context, workers cache only the latest token, and scheduler initialization validates the speculative-token budget.
Speculative decoding validation
tests/test_deepseek_v4.py, tests/test_batching.py
Tests cover depth propagation, layout selection, draft reuse, chunked verification, padding masking, scheduler limits, and placeholder resolution.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DeepSeekV4ModelRunner
  participant MTPVerifier
  participant DecodeExecutor
  Client->>DeepSeekV4ModelRunner: submit request with speculative depth K
  DeepSeekV4ModelRunner->>MTPVerifier: propose draft tokens
  MTPVerifier->>DecodeExecutor: verify fixed-width target chunks
  DecodeExecutor-->>MTPVerifier: return logits
  MTPVerifier-->>DeepSeekV4ModelRunner: commit matching prefix and target token
Loading

Possibly related PRs

Poem

A rabbit counted drafts in a row,
Then chunked them where the kernels glow.
One token stays, the old tails flee,
While padded slots map harmlessly.
“Hop!” said the hare, “K is free!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed It concisely matches the main change: adding arbitrary-depth MTP support for DeepSeek V4.
Description check ✅ Passed It clearly describes the same DeepSeek V4 speculative-depth and serving changes in the patch.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
pypto_serving/model/deepseek/npu_runner.py (1)

2331-2340: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Per-step INFO acceptance log on the decode hot path.

This emits one INFO line for every decode iteration; at K=9 with a busy batch it dominates the log and adds formatting/I/O to the step. The per-request summary already lands in release_finished_requests. Consider DEBUG or a periodic/counter-gated emit.

♻️ Suggested change
-        logger.info(
-            "DeepSeekV4 MTP acceptance progress: accepted=%d proposed=%d rate=%.2f%%",
+        logger.debug(
+            "DeepSeekV4 MTP acceptance progress: accepted=%d proposed=%d rate=%.2f%%",
             self._mtp_accepted_tokens,
             self._mtp_proposed_tokens,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pypto_serving/model/deepseek/npu_runner.py` around lines 2331 - 2340, Change
the acceptance-progress log in the decode path to DEBUG or gate it behind a
periodic/counter-based condition so it does not emit on every iteration.
Preserve the existing counters and percentage calculation, and keep per-request
summary reporting in release_finished_requests unchanged.
tests/test_deepseek_v4.py (1)

195-206: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider covering the fully-exhausted-context case.

_mtp_draft_count can legitimately return 0, which drives the num_drafts == 0 branch in _propose_mtp_tokens and the single-column path in _verify_mtp_drafts. Neither is exercised here; a case with seq_len == max_seq_len would pin that behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_deepseek_v4.py` around lines 195 - 206, Extend
test_deepseek_mtp_draft_depth_is_capped_by_remaining_context or add a focused
test covering a batch sequence length equal to model.runtime.max_seq_len. Assert
_mtp_draft_count returns 0, exercising the fully exhausted context and the
downstream zero-draft/single-column behavior without changing existing
nonzero-cap coverage.
pypto_serving/cli/main.py (1)

176-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Error message ignores the legacy alias path.

_resolve_num_speculative_tokens maps a bare --enable-mtp to 1, so a non-DeepSeek run with only --enable-mtp fails with a message naming a flag the user never passed. Consider mentioning both flags.

♻️ Suggested wording
-    elif num_speculative_tokens:
-        raise ValueError("--num-speculative-tokens is only supported for DeepSeek V4")
+    elif num_speculative_tokens:
+        raise ValueError(
+            "--num-speculative-tokens/--enable-mtp is only supported for DeepSeek V4"
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pypto_serving/cli/main.py` around lines 176 - 177, Update the ValueError in
the _resolve_num_speculative_tokens path to mention both
--num-speculative-tokens and the legacy --enable-mtp alias, so users receive an
accurate message when either flag triggers the unsupported configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pypto_serving/serving/server/serving_worker.py`:
- Around line 249-252: Update the `_last_tokens` field comment in `__init__` to
state that only the latest token is retained for async placeholder resolution,
matching the `recent[-1:]` behavior in the token-update method.

---

Nitpick comments:
In `@pypto_serving/cli/main.py`:
- Around line 176-177: Update the ValueError in the
_resolve_num_speculative_tokens path to mention both --num-speculative-tokens
and the legacy --enable-mtp alias, so users receive an accurate message when
either flag triggers the unsupported configuration.

In `@pypto_serving/model/deepseek/npu_runner.py`:
- Around line 2331-2340: Change the acceptance-progress log in the decode path
to DEBUG or gate it behind a periodic/counter-based condition so it does not
emit on every iteration. Preserve the existing counters and percentage
calculation, and keep per-request summary reporting in release_finished_requests
unchanged.

In `@tests/test_deepseek_v4.py`:
- Around line 195-206: Extend
test_deepseek_mtp_draft_depth_is_capped_by_remaining_context or add a focused
test covering a batch sequence length equal to model.runtime.max_seq_len. Assert
_mtp_draft_count returns 0, exercising the fully exhausted context and the
downstream zero-draft/single-column behavior without changing existing
nonzero-cap coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3647e323-c56e-4dc3-a924-006f9126a56a

📥 Commits

Reviewing files that changed from the base of the PR and between 627df90 and 19ec067.

📒 Files selected for processing (11)
  • docs/dev/model/deepseek-v4.md
  • pypto_serving/cli/main.py
  • pypto_serving/config/types.py
  • pypto_serving/model/deepseek/npu_executor.py
  • pypto_serving/model/deepseek/npu_runner.py
  • pypto_serving/serving/engine/async_engine.py
  • pypto_serving/serving/sched/scheduler.py
  • pypto_serving/serving/server/ipc.py
  • pypto_serving/serving/server/serving_worker.py
  • tests/test_batching.py
  • tests/test_deepseek_v4.py
💤 Files with no reviewable changes (1)
  • pypto_serving/config/types.py

Comment on lines +249 to +252
"""Remember the latest sampled token for async placeholder resolution."""
recent = self._last_tokens.get(request_id, [])
recent.extend(int(t) for t in tokens)
if len(recent) > 2:
recent = recent[-2:]
self._last_tokens[request_id] = recent
self._last_tokens[request_id] = recent[-1:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale _last_tokens comment.

The field comment in __init__ still says "up to 2 kept for MTP prev-token context"; only the latest token is retained now.

♻️ Suggested change (lines 70-73)
-        # Last-sampled tokens per request (most-recent-last, up to 2 kept for MTP
-        # prev-token context). Under async scheduling the engine sends
+        # Latest sampled token per request. Under async scheduling the engine sends
         # PLACEHOLDER_TOKEN for a decode input it hasn't sampled yet; the worker
         # substitutes from here. Entries cleared when a request is released.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pypto_serving/serving/server/serving_worker.py` around lines 249 - 252,
Update the `_last_tokens` field comment in `__init__` to state that only the
latest token is retained for async placeholder resolution, matching the
`recent[-1:]` behavior in the token-update method.

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