Skip to content

feat(mla): add decode context parallelism - #1162

Open
qimcis wants to merge 10 commits into
lightseekorg:mainfrom
qimcis:decode-context-parallelism
Open

feat(mla): add decode context parallelism#1162
qimcis wants to merge 10 commits into
lightseekorg:mainfrom
qimcis:decode-context-parallelism

Conversation

@qimcis

@qimcis qimcis commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adding decode context parallelism (DCP), building on top of the fp8 MLA kernel introduced in #364 , i subdivide each attention tensor-parallel group and cyclically place the sequence history across its ranks. tested and implemented with dsv4 model family and kimi k3 in mind

Logical KV history
  ↕ owner(position) = position % DCP, local_row = position // DCP
Rank-local MLA history (1 / DCP rows)
  ↕ query-head all-gather
Local decode over 1 / DCP context
  ↕ exact LSE merge
Original TP-local output heads

for sequence-sharded MLA history, the per-rank physical storage is:

DCP1: 100.0% of baseline  (0.0% saved)
DCP2:  50.0% of baseline (50.0% saved)
DCP4:  25.0% of baseline (75.0% saved)
DCP8:  12.5% of baseline (87.5% saved)

thought, it's importnant that we note that this is the saving for the eligible dcp history, not necessarily the entire model cache. dsv4 for example will keep SWA, the FP4 sparse indexer, and compressor state sequence-replicated, and models like kimi k3 keep kda state sequence-replicated because it is already partitioned by TP heads.

When testing with dsv4-flash with a fixed global 8,192-token capacity, dcp2 halved the compressed history rows and reduced the complete rank-local arena from 1,574,012,160 to 1,513,935,360 bytes (3.82%, about 57.3 MiB per rank).

Test Plan

dsv4 flash official checkpoint (deepseek-ai/DeepSeek-V4-Flash) on 2×B200:

  • Baseline TP2, TP2/DCP2 ag_rs, and TP2/DCP2 a2a end-to-end serving.
  • CUDA graphs at batch sizes 1 and 2; plumbing coverage also exercised sizes 1, 2, 4, 7, and 8.
  • 30K-token chunked prefill, prefix-cache replay, and two concurrent requests.
  • Matched 30K prompt / 128-token decode benchmark:
    • Baseline TP2: 76.018 tok/s; 334.765 ms short-context median.
    • DCP2 AG/RS: 55.464 tok/s; 426.305 ms short-context median.
    • DCP2 A2A: 65.117 tok/s; 378.743 ms short-context median.

MODEL=deepseek-ai/DeepSeek-V4-Flash
REVISION=60d8d70770c6776ff598c94bb586a859a38244f1

COMMON_ARGS=(
  --revision "$REVISION"
  --tensor-parallel-size 2
  --enable-expert-parallel
  --kv-cache-dtype fp8_e4m3
  --moe-backend mega_moe
  --attention-use-fp4-indexer-cache
  --max-model-len 32768
  --max-total-tokens 65536
  --max-num-seqs 2
  --max-cudagraph-capture-size 2
  --chunked-prefill-size 2048
  --disable-prefill-graph
  --gpu-memory-utilization 0.9
  --disable-kvstore
  --host 127.0.0.1
  --port 18000
)

Baseline TP2:
tokenspeed serve "$MODEL" "${COMMON_ARGS[@]}"

TP2/DCP2 with AG/RS:

tokenspeed serve "$MODEL" "${COMMON_ARGS[@]}" \
  --decode-context-parallel-size 2 \
  --dcp-comm-backend ag_rs

TP2/DCP2 with A2A:

tokenspeed serve "$MODEL" "${COMMON_ARGS[@]}" \
  --decode-context-parallel-size 2 \
  --dcp-comm-backend a2a
image

trace above shows dcp's decode overhead is primarily from comms, with A2A reducing the per-layer collective path from three operations and 378 µs to two operations and 292 µs.

A2A improved decode throughput by 17.4% over AG/RS, while remaining 14.3% below the no dcp baseline. dcp preserves the per-rank attention work product (DCP× gathered heads over 1/DCP context), so its primary benefit is kv capacity as the collectives add latency

kimi k3 on 8xb300, using the official checkpoint:

  • TP8 baseline, 4K input / 1K output, concurrency 1: 43.78 output tok/s; 870.0 ms median TTFT; 21.52 ms median TPOT.
  • TP8 baseline, 16K input / 256 output, concurrency 8: 67.50 output tok/s; 12.46 s median TTFT; 70.01 ms median TPOT.
  • TP8 + DCP2 (A2A), 4K input / 1K output, concurrency 1: 41.34 output tok/s; 837.2 ms median TTFT; 23.25 ms median TPOT.
  • TP8 + DCP2 (A2A), 16K input / 256 output, concurrency 8: 69.29 output tok/s; 12.01 s median TTFT; 65.92 ms median TPOT.
  • DCP4, 4K input / 1K output, concurrency 1: 41.29 output tok/s; 850.8 ms median TTFT; 23.35 ms median TPOT.
  • DCP4, 16K input / 256 output, concurrency 8: 67.97 output tok/s; 11.62 s median TTFT; 72.58 ms median TPOT.
  • DCP8, 4K input / 1K output, concurrency 1: 58.67 output tok/s; 695.8 ms median TTFT; 16.40 ms median TPOT.
  • DCP8, 16K input / 256 output, concurrency 8: 116.75 output tok/s; 6.95 s median TTFT; 41.50 ms median TPOT.

