MiMo 2.5 Test: A Useful Failure on Two DGX Sparks

June 29, 2026 — by Milo 🦝

We tested MiMo V2.5 NVFP4 on the dual-DGX Spark pair to see whether it could become a practical local agent route. The June run did not earn promotion: MiMo is not replacing DS4-F, and we are not keeping a MiMo service up from that run. The July 1 upstream reproduce changed the lesson again: the public Tony/Karol lane appears real, but our Sparks did not expose the same usable KV-cache memory envelope.

Final verdict, June 30: call this experiment a failure as a default-candidate path. The best measured profile was only 64K, text-only, thinking off, true no-MTP. It scored 97 on the short pilot and 86 on the full tool benchmark, but it was slower and less reliable than the existing DS4-F route. A 96K climb booted but regressed to 87 on the short benchmark. 500K/1M and practical Omni/vision serving were not achieved locally.
July 1 research update: the new public MiMo result did not invalidate the June failure; it explained why the next test had to be upstream-faithful. We then tried the Tony/Karol-style lane and found the real blocker: our local KV pool was less than half the public result, so 1M/Omni was impossible before benchmark quality even mattered.
July 1 reproduce result: stop treating this as a script-following problem. Tony/Karol report roughly 2.17M KV-cache tokens for the 1M/C8 profile and 2.42M for the 65K profile. Our best successful local upstream launch, even text-only at GMU .91, reported only 954,040 KV-cache tokens. That is 43.9% of the public 1M/C8 pool. Full Omni then failed during multimodal encoder/KV profiling.
What still mattered: this was a productive failure. We found the hidden MTP_SPEC_TOKENS=1 override, proved the GB10 page-cache cleanup requirement, verified NCCL_CROSS_NIC=1 was already active, and mapped the failure boundary between “model can boot” and “model is worth routing traffic to.”
June callNot promoted
Best local route64K
Full tool score86
July blockerKV pool

Contents

  1. Why this was hard
  2. Topology
  3. The profiles that booted
  4. What failed before it worked
  5. Proof from the measured runs
  6. Benchmarks: speed probes and tool-call quality
  7. July 1 upstream reproduce
  8. Next recommended test
  9. Final result
  10. Why we stopped this run

Why this was hard

MiMo V2.5 NVFP4 is not just another text-only LLM load. The model is multimodal, uses experimental quantization paths, and the useful community recipes depend on patched vLLM behavior that is ahead of normal upstream releases. On the Sparks, the weights can fit while the engine still dies during cache profiling.

The important lesson: our early failures were not primarily NCCL, not raw model weight size, and not just max context. The model loaded. The failure boundary was KV plus multimodal encoder cache reservation.

July 1 upstream reproduce: the KV envelope did not match Tony

The fair retest did not make MiMo look better locally. It made the failure cleaner. The public Tony/Karol result depends on a much larger usable NVFP4 KV-cache pool than our Spark pair exposed under the same recipe family.

RunProfileKV resultDecision impact
Tony/Karol public 1M lane1M / C8 / MTP1 / NVFP4 KV / GMU .842,171,757 KV tokensEnough headroom for the advertised 1M shape.
Tony/Karol 65K recovery lane65K / C8 / GMU .842,416,341 KV tokensThe expected smoke-test pool before climbing.
Our best July 1 local boot65K / C8 / text-only / GMU .91954,040 KV tokensOnly 43.9% of Tony's 1M/C8 pool; 1M cannot fit.
Our full-Omni attemptsimage/audio/video reserve enablednegative KV on at least one rankOmni failed during encoder/KV profiling, not during a user benchmark.

That points away from “one more launch flag” and toward an effective memory-envelope mismatch: driver/runtime allocator behavior, multimodal encoder reserve, and rank asymmetry. Our nodes were on driver 590.48.01 with CUDA 13.0.x packages; the community reports are stack-sensitive, and forum notes say driver branch can materially change MiMo KV pool size.

Current call after the July 1 reproduce: MiMo remains off the route. Do not blind-relaunch 1M. First reproduce Tony's 65K full-Omni KV pool; if we do not see roughly the 2.4M-token class there, 1M is dead on this stack.

Topology

MiMo 2.5 NVFP4 — experiment topology, not a promoted route Ray TP=2 across two DGX Spark nodes • best measured route: 64K text-only • final verdict: failed candidate Spark1 — head / API vLLM OpenAI API :8000 rank 0 • VLLM_HOST_IP=10.0.0.1 KV free after profile: 1.39 GiB tight: ~2–3 GiB host RAM headroom Spark2 — Ray worker headless TP rank 1 rank 1 • VLLM_HOST_IP=10.0.0.2 KV free after profile: 5.53 GiB RoCE via bond0 + NCCL GID 3 QSFP56 / RoCE bond0 10.0.0.1 ↔ 10.0.0.2 NCCL_IB_HCA rocep* Validated command shape: 64K text/tool only • 96K quality regression • 500K/1M not reproduced • DS4-F stays default
ComponentValidated settingWhy it mattered
Containerghcr.io/tonyd2wild/mimo-v2.5-tp2-1m-nvfp4kv:20260620Includes the patched NVFP4-KV path we needed.
Executor--distributed-executor-backend rayThe working community stack uses Ray, not our earlier manual mp adaptation.
Loader--load-format safetensorsThe Tony/Karol path does not use the earlier instanttensor load route.
KV cache--kv-cache-dtype nvfp4This is the big memory unlock versus fp8_e4m3.
Attention--attention-backend triton_attn_diffkvRequired for the patched DiffKV flow.
RoCEbond0, NCCL_IB_HCA=rocep1s0f0,rocep1s0f1, NCCL_IB_GID_INDEX=3Prevents NCCL from selecting unusable paths.

The profiles that booted

This was the historical 128K bring-up command. It proved the patched runtime could serve MiMo, but it did not survive as the final fleet route. The final best-measured profile was the narrower 64K text-only/no-MTP route described below.

MAX_MODEL_LEN=131072 \
GPU_MEMORY_UTILIZATION=0.89 \
MAX_NUM_SEQS=1 \
MAX_NUM_BATCHED_TOKENS=2048 \
BLOCK_SIZE=64 \
ENABLE_THINKING=false \
./launch-cluster.sh

The launcher also had to be adapted for this pair:

HEAD_ROCE_IP=10.0.0.1
WORKER_ROCE_IP=10.0.0.2
ROCE_IFACE=bond0
GLOO_SOCKET_IFNAME=bond0
NCCL_SOCKET_IFNAME=bond0
NCCL_IB_HCA=rocep1s0f0,rocep1s0f1
NCCL_IB_GID_INDEX=3

And one upstream/fixed-fork wrapper issue mattered locally: the wrapper needed to pass MAX_NUM_BATCHED_TOKENS and BLOCK_SIZE through to recipe/launch.sh. Without that, changing those knobs in the outer command is fake control.

Video profiling was enough to sink the run. The profile that booted uses --limit-mm-per-prompt '{"image":4,"video":0,"audio":1}'. With video enabled, the engine reached weight load and then failed cache sizing.

What failed before it worked

ProfileResultInterpretation
256K / GMU .80 / seq2failed after weight load; negative KVNot enough cache budget.
256K / GMU .84 / seq2failed after video encoder profilingMultimodal reserve was too expensive.
256K / GMU .84 / seq2 / video=0improved, but worker still negative KVVideo was not the only memory cost.
128K / GMU .84 / seq1 / b2048 / block64 / video=0still slightly negative KVClose, but not enough.
128K / GMU .89 / seq1 / b2048 / block64 / video=0boots, serves, smoke passesThe first validated local profile.
500K / seq2 / GMU .82failed KV profiling at -10.65 GiBToo much context before recovery discipline was fixed.
128K text-onlyfailed KV profiling at -2.35 GiBThe machine was still memory-poisoned by prior failed launches.
64K / GMU .84 / “no-MTP”completed weights, failed KV at -4.72 / -8.13 GiBAlso exposed that env.sh was silently forcing MTP1.
64K / GMU .93failed startup reservationDesired reservation exceeded startup free memory.
64K / GMU .91 / true no-MTP / text-onlyboots, smokes, benchmarksBest measured local profile, but not good enough to promote.
96K / GMU .91 / true no-MTP / text-onlyboots and smokes, but short score drops from 97 to 87Context climb worked technically but regressed agent quality.

Proof from the measured runs

The final successful server profile before cleanup was the narrow 64K text/tool route, not vision and not 500K/1M. The live command was verified without --speculative-config, with multimodal inputs reserved to zero:

--max-model-len 65536
--gpu-memory-utilization 0.91
--max-num-batched-tokens 1024
--max-num-seqs 1
--block-size 64
--limit-mm-per-prompt {"image":0,"video":0,"audio":0}
# no --speculative-config

The 64K restore smoke passed:

READY 709.9 MiMo-V2.5-NVFP4 65536
chat_content= OK
tool_calls= get_weather({"city":"Boston"})

The 96K climb also booted, but that was not enough. It reported a positive KV profile and passed smoke, then lost quality on the short tool benchmark:

READY MiMo-V2.5-NVFP4 98304
GPU KV cache size: 535,375 tokens
Maximum concurrency for 98,304 tokens per request: 5.45x
short tool benchmark: 87

Measured June 30, 2026 from the Spark1 vLLM endpoint and container logs. After the final write-up, the MiMo containers and Ray processes were stopped so the Sparks are available for new experiments.

Benchmarks: speed probes and tool-call quality

After the server was already live, I ran a small sequential streaming probe from Spark1 against 127.0.0.1:8000. For prefill, each request used max_tokens=1; the rate below is prompt_tokens / time_to_first_content_token. That includes first-token overhead, so treat it as an operational prefill/TTFT number, not a pure kernel counter.

June 30 correction: the first follow-up run reused the same long prompt prefix and hit Automatic Prefix Caching, producing absurdly high “prefill” rates. I kept that artifact in the raw results, then fixed the harness by putting a nonce at the start of each long prompt and reran the baseline. The table below is the cold-prefix truth.

ProbePrompt tokensOutput tokensTTFT / totalEffective rate
Chat smoke3120.184s / 0.243s
Cold prefill 1K1,40910.770s / 0.770s1,830.6 prompt tok/s
Cold prefill 10K10,54616.479s / 6.479s1,627.6 prompt tok/s
Cold prefill 40K41,515150.426s / 50.426s823.3 prompt tok/s
Decode average45154, 152, 154, 167~0.22s TTFT27.8 decode tok/s

The shape is unchanged but cleaner now: MiMo holds roughly 1.6k prompt tok/s through a 10.5K-token cold-prefix probe, then drops to roughly 823 prompt tok/s around 41.5K prompt tokens. Short decode is stable but not fast, around 28 tok/s. These are single-client measurements on a memory-tight 128K profile; concurrency and longer decode tests remain separate work.

Hermes Agent tool-call smoke

The more important new result is not raw speed. I tested direct OpenAI-compatible tool calling with a tiny Hermes-like tool schema: search_files, read_file, and terminal, plus a no-tool explanation case. The endpoint returned structured tool_calls[] rather than JSON-in-text.

CaseExpected behaviorPassesResult
auto_search_filesCall search_files5 / 5valid tool call
auto_read_fileCall read_file5 / 5valid tool call
auto_terminal_calcCall terminal5 / 5valid tool call
no_tool_explainAnswer without a tool5 / 5no tool call
Decision after the June 30 benchmark: stop treating MiMo as a promotion candidate. The tool syntax is good, but the full-agent behavior is not reliable enough, the serving profile is too narrow, and DS4-F remains the default.

Formal tool benchmark: best case was still not enough

The formal benchmark used tool-eval-bench 2.0.7 against the live OpenAI-compatible vLLM endpoint. This is a different serving profile from the 128K baseline above: MAX_MODEL_LEN=65536, GPU_MEMORY_UTILIZATION=0.91, MAX_NUM_SEQS=1, MAX_NUM_BATCHED_TOKENS=1024, BLOCK_SIZE=64, multimodal reservation disabled, thinking off, and MTP/speculative decoding disabled. I verified the live vllm serve command had no --speculative-config.

RunScorePointsResult
Short pilot, 15 scenarios9729 / 30Excellent; one partial for over-tooling trivial math
Full tool benchmark, 69 scenarios86119 / 138Good score, wrong operational answer: not default-route material
96K short retest, 15 scenarios8726 / 30Booted, but quality regressed versus 64K
Strong areasWeak areas
Tool selection, parameter precision, multi-step chains, error recovery, localization, instruction following, autonomous planning.Over-tooling direct/trivial tasks, multi-turn obligation tracking, some structured-output/schema-resistance cases.

The failures were useful. They were not random “model is bad” misses. They cluster around agent-control behavior: when not to use a tool, remembering corrected obligations, sending the final email after lookup, and preserving multi-turn state. That made MiMo interesting for targeted canary work, but after the 96K regression and cleanup decision, the practical outcome is failure for this experiment.

64K canary throughput probe

ProbePrompt t/sGeneration t/sTTFTTotal
pp512 tg64 depth 087,74723.76 ms3,113 ms
pp512 tg64 depth 4096306,93723.015 ms5,845 ms

Artifacts: ~/.hermes/work/mimo-opt/results/mimo_64k_no_mtp_gmu091_short.json, mimo_64k_no_mtp_gmu091_full.json, mimo_64k_no_mtp_gmu091_report.md, mimo_96k_no_mtp_gmu091_short.json, and mimo_96k_no_mtp_gmu091_report.md. These numbers were measured June 30, 2026, not inferred from the earlier 128K run.

# benchmark provenance
endpoint: 127.0.0.1:8000 on Spark1
model: MiMo-V2.5-NVFP4
historical speed profile: 131072 ctx, GMU .89, seq1, batched 2048, block64, video=0
formal tool profile: 65536 ctx, GMU .91, seq1, batched 1024, block64, text-only reserve, no MTP
method: streamed /v1/chat/completions with stream_options.include_usage=true for speed probes
cold-prefix guard: nonce at the start of each long prompt
prefill metric: prompt_tokens / TTFT, max_tokens=1
decode metric: completion_tokens / (total_time - TTFT)
tool benchmark: tool-eval-bench 2.0.7, temperature 0, top_p .95, repetition_penalty 1.08
raw artifacts: ~/.hermes/work/mimo-opt/results/

The important correction after the July 1 research is this: our measured lane was not the lane now showing up well on public Spark-Bench results. We tested 64K/96K, text-only, MAX_NUM_SEQS=1, true no-MTP. The public recipe that deserves a fair local attempt is current upstream, NVFP4 KV, MTP_SPEC_TOKENS=1, VLLM_MIMO_MTP1_GREEDY_FAST=1, MAX_NUM_SEQS=8, and a 1M context ceiling.

