Solar Open2 250B
Serve Solar-Open2-250B-NVFP4 with Dynamo on B200, aggregated or disaggregated.
Profiles
Prerequisites
- Kubernetes cluster with B200 nodes and the Dynamo operator installed
- A
ReadWriteManystorage class.model-cache.yamlrequests 1200Gi, which holds the checkpoint alongside benchmark traces and artifacts; the checkpoint itself is about 153 GB. - A Hugging Face token, as a secret named
hf-token-secret, used by the download Job to fetch the checkpoint:
- For the disaggregated profile, an RDMA/InfiniBand device plugin exposing
rdma/ibon the GPU nodes. If your plugin advertises a different resource name, substitute it in the worker resource requests.
Prepare the model cache
Edit storageClassName in model-cache.yaml first.
The checkpoint is approximately 153 GB. Both profiles share this cache.
Deploy
Aggregated:
Disaggregated:
Smoke test
Port-forward the frontend of the profile you deployed. For the aggregated profile:
For the disaggregated profile:
Then, against either:
Solar Open2 is a reasoning model. Short generations may return the answer in
reasoning_content with content empty; allow a larger max_tokens to see a
final answer in content.
Benchmark
Both performance Jobs read a mooncake trace from the shared-model-cache PVC. The
trace is not part of the model download, so stage it first. The published figures use
nim_turbo_8k_1k_70kv_chat_new_noschedule_short_15perc.jsonl (1805 requests, mean
ISL 37.4k, mean OSL 1008), which ships in this repository under
recipes/nemotron-3-ultra/perf/traces/.
Copy it in through any running pod that mounts the PVC. Note that the worker pods
mount that volume at /shared-model-cache, while the benchmark Job mounts the same
volume at /model-cache, so the file staged below is what TRACE_FILE refers to:
Set TRACE_FILE in the Job to match whichever trace you stage.
Then run the Job for the profile under test:
Each Job runs the trace once and writes its summary to ARTIFACT_DIR. To run it
again, delete the Job first and re-apply; Jobs are immutable, so re-applying over an
existing one fails.
Runtime image
Both manifests pin a purpose-built runtime image by digest. It extends the Dynamo 1.4.1 vLLM runtime with the Solar Open2 architecture and parser support and a Dynamo frontend compatibility patch. The changes are Python-only.
The stock Dynamo 1.4.1 vLLM runtime does not serve this model — it has no Solar Open2 model definition — so the pinned image is required rather than optional. Pin by digest, not by tag.
Configuration notes
Both profiles
- NVFP4 weights, FP8 KV cache. Both manifests set
--kv-cache-dtype fp8; the disaggregated profile additionally sets--calculate-kv-scales. FP8 halves KV bytes relative to BF16, which roughly doubles the KV pool and raises the realised prefix-cache hit rate. - MoE backend.
--moe-backend flashinfer_trtllmwith--enable-flashinfer-autotune. - Attention. FlashInfer is selected automatically. Note that FlashAttention is not compatible with an FP8 KV cache on this model and will fail at startup if forced.
- Hybrid KV pages. Solar Open2 has 12 grouped-query attention layers and 36
KDA linear-attention layers. The effective KV page is a hybrid page of roughly
1072 tokens at
--block-size 16, not the block size itself. Tools that assume block size equals page size need adjusting. - Speculative decoding is not supported on this checkpoint and is not enabled.
Aggregated profile
- Two worker replicas at tensor parallel size 2, fronted by a KV-aware router
(
--router-mode kv,--router-kv-events) with a ZMQ KV-event publisher on each worker. KV-aware routing outperforms round-robin on prefix-reuse workloads. - Expert parallelism is left off. It measured as neutral for aggregated serving on this checkpoint, so the simpler configuration is used.
Disaggregated profile
- Separate prefill and decode workers connected by
NixlConnector, withkv_role: kv_produceron prefill andkv_role: kv_consumeron decode. - The topology is one prefill worker and two decode workers — TP4 with expert parallelism for prefill (4 GPUs), TP2 for each decode worker (4 GPUs), 8 GPUs in total.
VLLM_SSM_CONV_STATE_LAYOUT=DSis required. Solar Open2’s KDA layers register as SSM layers, and their convolution state must cross the KV-transfer boundary. Without this variable the engine asserts during initialization and the workers will not start.- Each worker requests
rdma/ibequal to its GPU count, four on prefill and two on each decode worker. This is required, not optional: without it the device plugin does not inject/dev/infiniband, UCX falls back to TCP, and nothing logs an error. Verify inside a running worker withkubectl exec <pod> -c main -- ls /dev/infinibandand expectuverbsN,umadNandrdma_cm. One HCA per GPU is correct; requesting more measured worse. If your RDMA device plugin advertises a different resource name thanrdma/ib, substitute it in both the requests and the limits. - Transport is configured with
NCCL_IB_DISABLE=0,UCX_TLS=^cuda_ipc,UCX_RNDV_SCHEME=get_zcopyandUCX_MAX_RNDV_RAILS=4.UCX_MAX_RNDV_RAILStracks the number of HCAs per worker; adjust it if your nodes differ.UCX_NET_DEVICESis deliberately left unset; pinning it producedNIXL_ERR_BACKEND. - Per-user output throughput runs close to the floor. The disaggregated profile clears the 50 tok/s target with little margin, and individual runs on contended hardware may land at or below it.
- The frontend uses round-robin routing, not KV-aware routing. This is deliberate. KV-aware routing improves the aggregated profile, but with a single prefill worker there is nothing for a prefix-aware router to choose between. KV-aware and load-aware routing were both measured on this topology and neither improved on round-robin.
Known limitations
- The disaggregated profile delivers slightly lower per-GPU throughput than the 4-GPU aggregated profile, at roughly an order of magnitude higher time-to-first-token. Prefer the aggregated profile unless independent prefill/decode scaling is the priority.
- The 1M context length requires sufficient KV capacity; reduce
--max-model-lenif deploying on a smaller GPU allocation. - Speculative decoding is not available on this checkpoint.
API compatibility
Some OpenAI-compatible surfaces do not behave as advertised. These originate in the Dynamo frontend, are not specific to this model, and no recipe setting affects them.
- Logprobs. Requesting
logprobson/v1/chat/completionsreturns HTTP 500. - Responses API.
store=truesucceeds but the response is not retrievable afterwards (GET /v1/responses/{id}returns 404). Unsupported parameters such asfrequency_penaltyare accepted and silently ignored. - Completions.
stop_token_idswith more than three IDs returns HTTP 500 rather than a 400.
Reasoning budget and structured output
The recipe sets reasoning_effort: high. Generations are long: on GPQA Diamond the
median output is roughly 6,400 tokens, the 99th percentile 131,000, and the longest
observed 209,000. A small max_tokens therefore truncates the model mid-reasoning
and returns finish_reason=length with no final content, which shows up most often
on structured-output requests where the JSON never arrives in message.content.
Omit max_tokens and let the 1M context window bound the request, or set it well
above the reasoning length the workload needs. Values in the low thousands are not
sufficient for this model.
Measured performance
Chat workload, 15% subset of the internal nim_turbo 8k/1k 70kv trace
(1805 requests, mean ISL 37.4k tokens, mean OSL 1008 tokens). SLA: p50 TTFT
below 5 s and p50 user output throughput at or above 50 tok/s.
Figures are measured with the profile’s benchmark manifest (perf.yaml for
aggregated, perf-disagg.yaml for disaggregated). That configuration isolates the
warmup phase from profiling; it does not clear server-side KV cache. For runs that
must start from a cold cache, restart the worker pods between them.
Source
- Manifests:
recipes/solar-open2-250b/ - Model:
nota-ai/Solar-Open2-250B-Nota-NVFP4