Skip to content

Escha-W2 dense + OSCAR INT2 KV + HiCache on stock sglang 0.5.19 - #1

Open
Nakanokensetsu wants to merge 8 commits into
escha-0519-basefrom
escha-0519
Open

Nakanokensetsu wants to merge 8 commits into
escha-0519-basefrom
escha-0519

Conversation

@Nakanokensetsu

@Nakanokensetsu Nakanokensetsu commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

Stock v0.5.19 → the configuration this fork actually runs in production:
dense Qwen3.8-27B-Escha-W2 with an OSCAR INT2 KV cache and HiCache (L2,
host RAM) on 2× 12 GB consumer GPUs, 128K context, 210,000-token KV pool.

The base branch escha-0519-base is the unmodified v0.5.19 tag, so this
diff is exactly what the fork adds. The seven commits are separable on
purpose — the four hicache: ones are independent of the INT2 work and two
of them are not specific to this fork at all.

Commits

commit what
escha: OSCAR INT2 KV cache the 2-bit KV cache (from upstream PR sgl-project#32129 / FutureMLS-Lab/OSCAR) and the Escha-W2 quantization layers
escha: backport upstream cache fixes sgl-project#38191, sgl-project#37943, sgl-project#39526 (which also covers sgl-project#39342)
escha: mixed HP+INT2 KV window carried but never constructed on a hybrid GDN model — kept for the hybrid wiring work
hicache: host KV pool for the INT2 device pool packed codes and scales/zeros are two tensors; the stock host pool assumes one
hicache: use cudaHostAlloc where cudaHostRegister is not device-mappable WSL2: a kernel cannot store to cudaHostRegistered memory, with any flag
hicache: read host memory once per TP group backport of sgl-project#38157 (issue sgl-project#38156), plus a configurable reserve
hicache: verify the mamba transfer geometry turn an asynchronous illegal memory access into a named assertion

Why the two HiCache fixes exist

Enabling --enable-hierarchical-cache on this stack died with
CUDA error: an illegal memory access was encountered on the first long
request. Two independent causes:

  1. The INT2 host pool disagreed with itself about how wide a token is. The
    host stride, element_dim and the device row were 192 B, 512 B and
    128 B. _assert_transfer_geometry() now refuses to start on a mismatch.

  2. The mamba (GDN state) backup kernel stores to the host pool through its
    host pointer. On WSL2 that memory is not device-mappable when it comes
    from cudaHostRegister. Isolated with tools/mamba_transfer_repro.py:

    dst on device         -> ok
    dst torch pin_memory  -> ok                    (cudaHostAlloc)
    dst cudaHostRegister  -> illegal memory access (flags 0/1/2/3)
    dst pageable          -> illegal memory access
    

The second one is the reason HiCache looked like it "silently breaks hybrid
GDN models" here; it is unrelated to sgl-project#39830.

Measured (2× RTX 5060 Ti 12 GB, TP2, 32 GB host, WSL2)

HiCache on (--hicache-ratio 1) vs off, same build:

off on
cold prefill 1010 tok/s 964 tok/s
decode, bs1 51.5 tok/s 50.7 tok/s
8 concurrent, distinct 18K prefixes, second pass 5.2 s 1.6 s
Test D (96K aggregation/enumeration) 18/20 18/20, identical per question

--hicache-ratio 2 pins 10.3 GB of host memory here (KV 2.58 GB + mamba
2.55 GB, per rank, ×2 ranks). Pinned pages cannot be paged out, so
everything else is pushed to swap: page cache collapses to 589 MB and the
machine swaps continuously while idle. Ratio 1 pins 5.2 GB and is fine.

--hicache-mem-layout layer_first is rejected by MambaPoolHost; use
page_first. L3 storage backends raise NotImplementedError for INT2
because the flat data page carries the packed codes without their
scales/zeros.

Not included

Code comments are in Japanese in the parts written for this fork.


CI States

Latest PR Test (Base): ❌ Run #35548496035
Latest PR Test (Extra): ❌ Run #35548495844
Latest PR Test (AMD ROCm 7.2): ❌ Run #35548496059

Nakanokensetsu and others added 8 commits September 21, 2026 09:16
Port of the OSCAR 2-bit KV cache (FutureMLS-Lab/OSCAR, upstream PR sgl-project#32129)
onto stock v0.5.19, plus the Escha-W2 quantization layers it needs.
Packed codes are [slots, head_num, head_dim//4] uint8; the per-group
(scale, zero) pairs live in a separate k_scales_zeros/v_scales_zeros
tensor, which matters for anything that moves a KV slot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cherry-picked behaviour from upstream sgl-project#38191 (zero-length insert in
cache_finished_req), sgl-project#37943 and sgl-project#39526. sgl-project#39342 (--enable-mixed-chunk with
extra_buffer) is covered by the sgl-project#39526 backport.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnifiedInt2HPKVPool keeps a high-precision prefix/recent window next to the
quant arena. It is NOT reached by the hybrid GDN model this fork targets --
that path always builds MHATokenToKVPool(dtype="int2") -- so the code is
carried but never constructed. Kept for the hybrid wiring work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stock MHATokenToKVPoolHost treats one token as
head_num * head_dim * itemsize contiguous bytes. INT2 breaks that twice:
packed codes are only head_dim//4 bytes per head, and the scales/zeros live
in a separate tensor that must travel with them, or a restored slot is read
with the previous occupant's quantization parameters and corrupts silently.

The host body now mirrors the packed codes only and the scales/zeros get
their own host buffer, so the host token stride, element_dim and the device
row are all the same width. _assert_transfer_geometry() checks the three at
startup: a mismatch otherwise surfaces as "CUDA illegal memory access" on
the first long request, which is expensive to diagnose. element_dim moved
behind an overridable _compute_element_dim(), because the parent derives it
from the device head_dim, which is not a byte count for a quantized pool.

L3 storage backends and MTP draft pools raise NotImplementedError: the flat
data page carries the packed codes only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mamba backup kernel stores to the host pool through its host pointer,
which requires that memory to be mapped into the device address space. On
WSL2 that does not hold for malloc'd memory registered with
cudaHostRegister -- with any flag combination -- and the kernel faults.
Measured with the production shapes (tools/mamba_transfer_repro.py):

  dst on device         -> ok
  dst torch pin_memory  -> ok                       (cudaHostAlloc)
  dst cudaHostRegister  -> illegal memory access    (flags 0/1/2/3)
  dst pageable          -> illegal memory access

SGLANG_HICACHE_HOST_ALLOC=pin|register overrides the detection.

Also fixes the error path: cudaGetErrorString() only accepts
torch._C._cudart.cudaError, so passing the raw int raised TypeError and
swallowed the real reason a registration failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rable

Backport of upstream PR sgl-project#38157 (issue sgl-project#38156): ranks reach the sizing guard
at different times, so a rank arriving after its peers have pinned their
pools sees the reduced "available" while still dividing by the full rank
count. The peers are charged twice and the guard rejects pools that fit. A
MIN all-reduce over the TP cpu_group is also a barrier, so every rank reads
before any of them allocates.

SGLANG_HICACHE_HOST_RESERVE_GB overrides the fixed 10 GiB reserve, which is
sized for machines much larger than a 32 GB host: the pools are built while
the weights are still loading, i.e. at the low point of MemAvailable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The kernel only sees cached layer base pointers and two index tensors, so a
stale pointer, an out-of-range index or a tensor that is not tightly packed
all surface as an asynchronous "illegal memory access" at whatever
synchronises next -- which is usually unrelated code. Check them on the
first call of each transfer shape and log the real geometry.
SGLANG_DEBUG_MAMBA_TRANSFER=0 disables it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What runs in production, why each number is what it is, and how it fails:
the KV pool (not the mamba cache) is the long-context ceiling, HiCache needs
cudaHostAlloc under WSL2, --hicache-ratio 2 pins 10.3 GB and makes a 32 GB
host swap, layer_first is rejected by MambaPoolHost, L3 cannot carry INT2.

Includes the verification tools, two of which need no GPU.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation jit-kernel quant hicache labels Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation hicache jit-kernel quant

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant