Azure News - 2026-10-13
2026-10-13
最終更新: 2026-10-14 01:32:01 JST
AI による概要
この日は 1 件で、Apps on Azure Blog による大規模モデルのセルフホスティングと可観測性の実践記事でした。Azure Spot 仮想マシン 1 台の H100 GPU 2 枚で Qwen3.8-Flash-Next を動かし、どの指標を見るべきか、標準的なダッシュボードが読み違える 3 か所、効果が測れた 1 つの起動フラグを扱っています。モデルは 2026 年 8 月に qwen-community-1.0 ライセンスで公開された Mixture-of-Experts で、総パラメーター 125B・トークンあたり活性 6B、48 層のうち 36 層が固定サイズの状態を持つ Gated DeltaNet で残り 12 層が Qwen Sparse Attention、さらに 51B の n-gram 埋め込みテーブル (H100 ではホストメモリに置かれる) と投機的デコード用の MTP 層、262,144 トークンのコンテキストを備えます。FP8 チェックポイントは 185.6 GB で 94 GB の H100 1 枚には載らず、テンソル並列で 2 枚に分割します。H100 のテンソルコアは FP8 までなので、FP4 重みは Marlin カーネルで BF16 に展開されて計算されるため省メモリにはなるが高速化にはならない点も整理されています。VM は Standard_NC80adis_H100_v5 で、2026 年 10 月 12 日時点の英国南部の Spot 価格は 1 時間 3.22 USD、従量課金の 17.45 USD に対して約 80% 安い一方、30 秒の通知で回収され得ます。実際に同サイズの 2 台はそれぞれ 6 時間 17 分と 6 時間 30 分で回収されました。可観測性の指摘が中心で、投機的デコードがあると 1 デコードステップに約 2.4 トークンが含まれ、かつ 99.7% のステップがヒストグラムの 10〜25 ms バケットに入るため、トークン間レイテンシーの中央値から求める一般的な速度計算が実測 150〜260 トークン/秒に対して一定の 57 と読めてしまう、DCGM のヘルスチェックが NVLink ブリッジの未使用 6 リンクを down と報告して正常な VM を Fail と判定する、Azure Monitor ワークスペース上のネイティブヒストグラムでは histogram_sum や histogram_avg が 0 や NaN を返す、という 3 点が挙げられています。負荷はベンチマークではなくオープンソースのコーディングエージェント opencode を読み取り専用の設定で複数走らせて生成しており、エージェント型のトラフィックでは入力トークンの 88〜95% がプレフィックスキャッシュから供給され、10 万トークン超でも最初のトークンが 1 秒未満で返ることが示されました。ただし同時 5 リクエストから 7 に増えると互いのキャッシュを追い出し合い、1 ターンごとに 10 万トークン以上を再計算して p95 の初回トークンが 25.5 秒まで伸びました。そこで KV キャッシュを FP8 にする 2 つのフラグを加えると、同じメモリでキャッシュが 840,277 から 1,506,691 トークンへ増え、同時実行が倍になってプリエンプションが消え、p95 の初回トークンは 23.8 秒から 11.0 秒へ、全体の出力は 219 から 415 トークン/秒へ改善しました。まとめとして、サイジングはリクエスト数ではなくセッション数で考える (KV キャッシュ 150 万トークン ÷ 1 セッション 10〜15 万トークンで 10〜15 セッション)、待ち要因は capacity ラベルで見る、プレフィックスキャッシュのヒット率と再計算入力は併せて読む、出力トークンの 87〜94% が思考なので定型的なツール呼び出しでは reasoning_effort を下げる、といった指針が示されています。
主要トピック
セルフホスティング: Azure Spot VM (Standard_NC80adis_H100_v5) の H100 2 枚で Qwen3.8-Flash-Next (125B / 活性 6B) を FP8 でテンソル並列実行。Spot 価格は英国南部で 1 時間 3.22 USD、従量課金比で約 80% 安いが 30 秒通知で回収され得る
可観測性: 標準ダッシュボードの読み違い 3 点。投機的デコードとヒストグラムのバケット集中でトークン間レイテンシー由来の速度が実測 150〜260 に対し 57 と出る、DCGM が NVLink の未使用 6 リンクを down として Fail 判定、ネイティブヒストグラムで histogram_sum / histogram_avg が 0 や NaN
チューニング: KV キャッシュを FP8 にすると同一メモリでキャッシュが 840,277 → 1,506,691 トークンに増え、同時実行が倍、プリエンプションが 0、p95 の初回トークンが 23.8 秒 → 11.0 秒、出力が 219 → 415 トークン/秒
運用指針: サイジングはリクエスト数ではなくセッション数 (KV キャッシュ ÷ 1 セッションのコンテキストで 10〜15 セッション)。待ち要因は capacity ラベル、ヒット率と再計算入力を併読、出力の 87〜94% が思考なので reasoning_effort が client 側の調整レバー
Apps on Azure Blog
Qwen3.8-Flash-Next on two H100s in an Azure Spot VM
- Link: https://techcommunity.microsoft.com/t5/apps-on-azure-blog/qwen3-8-flash-next-on-two-h100s-in-an-azure-spot-vm/ba-p/4563893
- Published: 2026-10-13 02:05:45
- Fetched: 2026-10-14 01:32:01
詳細を表示
With 150,000 tokens of context, one request to Qwen3.8-Flash-Next decoded at about 200 tokens a second, and its first token arrived in under a second, because 95% of the prompt came from the cache. That held while the agents kept up to five requests running at once. At seven, one request in twenty waited 23 seconds or more for its first token. No error explained it; the dashboard did, one panel after another.
This post covers how to run the model on two H100 GPUs in one Azure VM and how to observe it: which signals matter for this kind of model, three places where a standard dashboard misreads it, and one serving flag that made a measurable difference. My previous post, Qwen3.8-27B on a $2 H100 Azure Spot VM: does it pay off?, walks through the base setup (the VM, the Compose stack, the LiteLLM gateway and the OpenTelemetry pipeline into Application Insights and Grafana) in more detail; here I focus on what changes for a bigger model.
What you're running
Qwen3.8-Flash-Next is a mixture-of-experts (MoE) model from Qwen, released in August 2026 under the qwen-community-1.0 license. A few of its properties shape how you serve it:
- 125B parameters, 6B active per token. Each token passes through a small share of the experts, so decoding reads far less than the full weights.
- Mostly linear attention. Its 48 layers come in 12 blocks of three Gated DeltaNet layers, which keep a fixed-size state, and one Qwen Sparse Attention layer. Only those 12 sparse-attention layers keep a per-token KV cache.
- A 51B n-gram embedding table (20 million bigram and trigram entries), which vLLM keeps in host memory on H100 GPUs.
- A multi-token prediction (MTP) layer for speculative decoding, a 262,144-token context window (extendable to about one million with YaRN), and vision input.
- Thinking on by default, with a
reasoning_effortsetting (xhighby default, ormediumorlow).
Qwen's FP8 checkpoint, Qwen/Qwen3.8-Flash-Next-FP8, is 185.6 GB, too much for one 94 GB H100 even with the n-gram table in host memory. Split across two with tensor parallelism, it fits with room left for the KV cache:
| Per GPU | GiB |
|---|---|
| Weights, n-gram table excluded, split in two | 65.6 |
vLLM's share of the GPU at --gpu-memory-utilization 0.90 | ~84 |
| Left for the KV cache, activations and CUDA graphs | ~18 |
Why not 4-bit weights?
FP4 weights would save memory, but on an H100 they can be slower. The H100's tensor cores compute in FP8, not FP4, which arrived with Blackwell. NVIDIA publishes an NVFP4 checkpoint of this model, 132.7 GB, quantized for and tested on B200 and B300 GPUs, under the NVIDIA Open Model License in addition to Qwen's. vLLM v0.31.0 still loads it on an H100 by falling back to a Marlin kernel, which keeps the expert weights in 4 bits and unpacks them to BF16 for each matrix multiplication. That saves memory, not compute: the experts then multiply in BF16, whose peak rate on an H100 is half that of FP8.
In that checkpoint, only the routed experts are FP4. Attention, the shared experts and the other layers stay in BF16, and the n-gram table and the MTP layer stay in FP8. My estimate from its file sizes:
| FP8 (this post) | NVFP4 on H100 (estimate) | |
|---|---|---|
| Weights on the GPUs, n-gram table excluded | 131 GiB | ~76 GiB |
| On one H100 | Doesn't fit | Only just: a few GiB left for the KV cache, about one long agent session |
| KV cache on two H100s | 1.5 million tokens, FP8 cache | About three times as much |
| Expert matrix math | FP8 tensor cores | BF16, after unpacking the weights |
Which way it goes depends on the workload. Decoding mostly reads weights, so smaller weights can keep it fast. Prefill is mostly math, and coding agents send long prompts, so a slower prefill shows up as a longer wait for the first token whenever the cache misses. A cache three times larger would miss less often. I haven't measured this trade; if you try it, the panels later in this post show it, with time to first token and computed prefill set against the prefix-cache hit rate. For one GPU today, see the one-GPU alternative near the end of this post.
Step 1: Get a two-GPU Spot VM
The size is Standard_NC80adis_H100_v5: two H100 NVL GPUs with 94 GB each, 80 vCPUs and 640 GiB of memory. On October 12, 2026 it cost $3.22 an hour as an Azure Spot Virtual Machine in UK South, against $17.45 at the pay-as-you-go price: about 80% less, for capacity that Azure can take back with 30 seconds' notice (prices change; check the pricing page). In the two regions I compared, the Spot price was exactly twice the one-GPU size's.
Spot draws on your regional low-priority vCPU quota. This size takes 80 vCPUs of it, which in my subscription left no room in the same region for the one-GPU VM from the previous post. Plan for evictions: my two VMs of this size in UK South zone 2 ran 6 h 17 min and 6 h 30 min before Azure reclaimed them. Both kept their disks under the Deallocate eviction policy, so a restart in place reloads the model from disk in a few minutes.
The sample's infra/provision-spot-vm.sh creates this size, with the same private network as in the previous post: no public IP, outbound traffic through a NAT gateway, and administration through az vm run-command:
LOCATION=southeastasia ZONE=2 infra/provision-spot-vm.sh
Step 2: Prepare the host
The host bootstrap is the same as for one GPU: the data disk, the NVIDIA datacenter driver (595 branch), Docker and the NVIDIA Container Toolkit, in about four minutes. Then check how the two GPUs are connected, because tensor parallelism exchanges data between them in every layer:
$ nvidia-smi topo -m
GPU0 GPU1 CPU Affinity NUMA Affinity
GPU0 X NV12 0-39 0
GPU1 NV12 X 40-79 1
NV12 means an NVLink bridge with 12 links. nvidia-smi nvlink --status lists 18 links per GPU, 12 active at 26.6 GB/s each and 6 inactive. Remember those six; they come back in the monitoring section.
Step 3: Serve it with vLLM
NVIDIA's NGC vLLM container that served the 27B predates this model, so the stack uses vLLM's own vllm/vllm-openai:v0.31.0 image, which knows the architecture and its MTP layer. The flags follow vLLM's recipe for this model, adjusted for two GPUs:
vllm serve /models/Qwen3.8-Flash-Next-FP8 \
--served-model-name qwen3.8-flash-next-vllm \
--tensor-parallel-size 2 \
--max-model-len 262144 \
--gpu-memory-utilization 0.90 \
--max-num-seqs 32 \
--max-num-batched-tokens 16384 \
--enable-prefix-caching \
--kv-cache-dtype fp8 \
--attention-config.indexer_kv_dtype fp8 \
--moe-backend triton \
--no-enable-flashinfer-autotune \
--spec-method mtp --spec-tokens 3 \
--reasoning-parser qwen3 \
--enable-auto-tool-choice --tool-call-parser qwen3_coder
| Flag | Why |
|---|---|
--tensor-parallel-size 2 | Splits every layer across the two GPUs. |
--gpu-memory-utilization 0.90 | Under load each GPU reached 92 of 96 GB at this setting, so I left it there. |
--kv-cache-dtype fp8, --attention-config.indexer_kv_dtype fp8 | Stores the KV cache, and the sparse-attention indexer's, in FP8. The recipe's H100 command leaves these out; its H200 command, also a Hopper GPU, includes them. Why I added them is the subject of One flag, measured, below. |
--moe-backend triton, --no-enable-flashinfer-autotune | The recipe's MoE kernels for Hopper GPUs. |
--spec-method mtp --spec-tokens 3 | Speculative decoding with the model's MTP layer. |
--reasoning-parser qwen3, --tool-call-parser qwen3_coder | Thinking returned separately from the answer, and OpenAI-style tool calls. |
Two container settings matter as well. vLLM pins each GPU's share of the n-gram table in host memory, so the container needs ulimits: memlock: -1, and the two tensor-parallel workers communicate through shared memory, so it needs ipc: host.
On a new VM, the download takes about 20 minutes and loading about 4 more. The startup log then tells you the two numbers that decide everything later:
Model loading took 65.58 GiB memory
GPU KV cache size: 1,506,691 tokens, Maximum concurrency for 262,144 tokens per request: 5.75x
Step 4: Check it
The smoke test from the previous post (an exact answer, a required tool call and a decode-speed run) passes, and one request generates 260 tokens a second with thinking off, against 166 for the 27B on one GPU. A 45-case basic evaluation (math and logic with and without thinking, knowledge, instruction following, generated code run against tests, tool calls, and needle retrieval at 30K and 120K tokens) scores 44 of 45, the same as the 27B. An agentic coding evaluation on 10 short Terminal-Bench 2.0 tasks solved 8.
Step 5: Observe it
The telemetry pipeline is the one from the previous post: an OpenTelemetry Collector on the VM scrapes vLLM, LiteLLM and NVIDIA's DCGM exporter, reads host and container statistics, receives LiteLLM's spans and tails the container logs, and sends all of it to Application Insights, which stores the metrics in an Azure Monitor workspace as Prometheus data. Grafana reads both. Two things changed for this model.
One dashboard for several models
The collector stamps every metric, span and log record with an upstream_model label naming the model the VM serves. A model variable at the top of the dashboard filters every panel by it and lets you pick one model, several or all. With more than one, each graph draws a line per model, so the 27B and Flash-Next appear side by side.
Two details made it work. The Azure Monitor workspace's label-values API requires exactly one metric name, so the variable's query is label_values(vllm:num_requests_running, upstream_model). And the VM's cost is now computed from the GPUs reporting in each minute times a price per GPU-hour, so a one-GPU and a two-GPU model are both costed correctly from one price.
Three numbers the dashboard got wrong at first
Per-request speed. The usual formula, 1 / histogram_quantile(0.5, ...inter_token_latency_seconds...), read a constant 57 tokens a second on this model while requests were really getting 150 to 260. Two things go wrong at once. With speculative decoding, vLLM records one inter-token interval per decode step, and each step produced about 2.4 tokens. And 99.7% of the steps landed in the histogram's 10–25 ms bucket, so the median is that bucket's midpoint, 17.5 ms, whatever the real speed. I used the same formula in the previous post's table; this one holds up better:
(sum(rate(vllm:generation_tokens_total[5m])) > 0)
/ clamp_min(sum(avg_over_time(vllm:num_requests_running[5m])), 1)
That's output tokens per second divided by the requests running. The clamp_min matters because the collector samples the running-request gauge every 15 seconds and misses short requests that start and end between samples. Without it, a minute of short requests read up to 668 tokens a second.
GPU health. DCGM's health check reported Fail on a healthy VM. Its NVLink watch reports the six inactive links on each GPU as down (dcgm_fr_nvlink_down), while the bridge's 12 links are up. The dashboard's health tile now leaves that one error code out, which leaves the Warn that power throttling raises under normal load.
Histogram means. OTLP histograms arrive in the Azure Monitor workspace as native histograms. histogram_quantile works on them, but histogram_sum, histogram_count and histogram_avg returned 0 or NaN for these metrics, so averages come from counter rates, as above, rather than from the histograms.
The panels that matter for this model
| Question | PromQL |
|---|---|
| Are requests waiting, and for what? | sum by (reason) (vllm:num_requests_waiting_by_reason): capacity means the KV cache is full |
| Is the KV cache holding the sessions? | max(vllm:kv_cache_usage_perc), and the prefix-cache hit rate sum(rate(vllm:prefix_cache_hits_total[5m])) / sum(rate(vllm:prefix_cache_queries_total[5m])) |
| Is the GPU recomputing prompts? | sum(rate(vllm:prompt_tokens_by_source_total{source="local_compute"}[5m])) against {source="local_cache_hit"}, and sum(increase(vllm:num_preemptions_total[5m])) |
| How far does speculative decoding get? | sum by (position) (rate(vllm:spec_decode_num_accepted_tokens_per_pos_total[5m])) / scalar(sum(rate(vllm:spec_decode_num_drafts_total[5m]))) |
| What crosses the PCIe bus? | sum(dcgm_fi_prof_pcie_rx_bytes), host to GPU in bytes per second |
The spans complete the picture. Each LiteLLM call carries its input and output tokens, time to first token, duration and finish reason, so decode speed per call is output tokens / (duration - time to first token), and you can group calls by prompt size. The spans also record each tool the client declares, which tells different agents apart without capturing a single prompt.
Load it with coding agents
A benchmark sends synthetic prompts at a fixed rate. Agents don't: their prompts grow with every turn, most of each prompt is the previous turn's, and between turns they run tools while the model waits. To see the model under that shape of load, I ran opencode, an open-source coding agent, as a load generator: one instance per local repository, each asked to review every file.
Agents that can edit files and run commands shouldn't be pointed at a dozen repositories for a load test, so every instance ran with an inline configuration that limits it to read-only tools. Passed in OPENCODE_CONFIG_CONTENT, it overrides the global and project opencode config files; only managed settings rank higher. The sample's load-test/review-readonly.json is a copy of the one I ran that also defines the gateway as an OpenAI-compatible provider and keeps agents away from .env files:
{
"provider": {
"litellm": {
"npm": "@ai-sdk/openai-compatible",
"name": "LiteLLM gateway",
"options": { "baseURL": "{env:LOCAL_LLM_URL}/v1" },
"models": { "local-llm": { "name": "local-llm", "limit": { "context": 245760, "output": 16384 } } }
}
},
"model": "litellm/local-llm",
"small_model": "litellm/local-llm",
"permission": {
"read": { "*": "allow", "*.env": "deny", "*.env.*": "deny", "*.env.example": "allow" },
"glob": "allow", "grep": "allow", "list": "allow", "todowrite": "allow",
"edit": "deny", "bash": "deny", "webfetch": "deny", "websearch": "deny",
"external_directory": "deny", "question": "deny", "task": "deny", "doom_loop": "deny"
},
"mcp": { "playwright": { "enabled": false } },
"lsp": false,
"formatter": false,
"snapshot": false,
"autoupdate": false,
"share": "disabled"
}
List your own MCP servers under mcp the same way. To run one agent by hand:
export LOCAL_LLM_URL=https://<your-domain>.ngrok-free.dev
export OPENCODE_CONFIG_CONTENT="$(cat load-test/review-readonly.json)"
cd ~/src/some-repo && opencode run --pure -m litellm/local-llm "Review every file in this repository."
The sample's load-test/launch.sh starts one per folder you list, a few seconds apart, and stops launching when the workstation runs low on memory.
Check it in a scratch folder first: ask an agent to create a file and run a command. In my test the agent wasn't offered those tools, and the folder stayed unchanged.
Three settings in it matter beyond safety. The context limit tells opencode when to compact the conversation; without it, sessions grew until the gateway rejected them as too long. Denying task stops an agent from starting sub-agents, so each instance is one session; with sub-agents allowed, 29 instances had 69 requests in flight. And each instance took 0.7–0.9 GB of memory on my workstation, so the workstation, not the GPU, capped the first run at 46 agents.
What the telemetry showed
Decode speed barely depends on context length
From the spans, here is the median decode speed of calls with at least 300 output tokens, by prompt size, next to the 27B's from the previous post's busy window:
| Prompt size | Flash-Next, 1–2 requests at once | Flash-Next, 3–5 at once | 27B, about 3 at once |
|---|---|---|---|
| Under 50K tokens | 197–221 | 201 | 60–78 |
| 50–100K | 192 | 183 | 63 |
| 150–200K | 217 | 200 | 54 |
| Over 200K | 195 | 166 | 51 |
The 27B slowed by about a fifth as its prompts grew; Flash-Next held its speed. The architecture explains it: 36 of its 48 layers keep a fixed-size state, and the other 12 use sparse attention, so the work per token grows little with the context.
The n-gram table shows up on the PCIe bus
With the model decoding, DCGM reported 4.6–8.6 GB/s flowing from host to GPU over PCIe, rising and falling with the output rate and dropping to almost nothing when idle. On the 27B, which keeps everything in GPU memory, the same counter read 0.03 GB/s. The n-gram table is the part of this model that lives in host memory, so it's the likely source. That's at most an eighth of what the GPUs' PCIe 5.0 x16 links carry, so it isn't a bottleneck at this load, but it's worth a panel if you run more requests at once.
Sessions, not requests, fill the KV cache
Agentic traffic is mostly reused prompt. For hours, 88–95% of input tokens came from the prefix cache, and time to first token stayed under a second at 100,000 tokens and more. vLLM keeps each session's prompt in the cache between turns, so the next turn only processes what's new.
That works only while every active session's prompt fits, including the sessions that are between turns, running tools. With the KV cache in BF16, the cache held 840,277 tokens. Here is what happened when the agents went from five requests running at once to seven, with prompts of about 110,000 tokens at the median and up to 230,000:
| 15 minutes to (UTC) | Requests running, peak | KV cache, peak | Prefix-cache hits | Input recomputed | Preemptions | Time to first token, p50 / p95 |
|---|---|---|---|---|---|---|
| 04:15 | 5 | 72% | 91% | 2.1M tokens | 0 | 0.7 s / 2.7 s |
| 04:30 | 7 | 97% | 45% | 9.0M tokens | 4 | 1.4 s / 25.5 s |
| 04:45 | 7 | 98% | 50% | 6.8M tokens | 2 | 3.9 s / 23.2 s |
| 05:00 | 7 | 99% | 74% | 5.2M tokens | 24 | 0.6 s / 14.5 s |
At no point did vLLM run more than seven requests, against a limit of 32. The limit that mattered was memory: the sessions evicted each other's cached prompts, and each turn recomputed 100,000 tokens or more. The waiting-requests metric says so directly: up to four requests at a time waited, all with the reason label capacity.
One flag, measured
The KV cache in FP8 takes half the memory per token. With --kv-cache-dtype fp8 and --attention-config.indexer_kv_dtype fp8, the cache grew from 840,277 to 1,506,691 tokens in the same memory, and from 3.2 to 5.75 full-length contexts. The weights don't change, and neither did the checks: 260 tokens a second for one request and 44 of 45 on the evaluation, including both long-context retrievals.
Then I repeated the load on a fresh VM with eight of the read-only review agents described above, one session each. Their prompts grew to a median of 122,000 tokens and up to 235,000, a little larger than in the BF16 window. Measured the same way for both windows:
| BF16 cache, 04:15–05:00 | FP8 cache, 07:23–07:38 | |
|---|---|---|
| Requests running, average / peak | 3.3 / 7 | 6.4 / 8 |
| Prompt size, p50 / max | 112K / 229K | 122K / 235K |
| KV cache, peak | 99% | 88% |
| Prefix-cache hits | 58% | 74% |
| Preemptions | 30 | 0 |
| Time to first token, p50 / p95 / max | 0.9 s / 23.8 s / 42.2 s | 1.1 s / 11.0 s / 20.7 s |
| Output tokens per second, all requests | 219 | 415 |
Twice as many requests ran at once, the cache never filled, nothing was preempted, and the slowest first tokens arrived in half the time. The hit rate didn't climb back to 90%: review agents read new files on every turn, and no cache can serve tokens it hasn't seen. The two windows come from different agents, so take the comparison as indicative rather than exact; the cache panels tell the same story.
Past the cache
More agents don't help once the cache is full. With 46 agents on the FP8 cache, 21 requests ran and 24 waited for capacity, the cache stayed 97% full, and the prefix-cache hit rate fell to 0%. The GPUs spent their time on about 12,000 tokens a second of recomputed prompts against 400–450 of output, and the median time to first token passed 30 seconds.
Stopping the agents didn't free the server right away either. Their disconnects didn't make it through the gateways to vLLM, which worked through the 35 requests the agents had left queued, a few minutes of work for nobody, before the next test could start.
Rules of thumb
- Size by sessions, not requests. The number of agent sessions that stay cached is roughly the KV cache size divided by a typical session's context: about 1.5 million / 100,000–150,000, so 10 to 15 here. The startup log gives you the first number, and the spans give you the second.
- Watch why requests wait.
vllm:num_requests_waiting_by_reason{reason="capacity"}rising means memory, which--max-num-seqswon't fix. - Read prefix-cache hits and recomputed input together. A falling hit rate with rising computed input is the cache thrashing, and time to first token follows within minutes.
- Mind the thinking. 87–94% of output tokens were reasoning. For routine tool calls, a lower
reasoning_effortis the client-side lever.
The one-GPU alternative
If one H100 is all you have, Strata, an open-source engine built for this model on consumer GPUs, runs Unsloth's ~4-bit GGUF on a single H100 NVL with every expert in GPU memory: 151 tokens a second for one request, 44 of 45 on the same evaluation and 9 of 10 agentic tasks. Concurrency is where it differs from vLLM: in version 0.1.42, four batch slots added only about 20% (177 tokens a second in total), and under the evaluation's mixed concurrent load they started failing requests, so I ran it one request at a time. Check newer releases if you need concurrency.
Try it
- Sample code: wuyi-labs/2610-h100-qwen-38-flash-next, with provisioning, the vLLM Compose stack, the dashboard and the read-only opencode load test.
- The model: Qwen3.8-Flash-Next on Hugging Face and vLLM's recipe for it.
- The base setup: Qwen3.8-27B on a $2 H100 Azure Spot VM: does it pay off?, and Ingest OTLP data into Azure Monitor with the OpenTelemetry Collector.
- KV cache quantization: Quantized KV cache in vLLM.
- Load generation: opencode and its permissions.
- Spot: Azure Spot Virtual Machines and Linux VM pricing.