Skip to content

fix(engine): upload cuda graphs at capture so a stream wait cannot block the first launch - #697

Merged
jason-fxz merged 1 commit into
mainfrom
fix/cuda-graph-upload
Oct 16, 2026
Merged

jason-fxz merged 1 commit into
mainfrom
fix/cuda-graph-upload

Conversation

@jason-fxz

Copy link
Copy Markdown
Collaborator

Bug

With --ple-backend disk in wait-sync mode (Qwen3.8-Flash-Next), the captured decode graph waits on a host flag at the PLE lookup, and the engine signals that flag only after replay() returns. torch captures the graph without uploading it, so the first replay() of each batch size also uploads it. When enough kernels follow the wait, that first replay() never returns: the engine thread stays in cuGraphLaunch, so the flag is never signaled. The Flash-Next GGUF decode graph on main does not hang as is; with 4000 more kernels after the lookup it does.

Reproduce

Put this sitecustomize.py on PYTHONPATH to add 4000 kernels after the PLE lookup in the captured graphs:

import torch
from freetoken.models.qwen4_exp import ple_disk

orig = ple_disk.DiskRowTable.lookup
pad = {}

def lookup(self, row_ids, out=None):
    res = orig(self, row_ids, out)
    x = pad.setdefault(res.device, torch.zeros(256, device=res.device))
    if torch.cuda.is_current_stream_capturing():
        for _ in range(4000):
            x.add_(1.0)
    return res

ple_disk.DiskRowTable.lookup = lookup

Serve ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF IQ3_XXS with --moe-strategy fused --ple-backend disk and send one chat request. On main the request gets no response, GPU utilization stays at 0%, and py-spy shows the engine thread in GraphRunner.replay -> cudaGraphLaunch -> cuGraphLaunch -> sched_yield.

Fix

Upload each graph with cuGraphUpload right after capture, so the first replay() does not upload it. Linux with CUDA only (through ctypes on libcuda.so.1); Windows and ROCm are unchanged.

With the patch above, this branch answers the request, and its greedy output matches the run without the patch. Without the patch, greedy output and decode speed match main. Tested on H100 80GB, torch 2.14.1+cu130.

@jason-fxz jason-fxz added the bug Something isn't working label Oct 16, 2026
@jason-fxz
jason-fxz merged commit 9b68e00 into main Oct 16, 2026
1 check passed
pretor pushed a commit to pretor/FreeToken that referenced this pull request Oct 16, 2026
KarrAcaRn pushed a commit to KarrAcaRn/FreeToken-ByAI that referenced this pull request Oct 17, 2026
Upstream 0bca88f..11c1adb: GGUF on vendored ggml (FlashML-org#690 and the per-arch
loaders), MoE slot pools (FlashML-org#689, FlashML-org#700), GGUF experts on the cpu/hybrid
executor (FlashML-org#704), the NVFP4 MoE prefill GEMM rewrite (FlashML-org#723), triton decode
split-k by context length (FlashML-org#729), graph upload at capture (FlashML-org#697),
transformers 5.18.

Resolution:
- GGUF: upstream's stack replaces ours (dense llama/qwen2/qwen3, qwen3moe,
  qwen35moe loaders, q4_0/gguf expert banks, our ggml kernels and their
  tests). Re-ported on top: rope-only --hf-overrides (YaRN) for the
  llama/qwen2/qwen3/qwen3moe GGUF parsers, and the scalar head_count_kv of
  Gemma-4 GGUFs (upstream PR FlashML-org#190). Gemma-4 E2B/E4B GGUF is not re-ported.
- offload cache: upstream's pooled cache, with our disk tier (one pool
  only), fill_slots preload per pool, sync-free prefill invalidation for a
  contiguous staging range, hit-D2D-by-default wording and the HIP graph
  per-bank copy.
- NVFP4 prefill: upstream's GEMM and per-GPU tuning supersede our sm_89
  tile config and JOIN_B.
- triton decode attention: upstream's split-k over our fp8/nvfp4 KV paths
  and HIP head padding; DFlash verify graphs are uploaded at capture too.
- embedding: upstream's embedding quant method, with --embed-device cpu in
  front and the tied head's kernel choice kept for unquantized tables.
- engine: DFlash, --kv-reserve-share, shrink-first rebuild, startup hybrid
  split and the WSL pin probe combined with the slot pools.

GPU-checked on the RTX 4090: full suite green; Qwen3.6-35B-A3B NVFP4
prefill ~13.2k / decode ~131 tok/s (baseline 13.6k / 131), Qwen3-30B-A3B
Q4_K_M GGUF 14.6k / 259 (was 8.3k / 225).

Assisted-by: Claude Opus 5.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant