Skip to content

[GLM5.3] Serve GLM-5.3-Flash on SM80 (A100): k-pool indexer boot + fp8 KV + sparse MLA split-K + CP threshold - #87

Merged
hippothewild merged 8 commits into
v0.5.22from
0.5.22-glm5.3flash-sm80
Nov 5, 2026
Merged

hippothewild merged 8 commits into
v0.5.22from
0.5.22-glm5.3flash-sm80

Conversation

@hippothewild

@hippothewild hippothewild commented Oct 24, 2026 •

Copy link
Copy Markdown

GLM-5.3-Flash를 A100(SM80)에서 서빙하기 위한 패치 8개 (v0.5.22 base)

한 줄 요약: GLM-5.3-Flash의 DSA k-pool indexer는 deep_gemm(SM90)과 하드웨어 fp8에 묶여 있어 A100에서 부팅되지 않습니다. 이 PR은 indexer를 Triton 텐서코어 커널로 옮겨 부팅되게 만들고(커밋 12), sparse MLA decode split-K·fp8 KV(인-커널 fp8→bf16 변환)·CP 토큰 임계·CP decode attention TP·pinned H2D를 얹습니다. A100-SXM4-80GB×8 서버 한 대에서 SLA(서버 TTFT p50 ≤ 5 s, TPOT p50 ≤ 20 ms)를 지키는 처리량은 고-hit trace 0.8 rps(2.19M TPM), 저-hit trace **0.6 rps(1.63M TPM)**이고, 초기 부팅 스택(0.25 rps) 대비 2.43.2배입니다.

비-SM8x GPU는 upstream v0.5.22와 같습니다. SM8x 분기는 compute capability 8.0~8.8(is_sm80_family())에서만 열리고, 새 env는 모두 기본 OFF/unset입니다. 예외 2건(k-pool 모델의 죽은 per-token 스케줄 생략, pinned H2D)은 값이 같고 아래 §6에 명시했습니다. 이미지에 모델 전용 ENV를 굽지 않았습니다.

  • Base: v0.5.22 (baa9befa38, lmsysorg/sglang:v0.5.22 이미지 소스와 동일 커밋)
  • Head: 0.5.22-glm5.3flash-sm80 @ 13d0665ec1 (8커밋, 28파일 +1249 −86)
  • 이미지: quay.io/vessl-ai/sglang:0.5.22-glm5.3flash-sm80-13d0665@sha256:0aba1f18cd215fe1fadbd61b5171fe773755c89036e7e24a42ed1c42c14faa7f (고정 태그 0.5.22-glm5.3flash-sm80 동일 digest)
  • Upstream: none (not submitted yet). 커밋 2는 [Perf] DSA: Skip the unused token-level paged MQA schedule under KPool sgl-project/sglang#44378, 커밋 3은 [DSA] Split topk across CTAs for small CUDA BF16 TileLang sparse decode sgl-project/sglang#43917, 커밋 5는 #44293과 같은 문제를 다룹니다(각 커밋 trailer 참고).
  • 측정 환경: A100-SXM4-80GB×8 단일 서버, NVLink, TP8 + interleave prefill CP8, 체크포인트 canada-quant/GLM-5.3-Flash-W4A16-MTP(W4A16 Marlin MoE + 내장 MTP).
  • 리뷰: pr-review-toolkit 리뷰 루프 5회(코드·커널 효율·silent failure·주석·테스트). 마지막 라운드 Critical 0 / Important 0. 남은 Minor는 §7.

1. 패치 지도 — forward pass 어디에 붙는가

