Skip to content

Nemotron 3 Super 120B (NVFP4) on Cortex

A worked example of a model that needs everything the per-model configuration offers: a driver-gated engine image, custom arguments for parsers and speculative decoding, custom environment variables, and request defaults that differ from Cortex's stock values. Everything here is done through the product (UI or API); no gateway code, no .env edits and no hand-built images are required.

Model: nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 (hybrid Mamba2 / LatentMoE, ~75 GiB of shards). Reference hardware: 4 x L40S 48 GB (sm89, PCIe, no NVLink); validated at 85 tok/s single stream with CUDA graphs and MTP on.


1. Pick the engine image from the driver

Nemotron 3 requires vLLM >= 0.24 (NemotronHForCausalLM, modelopt_mixed, qwen3_xml and nemotron_v3 parsers). The pinned Cortex default vllm/vllm-openai:v0.28.0 covers it, but the default image is CUDA 13 and the host driver decides which build can start:

nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -1
Driver engine_image to set on the model
>= 580 leave blank (uses VLLM_IMAGE = vllm/vllm-openai:v0.28.0)
575.51 - 579 vllm/vllm-openai:v0.28.0-cu129
550 - 575.50 vllm/vllm-openai:v0.24.0-ubuntu2404 (NVIDIA's pinned CUDA 12.8 recipe tag)
< 550 update the driver first (UPDATE_NVIDIA_DRIVERS)

Pre-pull the image on a connected host (docker pull <image>), or add it to the offline package with EXTRA_IMAGES="<image>" make prepare-offline. Starting a model whose image is not cached triggers a ~10 GB pull; in OFFLINE_MODE=true it fails fast with OfflineImageUnavailableError.

Check the image before a 15-minute load:

IMG=vllm/vllm-openai:v0.28.0
docker run --rm --entrypoint python3 $IMG -c \
  "from vllm.reasoning import ReasoningParserManager as R; print(sorted(R.reasoning_parsers))" | grep -o nemotron_v3
docker run --rm --entrypoint python3 $IMG -c \
  "from vllm.entrypoints.openai.tool_parsers import ToolParserManager as T; print(sorted(T.tool_parsers))" | grep -o qwen3_xml

FIPS-enabled hosts

On hosts with /proc/sys/crypto/fips_enabled = 1 the stock image can abort on import (ssl.SSLError: [CRYPTO] unknown error or FATAL FIPS SELFTEST FAILURE from a vendored OpenSSL inside the opencv wheel). The first is cleared with the environment variable OPENSSL_FORCE_FIPS_MODE=0 (below). The second needs an image without that wheel; build it once (FROM vllm/vllm-openai:v0.28.0 + RUN pip uninstall -y opencv-python-headless), tag it (for example cortex/vllm-fips:v0.28.0), and use that tag as engine_image. Cortex treats it like any other image: no code change, include it in the offline package.


2. Model record

Models → Add model (or POST /admin/models). Fields not listed keep their defaults.

Field Value Why
Engine vllm
Mode / local path offline, NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 folder name relative to /var/cortex/models; mounted at /models
Served model name nemotron
engine_image from the table above
engine_version v0.28.0 reference only
selected_gpus [0,1,2,3] tp_size is derived (4); moe_intermediate_size 2688 / 4 divides cleanly
dtype auto the checkpoint decides
max_model_len 32768 raise later; capacity falls proportionally
gpu_memory_utilization 0.90 drop to 0.85 / 0.80 on OOM during profiling
kv_cache_dtype auto not fp8: no calibrated KV scales in this checkpoint
max_num_seqs 32 conservative; raise with measured concurrency
trust_remote_code on required
enforce_eager off CUDA graphs are the single largest win (~6x)
enable_chunked_prefill on (default) vLLM warns against disabling it for Mamba2
quantization blank read from config.json
startup_timeout_sec 2400 ~3 min weight load + CUDA graph capture + MTP init

Custom startup args

Flag Value Purpose
--enable-auto-tool-choice (flag) tool-call parsing
--tool-call-parser qwen3_xml Nemotron 3 emits Qwen3-style <tool_call> XML (underscores)
--reasoning-parser nemotron_v3 separates <think> from content
--mamba-ssm-cache-dtype float16 numerical stability of the SSM state
--async-scheduling (flag) overlaps scheduling with execution
--speculative-config {"method":"mtp","num_speculative_tokens":2} the checkpoint ships an MTP head; ~2x decode
--prefix-caching-hash-algo sha256 FIPS-approved; explicit for audit

enable_auto_tool_choice, tool_call_parser, reasoning_parser, async_scheduling, speculative_config_json and prefix_caching_hash_algo also exist as form fields in current releases; use either. Values pass through as an argument list (no shell), so the JSON needs no extra quoting. If MTP makes startup fail, remove that one entry first.

If nemotron_v3 is missing from an older image, use the plugin shipped with the weights: --reasoning-parser-plugin /models/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4/super_v3_reasoning_parser.py and --reasoning-parser super_v3.

Custom environment variables

Key Value Purpose
OPENSSL_FORCE_FIPS_MODE 0 FIPS hosts only (see above)
VLLM_LOGGING_LEVEL INFO DEBUG while troubleshooting
NCCL_P2P_DISABLE 1 optional on PCIe-only hosts; measure both settings

NVIDIA_VISIBLE_DEVICES, CUDA_VISIBLE_DEVICES, HF_HUB_OFFLINE and VLLM_API_KEY are managed by Cortex and rejected (env_var_protected).

Request defaults

Cortex's stock sampling defaults damage reasoning models: penalties attack the structural tokens of <think> and <tool_call> blocks. Set, in the model's Request defaults:

{"temperature": 1.0, "top_p": 0.95, "top_k": -1,
 "repetition_penalty": 1.0, "frequency_penalty": 0.0, "presence_penalty": 0.0}

These are merged into requests that omit the key and apply immediately. Client guidance: generous max_tokens (4096-8192), no penalties, streaming on.


3. Start and validate

Save, run Dry-run (the rendered command should show the image, --tensor-parallel-size 4 and the custom flags), then Start. The API returns loading immediately; the row moves to running when /health answers, or to failed with a state_reason.

docker logs -f vllm-model-<id> 2>&1 | grep -iE 'marlin|fp4|nemotron|error|traceback'
curl -s -H "Authorization: Bearer $CORTEX_API_KEY" http://$HOST_IP:8084/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"nemotron","messages":[{"role":"user","content":"Name three properties of a Mamba state-space layer."}],"max_tokens":600}'

Expected log lines: a Marlin NVFP4 fallback warning on Ada (sm89), thousands of benign Unexpected gate/up projection names: up_proj warnings (non-gated experts), Custom allreduce is disabled on PCIe-only boxes. The response must carry reasoning separate from content and populated tool_calls when tools are given.


4. Troubleshooting

Symptom Cause Fix
container_exited, log cuda>=13.0 driver too old for the default image engine_image from section 1
invalid tool call parser / invalid reasoning parser old image, or hyphenated name image >= v0.24, underscores
startup_timeout_after_... graph capture still running startup_timeout_sec 2400
CUDA out of memory during profiling Marlin dequant footprint gpu_memory_utilization 0.90 → 0.85 → 0.80, then max_model_len 16384
fluent nonsense NVFP4 kernel path on this GPU try the FP8 checkpoint (...-FP8, same settings, only local_path changes)
raw <tool_call> XML in content parser mismatch qwen3_coder instead of qwen3_xml
repetitive / truncated reasoning penalties active request defaults above; check the client sends none
OfflineImageUnavailableError image not cached pre-pull or add to the offline package
ssl.SSLError [CRYPTO] / FIPS SELFTEST FAILURE FIPS host env OPENSSL_FORCE_FIPS_MODE=0; derived image

Confirm which image a Cortex-started container actually used:

docker inspect vllm-model-<id> --format '{{.Config.Image}}'

Related: vLLM guide, Setting custom environment variables, Runbooks, Offline deployment.