Dual Spark GB10 - SGLang - DFLASH2

#8
by emidgett1969 - opened

BAAI/AREX-2 FP8 + DFlash2 on SGLang β€” TP=2 across 2Γ— GB10

Companion to the Flash-Next card. Same launcher, same fabric, same two boxes;
a different model and a different outcome. AREX-2 is a Qwen3.8-27B fine-tune
with a vision tower and Gated DeltaNet layers, served here at FP8 alongside the
embedding indexer and a co-resident image backend.

The headline is in the Review: it is measurably slower than the base
Qwen3.8-27B-NVFP4 it derives from, and two correctness defects showed up in
long sessions. Both are recorded with sample counts rather than summarised.


Hardware

Nodes 2Γ— GB10, 121.7 GiB unified memory each
Fabric ConnectX, 200 Gb/s, direct-attached
Transport RoCE v2, gid 3/3, MTU 9000
Image NCCL 2.30.7
GPU Direct RDMA disabled β€” see Not measured

Unified memory means CUDA allocations do not reclaim page cache, and below
roughly 10 GiB free the box freezes rather than OOMs. Everything below is
written around that floor.


Model and image

  • Target: prithivMLmods/AREX-2-FP8, 67 GB on disk
  • Drafter: z-lab/Qwen3.8-27B-DFlash2
  • Image: flashnext-sglang:pr45142 (upstream dev + PR #45142)

The drafter was trained against base Qwen3.8-27B, not this fine-tune. It
transfers anyway β€” see Measured. That result is published nowhere else that we
found.

config.json reports model_type: qwen3_5, architectures: ["Qwen3_5ForConditionalGeneration"], and carries a vision_config. SGLang
instantiates Qwen3_5ForCausalLM from it β€” the text-only class β€” which is
worth knowing before reasoning about the vision path.

One trap for anyone loading this model through a tool that builds it with
from_config: the resulting config object has architectures set to None
rather than absent. Anything doing getattr(config, "architectures", []) gets
None back and the default never applies. ModelOpt's is_multimodal_model hits
this and raises TypeError: 'NoneType' object is not iterable; upstream fixed
it with or [].


Launch

Through the shared launcher:

./start-cluster.sh arex2

The resolved serve configuration, read off the startup log rather than from the
script:

flag value
--tp-size / --nnodes 2 / 2, rendezvous 192.168.100.1:29511
--context-length 262144
--max-total-tokens 1310720
--max-running-requests 24
--max-mamba-cache-size 120
--mamba-ssm-dtype float32
--mem-fraction-static 0.85
--page-size 64
--chunked-prefill-size 4096 (max prefill tokens 16384)
--kv-cache-dtype fp8_e4m3
--mamba-radix-cache-strategy extra_buffer
--cuda-graph-max-bs-decode 24, prefill graph disabled
speculative DFLASH, 8 draft tokens, steps 1, eagle-topk 1
parsers reasoning qwen3, tool-call qwen3_coder
FlashInfer autotune on

Three things differ from the base-model profile and are deliberate:

--mamba-ssm-dtype float32. This checkpoint's config asks for float32 where
the other profiles force bfloat16. It doubles GDN state traffic. The bfloat16
comparison has not been run.

8 draft tokens, not 4. Flash-Next caps at 4 because its QSA compress ratio
raises a hard NotImplementedError above it. This model has no such cap.

No --moe-runner-backend flag. Dense checkpoint, so the g1_scale_c
SM12x MoE-runner misroute does not apply. That bug is MoE-path only.

Co-residents

Both are started by the launcher after the LM is serving:

where port what
Indexer head 8002 vllm-embed, nginx proxies /v1/embeddings
Image backend worker 8188 ComfyUI 0.28.0, torch 2.11.0+cu130

The image backend's installed checkpoints are FLUX.1-schnell β€”
flux1-schnell-fp8.safetensors (17.2 GB) and flux1-schnell.safetensors
(23.8 GB bf16). Dashboards in this environment label the vision head "FLUX3";
what is on disk is FLUX.1-schnell.

It runs on the worker by design. The head carries LM rank 0 plus the
indexer and reported 13.2 GiB CUDA-visible free with no image model loaded,
against 17–24 GB for a FLUX checkpoint. The worker carries rank 1 only and had
~38 GiB. Running it on the head crosses the freeze floor on the first
generation.


Measured

Memory

At --max-mamba-cache-size 120 and mem-fraction-static 0.85, from the
server's own startup log:

GDN state, 120 slots, float32 22.85 GB
β€” conv_state 0.17 GB
β€” ssm_state 8.51 GB
β€” intermediate ssm_state cache 14.06 GB
β€” intermediate conv window 0.11 GB
KV per token 20.0 KB
KV at max_total_tokens 2,294,336 (uncapped) 45.94 GB
KV at max_total_tokens 1,310,720 (SESSIONS=5) 26.26 GB
available_gpu_mem, uncapped 11.53 GB

The 20.0 KB/token figure was taken twice β€” once uncapped and once capped β€” and
agreed to three figures both times. GDN state is reserved per request at a
fixed size regardless of sequence length, which is why slot count caps
concurrency rather than the pool does.

--max-total-tokens is the only lever that returns memory to the system here:
SGLang sizes the KV pool to fill mem-fraction-static, so freeing state yields
a bigger pool rather than free memory, and a cap above what the server would
choose on its own frees nothing.

Speculative decoding

Over 55,796 verify calls:

Accept length 3.125
Accept rate 0.304
Draft tokens 8

The base model's drafter transfers to the fine-tune. 3.125 accepted tokens
per verify at 8 drafts, from a drafter trained on Qwen3.8-27B and never
retrained for AREX-2. For comparison, Flash-Next with its own matched drafter
records 2.52 at 4 drafts over 1.39M verify calls.

Accept rate is capped by sampling temperature, not misconfiguration: SGLang's
acceptance thresholds default to 1.0, i.e. exact match, so above temperature 0
a deterministic draft is paid p(argmax) per slot.

Throughput

Peak, over the sample counts shown:

Peak decode 127.3 tok/s (4,567 samples)
Peak prefill 3,870 tok/s (trailing 7 days)
Vision tower on
Vision head on
Indexer on

Concurrency curve:

Streams Aggregate tok/s Per-stream Samples vs solo
1 33.4 33.4 3,190 1.00Γ—
2 52.1 26.0 1,025 0.78Γ—
3 78.7 26.2 73 0.79Γ—
4 98.7 24.7 2 0.74Γ—

Levels 1 and 2 are measured. Level 3 (n=73) and level 4 (n=2) are
indicative, not measured
β€” the same 100-sample bar the Flash-Next card uses.
Per-stream flattens between 2 and 3 streams rather than continuing to fall,
which is the shape worth re-measuring with real sample counts before relying on
it.


Review

Slower than the model it fine-tunes

Against RadixArk/Qwen3.8-27B-NVFP4 on the same two boxes:

AREX-2 FP8 Qwen3.8-27B NVFP4 AREX-2 as %
Peak decode 127.3 176.9 72.0%
Peak prefill 3,870 4,065 95.2%
Solo decode 33.4 53.5 62.4%
2 streams, aggregate 52.1 86.3 60.4%
3 streams, aggregate 78.7 106.7 73.8%

Solo decode is the cleanest number on both sides β€” 3,190 and 2,793 samples β€”
and AREX-2 gives up 37.6% of it.

The quantization accounts for most of that. AREX-2 is FP8 at ~13.5 GB/node;
the base is NVFP4 at ~6.8 GB/node. Decode on GB10 is memory-bandwidth-bound, so
reading twice the weight bytes per token is a direct, expected cost. It is not
a configuration fault.

Two confounds make this not a pure model comparison. AREX-2 ran with the
indexer on and the vision head on; the base ran with both off. Both
co-residents take memory and some compute on the same nodes. A like-for-like
comparison needs AREX-2 re-measured with EMBED=0 FLUX=0, which has not been
done.

Where AREX-2 holds up better: it degrades less with concurrency β€”
0.79Γ— vs 0.66Γ— at three streams β€” and its prefill is within 5%. The gap is
almost entirely in single-stream decode.

An NVFP4 build of AREX-2 could not be made to work

Four configurations were produced and all four served degenerate output: a wall
of token id 0 (! in Qwen's vocabulary), which is argmax over flat or NaN
logits. The same serving config with the FP8 checkpoint is clean, and
DRAFT=none does not change it, so neither the profile nor speculation is
implicated.

config result
NVFP4_DEFAULT_CFG (W4A4) degenerate
W4A16_NVFP4_CFG (4-bit weights, 16-bit activations) degenerate
NVFP4_MLP_ONLY_CFG degenerate
everything quantized but mtp* degenerate

What was ruled out along the way, each by measurement:

  • Not bad scales. mlp.gate_proj weight, weight_scale, weight_scale_2
    and input_scale were compared tensor-by-tensor against
    RadixArk/Qwen3.8-27B-NVFP4: identical dtypes and shapes, weight_scale_2
    agreeing to four significant figures, no NaNs, no zeros.
  • Not 4-bit activations. W4A16 fails identically to W4A4.
  • Not the GDN path specifically. The working reference quantizes it β€”
    linear_attn.in_proj_qkv carries 48 weight_scale tensors there.
  • Not metadata. Both checkpoints declare quant_method: modelopt,
    quant_algo: NVFP4, the same config_groups shape and the same ignore key.
  • Not an SGLang guard mismatch. qwen3_5.py in this image passes
    quant_config to Qwen3_5GatedDeltaNet unconditionally; there is no
    modelopt_fp4-keyed guard to miss.

The one structural difference not eliminated: the working reference has two
ignore entries (mtp*, mtp.layers.0*) while every build here excluded far
more, and SGLang's packed_modules_mapping fuses
in_proj_qkvz ← [in_proj_qkv, in_proj_z] and in_proj_ba ← [in_proj_b, in_proj_a] at load. The "quantize everything but mtp*" build was the test of
that and also failed, so it remains unexplained. Use FP8.

Correctness defects observed

defect occurrences notes
Chinese characters injected into context prompts 1 injected into the prompt context, not the completion
Session context corrupted after long runs multiple sessions over 8 hours; code output remained correct while session context was corrupted

The long-haul pattern is the more serious of the two, because the usable
artifact is fine while the state behind it is not β€” a session can appear
healthy from its output alone. Neither defect has a reproducer. Both were seen
with --mamba-ssm-dtype float32, 262144 context and 120 GDN slots; whether
either is implicated is unknown.


Relative comparison

Identical section in all four cards, from one snapshot so the numbers agree
across them.

This dataset is being retired. Collection is restarting from cleared
counters, because the configurations behind these samples cannot be
reconstructed. Read the provenance table before any figure below.

Recipe lineage

Recipe status Built on
Flash-Next original β€” first SGLang-path recipe for Flash-Next + DFlash2 on 2Γ— GB10 GLM-5.3-Flash 2Γ— Spark recipe (SGLANG_HOST_IP per rank, mamba sizing at TP=2 DFlash); ubehera/sglang-spark (NCCL sm_121 cubins); Flash-Next cookbook thread (PLE sharding by TP, two-Spark cell shape)
AREX-2 original β€” no prior recipe for this model on this hardware the Flash-Next and Qwen3.8-27B profiles in the same launcher
dsv41 derived, with original patch set sparkctl's launch model; Mia-AiLab's kit for the pinned KV pool (KV_CACHE_MEMORY_BYTES, ~774K tokens of fp8_ds_mla/rank); TensorFold grouped EXL3 prompt kernels (Apache-2.0); HeNryous's memory measurements
Qwen3.8-27B pre-existing recipe the published Qwen3.8-27B 2Γ— Spark recipe

Original findings in this set, not published elsewhere that we found:

finding card
A base-model DFlash2 drafter transfers to a fine-tune β€” accept length 3.125 at 8 draft tokens over 55,796 verify calls, drafter trained on Qwen3.8-27B and never retrained for AREX-2 AREX-2
NVFP4 of AREX-2 cannot be made to serve β€” four configurations, all token-0 degenerate, with bad scales, 4-bit activations, GDN quantization, metadata and the SGLang guard each eliminated by measurement AREX-2
Six GB10-specific fixes for Flash-Next + DFlash2 at TP=2, two of which became upstream issues (PR #45142 required, PR #45520 open) Flash-Next
nvidia-smi --query-gpu=memory.* returns [N/A] on GB10; --query-compute-apps is the only query that reports dsv41
RoCE GID must be selected by gid_attrs/types, not IPv4 match β€” a port exposes the same IPv4 at a v1 and a v2 index, and running v1 is silent Flash-Next

Provenance

Topology behind the samples Counters
Qwen3.8-27B all TP=2 cleared at cluster bring-up
dsv41 TP=2 only not cleared
AREX-2 TP=2 only not cleared
Flash-Next uncertain β€” TP=2, or mixed with single-node NVMe-offload not cleared

Flash-Next is the one profile whose topology is not established. A single-node
deployment of it is possible via file-backed PLE offload β€” the launcher carries
PLE_OFFLOAD=1 for this model specifically, ~/ple-tables holds 48 GB on the
head, and the stop list names a flashnext-sg container distinct from
flashnext-tp2. Single-node with NVMe in the decode path is slower, so any such
samples drag a cumulative aggregate down.

Known contamination in the Flash-Next series, beyond topology:

source measured cost
CONC=longhaul for part of the window 227.2 β†’ 183.0 aggregate, βˆ’19.4%
MTU 1500 / RoCE v1 for part of the window prefill and bulk transfer; decode change not observed
SESSIONS=5 since today 32% pool cut against a 95.50% hit rate

Counters are cumulative since process start, so the existing samples cannot be
segmented by configuration after the fact.

What the measurement says should be true and isn't

Flash-Next is a 206-shard MoE. Its decode cost is set by active parameters per
token, a fraction of the total, so it should beat a dense 27B comfortably.
It measures 8% below it. That inversion is the strongest single argument
that the Flash-Next series is not describing one configuration, and it stands
independent of any individual contamination source.

Flash-Next Qwen3.8-27B dsv41 AREX-2
Engine SGLang SGLang vLLM SGLang
Quantization NVFP4 NVFP4 EXL3 2.8bpw FP8
Speculation / draft tokens DFlash2 / 4 DFlash2 / 8 DSpark / 5 DFlash2 / 8
Indexer during measurement off off off ON
Vision head during measurement off off off ON

Aggregate throughput, tok/s

Streams Flash-Next Qwen3.8-27B dsv41 AREX-2
1 49.0 53.5 60.6 33.4
2 80.8 86.3 74.5 52.1
3 104.2 106.7 84.3 78.7
4 121.5 β€” 94.8 98.7
5 132.5 β€” β€” β€”
6 142.4 β€” β€” β€”
7 156.4 β€” β€” β€”
8 171.9 β€” β€” β€”
9 177.9 β€” β€” β€”
10 190.5 β€” β€” β€”
11 191.8 β€” β€” β€”
Peak decode 213.7 176.9 126.0 127.3
Peak prefill 4,539 4,065 not recorded 3,870

No winner is marked. Qwen3.8-27B is the only column with established provenance
and cleared counters, and it is also the thinnest β€” three levels, two clearing
100 samples.

Per-stream retention β€” do not rank on this

c2 c3 c4
AREX-2 0.78Γ— 0.79Γ— 0.74Γ—
Flash-Next 0.82Γ— 0.71Γ— 0.62Γ—
Qwen3.8-27B 0.81Γ— 0.66Γ— β€”
dsv41 0.61Γ— 0.46Γ— 0.39Γ—

Every value here is a ratio against the c1 figure, which may be projected
rather than sampled.
If it is, these are ratios to a projection and the
ranking they imply β€” including AREX-2 retaining best β€” is not established. The
aggregate columns above stand on their own; this table does not.

Cost of the first additional stream

c1 β†’ c2
Flash-Next βˆ’0.176
Qwen3.8-27B βˆ’0.193
AREX-2 βˆ’0.220
dsv41 βˆ’0.385

Same c1 dependency as the table above. dsv41's step is more than twice any
other, which is large enough to survive a projected baseline being somewhat off,
but it is not a clean figure either.

Peak against the highest measured aggregate

peak curve max ratio
Flash-Next 213.7 191.8 1.11Γ—
AREX-2 127.3 98.7 1.29Γ—
dsv41 126.0 94.8 1.33Γ—
Qwen3.8-27B 176.9 106.7 1.66Γ—

Sample depth

levels levels β‰₯ 100 samples n at top level decode samples
Flash-Next 11 8 8 24,050
dsv41 4 3 63 β€”
AREX-2 4 2 2 4,567
Qwen3.8-27B 3 2 10 3,167

Depth is not integrity. Flash-Next has the most samples and the least
established provenance.


Not measured

Stated plainly because it would otherwise look like data:

  • Concurrency above 2 streams. n=73 at three and n=2 at four. The curve
    above two streams is a shape, not a figure.
  • The like-for-like comparison against the base model. AREX-2's numbers
    carry a co-resident indexer and image backend; the base model's do not.
  • --mamba-ssm-dtype bfloat16. Would halve 22.85 GB of GDN state traffic
    and is the obvious response to the decode gap. Never run; the checkpoint's
    config asks for float32.
  • Vision, end to end. The tower loads, but no image request has been
    confirmed against this model with a data-URI payload. models.json still
    advertises "input": ["text"], so the agent harness cannot send one.
  • Cold prefill above 32K. Silent worker-rank death near 62K and a
    multi-node wedge at 95–100K are reported for 262K-context configs on this
    hardware. 32K passes here; above that is untested.
  • Whether GPU Direct RDMA helps. It is off: nvidia-peermem does not load
    on aarch64 (missing ib_register_peer_memory_client), and DMA-BUF reports
    unavailable even with NCCL_DMABUF_ENABLE=1, NCCL_NET_GDR_LEVEL=SYS and
    NCCL_NET_GDR_READ=1. Collectives stage through host memory. On unified
    memory, host and device are the same physical DRAM, so the usual argument for
    GDR is weaker than on a discrete card β€” but what it would buy here is
    unmeasured either way.
  • MTU persistence. 9000 is set and verified at launch with an 8972-byte
    DF ping, but is not persistent across reboot. No netplan or NetworkManager
    config holds it.

Terms, because they are easy to conflate

term here it means
unified one model across both boxes (TP=2)
split two different models, one per box
slot a GDN/mamba state reservation, --max-mamba-cache-size
stream one agent session, which issues 1–3 concurrent requests
pool the KV cache, sized in tokens by --max-total-tokens

Credits

This profile is original; it inherits its scaffolding:

  • The launcher, fabric handling and the six GB10 fixes β€” flashnext-dflash2-2x-gb10.md
  • PR #45142 β€” DFlash aux hidden-state capture, required by the image
  • z-lab/Qwen3.8-27B-DFlash2 β€” drafter, trained against the base model
  • The is_multimodal_model architectures=None fix β€” upstream ModelOpt
  • RadixArk/Qwen3.8-27B-NVFP4 β€” the working reference checkpoint the failed NVFP4 builds were compared against

Sign up or log in to comment