At a fixed 65,536-token capacity:
DCP2: 1,189,085,184 → 732,561,408 bytes/rank, saving 38.39% (435.4 MiB/rank).
DCP4: 1,189,085,184 → 508,723,200 bytes/rank, saving 57.22% (648.8 MiB/rank).
DCP8: 1,189,085,184 → 386,629,632 bytes/rank, saving 67.49% (765.3 MiB/rank).

The MLA history scales as 1/dcp, while about 263.3 MiB/rank of kda state remains replicated, which produces diminishing memory savings at higher DCP degrees.

@qimcis
qimcis force-pushed the decode-context-parallelism branch 4 times, most recently from ddc5e15 to 29a340e Compare August 25, 2026 19:20
@qimcis
qimcis marked this pull request as ready for review August 25, 2026 19:29
@qimcis
qimcis requested review from a team as code owners August 25, 2026 19:29

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

block_table_base_offsets=compressed_base_offsets,
compressed_block_size=compressed_block_size,
compressed_table_capacity=compressed_table_capacity,

P1 Badge Restore compressed-table metadata before V4 prefill

When a DeepSeek-V4 ratio-4 layer enters prefill, this call references compressed_base_offsets and compressed_table_capacity, but the refactor moved the offset calculation into gather_compressed_history as a local base_offsets and removed both outer bindings. Python therefore raises NameError before combining the CSA and SWA indices; the same undefined names are also used by the later dense compressed-history branch.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread python/tokenspeed/runtime/layers/attention/kv_cache/recipes/base.py Outdated
@qimcis
qimcis marked this pull request as draft August 25, 2026 19:43
@qimcis
qimcis force-pushed the decode-context-parallelism branch 2 times, most recently from bcd4823 to d0f6e5d Compare August 26, 2026 00:15
@qimcis
qimcis marked this pull request as ready for review August 26, 2026 00:33

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0f6e5d2bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +10 to +12
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore the full MIT license header

This newly added source file stops the license notice after the inclusion clause and omits the MIT warranty and liability disclaimer. Replace the abbreviated header with the repository's full MIT header before distributing the file.

AGENTS.md reference: AGENTS.md:L15-L16

Useful? React with 👍 / 👎.

Comment on lines +106 to +112
def dsv4_compact_dcp_topk_indices(
indices: torch.Tensor,
*,
dcp_size: int,
dcp_rank: int,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Stable fixed-shape compaction of one DCP rank's compressed indices."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the public compaction API arguments and returns

This function is exported through both tokenspeed_kernel.ops.attention and the top-level tokenspeed_kernel package, but its one-line docstring does not explain the global-index input contract, dcp_size/dcp_rank, or the returned compacted indices and lengths. Add explicit argument and return documentation for this new public API.

AGENTS.md reference: AGENTS.md:L51-L53

Useful? React with 👍 / 👎.

Comment on lines +2036 to +2038
parser.add_argument(
"--decode-context-parallel-size",
"--dcp-size",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add user documentation for the DCP CLI options

These new public flags introduce substantial backend, dtype, topology, disaggregation, and cache-storage constraints, but the commit adds no repository documentation explaining how to configure or use them. Add a user-facing DCP section covering supported models/backends, required options, communication modes, and incompatibilities.

AGENTS.md reference: AGENTS.md:L11-L14

Useful? React with 👍 / 👎.

@kangguangli kangguangli 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.

The overall design of virtually expanding the page size looks good, as it allows all ranks to share the same logical block-table structure. My concerns are:

  1. The SKIP_ZERO design is worth discussing. It requires every KV writer that may receive location 0 to skip writing to the reserved null page, while page 0 is frequently used as a placeholder in other paths.
    I am concerned that this implicit contract may create maintenance issues in future development. I suggest passing an explicit write mask instead of relying on default writer behavior. For example, Kimi-K3 reuses DeepseekV3AttentionMLA and may invoke apply_rope_mla_set_kv() to update the cache, but this fused path does not currently support SKIP_ZERO.
    Alternatively, page 0 could be writable, but in that case read and reconstruction paths should not rely on it always remaining zero, since that invariant is easy to break.
  2. During MLA prefill, each rank materializes a full-length cached-prefix tensor, for example [KV0, 0, KV2, 0, KV4, 0] on rank 0. This may introduce redundant memory loads in get_mla_kv_buffer() and extra communication in reconstruct_prefix_kv(), especially when the cached prefix is long. Could we read only the rank-local rows and reconstruct them afterward?
  3. Enabling DCP results in a 14.3% throughput regression compared with TP. This seems relatively large considering the communication payload and the NVLink bandwidth of Blackwell. Could you provide a short analysis or profile breakdown of the main sources of this regression?
  4. I suggest adding accuracy tests like evaluation on GSM8K or GPQA. A direct logit-parity comparison against the TP baseline would be even better.

Comment thread python/tokenspeed/runtime/layers/attention/registry.py Outdated
Comment thread python/tokenspeed/runtime/models/deepseek_v3.py Outdated
Comment thread python/tokenspeed/runtime/layers/attention/backends/deepseek_v4.py Outdated
@qimcis
qimcis force-pushed the decode-context-parallelism branch from d0f6e5d to f2d3731 Compare August 26, 2026 23:21
@kangguangli

Copy link
Copy Markdown

hello, any update?

qimcis added 9 commits August 29, 2026 08:14
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
@qimcis
qimcis force-pushed the decode-context-parallelism branch from f2d3731 to 8b05dd6 Compare August 29, 2026 14:44

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b05dd6a53

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

from typing import TYPE_CHECKING

import torch
from tokenspeed_kernel._triton import tl, triton

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move the A2A Triton kernel behind tokenspeed-kernel

Importing the MLA backend now unconditionally imports this module, which reaches through the public kernel boundary to the private _triton shim and defines a @triton.jit kernel in runtime code—even when the selected communication backend is ag_rs. Move the packing kernel into tokenspeed-kernel and expose it as a public operation so the runtime does not own or directly depend on third-party kernel implementation details.

AGENTS.md reference: AGENTS.md:L47-L51

Useful? React with 👍 / 👎.

Signed-off-by: Chi McIsaac <chixie.mcisaac@gmail.com>
@qimcis
qimcis force-pushed the decode-context-parallelism branch from 2a5adef to 2f27f52 Compare August 30, 2026 00:39

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f27f521e9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

*,
world_size: int,
) -> torch.Tensor:
"""Pack output head slices and lossless FP32 LSE words for DCP A2A."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the public DCP packing contract

This function is newly exported through both tokenspeed_kernel.ops.attention and the top-level tokenspeed_kernel package, but its one-line docstring does not explain the required output/LSE shapes and dtypes, the world_size head-partition contract, or the packed tensor's layout and return shape. Add explicit argument and return documentation for this public API.

AGENTS.md reference: AGENTS.md:L67-L69

Useful? React with 👍 / 👎.

@qimcis

qimcis commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

The overall design of virtually expanding the page size looks good, as it allows all ranks to share the same logical block-table structure. My concerns are:

  1. The SKIP_ZERO design is worth discussing. It requires every KV writer that may receive location 0 to skip writing to the reserved null page, while page 0 is frequently used as a placeholder in other paths.
    I am concerned that this implicit contract may create maintenance issues in future development. I suggest passing an explicit write mask instead of relying on default writer behavior. For example, Kimi-K3 reuses DeepseekV3AttentionMLA and may invoke apply_rope_mla_set_kv() to update the cache, but this fused path does not currently support SKIP_ZERO.
    Alternatively, page 0 could be writable, but in that case read and reconstruction paths should not rely on it always remaining zero, since that invariant is easy to break.
  2. During MLA prefill, each rank materializes a full-length cached-prefix tensor, for example [KV0, 0, KV2, 0, KV4, 0] on rank 0. This may introduce redundant memory loads in get_mla_kv_buffer() and extra communication in reconstruct_prefix_kv(), especially when the cached prefix is long. Could we read only the rank-local rows and reconstruct them afterward?
  3. Enabling DCP results in a 14.3% throughput regression compared with TP. This seems relatively large considering the communication payload and the NVLink bandwidth of Blackwell. Could you provide a short analysis or profile breakdown of the main sources of this regression?
  4. I suggest adding accuracy tests like evaluation on GSM8K or GPQA. A direct logit-parity comparison against the TP baseline would be even better.
  1. i looked into this a little more - I believe a fused writer path could bypass the null page protection, thus I kept page 0 reserved and moved the owner handling into the backend and now pass a write mask through both the fused and split MLA writers (which keeps the sentinel interpretation out of individual writers)

  2. there were indeed redundant memory loads, i've fixed this now (changed it to read only the rank local prefix rows, then it gathers the compact buffers, then interleaves them into global token order)

  3. the original regression was mainly because of overhead from collectives plus packing/merging, and index prep and can definitely be optimized, working on this now

  4. added a logit comparison test!

  • addressed other comments

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.

2 participants