<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Junyi's Lab</title><link>https://www.junyi.dev/</link><description>Recent blog posts on Junyi's Lab</description><generator>Hugo (https://gohugo.io)</generator><language>zh-cn</language><managingEditor>junyi.h@comp.nus.edu.sg (Junyi Hou)</managingEditor><webMaster>junyi.h@comp.nus.edu.sg (Junyi Hou)</webMaster><lastBuildDate>Thu, 11 Jun 2026 00:00:00 Z</lastBuildDate><atom:link href="https://www.junyi.dev/tags/tpu-v6e/index.xml" rel="self" type="application/rss+xml"/><item><title>LLM Inference on TPU v6e-4: Small Dense, Large MoE, and Large Dense Models</title><link>https://www.junyi.dev/posts/tpu-v6e/</link><pubDate>Thu, 11 Jun 2026 00:00:00 Z</pubDate><author>junyi.h@comp.nus.edu.sg (Junyi Hou)</author><description>
# Abstract We benchmark LLM inference on one Google TPU v6e-4 host (four chips, one VM) with four chips in one VM. We use vLLM 0.20.0 with the tpu-inference backend and an fp8 KV cache.
We test three Qwen3 models:
Model Type Params Parallelism Chips Qwen3.5-4B dense 4B active tp1 1 Qwen3-30B-A3B MoE 30B total / 3B active tp4 4 Qwen3-32B dense 32B active tp4 4 We measure three parts of inference: prefill, decode, and end-to-end online serving.</description><content:encoded>&lt;h2 id="abstract" &gt;
&lt;div&gt;
&lt;a href="#abstract"&gt;
#
&lt;/a&gt;
Abstract
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;We benchmark LLM inference on one Google &lt;code&gt;TPU v6e-4&lt;/code&gt; host (four chips, one VM) with four chips in one VM. We use &lt;code&gt;vLLM 0.20.0&lt;/code&gt; with the &lt;code&gt;tpu-inference&lt;/code&gt; backend and an &lt;code&gt;fp8&lt;/code&gt; KV cache.&lt;/p&gt;
&lt;p&gt;We test three Qwen3 models:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th style="text-align: right"&gt;Params&lt;/th&gt;
&lt;th style="text-align: right"&gt;Parallelism&lt;/th&gt;
&lt;th style="text-align: right"&gt;Chips&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td&gt;dense&lt;/td&gt;
&lt;td style="text-align: right"&gt;4B active&lt;/td&gt;
&lt;td style="text-align: right"&gt;tp1&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td&gt;MoE&lt;/td&gt;
&lt;td style="text-align: right"&gt;30B total / 3B active&lt;/td&gt;
&lt;td style="text-align: right"&gt;tp4&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td&gt;dense&lt;/td&gt;
&lt;td style="text-align: right"&gt;32B active&lt;/td&gt;
&lt;td style="text-align: right"&gt;tp4&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We measure three parts of inference: prefill, decode, and end-to-end online serving.&lt;/p&gt;
&lt;p&gt;The MoE model is fastest in all three tests. It reaches 26,063 tok/s in prefill, compared with 19,714 tok/s for the dense 32B model. At batch size 1, its decode latency is 7.0 ms per token, compared with 17.9 ms for the dense 32B model. In online serving, it reaches 1.27 req/s and 1,303 output tok/s, compared with 0.88 req/s and 901 output tok/s for the dense 32B model.&lt;/p&gt;
&lt;p&gt;The 4B model has good latency at low load, but it does not handle high concurrency well on one chip. Its stable serving capacity is about 0.45 req/s.&lt;/p&gt;
&lt;p&gt;This is not a TPU-versus-GPU comparison. We did not run a GPU baseline. The goal is to report clear TPU v6e numbers using the same vLLM tools that users would run in practice.&lt;/p&gt;
&lt;h2 id="1-motivation" &gt;
&lt;div&gt;
&lt;a href="#1-motivation"&gt;
#
&lt;/a&gt;
1. Motivation
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Public TPU data for LLM inference is limited. The only TPU submission Google has made to MLPerf Inference is SDXL image generation, not an LLM. The TPU LLM numbers is measured on Google&amp;rsquo;s JAX/JetStream stack rather than the vLLM path most practitioners deploy.&lt;/p&gt;
&lt;p&gt;We measured the performance ourselves on one TPU v6e-4 host. The goal is to give simple, reproducible numbers for three common model types:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a small dense model,&lt;/li&gt;
&lt;li&gt;a large MoE model,&lt;/li&gt;
&lt;li&gt;a large dense model.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All comparisons are between these three models on the same TPU host. We do not compare against GPUs, prices, or total cost.&lt;/p&gt;
&lt;h2 id="2-experimental-setup" &gt;
&lt;div&gt;
&lt;a href="#2-experimental-setup"&gt;
#
&lt;/a&gt;
2. Experimental Setup
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;All measurements use one TPU v6e-4 host with four chips in one VM. The 4B model uses one chip. The 30B-A3B and 32B models use all four chips. For per-chip numbers, we divide the four-chip throughput by four.&lt;/p&gt;
&lt;p&gt;We use &lt;code&gt;vLLM 0.20.0&lt;/code&gt;, &lt;code&gt;JAX 0.10.0&lt;/code&gt;, &lt;code&gt;libtpu 0.0.40&lt;/code&gt;, &lt;code&gt;Python 3.11&lt;/code&gt; with the &lt;code&gt;tpu-inference&lt;/code&gt; backend. The KV cache uses &lt;code&gt;fp8_e5m2&lt;/code&gt; in all runs. We report exact versions because TPU performance can change quickly across releases.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th style="text-align: right"&gt;Total / active params&lt;/th&gt;
&lt;th style="text-align: right"&gt;Parallelism&lt;/th&gt;
&lt;th style="text-align: right"&gt;Chips&lt;/th&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td&gt;dense&lt;/td&gt;
&lt;td style="text-align: right"&gt;4B / 4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;tp1&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td&gt;GDN, MBT2048&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td&gt;MoE&lt;/td&gt;
&lt;td style="text-align: right"&gt;30B / 3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;tp4&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td&gt;GMU0.8, MBT8192&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td&gt;dense&lt;/td&gt;
&lt;td style="text-align: right"&gt;32B / 32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;tp4&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td&gt;GMU0.8, MBT8192&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 1. Model configurations used in all experiments. GDN = guided decoding off, GMU = GPU memory utilization, MBT = max-num-batched-tokens.&lt;/figcaption&gt;
&lt;p&gt;The key comparison is between Qwen3-30B-A3B and Qwen3-32B. They have similar total size, but the MoE model only uses 3B parameters per token, while the dense 32B model uses all 32B.&lt;/p&gt;
&lt;p&gt;We run three tests.&lt;/p&gt;
&lt;p&gt;For prefill, we use concurrency 1 and output length 1. We vary context length over 512, 1024, 2048, 4096, and 8192 tokens. This measures how fast the model processes the input prompt.&lt;/p&gt;
&lt;p&gt;For decode, we fix the output length at 128 tokens and vary batch size over 1, 4, 16, and 64. We mainly report context length 1024, with context length 4096 as a longer-KV case. This measures the cost of generating tokens after prefill.&lt;/p&gt;
&lt;p&gt;For online serving, we use &lt;code&gt;vllm bench serve&lt;/code&gt; against the OpenAI-compatible endpoint. Input and output lengths are both 1024 tokens. We increase the request rate until the server saturates.&lt;/p&gt;
&lt;p&gt;Metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TTFT: time to first token. This mainly measures prefill.&lt;/li&gt;
&lt;li&gt;TPOT: time per output token. This mainly measures decode.&lt;/li&gt;
&lt;li&gt;E2E latency: full request time.&lt;/li&gt;
&lt;li&gt;Prefill throughput: context length divided by p50 TTFT.&lt;/li&gt;
&lt;li&gt;Decode throughput: batch size × 1000 divided by p50 TPOT.&lt;/li&gt;
&lt;li&gt;Output throughput: generated tokens per second.&lt;/li&gt;
&lt;li&gt;Request throughput: completed requests per second.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We report p50 unless stated otherwise.&lt;/p&gt;
&lt;h2 id="3-results" &gt;
&lt;div&gt;
&lt;a href="#3-results"&gt;
#
&lt;/a&gt;
3. Results
&lt;/div&gt;
&lt;/h2&gt;
&lt;h3 id="31-prefill" &gt;
&lt;div&gt;
&lt;a href="#31-prefill"&gt;
##
&lt;/a&gt;
3.1 Prefill
&lt;/div&gt;
&lt;/h3&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig1_prefill.png" alt="Prefill throughput versus context length on TPU v6e-4"&gt;
&lt;figcaption&gt;Figure 1. Prefill throughput versus context length. Higher is better. The 30B-A3B and 32B results are total throughput across four chips. The 4B result uses one chip.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The MoE model has the highest prefill throughput.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th style="text-align: right"&gt;Best prefill throughput&lt;/th&gt;
&lt;th style="text-align: right"&gt;Per-chip peak&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;26,063 tok/s&lt;/td&gt;
&lt;td style="text-align: right"&gt;~6.5k tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;19,714 tok/s&lt;/td&gt;
&lt;td style="text-align: right"&gt;~4.9k tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4,177 tok/s&lt;/td&gt;
&lt;td style="text-align: right"&gt;~4.2k tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 2. Peak prefill throughput and per-chip peak for each model.&lt;/figcaption&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th style="text-align: right"&gt;ctx 512&lt;/th&gt;
&lt;th style="text-align: right"&gt;ctx 1024&lt;/th&gt;
&lt;th style="text-align: right"&gt;ctx 2048&lt;/th&gt;
&lt;th style="text-align: right"&gt;ctx 4096&lt;/th&gt;
&lt;th style="text-align: right"&gt;ctx 8192&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;35 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;51 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;84 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;157 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;318 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;38 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;58 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;104 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;208 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;461 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;432 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;452 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;495 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;983 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,961 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 3. p50 TTFT at each context length (ms).&lt;/figcaption&gt;
&lt;p&gt;The 4B result is limited by its single-chip setup and by &lt;code&gt;max-num-batched-tokens=2048&lt;/code&gt;. For contexts above 2048, prefill is split into chunks, so the curve flattens.&lt;/p&gt;
&lt;h3 id="32-decode" &gt;
&lt;div&gt;
&lt;a href="#32-decode"&gt;
##
&lt;/a&gt;
3.2 Decode
&lt;/div&gt;
&lt;/h3&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig2_decode_tpot.png" alt="Decode p50 TPOT versus batch size at context 1024 and 4096"&gt;
&lt;figcaption&gt;Figure 2. Decode p50 TPOT versus batch size. Solid lines: context 1024. Dashed lines: context 4096. Lower is better.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig3_decode_throughput.png" alt="Aggregate decode throughput versus batch size at context 1024 and 4096"&gt;
&lt;figcaption&gt;Figure 3. Aggregate decode throughput versus batch size. Solid lines: context 1024. Dashed lines: context 4096. Higher is better.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th style="text-align: right"&gt;BS&lt;/th&gt;
&lt;th style="text-align: right"&gt;p50 TPOT&lt;/th&gt;
&lt;th style="text-align: right"&gt;p99 TPOT&lt;/th&gt;
&lt;th style="text-align: right"&gt;throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td style="text-align: right"&gt;7.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;7.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;143 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td style="text-align: right"&gt;8.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;9.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;456 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;16&lt;/td&gt;
&lt;td style="text-align: right"&gt;14.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;17.5 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,071 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;64&lt;/td&gt;
&lt;td style="text-align: right"&gt;48.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;152.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,331 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td style="text-align: right"&gt;17.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;17.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;56 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;19.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;220 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;16&lt;/td&gt;
&lt;td style="text-align: right"&gt;22.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;27.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;715 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;64&lt;/td&gt;
&lt;td style="text-align: right"&gt;58.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;65.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,096 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td style="text-align: right"&gt;10.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;10.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;93 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td style="text-align: right"&gt;16.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;247 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;16&lt;/td&gt;
&lt;td style="text-align: right"&gt;41.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;647.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;387 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;64&lt;/td&gt;
&lt;td style="text-align: right"&gt;80.5 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;97.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;795 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 4. Decode TPOT at context length 1024 (p50, p99, and aggregate throughput).&lt;/figcaption&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th style="text-align: right"&gt;BS&lt;/th&gt;
&lt;th style="text-align: right"&gt;p50 TPOT&lt;/th&gt;
&lt;th style="text-align: right"&gt;p99 TPOT&lt;/th&gt;
&lt;th style="text-align: right"&gt;throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td style="text-align: right"&gt;7.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;7.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;143 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td style="text-align: right"&gt;9.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;12.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;411 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;16&lt;/td&gt;
&lt;td style="text-align: right"&gt;22.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;28.5 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;719 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;64&lt;/td&gt;
&lt;td style="text-align: right"&gt;96.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;213.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;661 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td style="text-align: right"&gt;19.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;19.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;53 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td style="text-align: right"&gt;21.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;25.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;187 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;16&lt;/td&gt;
&lt;td style="text-align: right"&gt;37.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;130.6 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;424 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;64&lt;/td&gt;
&lt;td style="text-align: right"&gt;82.5 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;92.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;775 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1&lt;/td&gt;
&lt;td style="text-align: right"&gt;10.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;10.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;93 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;4&lt;/td&gt;
&lt;td style="text-align: right"&gt;25.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;34.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;158 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;16&lt;/td&gt;
&lt;td style="text-align: right"&gt;85.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;89.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;188 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;64&lt;/td&gt;
&lt;td style="text-align: right"&gt;85.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;89.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;753 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 5. Decode TPOT at context length 4096 (p50, p99, and aggregate throughput).&lt;/figcaption&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig6_decode_tail.png" alt="Decode p99/p50 TPOT ratio at context 1024 and 4096"&gt;
&lt;figcaption&gt;Figure 4. Decode tail latency: p99/p50 TPOT ratio at each batch size. A ratio of 1.0 means p99 equals p50 (no tail). Higher bars indicate worse scheduling irregularity.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At batch size 1, the MoE model is 2.5× faster than the dense 32B model: 7.0 ms versus 17.9 ms at context 1024. This follows active parameter count. Decode is memory-bandwidth-bound. The MoE model reads 3B active parameters per token; the dense 32B model reads all 32B.&lt;/p&gt;
&lt;p&gt;The p99/p50 ratio reveals scheduling pressure at high batch sizes. The 30B-A3B at BS64 ctx 1024 has p99 TPOT of 152.7 ms against p50 of 48.1 ms (3.2×). The 4B at BS16 ctx 1024 has p99 TPOT of 647.3 ms against p50 of 41.4 ms (15.6×) — at this operating point the single-chip KV cache is under pressure and requests queue unevenly.&lt;/p&gt;
&lt;p&gt;At context 4096, longer KV sequences reduce throughput across all models. The 30B-A3B at BS64 drops from 1,331 tok/s (ctx 1024) to 661 tok/s (ctx 4096). The dense 32B drops less in relative terms (1,096 to 775 tok/s) because its compute is already the bottleneck at ctx 1024. The 4B model saturates at BS16 for ctx 4096: p50 TPOT at BS64 equals BS16 (85.0 ms both), meaning additional concurrency gives no throughput gain on one chip.&lt;/p&gt;
&lt;h3 id="33-online-serving" &gt;
&lt;div&gt;
&lt;a href="#33-online-serving"&gt;
##
&lt;/a&gt;
3.3 Online serving
&lt;/div&gt;
&lt;/h3&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig4_e2e_pareto.png" alt="Online-serving throughput-latency curve"&gt;
&lt;figcaption&gt;Figure 5. Online serving throughput and latency. The x-axis is output throughput. The y-axis is p50 TPOT. Lower-right is better.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In online serving, higher request rate gives higher throughput, but also higher latency. Each model reaches a point where adding more requests no longer helps.&lt;/p&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig7_e2e_sweep.png" alt="End-to-end TTFT and TPOT versus output throughput"&gt;
&lt;figcaption&gt;Figure 6. End-to-end latency sweep. Left: p50 TTFT versus output throughput. Right: p50 TPOT versus output throughput. The × marker on the 4B line marks the concurrency-collapse point under unlimited load.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right"&gt;Req rate&lt;/th&gt;
&lt;th style="text-align: right"&gt;Actual req/s&lt;/th&gt;
&lt;th style="text-align: right"&gt;Output tok/s&lt;/th&gt;
&lt;th style="text-align: right"&gt;TTFT p50&lt;/th&gt;
&lt;th style="text-align: right"&gt;TTFT p99&lt;/th&gt;
&lt;th style="text-align: right"&gt;TPOT p50&lt;/th&gt;
&lt;th style="text-align: right"&gt;TPOT p99&lt;/th&gt;
&lt;th style="text-align: right"&gt;E2E p50&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.2&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.19&lt;/td&gt;
&lt;td style="text-align: right"&gt;197&lt;/td&gt;
&lt;td style="text-align: right"&gt;90 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;2,682 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;7.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;9.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;8.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.4&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.36&lt;/td&gt;
&lt;td style="text-align: right"&gt;366&lt;/td&gt;
&lt;td style="text-align: right"&gt;92 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;190 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;9.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;10.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;9.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.6&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.52&lt;/td&gt;
&lt;td style="text-align: right"&gt;537&lt;/td&gt;
&lt;td style="text-align: right"&gt;94 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,331 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;11.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;13.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;11.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.8&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.62&lt;/td&gt;
&lt;td style="text-align: right"&gt;635&lt;/td&gt;
&lt;td style="text-align: right"&gt;116 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;6,335 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;28.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;24.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;∞&lt;/td&gt;
&lt;td style="text-align: right"&gt;1.27&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,303&lt;/td&gt;
&lt;td style="text-align: right"&gt;712 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,092 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;22.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;22.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;23.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 6. Qwen3-30B-A3B online serving results (ISL = OSL = 1024 tokens).&lt;/figcaption&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right"&gt;Req rate&lt;/th&gt;
&lt;th style="text-align: right"&gt;Actual req/s&lt;/th&gt;
&lt;th style="text-align: right"&gt;Output tok/s&lt;/th&gt;
&lt;th style="text-align: right"&gt;TTFT p50&lt;/th&gt;
&lt;th style="text-align: right"&gt;TTFT p99&lt;/th&gt;
&lt;th style="text-align: right"&gt;TPOT p50&lt;/th&gt;
&lt;th style="text-align: right"&gt;TPOT p99&lt;/th&gt;
&lt;th style="text-align: right"&gt;E2E p50&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.15&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.14&lt;/td&gt;
&lt;td style="text-align: right"&gt;144&lt;/td&gt;
&lt;td style="text-align: right"&gt;115 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;2,243 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.3&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.26&lt;/td&gt;
&lt;td style="text-align: right"&gt;271&lt;/td&gt;
&lt;td style="text-align: right"&gt;115 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,042 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;18.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;20.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;19.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.45&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.37&lt;/td&gt;
&lt;td style="text-align: right"&gt;380&lt;/td&gt;
&lt;td style="text-align: right"&gt;116 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;295 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;19.5 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;20.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;20.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.6&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.45&lt;/td&gt;
&lt;td style="text-align: right"&gt;466&lt;/td&gt;
&lt;td style="text-align: right"&gt;123 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;3,983 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;21.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;28.1 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;21.7 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;∞&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.88&lt;/td&gt;
&lt;td style="text-align: right"&gt;901&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,190 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;9,249 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;43.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;44.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;45.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 7. Qwen3-32B online serving results (ISL = OSL = 1024 tokens).&lt;/figcaption&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right"&gt;Req rate&lt;/th&gt;
&lt;th style="text-align: right"&gt;Actual req/s&lt;/th&gt;
&lt;th style="text-align: right"&gt;Output tok/s&lt;/th&gt;
&lt;th style="text-align: right"&gt;TTFT p50&lt;/th&gt;
&lt;th style="text-align: right"&gt;TTFT p99&lt;/th&gt;
&lt;th style="text-align: right"&gt;TPOT p50&lt;/th&gt;
&lt;th style="text-align: right"&gt;TPOT p99&lt;/th&gt;
&lt;th style="text-align: right"&gt;E2E p50&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.2&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.19&lt;/td&gt;
&lt;td style="text-align: right"&gt;194&lt;/td&gt;
&lt;td style="text-align: right"&gt;509 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;33,147 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;12.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;15.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;13.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.3&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.28&lt;/td&gt;
&lt;td style="text-align: right"&gt;283&lt;/td&gt;
&lt;td style="text-align: right"&gt;507 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,185 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;12.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;14.3 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;13.7 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.4&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.36&lt;/td&gt;
&lt;td style="text-align: right"&gt;367&lt;/td&gt;
&lt;td style="text-align: right"&gt;506 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,099 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;13.4 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;15.7 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;14.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;0.45&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.40&lt;/td&gt;
&lt;td style="text-align: right"&gt;407&lt;/td&gt;
&lt;td style="text-align: right"&gt;507 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,141 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;13.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;15.9 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;14.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right"&gt;∞&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.14&lt;/td&gt;
&lt;td style="text-align: right"&gt;143&lt;/td&gt;
&lt;td style="text-align: right"&gt;6,762 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;219,855 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;133.8 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;237.2 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;206.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 8. Qwen3.5-4B online serving results (ISL = OSL = 1024 tokens).&lt;/figcaption&gt;
&lt;p&gt;The MoE model (30B-A3B) has a stable working zone up to about 0.6 req/s, where p50 TPOT stays below 12 ms and p99 TTFT stays below 1.4 seconds. At 0.8 req/s the server is already queuing: p99 TTFT jumps to 6.3 seconds while p50 TPOT jumps from 11 ms to 19 ms. Saturation at ∞ rate gives tight p99/p50 TPOT (22.8 ms versus 22.3 ms), meaning the server is consistently loaded with no idle cycles.&lt;/p&gt;
&lt;p&gt;The dense 32B model’s TPOT p50 is nearly flat from 0.15 to 0.6 req/s (18–21 ms), then doubles at saturation (43.2 ms). Its TTFT p99 is noisy across the sweep, suggesting occasional prefill bursts even at low load.&lt;/p&gt;
&lt;p&gt;The 4B model holds stable from 0.2 to 0.45 req/s. At 0.45 req/s it sustains 0.40 req/s and 407 output tok/s, with TPOT p50 of 13.8 ms and E2E p50 of 14.6 seconds. Under unlimited load it collapses entirely: output throughput drops from 407 tok/s to 143 tok/s, p50 TPOT rises from 13.8 ms to 133.8 ms, and p50 E2E latency reaches 206.8 seconds. The p99 TTFT at unlimited load is 219,855 ms — 220 seconds. This is not a degraded state; it is a broken one. We treat the unlimited-load point as a single-chip concurrency limit, not useful serving capacity.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th style="text-align: right"&gt;p50 TTFT&lt;/th&gt;
&lt;th style="text-align: right"&gt;p50 E2E latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-30B-A3B&lt;/td&gt;
&lt;td style="text-align: right"&gt;712 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;23.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,190 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;45.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td style="text-align: right"&gt;507 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;14.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 9. p50 TTFT and p50 E2E latency at the saturation or stable operating point.&lt;/figcaption&gt;
&lt;figure&gt;
&lt;img style="border-radius: 8px" src="https://www.junyi.dev/posts/tpu-v6e/figures/fig5_summary.png" alt="MoE-versus-dense efficiency summary"&gt;
&lt;figcaption&gt;Figure 7. Summary relative to the dense Qwen3-32B baseline. Higher is better.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Across all three tests, the MoE model beats the dense 32B model at similar total parameter count:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th style="text-align: right"&gt;Qwen3-30B-A3B&lt;/th&gt;
&lt;th style="text-align: right"&gt;Qwen3-32B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best prefill throughput&lt;/td&gt;
&lt;td style="text-align: right"&gt;26,063 tok/s&lt;/td&gt;
&lt;td style="text-align: right"&gt;19,714 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BS1 decode TPOT&lt;/td&gt;
&lt;td style="text-align: right"&gt;7.0 ms&lt;/td&gt;
&lt;td style="text-align: right"&gt;17.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serving capacity&lt;/td&gt;
&lt;td style="text-align: right"&gt;1.27 req/s&lt;/td&gt;
&lt;td style="text-align: right"&gt;0.88 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output throughput at saturation&lt;/td&gt;
&lt;td style="text-align: right"&gt;1,303 tok/s&lt;/td&gt;
&lt;td style="text-align: right"&gt;901 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;figcaption&gt;Table 10. Summary comparison of Qwen3-30B-A3B versus Qwen3-32B across all three tests.&lt;/figcaption&gt;
&lt;p&gt;The MoE model gives about 2.5× lower single-stream decode latency, about 1.4× higher serving capacity, and about 1.3× higher prefill throughput.&lt;/p&gt;
&lt;h2 id="4-discussion" &gt;
&lt;div&gt;
&lt;a href="#4-discussion"&gt;
#
&lt;/a&gt;
4. Discussion
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Three points stand out.&lt;/p&gt;
&lt;p&gt;First, active parameter count matters a lot for decode. The 30B-A3B MoE and the 32B dense model have similar total size, but very different active size. The MoE model uses 3B parameters per token. The dense model uses 32B. This explains the large decode gap: 7.0 ms versus 17.9 ms at batch size 1.&lt;/p&gt;
&lt;p&gt;Second, the MoE model also wins in prefill. Its peak prefill throughput is 26,063 tok/s, or about 6.5k tok/s per chip. The dense 32B model reaches 19,714 tok/s, or about 4.9k tok/s per chip. On this host, the MoE model is faster in both prefill and decode.&lt;/p&gt;
&lt;p&gt;Third, the small 4B model is limited by single-chip concurrency. It has good low-load latency, but one chip cannot absorb unlimited requests. Its practical serving capacity is about 0.45 req/s. To serve more traffic, the better path is to add chips or replicas, rather than pushing more concurrency onto one chip.&lt;/p&gt;
&lt;h2 id="5-conclusion" &gt;
&lt;div&gt;
&lt;a href="#5-conclusion"&gt;
#
&lt;/a&gt;
5. Conclusion
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;On one TPU v6e-4 host, using &lt;code&gt;vLLM 0.20.0&lt;/code&gt;, &lt;code&gt;tpu-inference&lt;/code&gt;, and an &lt;code&gt;fp8_e5m2&lt;/code&gt; KV cache, the large MoE model is the fastest of the three models we tested.&lt;/p&gt;
&lt;p&gt;Compared with the dense 32B model, Qwen3-30B-A3B has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;higher prefill throughput: 26,063 vs 19,714 tok/s,&lt;/li&gt;
&lt;li&gt;lower batch-size-1 decode latency: 7.0 vs 17.9 ms,&lt;/li&gt;
&lt;li&gt;higher serving capacity: 1.27 vs 0.88 req/s,&lt;/li&gt;
&lt;li&gt;higher output throughput at saturation: 1,303 vs 901 tok/s.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The 4B dense model has good latency at low load, but on one chip it is concurrency-limited. Its stable serving capacity is about 0.45 req/s.&lt;/p&gt;
&lt;h2 id="limitations" &gt;
&lt;div&gt;
&lt;a href="#limitations"&gt;
#
&lt;/a&gt;
Limitations
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;These results are for one host only: four TPU v6e chips in one VM. We did not test multi-host scaling.&lt;/p&gt;
&lt;p&gt;We did not run a GPU baseline, so this study makes no TPU-versus-GPU claim.&lt;/p&gt;
&lt;p&gt;The online serving test uses one input/output shape: 1024 input tokens and 1024 output tokens. Other workloads may shift the balance between prefill and decode.&lt;/p&gt;
&lt;p&gt;The results are version-specific. TPU support in vLLM changes quickly, so the numbers here apply to &lt;code&gt;vLLM 0.20.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;All runs use an &lt;code&gt;fp8_e5m2&lt;/code&gt; KV cache. We did not test other KV cache formats.&lt;/p&gt;
&lt;h2 id="reproducibility" &gt;
&lt;div&gt;
&lt;a href="#reproducibility"&gt;
#
&lt;/a&gt;
Reproducibility
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;The main numbers are &lt;a href="https://www.junyi.dev/posts/tpu-v6e/data.json" target="_blank" rel="noopener noreferrer"&gt;./data.json&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Figures are generated by &lt;a href="https://www.junyi.dev/posts/tpu-v6e/figures/plot.py" target="_blank" rel="noopener noreferrer"&gt;./figures/plot.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Per-model results are at &lt;a href="https://www.junyi.dev/posts/tpu-v6e/rawdata.zip" target="_blank" rel="noopener noreferrer"&gt;./rawdata.zip&lt;/a&gt;&lt;/p&gt;</content:encoded><category>TPU</category><category>TPU-v6e</category><guid isPermaLink="true">https://www.junyi.dev/posts/tpu-v6e/</guid></item></channel></rss>