flowchart LR
    BOOT["C1 SM8x 부팅<br/>k-pool indexer → Triton TC 커널<br/>fp8 store → SW e4m3fn 코덱"]:::boot
    SCHED["C2 k-pool 모델<br/>per-token paged-MQA 스케줄 생략"]:::boot
    subgraph PREFILL["prefill"]
        P1["C5 CP 토큰 임계<br/>N 미만 extend = prefill graph replay"]:::pre
        P2["C7 pinned non_blocking H2D<br/>CP·DSA 메타데이터"]:::pre
        P3["C3 V1 타일 cfg 32,1,128"]:::pre
    end
    subgraph KV["KV 풀"]
        K1["C4 fp8_e4m3 KV<br/>smem 타일에서 fp8x8→bf16x8 변환"]:::kv
    end
    subgraph DECODE["decode · MTP verify"]
        D1["C3 sparse MLA split-K<br/>q토큰×헤드 ≤ 768"]:::dec
        D2["C6 Glm5Next CP decode attn TP<br/>64 → 8 heads/rank"]:::dec
    end
    BOOT --> SCHED --> PREFILL --> KV --> DECODE
    classDef boot fill:#e8eefc,stroke:#4f46e5,color:#111
    classDef pre fill:#e9f7ef,stroke:#15803d,color:#111
    classDef kv fill:#f3e8ff,stroke:#7e22ce,color:#111
    classDef dec fill:#fff4e5,stroke:#c2410c,color:#111
Loading
커밋 역할 켜는 법 근거 측정 (A100)
C1 e20f17b k-pool indexer SM8x 부팅 deep_gemm fp8_mqa_logits·fp8_paged_mqa_logits·act_quant을 Triton 텐서코어 커널(kpool_indexer_sm80.py)로, k-pool fp8 store를 SW e4m3fn 코덱으로 SM 8.0~8.8 자동 float64 기준과 top-512 집합 동일, graph replay = eager, store 6 seed byte 일치. 8192q×35k logits 4.47 ms(bf16 TC 피크 37%)
C2 33ce759 죽은 스케줄 생략 index_kpool > 1이면 per-token paged-MQA 스케줄 메타데이터를 만들지 않음 자동(GPU 무관, 아래 §6) 없으면 init_forward_metadata가 첫 토큰 전에 실패
C3 6eb4bd9 sparse MLA split-K + 타일 decode·verify·draft-extend를 split-K(partial+combine)로, V1 prefill 타일을 env로 SGLANG_SM80_SPARSE_MLA_DECODE_SPLIT=1, SGLANG_SM80_SPARSE_MLA_V1_CFG=32,1,128 split vs 단일: 토큰×헤드 48에서 1.19×, 56에서 1.02×, 64에서 0.91× → 768 게이트. 타일 sweep 1위 32,1,128
C4 fc5e76a SM8x fp8 KV fp8 바이트를 gather한 뒤 packed 8-wide extern 변환기로 bf16 smem 타일 작성. DSA 제약 검사가 SM8x에서 fp8_e4m3 허용 --kv-cache-dtype fp8_e4m3 + tilelang DSA 백엔드 KV 풀 1.94×(3,760,640 tokens). 커널 시간 bf16 KV 대비 prefill 1.211.27×, decode 1.041.15×. 장문 검색 27/27
C5 5a8678d CP 토큰 임계 새 토큰 N 미만 extend는 CP를 끄고 breakable prefill graph replay SGLANG_CP_MIN_EXTEND_TOKENS=4096 + 버킷 < N 30k 캐시 prefix + 4토큰: bs1 TTFT 224.7 → 63.6 ms
C6 15f7893 Glm5Next CP decode attn TP Glm5NextForConditionalGeneration 화이트리스트 + DSA-MLA를 maybe_use_decode_attn_tp로 감쌈 --enable-cp-decode-attn-tp 0.75 rps TPOT p50 36.5 → 21.0 ms
C7 2fe02a0 pinned H2D CP·DSA extend 메타데이터 4곳을 pinned + non_blocking 복사로 항상(값 동일, eager 전용 사이트) CP extend당 ~50 ms cudaStreamSynchronize 제거. 콜드 35k 2.22 → 2.17 s
C8 13d0665 유닛 테스트 env 엄격 파서·fp8 KV 부팅 검사(CPU), SW 인코더 vs torch.float8_e4m3fn(CUDA) base-a-test-cpu / base-b-kernel-unit 1-gpu-large —

새 env

env 기본값 정본 값 형식 오류 시
SGLANG_CP_MIN_EXTEND_TOKENS 0 (upstream 동작) 4096 부팅 ValueError(변수명 포함). interleave 외 CP 전략과 함께 N>0이면 부팅 실패
SGLANG_SM80_SPARSE_MLA_DECODE_SPLIT 0 1 부팅 ValueError
SGLANG_SM80_SPARSE_MLA_V1_CFG unset (fp8 KV 경로는 32,1,128) 32,1,128 부팅 ValueError. fp8 KV이면 num_stages == 1 강제

