Skip to content

Kernel transfer backend causes decode TPOT tail spikes under concurrent KV transfers #408

Description

@xiaguan

Summary

The kernel transfer backend solves an important throughput problem for highly
fragmented KV transfers: it replaces many directional CUDA memcpy submissions
with one descriptor upload and one copy-kernel launch.

However, when the transfer runs concurrently with model decode on the same GPU,
the copy kernel competes for GPU execution and memory resources. In a
serving-oriented A/B test on an RTX 5090, bidirectional kernel transfers
sustained the target transfer rate but increased decode TPOT p99 by 46.6%
and max TPOT by 80.6%.

This suggests that TransferMode::Kernel should remain available, but backend
selection at registration should account for latency policy and transfer shape
rather than only the model type.

Relevant implementation

At the current repository head:

  • TransferMode is selected per instance at registration and is shared by both
    load and save worker pools.
  • KernelBackend launches up to one 256-thread block per descriptor, capped at
    65,535 blocks.
  • MemcpyBackend coalesces adjacent ranges, then submits one directional CUDA
    memcpy per merged range.
  • The connector currently selects kernel mode for MLA models and direct mode
    otherwise.

Test setup

The test used a release build and a real Qwen3-4B pure-decode workload, with a
standalone CUDA transfer stressor matching the current PegaFlow backend
semantics.

Decode workload:

  • GPU: RTX 5090, PCIe 5.0 x16
  • CUDA: 13.1
  • Context length: 2,048
  • Batch size: 1
  • Decode steps: 512
  • Warmup steps: 32
  • Iterations per run: 3
  • ITL samples per run: 1,437
  • Three runs per main mode, with rotated execution order
  • Transfer stress started only after model initialization and CUDA Graph capture

Transfer workload:

  • 16,384 shuffled descriptors per direction
  • 4 KiB per descriptor
  • 64 MiB H2D + 64 MiB D2H per burst
  • One burst every 50 ms
  • Target aggregate rate: 2.5 GiB/s
  • Host payload allocated as mapped pinned memory
  • H2D and D2H submitted concurrently on separate streams

The hybrid result below is an experimental policy using kernel for H2D and
direct memcpy for D2H; it is not a current PegaFlow mode.

Results

Values are means of the per-run metrics across three runs. Max is the maximum
observed across the three runs.

Mode Avg TPOT P50 P99 Max Effective transfer rate
No transfer 6.221 ms 6.220 ms 6.255 ms 6.335 ms
Direct 6.327 ms (+1.7%) 6.335 ms (+1.9%) 6.542 ms (+4.6%) 6.654 ms 2.436 GiB/s
Kernel 6.414 ms (+3.1%) 6.034 ms (-3.0%) 9.172 ms (+46.6%) 11.441 ms 2.497 GiB/s
Hybrid 6.234 ms (+0.2%) 6.074 ms (-2.3%) 7.683 ms (+22.8%) 8.807 ms 2.497 GiB/s

The lower kernel p50 should not be interpreted as a speedup. The periodic 50 ms
transfers affect only a subset of decode steps: unaffected steps remain fast
while transfer-overlapping steps form a separate tail.

Direction isolation produced similar kernel interference in both directions:

Backend Direction Decode TPOT p99 Effective transfer rate
Kernel H2D 7.632 ms 1.249 GiB/s
Kernel D2H 7.587 ms 1.249 GiB/s
Direct H2D 6.219 ms 1.249 GiB/s
Direct D2H 6.235 ms 1.249 GiB/s

On this GPU, H2D and D2H kernel interference is approximately symmetric. The
larger bidirectional tail comes from concurrent kernel transfers rather than one
direction being universally worse.

As a PCIe sanity check, contiguous pinned-memory DMA reached 52.54 GiB/s H2D
and 53.20 GiB/s D2H. The lower direct result above is therefore caused by
per-fragment submission overhead, not PCIe bandwidth.

Discussion

I think the kernel backend is still valuable, especially when a direct transfer
expands into thousands of merged ranges. The open question is how registration
should express the tradeoff between transfer throughput and serving tail
latency.

Possible directions:

  1. Keep Kernel as an explicit backend, but avoid selecting it solely from
    model type for latency-sensitive serving.
  2. Add an Auto policy based on observable transfer shape, such as merged range
    count, total bytes, and average range size.
  3. Allow load and save policies to differ, while avoiding a hard-coded
    directional choice because the direction-specific result is GPU-dependent.
  4. Evaluate lower-priority transfer streams, a bounded grid size, or
    chunked/yielding kernel launches to reduce decode interference.
  5. Add a serving-interference benchmark alongside backend-only throughput
    benchmarks, since transfer throughput alone misses this regression.

The desired outcome is to preserve the fragmented-transfer throughput advantage
without making TPOT p99 unpredictable for colocated decode.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions