Research
San Francisco, California
Zyphra Inference Wave 2: Faster MoE and Long-Context Serving on MI355X
Skyler Szot, Srivatsan Rajagopal, Abhinav Sarje, Varun Subramanian, Bhavana Mehta, Praneeth Medepalli, Quentin Anthony
Zyphra presents the second wave of inference optimizations in the Zyphra Optimization Engine (ZOE) for AMD Instinct MI355X GPUs. We measure the empirical compute and memory-bandwidth limits of the MI355X, write custom HIP kernels for mixture-of-experts layers and sparse attention in our production forks of vLLM and SGLang, and evaluate MXFP4 quantization and speculative decoding. ZOE speeds up GLM-5.3 time to first token at 512K tokens of context by 2.8x with no prefix-cache hits and by 3.6x at a 90% hit rate, and it raises DeepSeek-V4-Pro single-user decode throughput at 1M tokens of context by 1.40x.
In frontier MoE models, much of the inference time goes to operations that use little of the GPU’s matrix-core throughput. These include small expert GEMMs with uneven row counts, reading expert weights from High-Bandwidth Memory (HBM), sparse-attention indexing, and top-k selection over long contexts. For GLM-5.3 at 512K tokens of context, our fused MoE and DSA indexer kernels replace operations that take most of the prefill time, which speeds up TTFT by 2.8x to 3.6x (Figure 1).
To further improve Zyphra Cloud performance for our users, we targeted those bottlenecks directly. We measured the practical compute and bandwidth limits of MI355X, wrote custom HIP kernels for MoE and sparse attention, and integrated them into our production forks of vLLM and SGLang. We have added these optimizations to our core Zyphra Optimization Engine (ZOE) which powers our inference cloud and can be deployed on-prem.
For these results, we built upon our first round of Zyphra inference optimizations, which focused on tuning, parallelism, and communication. In this second phase, we go deeper into the model forward pass. We measure the empirical rooflines of the MI355X, write custom HIP kernels for the most expensive operations (in particular the MoE feed-forward block and sparse attention (CSA/DSA)), and evaluate how those kernels compose with quantization and speculative decoding in deployed configurations of our forked versions of vLLM and SGLang. All work in this blog targets the MI355X (gfx950 / CDNA4). Our goals in this blog are to:
a.) Demonstrate the performance we can deliver in our inference cloud using ZOE
b.) Share our optimization research progress
c.) Preview some of our upcoming inference work
FASTER TTFT
GLM-5.3 / 512K context / 90% prefix-cache / vs. vLLM v0.29.0 on MI355X
Key Results
Prefill: ZOE reduces GLM-5.3 time to first token (TTFT) at 512K tokens from 178.8 s to 64.6 s with no prefix-cache hits, a 2.8x speedup[^1]. At a 90% hit rate it reduces TTFT from 42.3 s to 11.8 s, a 3.6x speedup. These results use vLLM v0.29.0 with our DSA lightning indexer and fused MoE kernels.
Decode: Our CSA top-K selection HIP kernel increases end-to-end DeepSeek-V4-Pro single-user decode throughput at 1M tokens of context from 43.5 to 61.1 tokens/s in SGLang v0.5.19, a 1.40x speedup.
Kernels: Our CSA top-K selection kernel is 11.1x faster than the SGLang kernel for one request at 1M tokens of context and 9.9x faster for a batch of 32 requests. Our lightning indexer is 1.15x to 2.37x faster than AITER (best per shape) and runs at 0.73x to 1.37x of the throughput of AITER’s Gluon kernel (Figure 11). Our fused MoE kernel is up to 2.0x faster than the vLLM Triton WNA16 kernel on Kimi-K2.6 (a16w4). It is up to 1.17x faster than AITER on DeepSeek-V3.2 (FP8) for forward passes of up to 256 tokens.
Quantization: The AMD MXFP4 quantization of GLM-5.3 reduces the model size from 756 GB to 410 GB, and its scores stay within 3 points of the FP8 model on AIME25, AIME26, Apex-Shortlist, and HMMT. At low concurrency it decodes slower than the FP8 model. A decode step with few requests reads only a small part of the expert weights, so halving their size saves little time, and the current MXFP4 kernels in vLLM add more time than that. With 32 concurrent requests each step reads more of the expert weights, and the MXFP4 model is 1.09x faster than the FP8 model.
Hardware: We carried out a detailed roofline analysis of MI355X hardware so that kernel performance can be measured against achievable hardware limits rather than just the datasheet-advertised FLOP/s numbers. In our microbenchmarks the MI355X reaches 98% of its datasheet FP8 MFMA peak and 78% of its datasheet HBM bandwidth.

Figure 1. ZOE’s largest gains appear at long context. Single-request speedup of ZOE over the stock vLLM and SGLang ROCm images across context lengths. The dashed line at 1× is the stock image. Left: GLM-5.3 TTFT in vLLM v0.29.0 at three prefix-cache hit rates. Right: DeepSeek-V4-Pro single-user decode throughput in SGLang v0.5.19 with our CSA top-K kernel. Full results are in Figures 12 and 14 and the Appendix.
We describe our methodology and its limitations at the end of this post. In short, all results come from a single MI355X node without live traffic, speculative decoding results depend on the prompt, and both our stack and the baselines are changing quickly, so all advertised results are point-in-time.
What This Post Covers
Hardware Rooflines: the compute and HBM bandwidth the MI355X delivers in practice, which we use as the ceiling for every kernel that follows.
Optimizations: our four new HIP kernels (fused MoE, DSA lightning indexer, CSA top-K selection, and CSA dual-scope decode), and the speculative decoding and quantization methods we evaluated.
Kernel Results: each kernel measured in isolation against the AITER, vLLM, and SGLang kernels it replaces.
Serving Results: how the kernels change end-to-end prefill TTFT and decode throughput.
Quantization: accuracy and decode throughput of the MXFP4 GLM-5.3 checkpoint, and why today it is faster than FP8 only at high concurrency.
Agentic Performance Benchmarks and Future Work: the AgentX benchmark and what we are working on next.
Methodology and Limitations: how we measured and how to read the numbers.
Appendix A and Appendix B: the data behind every figure and the roofline measurement setup.
Introduction
Many frontier open-weight models have converged on two architectural choices that shape inference cost. The first is fine-grained mixture-of-experts (MoE) models which were popularized by models such as DeepSeek-V3. In MoE models, rather than every parameter being activated in the forward pass, a router selects k of N experts to activate. This allows the parameter count to grow without increasing FLOPs per token. As the parameter count grows, a greater proportion of the total parameters are spent on expert MLPs rather than sequence mixers such as attention. Fine-grained MoEs prefer to increase expert count and decrease expert width, resulting in many smaller experts rather than fewer larger ones. While this is attractive from a modeling perspective, it is awkward for inference. Specifically, since there are now many small experts, fine-grained MoEs provide less work to easily parallelize across[^2]. They also have many more kernels to launch, and they increase the chances of an uneven workload across experts[^3]. Therefore, at decode the MoE block can spend much of its time moving expert weights from HBM, and at prefill it becomes a stack of independent GEMMs with widely varying compute per expert.
The second architectural choice is cheaper (per token at long context) attention variants such as linear attention and sparse attention. For example, DeepSeek-V3.2 and GLM-5.1 use DeepSeek Sparse Attention (DSA), where an indexer scores past positions and main attention reads only the selected KV entries. Similarly, DeepSeek-V4 uses compressed sparse attention (CSA), which attends over the highest-scoring compressed positions plus a local window of about 128 recent tokens. In both designs the attention step reads a fixed number of KV entries for each query (2,048 for DSA, and 512 compressed entries plus the window of about 128 tokens for CSA), so its cost per query stays constant as the context grows. The selection step scores every past position (every compressed position in CSA) for each query. Its cost per query grows linearly with context length, and its cost over a full prompt grows with the square of the prompt length. As the context grows the selection step becomes the larger part of the attention cost.
DeepSeek Sparse Attention (DSA)
Figure 2. DSA separates selection from attention. Both panels decode the same 16-token sequence one token at a time. The newest token produces a query (q). Attention computes a weighted sum of the value vectors it reads, with weights from the query-key scores, and this sum is the output (o) for that token. Regular attention (left) reads every KV entry. In DSA (right) an indexer first scores every past position with its own query (qᵢ) and a 128-dimensional FP8 index key per position. Main attention then reads the k highest-scoring entries. One indexer score costs fewer FLOPs and reads fewer bytes than attending to the same position (main attention uses 576-dimensional keys and also sums the value vectors). The indexer still scores every past position, so its cost per token grows linearly with context length.
Compressed Sparse Attention (CSA)
Figure 3. CSA similarly keeps the expensive attention read sparse, but still requires a top-k selection pass over the compressed history. CSA reduces the indexer cost by performing selection over compressed keys prior to the main attention.
MI355X Empirical Rooflines
Figure 4. The roofline model on MI355X[^6]. Left: HBM stacks transfer data to the compute die. Three gauges show the HBM bandwidth used, the MFMA throughput, and the arithmetic intensity of the kernel. Right: roofline chart. The sloped line is the measured HBM bandwidth and the horizontal lines are the measured MFMA peaks for BF16, FP8, and FP4. The animation first sweeps one kernel’s arithmetic intensity from the memory-bound region to the compute-bound region. It then lowers the precision of one memory-bound kernel and one compute-bound kernel. Last, it places the fastest measured GEMM shape for each precision on the chart.
Appendix B lists the instructions, flags, and sweep settings for each measurement.
Test | Measured | Datasheet (dense) | % of datasheet |
|---|---|---|---|
FP4 MFMA peak (TFLOP/s) | 9830.2 | 10066.3 | 97.7% |
FP8 MFMA peak (TFLOP/s) | 4936.2 | 5033.2 | 98.1% |
BF16 MFMA peak (TFLOP/s) | 2363.1 | 2516.6 | 93.9% |
HBM Bandwidth, read-only reduction (GB/s) | 6271.2 | 8000.0 | 78.4% |
HBM Bandwidth, C++ copy kernel (GB/s) | 6068.8 | 8000.0 | 75.9% |
HBM Bandwidth, torch memcpy, median over runs (GB/s) | 5280.1 | 8000.0 | 66.0% |
Table 1. MI355X peaks measured on one GPU against the AMD datasheet peak theoretical values without sparsity. MFMA peaks are the mean of 100 experiments. The datasheet compute peaks assume a matrix instruction issues on every cycle at the 2.4 GHz peak clock, and the datasheet bandwidth assumes the HBM interface transfers data on every clock cycle, which is often not achievable in practice.

Figure 5. A visualization of empirical MI355X capabilities. Left: empirical MI355X roofline. The x-axis is arithmetic intensity, which is the number of FLOPs a kernel performs per byte it moves to or from HBM. Solid lines are the MFMA ceilings, measured by running matrix fused multiply-add (MFMA) instructions in a loop (Table 1). Dashed lines are the throughput of the fastest GEMM (general matrix-matrix multiplication) shape for each data type, and the circle on each line marks that shape’s arithmetic intensity. Squares are the AITER paged-attention decode results from Table 2. The shaded band covers 26 to 137 FLOP/byte, which spans the median arithmetic intensities of the MoE GEMM paths in Table 3. Right: throughput of the fastest GEMM shape as a percentage of the MFMA peak, shown separately for PyTorch (hipBLASLt) and Transformer Engine. Transformer Engine dispatches MXFP4 GEMMs to AITER a4w4 kernels.
Selection | KV dtype | KV-read GB/s | Config | % of HBM read-only bandwidth | % of datasheet HBM bandwidth | % of HBM copy bandwidth | TFLOP/s | FLOP/byte |
|---|---|---|---|---|---|---|---|---|
Best KV-read bandwidth | BF16 | 6010.1 | B32, L16384, kvH16, gqa1 | 95.8% | 75.1% | 99.0% | 6.0 | 1.0 |
Best KV-read bandwidth | FP8 | 5574.1 | B32, L65536, kvH8, gqa4 | 88.9% | 69.7% | 91.8% | 43.2 | 7.8 |
Best throughput | BF16 | 5847.1 | B128, L16384, kvH4, gqa16 | 93.2% | 73.1% | 96.3% | 93.6 | 16.0 |
Best throughput | FP8 | 5556.0 | B32, L65536, kvH8, gqa16 | 88.6% | 69.5% | 91.6% | 172.4 | 31.0 |
Table 2. AITER pa_fwd_asm paged-attention decode on MI355X GPU 0. Head dimension 128, block size 16, contiguous block tables. In the Config column, B is batch size, L is sequence length, kvH is the number of KV heads, and gqa is the GQA ratio. The three percentage columns divide the KV-read bandwidth by HBM bandwidth figures from Table 1. The read-only reduction kernel reached 6271.2 GB/s and the C++ copy kernel reached 6068.8 GB/s. The datasheet value is 8000 GB/s. The expected arithmetic intensity of decode attention is approximately 2 × GQA ratio / bytes per KV element.
Table 1 compares the empirical ceilings per workload with the AMD datasheet values. Each datasheet value describes an idealized workload, and we are attempting to demonstrate the spectrum from HW maxima to SW state. The compute peak is 256 compute units × the 2.4 GHz peak clock × the FLOPs each compute unit’s matrix cores complete per clock, so it assumes a matrix instruction issues on every clock cycle. The bandwidth peak assumes the HBM interface transfers data on every cycle.
Looped MFMA instructions are nearly the datasheet’s idealized compute workload. They issue back-to-back on operands already in registers, with no memory traffic or synchronization, so they can fall short only by running below the peak clock or by leaving issue cycles empty. Therefore, MFMA reaches 98% of the datasheet peak for FP8 and FP4 and 94% for BF16.
No memory workload can match advertised bandwidth assumptions, because DRAM cannot transfer on every cycle. Its banks pause for refresh, and opening a new row or switching the bus between reads and writes costs cycles. Of our three bandwidth tests, a read-only pass over a large contiguous buffer comes closest to the assumption and reaches 6.27 TB/s (78% of the datasheet). A copy kernel also writes, and the switches between reads and writes introduces some overhead, so it reaches 6.07 TB/s (76%). PyTorch’s copy of a 256 MiB buffer uses PyTorch’s general-purpose copy kernel and reaches 5.28 TB/s (66%). Choosing a bandwidth benchmark is therefore a choice of where to sit between the hardware’s limit and the current software (See Section III.A of our earlier report). A low-level benchmark kernel demonstrates hardware performance, and an application benchmark measures both how well that application is optimized for the hardware and that application workload’s drift from idealized conditions for hardware.
Figure 5 shows how close GEMMs and the AITER paged-attention decode kernel get to the measured ceilings. The fastest GEMM shapes reach 66% of the measured MFMA peak in BF16 and 66% in FP8. In FP4 they reach 53% with Transformer Engine and 36% with hipBLASLt. A GEMM loads its operands from HBM and stages them through L2 and LDS before the MFMA instructions run, and the looped MFMA benchmark keeps its operands in registers. The AITER paged-attention decode kernel reads the BF16 KV cache at 6,010 GB/s, which is 96% of the read-only bandwidth (Table 2). Its arithmetic intensity is 1 to 31 FLOP/byte. That is below every ridge point, so the kernel is memory-bound and HBM bandwidth determines the best possible kernel throughput.
We profiled every MoE GEMM and helper kernel at served shapes with rocprofv3 hardware counters and placed each one on this roofline (Table 3). At the median these GEMMs use 4% to 7% of peak compute. Their median arithmetic intensity is 26 to 137 FLOP/byte (shaded band in Figure 5). This is below every ridge point, so the highest throughput they can reach is set by HBM bandwidth. For example, the dense BF16 path has a median arithmetic intensity of 26.4 FLOP/byte. At that intensity the bandwidth limit is 6.07 TB/s × 26.4 FLOP/byte = 160 TFLOP/s, which is 7% of the BF16 MFMA peak. Table 3 therefore also reports the HBM bandwidth each path achieves as a percentage of peak HBM bandwidth. At the median this is 69% for the dense BF16 path and 32% for the FP8 split path. The Bound by column in Table 3 gives the main limit for each path. The dense BF16 path is limited by HBM bandwidth. The FP8 block-scale path is limited by fixed overhead at low M and by HBM bandwidth at mid M. The a16w4 path is limited by tile padding at low M. The FP8 split path is limited by latency and occupancy. We designed the fused MoE kernel in the next section around these limits.
MoE GEMM Path | Arithmetic intensity (FLOP/byte), median over profiled GEMM shapes | % of peak compute (median / max over shapes) | % of peak HBM bandwidth (median / max over shapes) | Tile padding (median over shapes) | Bound by |
|---|---|---|---|---|---|
Dense BF16 | 26.4 | 5.1% / 6.6% | 69.0% / 88.8% | 1.5x | HBM bandwidth |
FP8 block-scale (2-stage) | 31.8 | 4.0% / 6.7% | 65.7% / 84.9% | 6.9x (16x at M=1) | HBM BW at mid-M, overhead at low-M |
a16w4 (BF16 x INT4) | 55.0 | 5.6% / 22.5% | 42.9% / 81.6% | 10.3x (16x at M≤4) | padding at low-M, mixed otherwise |
FP8 split (3-kernel) | 136.7 | 7.2% / 13.1% | 32.3% / 47.9% | 1.5x | latency / occupancy |
Table 3. MoE GEMM code paths profiled with rocprofv3. Each row is one MoE GEMM implementation. For each implementation we profiled one GEMM shape for every combination of stage (stage 1 is gate+up and stage 2 is down-projection), M, and parallelism setting used by the models we serve on Zyphra Cloud. The median and maximum values in each row are taken over these shapes. Arithmetic intensity is FLOPs per byte moved to or from HBM. Tile padding is the number of rows the matrix cores compute divided by the number of real token rows.
At low M the largest source of wasted MFMA work is tile quantization[^7]. The MFMA instructions we use compute tiles of at least 16 rows, and a grouped GEMM cannot place rows from two experts in one tile (every row in a tile is multiplied by the same expert’s weights). At low M each active expert holds one or two token rows, and the rest of each 16-row tile is padding. With tokens spread evenly the padding factor is min(16 × E / (M × k), 16), so tiles become full near M = 512 for E = 256 (DeepSeek-V3.2 and GLM) and near M = 768 for E = 384 (Kimi). The time cost of the padding depends on what limits each path (Table 3). On the dense BF16 path the padded MFMA work runs while the kernel waits for expert weights from HBM, and the kernel loads each weight tile once regardless of how many of its rows hold real tokens. On the a16w4 path padding is the main limit at low M. Its INT4 weights are a quarter of the size of BF16 weights, so each weight tile loads in a quarter of the time, and the padded MFMA work and the INT4-to-BF16 conversion take a larger share of each tile’s time. On the FP8 paths Table 3 lists fixed overhead (block-scale) and latency and occupancy (split) as the limits at low M. Figure 6 shows the padding for DeepSeek-V3.2 shapes.
Optimizations
The roofline measurements give the hardware limits that we compare each kernel against. Our second wave of optimizations includes four custom kernels, which replace one MoE kernel and three sparse attention kernels in vLLM and SGLang (each is described below). We also evaluated existing quantization and speculative decoding methods on MI355X (the MXFP4 recipe is from AMD Quark, and MTP and DSpark are published methods).
First we describe our new kernels and the mechanisms by which they lead to improved utilization and performance.
Zyphra Fused MoE Kernel Tile Scheduling
Figure 6. Expert tiles in the fused MoE kernel at DeepSeek-V3.2 shapes. The model has 256 experts and sends each token to 8 of them. The matrix cores compute tiles of 16 rows. With one token the sort kernel builds a worklist with one tile for each of the 8 selected experts, and the other 248 experts get no tiles. Each tile holds the token in one row and padding in the other 15 rows. At decode the kernel spends most of its time waiting for each expert’s weights to arrive from HBM. It loads each weight tile once regardless of how many rows hold real tokens, so the padding rows add MFMA work without adding HBM traffic. As the batch grows the tokens first spread across more experts until all 256 are active at 32 tokens. They then fill the existing tiles until every tile is full at 512 tokens. The animation assumes that tokens spread evenly across experts.
Fused MoE. We split the expert MLP into two fused stages. The first stage is a gate+up GEMM whose epilogue computes the SwiGLU activation in registers, which removes one HBM round trip. The second stage is a down-projection GEMM whose epilogue applies the top-k weighted combine. A sort kernel buckets token-to-expert edges by expert and emits a worklist of tiles, so experts that receive no tokens get no tiles. The kernel loads the weights for the next tile from HBM while the current tile computes. The same structure serves BF16, FP8 block-scale, W4A16 INT4, and the MXFP4 and MXFP8 microscaling formats. The microscaling formats feed their E8M0 block scales directly into the hardware scaled MFMA instruction.
Zyphra DSA Kernel
Zyphra CSA Top-K Kernel
Figure 8. CSA top-K selection for one request at 1M tokens of context. The SGLang radix-select kernel (top) runs the whole selection on one of the GPU’s 256 compute units, and each thread issues one load at a time. Our kernel (bottom) issues eight loads per thread at once and splits the scores into 32 chunks on 32 compute units. Each unit finds the 512 highest scores in its chunk. One unit then merges the 32 lists into the 512 highest scores over the full context. The timers show the measured kernel times (748.0 µs and 67.2 µs).
CSA top-K selection. The SGLang HIP top-K kernel uses radix select and runs the entire computation for one request on a single compute unit out of 256, consuming scores one load at a time. To improve this, we made two changes. Each thread now issues eight loads together and processes the batch when it arrives. For contexts of roughly 32K tokens and above, the context is partitioned into 8, 16, or 32 chunks on different compute units. Each unit finds its local top 512, and a short merge stage produces the true top 512.
CSA dual-scope decode. Our attention kernel fuses the sliding-window scope and the compressed top-K scope into a single streaming pass with online softmax. SGLang implements this stage in Triton. Ours is a custom HIP kernel for gfx950 that computes an identical result up to numerical tolerances. For a single-user decode there is little work per call, so we shard the selected keys across the GPU. Each shard produces a partial result and a log-sum-exp statistic, and a combine step merges the partials into exactly the single-pass answer. Keys are dequantized from FP8 into LDS once and reused.
Speculative Decoding
Figure 9. Speculative decoding on MI355X. Top: decode timelines for no speculation, MTP, EAGLE, and DSpark over the same time span, with two rounds for each method. Yellow blocks are drafter passes and blue blocks are target passes. The labels above each timeline are the tokens that each round produces. Middle: tokens pass through the target’s transformer layers, and each method’s drafter reads hidden states from the layers marked. Bottom: the GPU reads each weight tile from HBM once per pass and applies it to every token in the pass. A pass with more tokens therefore has a higher arithmetic intensity and moves up the bandwidth line of the roofline. The last step raises the load to 64 concurrent requests, where the DSpark target pass in the animation reaches the FP8 MFMA peak. The MTP timing in the animation matches the measured 2.7x single-user speedup for GLM-5.3. The EAGLE and DSpark acceptance in the animation is illustrative.
Speculative decoding. At low concurrency, the time for a decode step grows slowly with the number of tokens in the step. For GLM-5.3 in the vLLM baseline with MTP disabled, a step of 1 token takes 17.7 ms and a step with 32 tokens (one for each of 32 requests) takes 27.0 ms (Table A7). The step reads each weight it needs from HBM once and applies it to every token in the step, and it runs the same sequence of kernels and all-reduces for any number of tokens. Speculative decoding (Leviathan et al.; Chen et al.) uses this property. A small drafter proposes the next few tokens. The full model (the target) then processes the current token and all proposed tokens in one forward pass. The target checks the proposed tokens in order with a rejection-sampling rule. It keeps the tokens before the first rejected token and then samples one token of its own. This rule keeps the output distribution identical to decoding with the target alone. The speedup depends on how many proposed tokens the target accepts, and the acceptance rate depends on the prompt (see the Decode section).
The methods differ in how they draft. Multi-Token Prediction (MTP) adds a transformer layer that is trained with the model. It takes the target’s last hidden state and the embedding of the newest token. It uses the target’s LM head and proposes one token per drafter pass. EAGLE trains a separate small head on the target’s hidden states. It proposes a tree of candidate continuations, and the target checks all branches in one pass. DSpark proposes a block of tokens in one drafter pass, and a small sequential head conditions each proposed token on the tokens before it. A confidence head estimates how likely each proposed token is to be accepted. The scheduler uses these estimates and the server load to set how many proposed tokens each request sends to the target. We use MTP for GLM-5.3 and DeepSeek-V4 and DSpark for Kimi-K3 (for each model this is the method with the highest decode throughput among those we tested on MI355X).
The speedup decreases as concurrency increases (2.7x at 1 concurrent request and 2.0x at 32 for GLM-5.3 in the vLLM baseline, Table A7). A straight line through the two MTP-disabled step times gives about 17.4 ms per step plus 0.3 ms per token ((27.0 − 17.7) / 31). At 1 request each proposed token adds about 2% to the step time. At 32 requests each proposed position adds 32 tokens (one per request) and about 36% to the step time.
Quantization. Quantization stores weights in fewer bits. Fewer bits mean less memory for the weights and fewer bytes read from HBM in each forward pass, and on MI355X the FP4 MFMA peak is twice the FP8 peak (Table 1). The cost is lower precision, so each quantized model has to be checked for lost accuracy. MXFP4 is one of the microscaling (MX) formats in the Open Compute Project MX specification (Rouhani et al.). Each block of 32 values shares one 8-bit power-of-two scale, and each value is a 4-bit float with one of eight magnitudes (0, 0.5, 1, 1.5, 2, 3, 4, or 6). This is 4.25 bits per value, about half the size of FP8. The MI355X scaled MFMA instruction takes MXFP4 values and their block scales as inputs. We evaluate the MXFP4 quantization of GLM-5.3’s experts that AMD produced with its Quark toolkit, for retained reasoning ability and for decode throughput.
Kernel Results
The kernel benchmarks use the shapes of Kimi-K2.6, DeepSeek-V3.2, and GLM-5.1. The serving results use the models we serve now. GLM-5.1 and GLM-5.3 have the same MoE and indexer shapes, so the GLM kernel results carry over directly.
Our fused MoE baselines are the AMD AITER kernels for FP8 and the vLLM Triton WNA16 kernel for a16w4. A dense BF16 PyTorch implementation serves as our correctness reference.

Figure 10. Fused MoE kernel speedup over the baseline kernel as a function of M, the number of tokens in one forward pass. At decode M is the number of tokens decoded in one step[^8], and at prefill it is the number of prompt tokens in the pass. Values above 1 mean our kernel is faster[^9]. Left: Kimi-K2.6 a16w4 (H=7168, E=384, top-k=8) against the vLLM Triton WNA16 kernel. Center: DeepSeek-V3.2 FP8 (H=7168, E=256, top-k=8) against AITER. In these two panels Full-N uses the full expert width and TP8 uses one eighth of it, which is the shape each GPU runs under 8-way tensor parallelism. Right: ZAYA1 FP8 (E=24, top-k=1) against AITER. All timings use HIP graphs, as in production.
Our kernel is faster than the baselines at most values of M up to 256. Small values of M correspond to decode steps and short prefill chunks. On Kimi-K2.6 a16w4 the baseline is a Triton kernel, and our kernel is faster at every M except M = 1 at full expert width, by up to 2.0x. On the FP8 shapes AITER is faster at higher M. The crossover is between M = 512 and M = 1,024 at full expert width and between M = 256 and M = 512 under TP8. We designed our kernel for decode, where each expert receives a few token rows (M × k / E is 8 or less for M up to 256 on DeepSeek-V3.2). ZOE selects the MoE kernel by M from these measurements. It runs our kernel below the crossover and AITER above it.
We also measured several variants that ran slower than the kernel described in the Optimizations section. We list them so that others can skip them. A single kernel that fuses both stages produced correct output and ran 1.7x to 2x slower for M ≥ 256 (the fused kernel needs the registers of both stages at once and spills the excess to scratch memory). Reordering the grid so that the most-used experts run first ran slower at every M. That reordering aimed to reuse expert weights from L2, and it lost the reuse of activations from L2 that the default column-major order provides. For the MX formats, an epilogue that sums each token’s k expert outputs with atomic adds ran slower than our default epilogue. Three more variants also ran slower. These were wider M-row weight reuse, an LDS cache for activations at M = 1 for a16w4, and the AITER-style INT4 weight preshuffle.

Figure 11. Lightning indexer throughput in TFLOP/s on MI355X with a causal window, for our kernel and two AITER baselines. AITER Gluon is AITER’s kernel written in Gluon, a lower-level language in the Triton project. AITER (best per shape) is the fastest of the other AITER kernels on each shape. H is the number of indexer heads and D is the head dimension. Shapes are query rows × KV positions. A square shape has as many query rows as KV positions (prefill of one prompt). A skinny shape has 2,048 or 4,096 query rows and 16K to 64K KV positions (prefill of a short chunk after a long cached prefix). The label above each of our bars is our throughput divided by AITER Gluon’s.
Our kernel is 1.9x to 2.4x faster than AITER (best per shape) on the square shapes and 1.15x to 2.1x faster on the skinny shapes. AITER’s throughput stays between 445 and 492 TFLOP/s at H=32 and between 670 and 782 TFLOP/s at H=64 on every shape. These shapes range from 2,048 to 65,536 query rows. Their KV arrays range from 0.5 MiB, which fits in the 4 MiB L2 of each XCD, to 8 MiB, which does not. AITER’s throughput changes little across these shapes, so neither memory nor the number of rows limits it. Its speed is set by the work it does for each block of KV positions.
On the square shapes our kernel is 1.07x to 1.25x faster than AITER Gluon at H=32 and runs at 0.98x to 1.11x of its throughput at H=64. Gluon lets the kernel author set data layouts, allocate LDS, and issue asynchronous copies. AITER’s Gluon kernel uses these to copy KV tiles into LDS while it computes scores with MFMA instructions, which is the same structure as our kernel (Optimizations section). Moving the causal bounds check into the buffer-load descriptor raised our throughput by about 10% on every shape.
AITER Gluon is 1.12x to 1.37x faster than our kernel on the skinny shapes with 2,048 query rows and 32K or 64K KV positions. With 2,048 rows and 16K positions the two kernels are within 3%. Our kernel divides the work across the GPU by query row (one wave per row), so the number of rows sets how much work runs in parallel. At 64K positions and H=32, going from 2,048 to 4,096 rows raises our throughput from 630 to 1,006 TFLOP/s, and our kernel becomes 1.37x faster than Gluon. Each row does about the same work in both shapes, so at 2,048 rows our kernel leaves part of the GPU unused. Gluon’s throughput rises by 4% for the same change. At H=64 the change raises our throughput from 1,045 to 1,176 TFLOP/s, and Gluon stays 1.10x faster. We fall back to the Gluon kernel on most of the skinny shapes.
Two extensions target these shapes. A BLOCK_Q > 1 variant groups several query rows and reads the union of their KV windows once, which reduces KV reads by up to a factor of BLOCK_Q. This variant is implemented and under evaluation. The prefetch pipeline currently keeps two tiles in flight, and we can increase it to three or four.

Figure 12. DeepSeek-V4 sparse attention kernels on MI355X against the SGLang baseline. Left: top-K selection kernel time for one request. Center: end-to-end single-user decode throughput on DeepSeek-V4-Pro with only our top-K kernel added. Right: dual-scope decode kernel time per call under HIP graphs, against SGLang’s Triton kernel.
Change | Measurement | Speedup |
|---|---|---|
Top-K kernel under batching | Kernel time at batch 32, 1M context (1376.8 µs → 139.2 µs) | 9.89x |
Dual-scope decode kernel | End-to-end decode throughput at batch 1 / 4 / 8 | 1.05x / 1.05x / 1.04x |
MTP on top of both kernels | Single-user decode throughput at 256K / 512K / 1M context | 1.04x / 1.05x / 1.15x |
Table 4. Additional DeepSeek-V4-Pro measurements on 8x MI355X (TP=8). Speedups are relative to the same configuration without the listed change.
The top-K selection scores every compressed position, so its work grows linearly with context. The SGLang kernel runs the selection for one request on one compute unit, and our kernel splits long contexts across up to 32 compute units, so our speedup grows with context (Figure 12, left). At 4K tokens our kernel is slightly slower. At 1M tokens it is 11.1x faster for one request and 9.9x faster for a batch of 32 (Table 4).
In end-to-end decode the selection is one of many steps, so its effect follows its share of the step time. That share grows with context. Adding only the top-K kernel raises DeepSeek-V4-Pro single-user throughput by 1.40x at 1M tokens, while at 32K and 64K the change is within run-to-run noise (Figure 12, center).
The dual-scope decode kernel splits each call across the GPU, because single-user decode has little work per call (Optimizations section). It is 1.30x faster per call than SGLang’s Triton kernel at batch 1. As the batch grows each call has more work, and the advantage shrinks until our kernel is slightly slower at batch 32 (Figure 12, right). The kernel raises end-to-end decode throughput by about 5% at batch 1 to 8, and MTP on top of both kernels adds 4% to 15% at 256K to 1M tokens (Table 4).
Serving Results
Microbenchmarks show whether an individual kernel is faster. Our customers see end-to-end prefill TTFT and decode throughput in tokens per second, which also depend on scheduling, parallelism, caching, and the rest of the serving stack.
To measure these, we use fixed prefill and decode benchmarks with a fixed input sequence length (ISL), output sequence length (OSL), and request concurrency. These benchmarks do not represent production traffic. They isolate the effect of our changes across models and serving recipes. Our benchmarking scripts are modeled on the vllm bench and sglang bench tools. We wrote our own so we could control prefix-cache hit rates in the prefill benchmark and use identical settings across both inference engines.
Prefill
Our prefill benchmark runs a single request at a time and generates a single output token. It sweeps input sequence length and records TTFT averaged over 10 iterations after 1 warmup iteration. We also sweep prefix-cache hit rates that correspond to different types of workload. We use 0% (no cached prefix), 60% to represent common chat, and 90% to represent agentic workloads.

Figure 13. Kimi-K3 single-request TTFT on MI355X with the best SGLang configuration we found. Random input, OSL=1, 1 concurrent request, mean ± std over 10 runs.
Figure 13 shows how prefix caching changes TTFT for Kimi-K3. Without cache hits, TTFT grows faster than the input length at long context (about 2.5x per doubling above 128K tokens), because in Kimi-K3’s full-attention layers each token attends over all earlier tokens. A cached prefix removes the per-token work for the cached tokens, but each new token still attends over the full context, cached tokens included. With 90% of the prompt cached, per-token work drops 10x while the work in those attention layers drops only about 5x[^10], and the fixed costs of each request do not drop at all. The TTFT reduction is therefore largest at medium lengths (8x at 128K tokens). It is smaller at short context, where fixed costs dominate (2.6x at 8K), and at long context, where attention dominates (6x at 512K).
For GLM-5.3 we compare vllm/vllm-openai-rocm:v0.29.0 out of the box against the same image with ZOE, which adds our DSA lightning indexer and fused MoE kernels.

Figure 14. GLM-5.3 single-request TTFT on MI355X, vLLM v0.29.0 baseline against the same image with ZOE. Random input, OSL=1, 1 concurrent request, mean ± std over 10 runs. ZOE always matches or outperforms the vLLM implementation and pulls away strongly at long context lengths.
ZOE adds two kernels to GLM-5.3, the lightning indexer and the fused MoE kernel. In a DSA model the indexer is the only part of prefill whose work per token grows with the context. It scores each new token against every earlier position, while main attention reads a fixed 2,048 entries per token and the MoE and projection layers do the same work for every token. At 16K tokens and below the indexer is a small part of prefill and the speedup is close to 1x, which also shows that the MoE kernel changes prefill time little. As the context grows the indexer takes most of the prefill time, and the speedup grows with it, to 2.8x at 512K tokens. Prefix-cache hits raise the speedup further. Cached tokens skip the per-token work, but each new token is still scored against the full context, so the indexer is a larger share of the work that remains (3.6x at 512K with a 90% hit rate). The one result below 1x (0.80x at 32K with a 90% hit rate) comes from high run-to-run variance in the ZOE runs (a standard deviation of 0.16 s against a mean of 0.30 s).
Decode
Our decode benchmark sweeps request concurrency. Low concurrency produces the best per-user throughput, and high concurrency produces the best per-node throughput. Each decode step reads the weights once for every request in it, so more requests share each read, but each step also takes longer. We exclude TTFT and report only per-user and per-node throughput. Decode performance depends substantially on speculative decoding and is thus sensitive to the prompt/output distribution. Our benchmarks use temperature=1 and top_p=0.95, which can produce large variance between runs with the same prompt because the speculator acceptance rate changes. To show this sensitivity we report Kimi-K3 results for two prompts, one from Apex-Shortlist (heavy reasoning) and one from GSM8K (simple math).
Apex. "Determine the sum of the smallest 50 possible values of \(gcd(a2+b2+c2,abc)\) across all triples of positive integers \((a,b,c)\). Let's think step by step and output the final answer within \boxed{}."
GSM8K. "Tobias is buying a new pair of shoes that costs $95. He has been saving up his money each month for the past three months. He gets a $5 allowance a month. He also mows lawns and shovels driveways. He charges $15 to mow a lawn and $7 to shovel. After buying the shoes, he has $15 in change. If he mows 4 lawns, how many driveways did he shovel?"

Figure 15. Kimi-K3 decode throughput on MI355X with the best SGLang configuration we found (DSpark included), OSL=1024, mean ± std over 10 runs.
The model and kernels are the same for both prompts, so the gap between the two curves comes from DSpark acceptance. The target accepts more of the drafter’s proposed tokens on the GSM8K answer than on the Apex reasoning, so each step produces more tokens. Speculative decoding results therefore have to be read together with the prompt distribution.
For GLM-5.3 we report two settings. With MTP disabled the benchmark has close to zero variance, which isolates the throughput change from the kernels. With MTP enabled the configuration matches what we would use in production, and the variance from speculative decoding is larger than most of the kernel gains.

Figure 16. GLM-5.3 per-node decode throughput on MI355X, vLLM v0.29.0 baseline against the same image with ZOE. Apex prompt, OSL=1024, mean of 100 runs. Error bars show one standard deviation.
ZOE changes GLM-5.3 decode less than prefill. The Apex runs generate 1,024 tokens from a short prompt, so the context stays short and the indexer does little work. The remaining change is the fused MoE kernel, which speeds up only the MoE part of each decode step. With MTP disabled ZOE raises throughput by 1% to 7%. With MTP enabled the changes range from −3% to +13%, and most are within one standard deviation of the run-to-run variation. At short context, speculative decoding changes decode throughput far more than the kernels in this post (MTP raises GLM-5.3 throughput by 2x to 2.7x), and the long-context kernels matter once the context is long (Figure 12).
Quantization
Kimi-K3 already ships with MXFP4 experts, which leaves little room for further reduction, so we focused on GLM-5.3, which ships with FP8 weights. AMD has released amd/GLM-5.3-Quark-MXFP4-AttnFP8, which stores the routed and shared experts in MXFP4 and keeps attention in FP8. This reduces the model size from 756 GB to 410 GB. The Hugging Face model card reports a GSM8K score of 91.28 against 91.89 for the FP8 model (99.3% recovery). We have found that GSM8K alone does not show whether a model keeps its reasoning ability through post-training quantization, so we ran both models through our own eval harness on four reasoning benchmarks (Table 5).
Data source | Samples (problems × 8 repeats) | zai-org/GLM-5.3 score (%) | amd/GLM-5.3-Quark-MXFP4-AttnFP8 (410 GB) score (%) | Change (points) |
|---|---|---|---|---|
AIME25 | 240 | 97.5 | 99.17 | +1.67 |
AIME26 | 240 | 96.67 | 96.67 | 0.00 |
Apex-Shortlist | 376 | 53.72 | 53.46 | −0.26 |
HMMT | 240 | 96.67 | 93.75 | −2.92 |
Table 5. GLM-5.3 quantization eval comparison with the Zyphra internal eval harness. 8 repeats per prompt, max-tokens 131072, temperature=1.0, top_p=0.95. Change is in percentage points. Image: vllm/vllm-openai-rocm:nightly-rocm100-9ea8f3ffc354901b740f0b31988900897b7221d7.
The MXFP4 model stays within 3 points of the FP8 model on all four benchmarks. Each benchmark has 30 problems (47 for Apex-Shortlist) sampled 8 times, so one problem that goes from always solved to never solved moves the score by 3.3 points (2.1 points for Apex-Shortlist). Each difference in Table 5 is smaller than that.
For serving throughput we disable speculative decoding and use our fixed decode benchmark. At 410 GB the MXFP4 model fits on four MI355X GPUs (1,152 GB of HBM) with room left for the KV cache, so we also tried two TP=4 replicas on the node (DP=2). These runs use the nightly image listed under Table 5, so the absolute numbers are not directly comparable to the v0.29.0 numbers in the Decode section.