파싱은 새 EnvField.get_strict()로 합니다. get()은 형식 오류 때 경고 후 기본값으로 떨어지는데, 성능 레버가 조용히 꺼진 채 측정되는 것을 막으려고 부팅을 실패시킵니다.


2. 누적 효과 — 저-hit trace 0.75 rps 창 (서버 p50)

같은 trace·시드 쌍으로 레버를 하나씩 더한 순서입니다. config 레버(hrrn 스케줄링, chunked-prefill 16384)는 이 PR의 코드가 아니지만 순서를 보이려고 함께 적었습니다.

xychart-beta
    title "TPOT p50 (ms), 0.75 rps 저-hit — 레버 누적"
    x-axis ["hrrn 적용 후", "+V1 타일(C3)", "+CP·decode attn TP(C6)", "+CP 임계(C5)", "+fp8 KV(C4)·chunk16k", "최종 정본"]
    y-axis "TPOT p50 (ms)" 0 --> 70
    bar [62, 36.5, 21.0, 18.2, 28.7, 20.8]
    line [20, 20, 20, 20, 20, 20]
Loading

막대가 TPOT p50, 선이 SLA 20 ms입니다. fp8 KV 단계는 hit가 낮은 다른 창(TPOT 108 ms 기준선)에서 잰 값이라 앞 단계와 직접 이어지지 않습니다. 같은 창에서 108 → 28.7 ms였습니다.

지표 초기 부팅 스택 이 PR + 정본 config
콜드 TTFT p50, ISL 35k 2.71 s 2.08 s
콜드 TTFT p50, ISL 105k 8.23 s 5.90 s
KV 풀 (tokens, TP8) 1.94M (bf16) 3.76M (fp8_e4m3)
in-SLO 처리량, 고-hit (hit 0.78) 0.25 rps 0.8 rps · 2.19M TPM
in-SLO 처리량, 저-hit 0.25 rps 0.6 rps · 1.63M TPM
1M 토큰 단일 요청 — 생존, 76.3 s

3. 처리 한계 (knee) — 실 trace open-loop, 서버 p50

xychart-beta
    title "작업점 서버 TPOT p50 (ms)"
    x-axis ["고-hit 0.75 (3창 중앙)", "고-hit 0.8", "저-hit 0.6", "저-hit 0.75 (3창 중앙)"]
    y-axis "TPOT p50 (ms)" 0 --> 25
    bar [15.0, 16.4, 12.7, 22.1]
    line [20, 20, 20, 20]
Loading
trace rps 서버 TTFT p50 TPOT p50 TPM 판정
고-hit (hit 0.78) 0.75 — 15.0 ms (3창 중앙, 3/3 통과) — IN-SLO
고-hit 0.8 1.18 s 16.4 ms 2.19M IN-SLO (작업점)
고-hit 0.9 — — — BREACH
저-hit (hit 0.73) 0.6 1.44 s 12.7 ms 1.63M IN-SLO (작업점)
저-hit 0.75 — 22.1 ms (3창 중앙, 3/3 초과) — BREACH

knee는 고-hit (0.8, 0.9], 저-hit (0.6, 0.75] 구간에 있습니다. 작업점 창은 창당 1회 측정입니다.

4. 정확도

항목 결과
GSM8K (bf16 KV 초기 스택 대비 페어) 0.9742, 무손실
GPQA-Diamond (temp 0.6) 0.9192, 무손실
장문 단일 사실 검색 (fp8 KV) 27/27
이 이미지 서버 재확인 GSM8K greedy 1286/1319 = 0.9750 (정본 0.9742 대비 +0.08%p, PASS)

5. 쓰는 법

docker run --gpus all --ipc=host --cap-add SYS_NICE \
  -e SGLANG_CP_MIN_EXTEND_TOKENS=4096 \
  -e SGLANG_SM80_SPARSE_MLA_DECODE_SPLIT=1 \
  -e SGLANG_SM80_SPARSE_MLA_V1_CFG=32,1,128 \
  -e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
  quay.io/vessl-ai/sglang:0.5.22-glm5.3flash-sm80-13d0665@sha256:0aba1f18cd215fe1fadbd61b5171fe773755c89036e7e24a42ed1c42c14faa7f (고정 태그 `0.5.22-glm5.3flash-sm80` 동일 digest) \
  python3 -m sglang.launch_server --model-path <GLM-5.3-Flash-W4A16-MTP> \
    --tp 8 --enable-prefill-cp --attn-cp-size 8 --cp-strategy interleave --enable-cp-decode-attn-tp \
    --kv-cache-dtype fp8_e4m3 --dsa-prefill-backend tilelang --dsa-decode-backend tilelang \
    --speculative-algorithm EAGLE --speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4 \
    --chunked-prefill-size 16384 --max-prefill-tokens 32768 --context-length 1048576 \
    --mem-fraction-static 0.80 --schedule-policy hrrn --disable-shared-experts-fusion \
    --cuda-graph-max-bs-decode 64 --cuda-graph-bs-prefill 8 16 24 32 48 64 ... 1280 1536 1792

정본 런처 전문은 inference-playbook tpm-measurements/nvidia-a100/glm-5.3-flash/canon/run.sh입니다.

6. 리뷰 포인트

  1. 비-SM8x 동작: 기본 env에서 _runs_with_prefill_cp == dsa_enable_prefill_cp, can_apply의 추가 조건 seq_len >= 0은 항상 참, in_prefill_graph는 False입니다. graph·model·cp_tp 훅 결정, tilelang_sparse_fwd, k-pool 커널(SW_FP8=False constexpr)은 upstream 경로 그대로입니다. SM89 이상은 SM8x 분기에 들어가지 않습니다(DeepSeek-V3.2/GLM-5의 SM89 tail_dim assert 경로 보존).
  2. GPU 무관한 변경 2건: C2(index_kpool > 1 모델은 모든 GPU에서 죽은 per-token 스케줄 생략, index_kpool <= 1은 불변)와 C7(pinned H2D, 값 동일·eager 전용 사이트). C7 사이트가 나중에 CUDA graph capture 안으로 들어가면 조용히 캡처될 수 있어 각 사이트 주석에 "eager 전용"을 적었습니다.
  3. fp8 KV 변환기는 파이프라인 sync 밖에서 smem을 쓰므로 V1 타일 num_stages == 1을 부팅과 커널 팩토리 양쪽에서 강제합니다.
  4. 포함하지 않은 것: host 2차 캐시 계열(저-hit +20% / 고-hit TPOT +93%), KDA 투영 융합, W4 decode CUDA-C, upstream [Perf] mamba: Copy tracked decode states at HBM bandwidth in one persistent launch sgl-project/sglang#45213·[Feat] scheduler: Gate --prefill-decode-interval on running batch size sgl-project/sglang#44001 선별 반영(저-hit TPOT 회귀로 기각). 판정과 재개 조건은 캠페인 리포트에 있습니다.
  5. shared experts fusion: 체크포인트가 shared expert 504항을 ignore하고 glm5_next.py에 compressed-tensors 분기가 없어 --disable-shared-experts-fusion이 필요합니다. 이 PR은 고치지 않았습니다.

7. 남은 Minor (follow-up)

  • parallel_hook.py:150-154의 info 로그 "has no effect without --enable-prefill-cp"는 attn_cp_size > 1이면서 prefill CP가 꺼진 니치 구성에서 부정확합니다(cuda_graph_hook.py:175, cp_decode_attn_tp.py:89가 env를 소비). 수정안은 env 쪽 조건에 cfg.enable_prefill_cp를 거는 것입니다.
  • EnvField.get_strict()는 _set_to_none과 deprecated alias를 처리하지 않습니다. 현재 호출처 3곳은 해당 없습니다.
  • 커널 효율 후보(측정 후 보류): indexer F2F 제거(K1, TPOT −0.3~0.6%), MTP verify 행 공유(K2, 작업점 −1.3%), split-K occupancy(K4), 1-stage fp8 변환(K6). knee를 한 칸 옮기려면 −20%가 필요해 이 PR에 넣지 않았습니다.

8. 검증

  • 커밋마다 단독 py_compile 통과, 각 커밋이 쓰는 심볼은 그 커밋 또는 이전 커밋에 정의
  • 기존 CPU 테스트 영향 점검(test_dsa_tilelang_fp8_validation.py 갱신: (7,5) 실패 메시지 "SM80", (8,0) 통과; test_dsa_metadata_replay 픽스처 호환)
  • 이미지 빌드: lmsysorg/sglang:v0.5.22@sha256:e3a4f060… + 이 브랜치 python/sglang 덮어쓰기, 5,571파일 md5 대조 PASS(이미지 전용 파일 = Rust 확장 .so 5개 + _version.py)
  • 이미지 서버 부팅: RustUnifiedTreeCore 8 rank, fp8 KV 3,760,640 tokens, V1 tile (32,1,128), prefill graph 버킷 < 4096, smoke 5/5, Traceback·restart 0
  • 이미지 서버 정확도: GSM8K greedy 1286/1319 = 0.9750 (정본 0.9742)
  • 정본 대비 동등성(같은 VM, 같은 closed-loop 드라이버·시드·프라임 순서, 사전 규칙 ±10%): c4 TTFT p50 0.739/0.756 s, TPOT p50 12.60/12.57 ms, TPM 3.494M/3.476M · c6 TTFT 0.768/0.820 s, TPOT 17.63/17.42 ms, TPM 3.955M/3.957M → EQUIV (최대 차 6.3%)
  • closed-loop BEP 사다리(ISL 70k, OSL 300, hit 0.85): stable = max = c6, TTFT p50 0.77 s, TPOT p50 17.6 ms, 3.95M TPM; c8은 TPOT 22.5 ms로 SLA 초과
  • verify 프로파일: split-K partial·combine 커널이 replay당 11회씩 실행(DECODE_SPLIT 경로 활성)

What: replace the SM90-only deep_gemm calls of IndexerKPool with Triton
tensor-core kernels (kpool_indexer_sm80.py: ragged-prefill MQA logits, paged
decode MQA logits, act_quant), read the SM count from device properties, return
no paged-MQA schedule, and route every k-pool fp8 store through a software
e4m3fn codec (fp8_e4m3_sw.py), because Triton rejects fp8e4nv below SM89.

Why: GLM-5.3-Flash (index_kpool > 1) cannot boot on A100: deep_gemm.fp8_mqa_logits,
fp8_paged_mqa_logits, get_num_sms and every fp8 tl.store of the k-pool write
kernels fail on compute capability 8.0.

Enable: automatic on compute capability 8.0 to 8.8 (is_sm80_family). SM89 and
newer, and ROCm, keep the original deep_gemm / hardware-fp8 path; SW_FP8 is a
constexpr False there, so the generated kernels are unchanged.

Evidence (A100-SXM4-80GB): the indexer kernels match a float64 reference (codec
values bit-exact, top-512 sets equal, CUDA graph replay equals eager) and the
k-pool write kernels match it byte-wise over 6 seeds; prefill logits for 8192
queries over 35k keys take 4.47 ms (37% of bf16 tensor-core peak).

Upstream: none (not submitted yet)
What: build the deep_gemm paged-MQA schedule metadata only when
index_kpool <= 1 (needs_paged_mqa_schedule). With a k-pool the indexer reads only
the pooled schedule, so the per-token schedule is dead work.

Why: the per-token schedule calls get_paged_mqa_logits_metadata, which needs
deep_gemm and aborts metadata initialisation on SM8x. The same idea as upstream
sgl-project#44378.

Enable: automatic. This is not limited to SM8x: models with index_kpool > 1
skip the dead schedule on every GPU; index_kpool <= 1 is unchanged.

Evidence: boot gate on A100, without it init_forward_metadata fails before the
first token; no kernel timing was measured.

Upstream: sgl-project#44378 (same idea, written independently)
What: two env-gated levers for the TileLang sparse MLA kernels on SM8x.
SGLANG_SM80_SPARSE_MLA_V1_CFG ("block_I,num_stages,threads") overrides the tile
config of the V1 prefill kernel. SGLANG_SM80_SPARSE_MLA_DECODE_SPLIT=1 runs
decode, target-verify and draft-extend steps with few query rows as a split-K:
the existing partial kernel writes into a reusable per-stream workspace and the
existing combine kernel merges it, when query tokens x padded heads <= 768.
Prefill and plain extend keep the V1 kernel (the caller passes
allow_decode_split), because under the breakable prefill graph their attention
runs as an eager break on bucket-padded q.

Why: at the default tile the A100 sparse MLA kernel is the largest term of a
verify step, and a few query rows leave most of the 108 SMs idle.

The settings are parsed strictly (new EnvField.get_strict, which raises a
ValueError naming the variable where get() would warn and fall back) and checked
at boot on SM8x with TileLang.

Enable: SGLANG_SM80_SPARSE_MLA_DECODE_SPLIT=1 and
SGLANG_SM80_SPARSE_MLA_V1_CFG=32,1,128. Both are off / unset by default and only
apply on compute capability 8.0 to 8.8.

Evidence (A100): split vs single kernel by token-heads: 1.19x faster at 48,
1.02x at 56, 0.91x at 64, which sets the 768 gate; 32,1,128 won the tile sweep
and held in the end-to-end runs.

Upstream: the split follows sgl-project#43917
What: A100 has no fp8 MMA, so keep the MLA KV pool in fp8_e4m3 and convert it to
bf16 while the TileLang V1 and decode-partial kernels write their shared-memory
tile: the raw bytes are gathered first, then a packed 8-wide extern converter
(tl_fp8x8_to_bf16x8) fills the bf16 tile. On compute capability 8.0 to 8.8 the
TileLang DSA constraint check accepts --kv-cache-dtype fp8_e4m3 instead of
raising "requires SM89 or newer"; SM89 and newer use the upstream fp8 kernels.
The converter writes shared memory outside the pipeline's sync, so the V1 tile
must use num_stages == 1; this is checked at boot, together with the format of
SGLANG_SM80_SPARSE_MLA_V1_CFG, and again in the kernel factories.

Why: the KV bytes per token and layer drop from 1024 to 512, so the pool grows
1.94x (3,760,640 tokens at TP8, mem-fraction 0.80). That is what lets 1M-token
contexts and a high prefix-cache hit rate fit on 8 x 80 GB.

Enable: --kv-cache-dtype fp8_e4m3 --dsa-prefill-backend tilelang
--dsa-decode-backend tilelang. The V1 tile comes from
SGLANG_SM80_SPARSE_MLA_V1_CFG and is 32,1,128 on this path when it is unset.
There is no codec switch: the packed converter is the only path.

The existing CPU validation test is updated: compute capability (7, 5) still fails
(now with "SM80") and (8, 0) passes.

Evidence (A100): kernel time vs bf16 KV is 1.21-1.27x for prefill and
1.04-1.15x for decode; the accuracy gate passed and long-context retrieval is
27/27 on the fp8 KV build.

Upstream: none (not submitted yet)
…graph

What: SGLANG_CP_MIN_EXTEND_TOKENS=N makes interleave prefill CP apply only to
extend batches with at least N new tokens (InterleaveCPStrategy.can_apply and
can_dsa_prefill_cp_interleave). Smaller extends run the non-CP body and may
replay the breakable prefill CUDA graph. With N set, the three places that
forced the prefill graph off for any CP server are skipped (the CP-TP group
sharing hook, the "context parallel" rule in cuda_graph_hook, the DSA + prefill
CP branch in model_hook), the captured buckets are limited to sizes below N,
the runner gate admits non-CP plain extends, and the DSA indexer decides
per batch (attn_cp_metadata is not None) instead of per server. N > 0 with a
CP strategy other than interleave fails at boot. --enable-cp-decode-attn-tp
(DeepSeek-V4, GLM-5) does not slice the attention weights inside such a graph:
its break functions run attention with the unsliced head count, so the slicing
would fail at the first replay; decode and verify full graphs are unchanged.

Why: with CP on, every prefill is eager, so even a 4-token request pays the
launch cost of all 45 layers (a ~225 ms floor on a 30k cached prefix).

Enable: SGLANG_CP_MIN_EXTEND_TOKENS=4096 together with --enable-prefill-cp
--attn-cp-size 8 --cp-strategy interleave and --cuda-graph-bs-prefill buckets
below N. Unset or 0 keeps the upstream behavior.

Evidence (A100, TP8, CP8): bs1 TTFT p50 on a 30k cached prefix with a 4-token
tail is 224.7 ms with CP forcing eager prefill and 63.6 ms once the small batch
replays the graph; 0.75 rps TPOT p50 stays at about 21 ms.