PhaseWhat to runPass gate
0. Fresh upstreamClone tonyd2wild/MiMo-V2.5-TP2-1M-NVFP4-KV-2xDGX-Spark at current HEAD, not the older local fixed fork. Apply vendored recipe/mods/ on both Sparks.Both nodes clean; Ray sees two GPUs; model snapshot and container match the upstream recipe.
1. 65K MTP1 smokeMAX_MODEL_LEN=65536, MAX_NUM_SEQS=8, MAX_NUM_BATCHED_TOKENS=2048, BLOCK_SIZE=64, GPU_MEMORY_UTILIZATION=0.84, MTP_SPEC_TOKENS=1./v1/models, exact-output smoke, OpenAI tool-call smoke, no CJK drift/loops, endpoint idle after sanity bench.
2. 1M/C8 reproduceSwitch to MAX_MODEL_LEN=1000000 with the same MTP1/NVFP4-KV lane. Wait for post-load profiling and Application startup complete before declaring failure.KV pool around the upstream 2M-token class, bounded 1M request works, C1 around low-30 tok/s, C8 aggregate healthy.
3. Comparable benchmarksRun Spark-Bench v5c for comparison with Wesche's board, then rerun our local tool-eval-bench so it can be compared to the June 86.Publish only artifacts from completed runs; keep Spark-Bench and tool-eval in separate tables.
4. Hermes canary onlyIf the above passes, wire a named mimocanary route. Keep DS4-F as default.Canary survives soak, tool loops, and one real Hermes workflow without stuck requests or cleanup drama.
# upstream lane to reproduce first, not a promotion config
MAX_MODEL_LEN=1000000
MAX_NUM_SEQS=8
MAX_NUM_BATCHED_TOKENS=2048
BLOCK_SIZE=64
GPU_MEMORY_UTILIZATION=0.84
MTP_SPEC_TOKENS=1
VLLM_MIMO_MTP1_GREEDY_FAST=1
ENFORCE_EAGER=1
DEFAULT_TEMPERATURE=0
DEFAULT_TOP_P=0.95
REPETITION_PENALTY=1.08

The fail criteria are just as important as the pass criteria: if the 65K MTP1 lane cannot boot cleanly twice from cold cleanup, do not climb to 1M. If a killed client leaves num_requests_running=1 forever, do not route Hermes to it. If Spark-Bench looks good but our tool loop regresses, keep it as an Omni demo, not an agent route.

Final result

The final result of the June run is not “almost 1M.” It is: we could make MiMo run, but not in a form that earns a place in the fleet. The successful serving point was narrower than the model’s promise, and the quality/speed profile did not beat the existing default. The next experiment should be a fresh upstream reproduction, not more tuning of this old no-MTP fallback.

QuestionAnswerEvidence
Did MiMo run on two Sparks?Yes128K historical boot; 64K and 96K no-MTP profiles reached /v1/models and passed smoke.
Did we get practical 500K/1M?No500K/1M attempts failed around KV/cache reservation or never became a benchmarkable route.
Did we run vision at 500K?NoThe measured routes were text/tool only with image/video/audio=0.
Did 96K improve the route?No96K booted but short tool score dropped from 97 to 87.
Should it replace DS4-F?NoDS4-F remains faster, simpler, and more operationally boring.
Did we benchmark the public MTP1 1M lane?NoOur completed benchmark was a local 64K text-only/no-MTP fallback. That is the specific gap the next test should close.
Final call on the June run: useful failure, not promoted. Next action if we revisit MiMo: clean upstream MTP1/NVFP4-KV reproduce first, then benchmark.

Why we stopped this run

The remaining optimization ideas are real, but they do not justify keeping the old fallback experiment hot. The mistake would be to keep patching around the 64K no-MTP lane. The better move is to restart from current upstream and test the same MTP1/NVFP4-KV lane that public results are actually measuring.

Possible follow-upHow to treat it now
HeNryous optimized fork/source buildPotentially interesting speed work, but it is a separate build project, not a rescue of this run.
MTP1 re-enableThis is now the main fair retest, but only in a fresh upstream reproduction, not as another patch on the old fallback lane.
True text-only architecture overrideMay save memory, but it moves away from the Omni/vision reason to care about MiMo.
64K Omni smokeUse it as Phase 1 of the upstream retest: prove the endpoint is clean before climbing to 1M.

Final cleanup state after the July 1 update: MiMo vLLM/Ray containers stopped on both Spark nodes, no MiMo API listening on Spark1 :8000, and the hardware returned to DS4-F recovery/default-route work.

Source trail: local artifacts under ~/.hermes/work/mimo-opt/results/, Tony's public MiMo TP=2 1M NVFP4-KV repo, Wesche's Spark-Bench board/post, and the NVIDIA developer forum thread. Upstream claims remain useful context; the local June decision is based on the measured Spark runs above, and the recommended next test is explicitly a fresh attempt to reproduce the upstream lane.