← All posts

Low-Latency Inference on AMD MI355X: 2,100+ Tokens per Second per User at Full Model Quality

· 11 min read · Article

Tokens/second/user on 8× AMD MI355X

Higher is better.

DeepSeek V4.1 FlashUp to 3.4× faster

  • Lithos2,116
  • OSS-1752
  • OSS-2630

Kimi K3Up to 2.5× faster

  • Lithos652
  • OSS-2271
  • OSS-1256

Today we are announcing the fastest inference engine on AMD GPUs. Compared with the other major open-source engines on the same eight AMD Instinct MI355X GPUs, the Lithos engine runs DeepSeek V4.1 Flash up to 3.4× faster and Kimi K3 up to 2.5× faster, and it is the only one of them that runs each model at full model quality.

2,116tokens/s/user

DeepSeek V4.1 Flash on 8×MI355X

Up to 3.4× faster than alternative engines on the same GPUs at full model quality

652tokens/s/user

Kimi K3 on 8×MI355X

Up to 2.5× faster than alternative engines on the same GPUs at full model quality

Results: stacking the deck against ourselves

We benchmarked the Lithos engine against four other major open-source engines, OSS-1 to OSS-4, each in its low-latency configuration, including development builds ahead of stable releases. Some configurations use reduced precision or more GPUs. The Lithos engine preserves each model’s full quality and official precision formats on AMD MI355X.

Speed

We measure decode speed as tokens per second per user, as shown in the plots below; higher is better. Every engine is credited the same speculative acceptance, 75% of speculated tokens, so the main difference between them is step time, the time to run one decode step; lower is better.

DeepSeek V4.1 Flash

Lithos · 8× MI355X2,116 · 2.83 ms

official formats

Lithos · 4× MI355X1,598 · 3.75 ms

official formats

OSS-2 low-latency · 4× MI355X928 · 6.47 ms

INT4 all-reduce

OSS-1 low-latency · 4× MI355X910 · 6.59 ms

INT8 all-reduce · MXFP4 shared expert

OSS-1 · 8× MI355X752 · 7.98 ms

INT8 all-reduce · MXFP4 shared expert

OSS-2 · 8× MI355X630 · 9.53 ms

INT4 all-reduce

Kimi K3

Lithos · 8× MI355X652 · 9.20 ms

official formats

OSS-3 · 8× MI355X373 · 16.07 ms

FP8 kv cache · FP8/tensor moe activations · FP8 decode attention

OSS-4 · 8× MI355X340 · 17.66 ms

FP8 kv cache · 4-bit moe activations · FP8 linear layers · INT4 all-reduce · FP8 prefill attention · FP8 decode attention

OSS-2 · 8× MI355X271 · 22.18 ms

FP8 kv cache · 4-bit moe activations · FP8 linear layers · INT4 all-reduce · FP8 decode attention

OSS-1 · 8× MI355X256 · 23.42 ms

INT8 all-reduce

One request at a time, ~10K-token prompts, 1,024 generated tokens, top_p 0.95; step times are per-request medians. Big number: tokens per second at the shared acceptance; small number: step time. Red marks show where a recipe lowers precision below the model’s official format. “Verified/step” is how many tokens the model checks per step, which sets the step’s cost; widths narrower than ours are marked, and an engine that checks 4 can accept at most 4 tokens per step, so its bar credits it with more than it can ever reach.

On DeepSeek V4.1 Flash, the other engines take 2.8–3.4× as long per step on eight MI355Xs. Even with half the GPUs, the Lithos engine is still the fastest: on four MI355Xs it takes 3.75 ms per step, less than any other engine on four or eight GPUs.

On Kimi K3, every other engine cuts precision and still takes 1.7–2.5× as long, even OSS-3, which checks only half as many tokens per step.

Cost per token

Fast decode is usually the expensive end of serving, so we priced it. We estimate cost per token using SemiAnalysis’s hyperscaler GPU ownership rates: $1.50 per MI355X-hour. For each configuration, we divide the total hourly GPU cost by the number of tokens generated per hour.

At the shared acceptance rate, Kimi K3 on our engine costs $5.11 per million tokens at 652 toks/s/user; the fastest other engine, OSS-3, costs $8.93 at 373 toks/s/user. For DeepSeek V4.1 Flash, ours costs $1.04 per million tokens on four MI355Xs at 1,598 toks/s/user and $1.57 on eight at 2,116 toks/s/user; the fastest other engine, OSS-2’s low-latency mode on four GPUs, costs $1.80 at 928 toks/s/user.

Speed and cost per token

DeepSeek V4.1 Flash

$0$1.5$3$4.5$606251,2501,8752,500tokens/s per user →Lithos, 8× MI355XLithos, 4× MI355X

Kimi K3

$0$3.75$7.5$11.25$150200400600800tokens/s per user →Lithos, 8× MI355X
Right is faster, down is cheaper. Cost per million generated tokens = number of GPUs × price per GPU-hour ÷ tokens per hour, with every stack at the same acceptance as in the chart above. Price: SemiAnalysis’s ownership cost for a large hyperscaler, $1.50 per MI355X-hour (July 2026), as used on InferenceX. Black: ours. Gray: other engines on MI355X. Point at a dot to see which engine it is.

Long context

Low latency is especially challenging for long-context agents: as conversation history, documents, and tool outputs accumulate, each decode step must process a larger context. To evaluate how latency scales, we tested each engine on the same LongBench v2 documents, using eight documents at each prompt length from 1K to 1M tokens. When a published configuration could not fit a 1M-token prompt, we adjusted its documented memory settings to allow it.

On eight MI355X GPUs, the Lithos engine’s DeepSeek V4.1 Flash step time rises from 2.8 ms at 1K tokens to 3.3 ms at 1M. At that longest context, the fastest other engine we tested, OSS-2’s low-latency mode on four GPUs, takes 6.7 ms. For Kimi K3, our step time increases by just 15% from 1K to 128K tokens, reaching 10.4 ms, and rises to 16.5 ms at 1M.

Decode step time (ms) vs prompt length

0246810121K2K4K8K16K32K64K128K256K512K1MOSS-1, 1K tokens: 7.97 ms per stepOSS-1, 2K tokens: 7.97 ms per stepOSS-1, 4K tokens: 7.98 ms per stepOSS-1, 8K tokens: 7.99 ms per stepOSS-1, 16K tokens: 8.03 ms per stepOSS-1, 32K tokens: 8.08 ms per stepOSS-1, 64K tokens: 8.15 ms per stepOSS-1, 128K tokens: 8.18 ms per stepOSS-1, 256K tokens: 8.31 ms per stepOSS-1, 512K tokens: 8.43 ms per stepOSS-1, 1M tokens: 8.77 ms per stepOSS-1OSS-1 low-latency, 1K tokens: 6.88 ms per stepOSS-1 low-latency, 2K tokens: 6.66 ms per stepOSS-1 low-latency, 4K tokens: 6.65 ms per stepOSS-1 low-latency, 8K tokens: 6.64 ms per stepOSS-1 low-latency, 16K tokens: 6.70 ms per stepOSS-1 low-latency, 32K tokens: 6.81 ms per stepOSS-1 low-latency, 64K tokens: 6.73 ms per stepOSS-1 low-latency, 128K tokens: 6.74 ms per stepOSS-1 low-latency, 256K tokens: 6.91 ms per stepOSS-1 low-latency, 512K tokens: 6.84 ms per stepOSS-1 low-latency, 1M tokens: 6.91 ms per stepOSS-1 low-latencyOSS-2 low-latency, 1K tokens: 6.47 ms per stepOSS-2 low-latency, 2K tokens: 6.50 ms per stepOSS-2 low-latency, 4K tokens: 6.47 ms per stepOSS-2 low-latency, 8K tokens: 6.49 ms per stepOSS-2 low-latency, 16K tokens: 6.51 ms per stepOSS-2 low-latency, 32K tokens: 6.54 ms per stepOSS-2 low-latency, 64K tokens: 6.51 ms per stepOSS-2 low-latency, 128K tokens: 6.57 ms per stepOSS-2 low-latency, 256K tokens: 6.57 ms per stepOSS-2 low-latency, 512K tokens: 6.67 ms per stepOSS-2 low-latency, 1M tokens: 6.71 ms per stepOSS-2 low-latencyOSS-2, 1K tokens: 9.56 ms per stepOSS-2, 2K tokens: 9.54 ms per stepOSS-2, 4K tokens: 9.51 ms per stepOSS-2, 8K tokens: 9.54 ms per stepOSS-2, 16K tokens: 9.59 ms per stepOSS-2, 32K tokens: 9.61 ms per stepOSS-2, 64K tokens: 9.61 ms per stepOSS-2, 128K tokens: 9.63 ms per stepOSS-2, 256K tokens: 9.71 ms per stepOSS-2, 512K tokens: 9.76 ms per stepOSS-2, 1M tokens: 9.93 ms per stepOSS-2Lithos, 1K tokens: 2.84 ms per stepLithos, 2K tokens: 2.84 ms per stepLithos, 4K tokens: 2.83 ms per stepLithos, 8K tokens: 2.85 ms per stepLithos, 16K tokens: 2.89 ms per stepLithos, 32K tokens: 2.92 ms per stepLithos, 64K tokens: 2.92 ms per stepLithos, 128K tokens: 2.95 ms per stepLithos, 256K tokens: 3.01 ms per stepLithos, 512K tokens: 3.11 ms per stepLithos, 1M tokens: 3.27 ms per stepLithos
MI355X, eight GPUs per engine except the low-latency modes, which run on four; one request at a time, 1,024 generated tokens per request. Prompt length on a log scale.

Different hardware architectures require different specialization

Both MI355X and B300 have the same memory on paper: 288 GB of HBM at 8 TB/s of peak bandwidth, so reading the weights once per step costs about the same on both. However, compute, caches and the links between GPUs are organized differently. The result is a vastly different optimization space.

AMD Instinct MI355X and NVIDIA B300 hardware architectures

MI355X

8 GPUS · DIRECT INFINITY FABRIC MESHONE MI355XHBMHBMHBMHBMHBMHBMHBMHBMINFINITY CACHE 256 MiBL2L2L2L2L2L2L2L2ONE COMPUTE UNIT (CU)SIMDRegisters 512 KBMatrix coresResults → registersLDS 160 KBLoads by waves256 CUS PER GPU

B300

8 GPUS · NVLINK SWITCHNVSWITCH · SUMS IN SWITCH (NVLS)ONE B300HBMHBMHBMHBMHBMHBMHBMHBMNVLINK I/ONVLINK I/OL2HALF OF 126.5 MiBL2HALF OF 126.5 MiBONE STREAMING MULTIPROCESSOR (SM)PartitionRegisters 256 KBTensor coresTensor Mem 256 KBShared 228 KBTMA tile loads148 SMS ENABLED PER B300
MI355X and B300 compared
MI355XB300
GPU-to-GPU1.075 TB/s, direct mesh1.8 TB/s, switch adds sums
HBM288 GB · 8 TB/s288 GB · 8 TB/s
Compute dies8 × 32 CUs on 2 I/O dies, 5.5 TB/s bisection between them2, with a 10 TB/s die-to-die link
L2 cache8 × 4 MiB, one per die, + 256 MiB Infinity Cache126.5 MiB in 2 coherent halves
Registers · native assembly512 KB per CU · yes256 KB per SM · no
Matrix resultsInto the wave’s registersInto Tensor Memory
On-chip memory160 KB LDSup to 228 KB shared + 256 KB TMEM
Programmatic dependent launch (PDL)NoYes
Point at or tap any part, or any row of the table. Schematic, not to scale; the red block at each level is the one drawn enlarged below it. Sources: AMD CDNA 4 whitepaper, AMD MI355X brochure, ROCm MI350 architecture, NVIDIA Blackwell architecture, Inside NVIDIA Blackwell Ultra, CUDA dependent launch; B300 SM count and L2 size as the GPU reports them (CUDA device attributes, B300 SXM6), since NVIDIA has not published them; the split of the L2 between the two dies from our latency measurements and Chips and Cheese’s on B200.

At low latency, a decode step is a long chain of small dependent stages: dozens per layer, across Kimi K3’s 93 layers, each waiting for the one before. Large batches hide these waits behind computation; low-latency serving exposes them. We call this waiting the coordination tax. The two architectures differ in three places that determine the overall cost.

Where software has to do more

1. Starting the next kernel

On NVIDIA GPUs starting with Hopper, Programmatic Dependent Launch (PDL) lets a dependent kernel begin before its predecessor in the same CUDA stream finishes. The first kernel signals when the next may launch; the next kernel can then perform independent setup, such as loading constants, while the first continues. Before consuming the first kernel’s outputs, it synchronizes to ensure that kernel has finished and its writes are visible. This can hide launch latency and setup work behind useful computation, though overlap is not guaranteed. AMD’s HIP has no equivalent early-launch mechanism within a single stream: dependent kernels normally wait for the previous kernel to finish, leaving launch latency exposed.

B300, with PDLKERNEL nKERNEL n+1OVERLAPMI355X, standard launchKERNEL nKERNEL n+1IDLE
Each kernel on its own lane, time running left to right; the lighter segment is the next kernel’s setup. With PDL, that setup overlaps the end of the previous kernel. Without it, the next kernel starts only after the previous one completes, and the GPU sits idle in between (red hatching). Schematic, not to scale.

2. Summing across GPUs

Eight B300s connect through NVLink switches that can add values inside the switch (NVLS), so in an all-reduce the GPUs do none of the additions and each sends less data. Eight MI355Xs are wired to each other directly over Infinity Fabric, with no switch in between. The GPUs do all of the adding themselves, typically in two phases (reduce-scatter, then all-gather), or in one phase in which every GPU sends its full vector to all seven others, four times the data of the two-phase method.

B300: the NVLink switch addsNVSWITCH012345671 · INTO THE SWITCH, WHICH ADDSthen 2 · SUM BACK TO EVERY GPUMI355X: direct links, the GPUs add012345671 · REDUCE-SCATTERthen 2 · ALL-GATHER
The sum of one layer’s output across eight GPUs, in two phases. On B300 every GPU sends its values into the NVLink switch, which adds them and sends the sum back to every GPU (NVLS). On MI355X there is no switch, so the GPUs add: first each GPU collects one eighth of every other GPU’s vector and adds it up (reduce-scatter), then sends its summed eighth to all seven others (all-gather). All eight GPUs do this at once, over every link in both directions. Schematic, not to scale.

3. Matrix cores

On B300, Tensor Memory Accelerator (TMA) copies tiles in asynchronously, a single thread can start a matrix multiply, and the tensor cores write their results into a dedicated Tensor Memory of 256 KB per SM rather than into registers, so loading, matrix math and the softmax of attention can run on different warps at the same time; a pair of SMs can also work on one large matrix tile together. On MI355X, the wave that does the math reads its operands into its own registers and receives the results there, so in practice it also runs the softmax on them: handing the results to another wave costs a round trip through Local Data Share (LDS). Overlap has to come from interleaving waves and instructions in software, within each wave’s fixed register budget.

B300: one lane per kind of workTMATENSOR CORESSOFTMAX WARPSMI355X: each wave does all three, waves interleaveWAVE 0WAVE 1LOADMULTIPLYSOFTMAX
Time runs left to right; each block is one tile’s load, multiply or softmax. On B300, TMA loads tiles, the tensor cores write results into Tensor Memory, and separate warps run the softmax, so each kind of work has its own lane. On MI355X the wave that multiplies reads its operands into its own registers and receives the results there, so in practice it also runs the softmax; overlap comes from interleaving waves in software. Schematic, not to scale.

Where MI355X gives software more room

Two other differences of the MI355X architecture are a larger register file and a documented instruction set that kernels can use directly.

1. A large register file

Each MI355X compute unit has 512 KB of registers, as much as a B300 SM’s registers and Tensor Memory combined; across the GPU that is 128 MiB, against 74 MiB on B300. More registers let a kernel keep more work staged: while one task runs, the next can already load its inputs into registers, so it starts without waiting on memory.

TASK ALOAD FOR TASK BTASK BREGISTERS HELDLittle room: load after task AROOMMore room: load during task AROOM
Time runs left to right, on the same scale in both panels. What task B needs is loaded into registers ahead of time and held there until B runs. With little room, there is no space for it while task A runs, so the load waits for A to finish and B waits for the load (red hatching). With more room, the load runs during A, more registers are held for longer, and B starts as soon as A ends. Schematic, not to scale.

2. Control down to the instruction

On B300, the lowest level a kernel author can write is PTX, a virtual instruction set that NVIDIA’s closed assembler turns into machine code. On MI355X, the CDNA 4 instruction set is documented by AMD and compiled by the open-source LLVM AMDGPU backend, and kernels can use it directly from HIP through inline assembly. That gives us direct control over how each step’s work is laid out and scheduled.

SOURCEVIRTUAL ISAMACHINE CODENVIDIA B300Triton, CuTe DSLCUDA C++PTX, inline PTXPTXAS (CLOSED)SASS: read onlyAMD MI355XTriton, FlyDSLHIP C++none: compiled straight to the ISALLVM (OPEN)CDNA4 ISA, inline assembly
The layers a kernel author can write, from source down to the instructions the GPU runs. Solid layers can be programmed; dashed ones can only be read. On NVIDIA, the lowest layer NVIDIA supports writing is PTX, which its closed assembler, ptxas, turns into SASS; on MI355X, the instruction set itself is documented and can be written directly, and the compiler is open source.

Specialization unlocks engine performance

To cut the coordination tax, we rewrote how a whole layer executes instead of tuning kernels one at a time. The work has four parts.

1. Megakernels

A stock engine launches each operator as its own kernel, and every launch is a boundary where the GPU waits for one kernel to finish and the next to start. A decode step has many such boundaries, and with one request at a time they add up to a large part of the step. We turn a layer’s operators into tasks inside a few persistent megakernels, which stay resident and move from task to task as their inputs become ready, so most of those boundaries disappear.

Stock engine: one kernel per operatorOurs: a few persistent megakernels
Each block is a kernel launch; the red hatching between blocks is time the GPU waits for the next kernel. A megakernel keeps running and picks up the next piece of work as soon as its inputs are ready. Schematic, not to scale.

2. Overlapping one megakernel with the next

Some boundaries remain. On NVIDIA, PDL lets the next kernel start its setup early; on MI355X the next kernel normally waits until the previous one completes. Where a boundary remains, we either fold its two sides into one megakernel or let the next one do its setup while the current one completes, doing in software what PDL does in hardware.

Back to backKERNEL nKERNEL n+1IDLEOurs, overlappedKERNEL nKERNEL n+1OVERLAP
Each megakernel on its own lane; the lighter segment is the next one’s setup. Our runtime starts that setup while the current megakernel finishes, which plays the role of NVIDIA’s PDL on MI355X. Schematic, not to scale.

3. Communication inside the computation

Communication across GPUs usually runs as its own step between kernels, and every GPU waits for it to finish. At decode’s small message sizes, that wait costs more than moving the data. We run the communication inside the megakernels instead, sending each piece of the result as soon as it is computed, so it overlaps the computation around it.

Stock engine: compute, then communicateCOMPUTECOMMUNICATIONOurs: communicated as it is computedCOMPUTECOMMUNICATION
Each row is one stream of work, time running left to right. A stock engine finishes a layer’s computation, then exchanges the result across the eight GPUs while the compute units wait (red hatching). Ours sends each piece of the result as soon as it is computed, so the communication runs alongside the rest of the computation. Schematic, not to scale.

4. A recursive, self-improving kernel harness

Making a kernel fast takes both theory and practice: theory says which ideas are worth trying, and measurement on the hardware identifies which ones work. We built a recursive, self-improving kernel optimization harness around this loop. It is fast and effective. It gives our engineers and researchers an exponential gain in efficiency, and it extends naturally to new models, new configurations and heterogeneous hardware architectures.

What comes next

Specialization goes a long way. Building the engine around how MI355X actually works made it the fastest engine on AMD GPUs, at full model quality. And the hardware still has more to give: a roofline analysis of the decode step, built from our own measurements on MI355X, shows there is still room before it reaches the GPU’s memory and compute limits. This is just the beginning of the journey.

We will keep working with AMD on fast inference on Instinct GPUs. Our engine is available today to a small group of partners. If you’d like to try it on your workload, don’t hesitate to reach out!