Upstream: related to sgl-project#44293 (CP + breakable graph, which
excludes speculative decoding); this takes the threshold route instead.
What: register Glm5NextForConditionalGeneration (and its NextN) in
CP_DECODE_ATTN_TP_SUPPORTED_ARCHS and wrap the DSA-MLA self_attn call of
Glm5NextDecoderLayer in maybe_use_decode_attn_tp. KDA layers keep their TP
partition (nullcontext); the MTP layer shares the decoder layer class.

Why: under CP-TP group sharing the DSA MLA attention is replicated at
attn_tp=1, so decode and verify run 64 heads per rank (q_b_proj, o_proj, w_kc /
w_vc and the sparse MLA kernel are 8x too large). The flag slices them to 8 heads
per rank for decode, target-verify and draft-extend and all-reduces at o_proj.
Without the whitelist entry model_hook raises ValueError.

Enable: --enable-prefill-cp --attn-cp-size 8 --cp-strategy interleave
--enable-cp-decode-attn-tp. The context logs "Enable CP decode attention TP".

Evidence (A100, TP8, CP8): 0.75 rps open-loop TPOT p50 36.5 -> 21.0 ms against
the same CP configuration without the flag.

Upstream: none (not submitted yet)
What: build four tiny index / length tensors with torch.tensor(...,
pin_memory=True).to(device, non_blocking=True) instead of torch.tensor(...,
device=cuda): interleave_kpool.local_query_inputs,
InterleaveCPStrategy.moe_num_token_non_padded, the extend_seq_lens of
DeepseekSparseAttnBackend.init_forward_metadata and the seq_len_t of the
IndexerKPool ragged top-k.

Why: a pageable host-to-device copy synchronizes the stream. Profiling five CP
extends showed 249 ms of cudaStreamSynchronize (about 50 ms per extend) on the
overlap scheduler. The copied values are unchanged and these sites run eagerly,
never inside CUDA graph capture.

Enable: always on. This is not gated by GPU or model: every path that reaches
these four sites uses the pinned copy.

Evidence (A100, TP8, CP8): bs1 TTFT p50 at 2048 / 4096 new tokens 264.5 / 307.0 ms,
3.8% / 3.0% lower than without; cold 35k floor 2.22 -> 2.17 s.

Upstream: none (not submitted yet)
…he fp8 encoder

What: CPU tests for the strict env parsers (SGLANG_CP_MIN_EXTEND_TOKENS,
SGLANG_SM80_SPARSE_MLA_V1_CFG, SGLANG_SM80_SPARSE_MLA_DECODE_SPLIT: unset and
valid values pass; malformed, negative and wrong-arity values raise with the
variable name) and for the fp8 KV boot check (num_stages must be 1). A CUDA test
compares the software e4m3fn encoder with torch.float8_e4m3fn, including random
values, subnormals, the round-to-nearest-even ties 1.0625, 1.1875 and 15.5, and
the exponent carry at 15.5; fixed bytes check the saturation to +-448 and NaN.

Why: these settings fail the boot instead of falling back to a default, so the
error paths need tests that run without a GPU; the encoder replaces a hardware
convert and has to match it bit for bit.

Enable: the settings tests join base-a-test-cpu and need no tilelang or triton;
the encoder test is registered for the base-b-kernel-unit 1-gpu-large runner.

Upstream: none (not submitted yet)
@hippothewild

Copy link
Copy Markdown
Author

머지하지 않고 열어 둡니다(#83과 같은 방식). 결과물은 브랜치 0.5.22-glm5.3flash-sm80(head 13d0665)과 이미지 quay.io/vessl-ai/sglang:0.5.22-glm5.3flash-sm80-13d0665 (sha256:0aba1f18cd215fe1fadbd61b5171fe773755c89036e7e24a42ed1c42c14faa7f)입니다. base v0.5.22는 upstream 미러라 여기에 머지하지 않습니다. 브랜치는 이미지 태그와 런처가 참조하므로 지우지 않습니다.

@hippothewild
hippothewild merged commit b532ac8 into v0.5.22 Nov 5, 2026
69 of 78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant