Replaces the interim ack_queue_len==0 band-aid with a structural fix so the perf skip is
SAFE rather than removed.
Root cause: the radix attach/defer/prune/split probes read `_node_host_write_pending`
(membership in ongoing_write_through/pending_host_backups, which are drained ASYNCHRONOUSLY
at per-rank ack completion), and `_cp_subtree_has_unprunable_state` also read the rank-local
`lock_ref`. So per-rank ack-drain timing leaked into the match_prefix truncation ->
cache_protected_len -> logical_len/len(value) -> the attach predicate. On some ranks the
prepared backup attached, on others it was dropped to the synchronous write_backup catch-up
(catch_up_all_layers draft=True), whose ack-append is per-rank. ack_write_queue then diverged
across CP ranks, so writing_check's ack_queue_len==0 early-return diverged: a strict subset
entered the writing_check_min all_reduce while the rest advanced to recv_requests' broadcast
on the same tp_cpu_group (8 ranks; dp_size==1 resets enable_dp_attention to False) -> NCCL
deadlock (prod hang: node_id=50713, all 8 GPUs 0% util).
Fix: introduce TreeNode.cp_backup_pending, a rank-replicated marker set at the rank-replicated
prepared-backup attach (_attach_prepared_cp_backup) and the guarded write_backup registration,
cleared only at the MIN-committed commit (_commit_pending_backup) / rollback. Rewire the CP
radix probes (_cp_node_split_still_pending, _split_node guard, _cp_subtree_has_unprunable_state,
_inc_hit_count) to read this marker instead of the drain-timed dicts, and DROP the lock_ref read
in the prune probe (its only cross-rank skew is the backup lock, now covered by the marker).
_node_host_write_pending stays the drain-timed accessor used only by writing_check / eviction /
the visibility check (_node_host_write_ready). With the radix-path reads replicated by
construction, len(value)==logical_len on every rank -> the prepared backup always attaches (or
rolls back) symmetrically -> the asymmetric catch-up never fires -> ack_write_queue is symmetric
-> the existing ack_queue_len==0 skip is all-or-none (no per-tick no-op MIN, best perf).
Correctness: the marker is cleared only at/after _commit_pending_backup, which runs only under
the MIN frontier, so the MIN remains the SOLE host-visibility gate (no KV-corruption class
reopened); the marker is strictly more conservative than the old dicts (stays True until commit).
Fail-fasts: double-attach raises; _split_node propagates the marker. hi_mamba unchanged (gates
on ongoing only, no async ack skip). Single-layer-draft (c3fc3ff752) preserved: the design
touches only the attach decision; the draft notifier already fires post-MoE.
Adds test_cp_hicache_symmetric_attach.py: the probes read the replicated marker not the dicts,
the prune probe ignores lock_ref, and the marker lifecycle (attach->commit->rollback) + the
double-attach guard. All pass on the fix; all fail on the pre-fix code.
Design: docs_internal/cp_hicache_symmetric_attach_design.md. Pending: ETE (GSM8K + no-hang
flood + TTFT) on the dev-cu13 container.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 876a98177cd749b5a63623ba2ae8b868289b8e59)
Run Unit Tests
SGLang uses the built-in library unittest as the testing framework.
Test Backend Runtime
cd sglang/test/srt
# Run a single file
python3 test_srt_endpoint.py
# Run a single test
python3 test_srt_endpoint.py TestSRTEndpoint.test_simple_decode
# Run a suite with multiple files
python3 run_suite.py --suite per-commit
Test Frontend Language
cd sglang/test/lang
# Run a single file
python3 test_choices.py
Adding or Updating Tests in CI
- Create new test files under
test/srtortest/langdepending on the type of test. - For nightly tests, place them in
test/srt/nightly/. Use theNightlyBenchmarkRunnerhelper class innightly_utils.pyfor performance benchmarking tests. - Ensure they are referenced in the respective
run_suite.py(e.g.,test/srt/run_suite.py) so they are picked up in CI. For most small test cases, they can be added to theper-commit-1-gpusuite. Sort the test cases alphabetically by name. - Ensure you added
unittest.main()for unittest andsys.exit(pytest.main([__file__]))for pytest in the scripts. The CI run them viapython3 test_file.py. - The CI will run some suites such as
per-commit-1-gpu,per-commit-2-gpu, andnightly-1-gpuautomatically. If you need special setup or custom test groups, you may modify the workflows in.github/workflows/.
CI Registry System
Tests in test/registered/ use a registry-based CI system for flexible backend/schedule configuration.
Registration Functions
from sglang.test.ci.ci_register import (
register_cuda_ci,
register_amd_ci,
register_cpu_ci,
register_npu_ci,
)
# Per-commit test (small 1-gpu, runs on 5090)
register_cuda_ci(est_time=80, suite="stage-b-test-1-gpu-small")
# Per-commit test (large 1-gpu, runs on H100)
register_cuda_ci(est_time=120, suite="stage-b-test-1-gpu-large")
# Per-commit test (2-gpu)
register_cuda_ci(est_time=200, suite="stage-b-test-2-gpu-large")
# Nightly-only test
register_cuda_ci(est_time=200, suite="nightly-1-gpu", nightly=True)
# Multi-backend test
register_cuda_ci(est_time=80, suite="stage-b-test-1-gpu-small")
register_amd_ci(est_time=120, suite="stage-a-test-1-gpu-small-amd")
# Temporarily disabled test
register_cuda_ci(est_time=80, suite="stage-b-test-1-gpu-small", disabled="flaky - see #12345")
Choosing Between 1-GPU Suites (5090 vs H100)
When adding 1-GPU tests, choose the appropriate suite based on hardware compatibility:
| Suite | Runner | GPU | When to Use |
|---|---|---|---|
stage-a-test-1-gpu-small |
1-gpu-5090 |
RTX 5090 (32GB, SM120) | Stage A per-commit smoke on 5090 (CUDA) |
stage-a-test-1-gpu-small-amd |
AMD CI runners | ROCm | Stage A per-commit smoke (AMD) |
stage-b-test-1-gpu-small |
1-gpu-5090 |
RTX 5090 (32GB, SM120) | 5090-compatible tests (preferred) |
stage-b-test-1-gpu-large |
1-gpu-h100 |
H100 (80GB, SM90) | Large models or 5090-incompatible tests |
Use stage-b-test-1-gpu-small (5090) whenever possible - this is the preferred suite for most 1-GPU tests.
Use stage-b-test-1-gpu-large (H100) if ANY of these apply:
-
Architecture incompatibility (SM120/Blackwell):
- FA3 attention backend (requires SM≤90)
- MLA with FA3 backend
- FP8/MXFP4 quantization (not supported on SM120)
- Certain Triton kernels (shared memory limits)
-
Memory requirements:
- Models >30B params or large MoE
- Tests requiring >32GB VRAM
-
Known 5090 failures:
- Weight update/sync tests
- Certain spec decoding tests
If a test cannot run on 5090 due to any of the above, use stage-b-test-1-gpu-large which runs on H100.
Available Suites
Per-Commit (CUDA):
- Stage A:
stage-a-test-1-gpu-small(5090),stage-a-test-2,stage-a-test-cpu - Stage B:
stage-b-test-1-gpu-small(5090),stage-b-test-1-gpu-large(H100),stage-b-test-2-gpu-large - Stage C (4-GPU):
stage-c-test-4-gpu-h100,stage-c-test-4-gpu-b200,stage-c-test-4-gpu-gb200,stage-c-test-deepep-4-gpu-h100 - Stage C (8-GPU):
stage-c-test-8-gpu-h20,stage-c-test-8-gpu-h200,stage-c-test-8-gpu-b200,stage-c-test-deepep-8-gpu-h200
Per-Commit (AMD):
stage-a-test-1-gpu-small-amd,stage-b-test-1-gpu-small-amd,stage-b-test-2-gpu-large-amd
Nightly:
nightly-1-gpu,nightly-2-gpu,nightly-4-gpu,nightly-8-gpu, etc.
Running Tests with run_suite.py
# Run per-commit tests
python test/run_suite.py --hw cuda --suite stage-b-test-1-gpu-small
# Run nightly tests
python test/run_suite.py --hw cuda --suite nightly-1-gpu --nightly
# With auto-partitioning (for parallel CI jobs)
python test/run_suite.py --hw cuda --suite stage-b-test-1-gpu-small \
--auto-partition-id 0 --auto-partition-size 4
Writing Elegant Test Cases
- Learn from existing examples in sglang/test/srt.
- Reduce the test time by using smaller models and reusing the server for multiple test cases. Launching a server takes a lot of time.
- Use as few GPUs as possible. Do not run long tests with 8-gpu runners.
- If the test cases take too long, considering adding them to nightly tests instead of per-commit tests.
- Keep each test function focused on a single scenario or piece of functionality.
- Give tests descriptive names reflecting their purpose.
- Use robust assertions (e.g., assert, unittest methods) to validate outcomes.
- Clean up resources to avoid side effects and preserve test independence.
- Reduce the test time by using smaller models and reusing the server for multiple test cases.
Adding New Models to Nightly CI
- For text models: extend global model lists variables in
test_utils.py, or add more model lists - For vlms: extend the
MODEL_THRESHOLDSglobal dictionary intest/srt/nightly/test_vlms_mmmu_eval.py