Figure 17. GLM-5.3 per-node decode throughput on MI355X with FP8 experts, with and without our fused MoE kernel, and with MXFP4 experts. All runs use the best vLLM configuration we found and the vLLM ROCm nightly image listed under Table 5. The hatched bars split the node into two TP=4 replicas. MTP disabled, Apex prompt, OSL=1024, mean of 10 runs.
As we described in our roofline experiments, quantization to FP8 or FP4 both reduces the HBM transfer time and increases the possible compute-bound ceiling. There are implementation caveats to both of these effects. For HBM transfer time, achievable HBM bandwidth depends on the size of the buffer being transferred, and requires a large enough buffer to saturate bandwidth[^11]. For compute speeds, the relative achievable throughput tends to decrease as one steps down the ladder to FP8 and FP4, despite the absolute throughput ceiling increasing. This is due to a few implementation-specific reasons:
FP8 and FP4 quantization isn’t simple truncation and rounding like between BF16/FP16 and FP32. It requires quantization operations[^12] (often fused into the core op kernels) that have overhead. Those overheads take up a larger proportion of the overall operation because compute and data transfer times have decreased, and we significantly push up the required problem size to hide these operations.
Overlap is much more fine-grained and difficult to achieve, since compute latency has decreased and we have added more quantization ops that don’t run on matrix cores and have strict ordering.
Shapes and occupancy often introduce more constraints for FP8 and FP4 matrix cores, namely the divisor of your model dims are larger so there are fewer models that can be cleanly divided, and those models are much larger. Take for example MI355X, whose FP8 and FP4 matrix cores have a 4X increase in depth in the K GEMM dimension compared to BF16. Take the 16x16 MFMA instructions as an example, where we have a K depth of 128 for FP8 and FP4, but only 32 for BF16. That means, for example, that head dims of 96 or 192 which were divisible in BF16 now aren’t, which requires custom handling (e.g. custom padding or overlapping multiple workstreams) in the FP4/FP8 kernels to achieve speed-of-light performance. This kind of doubling or halving shows up a lot, like how low-precision kernels use larger tiles to keep the faster matrix cores fed, so a given GEMM splits into fewer tiles and fewer waves, and the partial last wave is a larger fraction of the overall run time. This wasn’t such a big deal for older GPUs like Hopper because it was a small increase that still divides basically anything, 16 for BF16 and 32 for FP8, but for FP8/FP4 on MI355X, FP4 on Blackwell, etc, this is no longer the case.
In short, MFMA gets significantly faster but most other hardware operations don’t get proportionally faster, which is why, for example, Flash Attention 4 spends a lot of its design time on removing exp2f() as a bottleneck, and kernel developers are moving to fused epilogues (e.g. QuACK in CuTe DSL) and megakernels to better hide non-GEMM work. Hardware continues to increase low-precision FLOPs much faster than all memory bandwidths. Speculative decoding and diffusion language models both process several tokens per weight read, which raises the arithmetic intensity of decoding and moves it toward the compute-bound region. For BF16 this is straightforward, since we get the ratio of achieved to advertised TFLOP/s that people are used to, and virtually all model shapes benefit. If you put quantization on top of anything memory-bandwidth-bound, you can achieve speedup assuming that quantization overhead is hidden (a linear speedup if your quantized buffer is still past the bandwidth saturation point). If you put quantization on top of anything compute-bound, you take on the implementation complexity described above, which can usually be overcome.
From here, we are equipped to understand our quantized results. Quantization speeds up decoding largely by reducing the bytes read from HBM, so its benefit depends on how much of each decode step goes to reading weights. In an MoE model at concurrency 1 each token uses only its Top-K routed experts plus the number of shared experts per layer, so only these are transferred from HBM. For example, at 1 concurrent request each GLM-5.3 MoE layer uses 9 of its 257 experts (8 routed and 1 shared). Over GLM-5.3’s 75 MoE layers these experts hold 25.5 GB in FP8, which the node’s 8 GPUs read in about 0.5 ms at the measured HBM bandwidth (Table 1). The decode step takes about 16 ms (Figure 17), and MXFP4 halves the 0.5 ms. Most of the step goes to work that the expert format does not change, such as attention, communication between GPUs, and the fixed cost of each kernel. Halving the expert bytes can shorten such a step by only about 2%. As concurrency grows, the tokens in a step use more distinct experts, the step reads more of the expert weights, and the bytes that quantization removes become a larger share of the end-to-end time.
The MXFP4 experts run the same operations as the FP8 experts (quantize the activations, run the two expert GEMMs, and combine the results), with half the weight bytes and twice the matrix-core peak. Slower MXFP4 compute time therefore comes down to kernel implementation and how well one optimizes around the challenges described above. Our Fused MoE implementation’s FP4 support (in particular for low-batch) is ongoing work. In vLLM the MXFP4 model is slower than the FP8 model at 1 and 8 concurrent requests and faster at 32 (Figure 17). Specifically at low concurrency the MXFP4 kernels add more time per layer than the smaller weights save, and at 32 requests the bytes saved outweigh that cost. Splitting the node into two TP=4 replicas was slower at every concurrency (each GPU in a TP=4 replica reads twice as many weight bytes per step). With our fused MoE kernel the FP8 model stays faster than the MXFP4 model at low concurrency and within about 1% at 32 requests, so we continue to serve the FP8 model in production in low-concurrency cases.
Agentic Performance Benchmarks
Fixed prefill and decode benchmarks do not reliably indicate performance in production. Real traffic is multi-turn and long-context, and it includes sub-agent bursts, KV cache offloading, and tool calling. At Zyphra we use the AgentX benchmark from SemiAnalysis, which is an open-source, multi-turn agentic coding inference benchmark at 1 million tokens of context. It is available in the InferenceX repository, which describes it in more detail. We will publish agentic benchmark results soon.
Future Work
This post presents our second round of full-stack inference optimization on Zyphra Inference. We will continue to improve our inference platform with a focus on the following areas:
Closing the large-M gap on the FP8 frontier MoE shapes, so that prefill no longer falls back to vendor kernels through the hybrid dispatcher.
Finishing the BLOCK_Q > 1 lightning indexer variant and a deeper prefetch pipeline for the skinny shapes.
MXFP4 MoE kernels that are faster at low concurrency, and MXFP4 GEMMs that reach a larger fraction of the MI355X FP4 peak (Figure 5), so that the MXFP4 model decodes faster than the FP8 model at every concurrency.
Quantization recipes informed by our pretraining experience, and custom speculators that take advantage of the MI355X's HBM capacity.
Agentic benchmark results with AgentX at 1 million tokens of context.
Zyphra Inference is available now at cloud.zyphra.com.
Methodology and Limitations
As in our first post, we describe our methodology and its limits, since there are many regimes in inference and we do not want to make apples-to-oranges comparisons. The limitations described at the top of our previous blog apply here as well. We restate the relevant ones below, along with those specific to our kernel benchmarking.
All results are taken on a single node with no live traffic. Requests arrive at fixed intervals instead of randomly, and we do not test scheduling in real settings because it is outside the focus of this work.
The performance of speculative decoding methods is sensitive to the prompts used in the benchmark, because draft acceptance rates vary across input distributions. A proposed token is accepted with probability equal to the overlap between the drafter’s and the target’s next-token distributions, and drafters match the target more closely on predictable text, such as arithmetic or code, than on open-ended text. Reading the target’s hidden states, as MTP and EAGLE do, improves the match but does not remove this dependence. EAGLE-2 averages between 3.5 and 4.9 tokens per target pass across six benchmarks at temperature 1 (Li et al.), and Figure 15 shows the same effect for DSpark.
We hold input prompts fixed across configurations so that KV and prefix cache behavior is as similar as possible, and we report decode results for more than one prompt where the sensitivity is large.
Kernel benchmarks flush the L2 cache with a 256 MB buffer between iterations and report medians over 200 iterations after 20 warmup iterations.
We report timings under HIP-graph capture and replay, which we believe reflects production behavior, since per-launch (eager) timing includes launch overhead that graphs remove. Speedup is median(baseline) / median(ours).
Both our inference software stack and the external baselines change quickly, so any results we show here are valid only for a point in time. This point in time may differ across models depending on when the experiments were run. These blogs are sliding windows that summarize the work done since the previous blog. The serving results use vLLM v0.29.0 and the SGLang ROCm image v0.5.19 (lmsysorg/sglang-rocm:v0.5.19-rocm720-mi35x-20260910), and the quantization results use a vLLM ROCm nightly image.
Appendix A: Data Tables
The tables below contain the values plotted in the figures.
[wide]A1. Fused MoE speedup (Figure 10)
[wide]Kimi-K2.6 a16w4 (H=7168, E=384, top-k=8), speedup vs vLLM Triton WNA16 under HIP graphs.
M | 1 | 8 | 16 | 64 | 256 | 512 | 1024 | 2048 | 4096 | 8192 |
|---|---|---|---|---|---|---|---|---|---|---|
Full-N | 0.71 | 1.07 | 1.04 | 1.20 | 2.03 | 1.81 | 1.51 | 1.24 | 1.24 | 1.16 |
TP8 | 1.81 | 1.48 | 1.50 | 1.45 | 1.96 | 1.68 | 1.39 | 1.78 | 1.51 | 1.65 |
[wide]DeepSeek-V3.2 FP8 (H=7168, E=256, topk=8), speedup vs AITER under HIP graphs.
M | 1 | 8 | 16 | 64 | 256 | 512 | 1024 | 2048 | 4096 | 8192 |
|---|---|---|---|---|---|---|---|---|---|---|
Full-N | 1.10 | 0.99 | 1.03 | 1.15 | 1.14 | 1.07 | 0.93 | 0.65 | 0.59 | 0.55 |
TP8 | 1.09 | 1.03 | 1.00 | 1.17 | 1.06 | 0.84 | 0.75 | 0.56 | 0.55 | 0.55 |
[wide]Zyphra ZAYA1 FP8 (E=24, topk=1), speedup vs AITER.
M | 1 | 8 | 16 | 64 | 256 | 512 | 1024 | 2048 | 4096 | 8192 |
|---|---|---|---|---|---|---|---|---|---|---|
Graph | 1.07 | 1.04 | 0.98 | 1.42 | 1.46 | 1.23 | 1.00 | 0.65 | 0.58 | 0.54 |
[wide]A2. Lightning indexer throughput (Figure 11)
[wide]TFLOP/s, causal window, MI355X. Ratios above 1.00x mean our kernel is faster. H=32, D=128
Shape (seq x kv) | AITER (best per shape) | AITER Gluon | Ours | Ours / AITER Gluon | Ours / AITER (best per shape) |
|---|---|---|---|---|---|
4096 x 4096 | 445 | 732 | 852 | 1.16x | 1.91x |
8192 x 8192 | 482 | 914 | 1142 | 1.25x | 2.37x |
16384 x 16384 | 492 | 949 | 1040 | 1.10x | 2.12x |
32768 x 32768 | 465 | 953 | 1019 | 1.07x | 2.19x |
65536 x 65536 | 465 | 916 | 1026 | 1.12x | 2.21x |
2048 x 16384 | 471 | 950 | 921 | 0.97x | 1.96x |
2048 x 32768 | 480 | 648 | 551 | 0.85x | 1.15x |
2048 x 65536 | 469 | 705 | 630 | 0.89x | 1.34x |
4096 x 65536 | 474 | 735 | 1006 | 1.37x | 2.12x |
[wide]H=64, D=128
Shape (seq x kv) | AITER (best per shape) | AITER Gluon | Ours | Ours / AITER Gluon | Ours / AITER (best per shape) |
|---|---|---|---|---|---|
4096 x 4096 | 670 | 1273 | 1411 | 1.11x | 2.11x |
8192 x 8192 | 761 | 1550 | 1531 | 0.99x | 2.01x |
16384 x 16384 | 782 | 1618 | 1588 | 0.98x | 2.03x |
32768 x 32768 | 755 | 1623 | 1613 | 0.99x | 2.14x |
65536 x 65536 | 758 | 1600 | 1562 | 0.98x | 2.06x |
2048 x 16384 | 763 | 1448 | 1437 | 0.99x | 1.88x |
2048 x 32768 | 755 | 1233 | 898 | 0.73x | 1.19x |
2048 x 65536 | 751 | 1256 | 1045 | 0.83x | 1.39x |
4096 x 65536 | 747 | 1297 | 1176 | 0.91x | 1.57x |
[wide]A3. DeepSeek-V4 sparse attention (Figure 12)
[wide]Top-K kernel time, single request, MI355X.
Context | Baseline (µs) | Ours (µs) | Speedup |
|---|---|---|---|
4K | 9.7 | 11.2 | 0.87x |
64K | 58.2 | 30.5 | 1.91x |
256K | 178.4 | 43.1 | 4.14x |
1M | 748.0 | 67.2 | 11.1x |
[wide]End-to-end single-user decode, DeepSeek-V4-Pro, SGLang, tok/s.
Context | 32K | 64K | 256K | 512K | 1M |
|---|---|---|---|---|---|
Baseline | 67.1 | 65.8 | 59.4 | 52.7 | 43.5 |
Zyphra Top-K | 66.0 | 65.6 | 64.3 | 63.0 | 61.1 |
[wide]Dual-scope per-call decode latency under HIP graphs, vs SGLang's production Triton path.
Batch | 1 | 2 | 4 | 8 | 16 | 32 |
|---|---|---|---|---|---|---|
Triton (µs) | 22.9 | 26.9 | 28.4 | 34.1 | 43.9 | 51.7 |
Ours (µs) | 17.6 | 20.8 | 24.5 | 30.6 | 40.2 | 54.6 |
Speedup | 1.30x | 1.29x | 1.16x | 1.12x | 1.09x | 0.95x |
[wide]A4. Kimi-K3 prefill TTFT (Figure 13)
[wide]MI355X, Kimi-K3, best SGLang config, random input, OSL=1, fixed prefix to simulate cache hit rate, with warmup iteration, 1 concurrent request. TTFT in seconds.
Input length (tokens) | 0% cache hit | 60% cache hit | 90% cache hit |
|---|---|---|---|
8192 | 0.563±0.160 | 0.314±0.050 | 0.217±0.100 |
16384 | 1.017±0.155 | 0.522±0.155 | 0.234±0.020 |
32768 | 1.965±0.016 | 0.859±0.007 | 0.404±0.222 |
65536 | 4.284±0.275 | 1.964±0.412 | 0.620±0.183 |
131072 | 9.074±0.049 | 4.385±0.342 | 1.137±0.004 |
262144 | 22.861±0.148 | 10.730±0.504 | 2.905±0.002 |
524288 | 56.896±0.098 | 29.690±0.822 | 9.275±1.103 |
[wide]A5. GLM-5.3 prefill TTFT (Figure 14)
[wide]MI355X, GLM-5.3, best vLLM config (v0.29.0), random input, OSL=1, fixed prefix to simulate cache hit rate, with warmup iteration, 1 concurrent request. Baseline against ZOE (DSA indexer and fused MoE). TTFT in seconds.
Input length (tokens) | Cache hit rate | TTFT baseline | TTFT with ZOE | Change |
|---|---|---|---|---|
8192 | 0% | 0.436±0.135 | 0.407±0.017 | -6.65% |
16384 | 0% | 0.833±0.002 | 0.830±0.004 | -0.36% |
32768 | 0% | 1.752±0.002 | 1.838±0.405 | +4.91% |
65536 | 0% | 3.825±0.005 | 3.613±0.030 | -5.54% |
131072 | 0% | 10.122±0.451 | 8.113±0.006 | -19.85% |
262144 | 0% | 35.044±0.003 | 20.772±0.304 | -40.73% |
524288 | 0% | 178.770±0.790 | 64.569±0.317 | -63.88% |
8192 | 60% | 0.205±0.001 | 0.204±0.001 | -0.49% |
16384 | 60% | 0.367±0.001 | 0.360±0.001 | -1.91% |
32768 | 60% | 0.764±0.002 | 0.734±0.006 | -3.93% |
65536 | 60% | 1.715±0.001 | 1.564±0.003 | -8.80% |
131072 | 60% | 5.206±0.005 | 3.746±0.001 | -28.04% |
262144 | 60% | 21.763±0.008 | 10.443±0.010 | -52.01% |
524288 | 60% | 126.867±0.055 | 36.682±0.019 | -71.09% |
8192 | 90% | 0.129±0.003 | 0.129±0.001 | 0.00% |
16384 | 90% | 0.132±0.002 | 0.132±0.003 | 0.00% |
32768 | 90% | 0.240±0.001 | 0.301±0.161 | +25.42% |
65536 | 90% | 0.512±0.023 | 0.437±0.003 | -14.65% |
131072 | 90% | 1.552±0.004 | 1.038±0.004 | -33.12% |
262144 | 90% | 6.928±0.003 | 3.029±0.001 | -56.28% |
524288 | 90% | 42.301±0.190 | 11.821±0.735 | -72.06% |
[wide]A6. Kimi-K3 decode throughput (Figure 15)
[wide]MI355X, Kimi-K3, best SGLang config (DSpark included), OSL=1024, average of 10 runs, tok/s.
Concurrent requests | Apex per node | Apex per user | GSM8K per node | GSM8K per user |
|---|---|---|---|---|
1 | 91.6 ± 5.4 | 91.6 ± 5.4 | 247.9 ± 19.7 | 247.9 ± 19.7 |
2 | 161.8 ± 7.2 | 82.5 ± 3.5 | 426.1 ± 34.3 | 226.0 ± 15.6 |
4 | 285.8 ± 10.6 | 74.3 ± 2.5 | 761.4 ± 44.5 | 209.0 ± 9.3 |
8 | 449.7 ± 13.2 | 59.1 ± 1.7 | 1206.0 ± 80.9 | 170.2 ± 9.8 |
16 | 666.3 ± 16.4 | 43.9 ± 1.0 | 1790.2 ± 100.6 | 128.2 ± 6.1 |
32 | 1112.7 ± 24.2 | 36.9 ± 0.8 | 2964.1 ± 142.2 | 107.6 ± 4.5 |
[wide]A7. GLM-5.3 decode throughput (Figure 16)
[wide]MI355X, GLM-5.3, best vLLM config (v0.29.0), Apex prompt, OSL=1024, DSA + fused MoE, average of 100 runs, per-node tok/s.
[wide]MTP disabled
Concurrent requests | Baseline | ZOE | Change |
|---|---|---|---|
1 | 56.4 | 58.8 | +4.26% |
2 | 108.1 | 113.3 | +4.81% |
4 | 204.8 | 214.7 | +4.83% |
8 | 389.9 | 402.3 | +3.18% |
16 | 721.2 | 726.0 | +0.67% |
32 | 1186.5 | 1267.9 | +6.86% |
[wide]MTP enabled (mean ± std)
Concurrent requests | Baseline | ZOE | Change |
|---|---|---|---|
1 | 152.7 ± 13.6 | 160.4 ± 14.1 | +5.04% |
2 | 282.7 ± 19.8 | 297.1 ± 19.9 | +5.09% |
4 | 461.8 ± 23.7 | 519.6 ± 28.3 | +12.52% |
8 | 810.1 ± 37.0 | 860.4 ± 41.4 | +6.21% |
16 | 1408.7 ± 59.4 | 1430.3 ± 57.6 | +1.53% |
32 | 2431.3 ± 86.5 | 2363.6 ± 81.8 | −2.78% |
[wide]A8. GLM-5.3 quantization throughput (Figure 17)
[wide]MI355X, GLM-5.3, best vLLM config (MTP disabled), Apex prompt, OSL=1024, average of 10 runs, per-node tok/s. vLLM ROCm nightly image (see Table 5).
Quantization recipe | Parallelism | Concurrency=1 | Concurrency=8 | Concurrency=32 |
|---|---|---|---|---|
zai-org/GLM-5.3 | TP=8 | 62.4 | 412.3 | 1223.6 |
zai-org/GLM-5.3 + zyphra fused_moe | TP=8 | 64.6 | 430.8 | 1322.4 |
amd/GLM-5.3-Quark-MXFP4-AttnFP8 | TP=8 | 52.2 | 395.6 | 1338.7 |
amd/GLM-5.3-Quark-MXFP4-AttnFP8 | TP=4, DP=2 | 48.3 | 351.8 | 1185.3 |
Appendix B: Roofline Measurement Setup
MI355X runs FP8 and FP4 matrix math at full rate only through the new CDNA4 f8f6f4 instructions. Our FP8 and FP4 loops use one of them, v_mfma_scale_f32_32x32x64_f8f6f4, and the BF16 loop uses v_mfma_f32_32x32x16_bf16. The upstream benchmark (rocm-amdgpu-bench) used v_mfma_f32_32x32x16_fp8_fp8, the FP8 instruction carried over from CDNA3, which runs at about half the FP8 rate on MI355X. The MFMA and GEMM benchmarks set all quantization scales to 1. Before each timed GEMM we flush L2 and run a short GPU busy-wait, so the timing events measure only the GEMM kernel. TunableOp is disabled for the FP4 GEMM runs because its scaled-GEMM dispatcher has no FP4 path. For FP8 and BF16, results with and without TunableOp differ by less than 2%. For attention decode, pa_fwd_asm requires a head dimension of 128 and supports GQA ratios up to 16. We sweep batch size (1 to 256), sequence length (1K to 64K tokens), number of KV heads (4, 8, 16), and GQA ratio (1, 4, 8, 16). All sequences in a batch have the same length (production batches contain sequences of different lengths).