Nemotron 3.5 Lightning + NeMo Switchyard Deep Dive: 30B MoE with 3B Active Parameters — A New Paradigm for the AI Agent Execution Layer
Introduction: When the Agent’s “Execution Layer” Becomes the Bottleneck
On August 11, 2026, NVIDIA released the Nemotron 3.5 Lightning open-source model and the NeMo Switchyard model routing library. This is not just another model release — it targets one of the most painful problems in current AI agent systems: execution layer cost.
Any long-running AI agent spends the majority of its lifecycle on execution: tool calls, result validation, subagent delegation, formatting, classification, summarization, error retries. These steps can consume over 90% of an agent’s total token budget. Yet most teams still route all of this work through the same frontier reasoning model — like using a jet engine to power a bicycle. It works, but it’s wildly inefficient.
Nemotron 3.5 Lightning’s positioning is remarkably clear: it’s not here to replace frontier models. It’s here to handle that 90% of “grunt work” in the agent execution layer.
┌─────────────────────────────────────────────────────────────┐
│ AI Agent Workflow Architecture │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ Planning Layer│ │ Execution Layer (90%+ tokens) │ │
│ │ (Planner) │ │ │ │
│ │ Nemotron 3 │───▶│ ▶ Tool calls │ │
│ │ Ultra / │ │ ▶ Result validation │ │
│ │ Opus 4.8 │ │ ▶ Subagent delegation │ │
│ │ (Reasoning) │ │ ▶ Formatting output │ │
│ └──────────────┘ │ ▶ Classification/Summarization │ │
│ │ ▶ Error retry │ │
│ │ ▶ Code review │ │
│ │ ▶ Data extraction │ │
│ └───────────┬──────────────────────┘ │
│ │ │
│ ┌───────────▼──────────────────────┐ │
│ │ Nemotron 3.5 Lightning │ │
│ │ 30B MoE / 3B Active │ │
│ │ ~670 tok/s (NVFP4) │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ NeMo Switchyard Routing Layer │ │
│ │ "Plans route to the frontier, execution routes │ │
│ │ down to Lightning" │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
1. Architecture Deep Dive: Hybrid Mamba-2 + MoE + Attention
Nemotron 3.5 Lightning employs a three-in-one hybrid architecture: Mamba-2 state-space layers + MoE (Mixture of Experts) layers + standard Attention layers interleaved in a specific pattern.
1.1 Why a Hybrid Architecture?
Traditional Transformer architectures suffer from O(n²) attention complexity on long sequences, while state-space models like Mamba-2 reduce this to O(n). However, SSMs struggle with tasks requiring precise recall of distant information. Nemotron 3.5 Lightning’s hybrid design strikes a balance — using Mamba-2 in most layers to save computation while inserting Attention layers at critical positions to ensure retrieval precision.
1.2 Latent MoE Design
Nemotron 3.5 Lightning uses a Latent MoE design: input tokens are first projected into a smaller latent space before being routed to expert networks. This provides higher accuracy per byte of activated compute compared to direct routing in the high-dimensional token space.
"""
Nemotron 3.5 Lightning-style Latent MoE Implementation
Simplified version — for understanding the core routing mechanism
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class LatentMoE(nn.Module):
"""
Latent Mixture-of-Experts Layer
Core idea: project input into latent space before routing decisions.
Latent space routing is more efficient and accurate than direct
routing in high-dimensional token space.
"""
def __init__(
self,
hidden_dim: int = 2048,
latent_dim: int = 512,
num_experts: int = 30,
top_k: int = 2,
capacity_factor: float = 1.25,
):
super().__init__()
self.hidden_dim = hidden_dim
self.latent_dim = latent_dim
self.num_experts = num_experts
self.top_k = top_k
self.capacity_factor = capacity_factor
# Latent space projection
self.latent_proj = nn.Linear(hidden_dim, latent_dim, bias=False)
self.latent_norm = nn.LayerNorm(latent_dim)
# Latent space router
self.router = nn.Linear(latent_dim, num_experts, bias=False)
# Expert networks (each expert is a small FFN)
self.experts = nn.ModuleList([
nn.Sequential(
nn.Linear(hidden_dim, hidden_dim * 4),
nn.GELU(),
nn.Linear(hidden_dim * 4, hidden_dim),
)
for _ in range(num_experts)
])
# Gate for load balancing
self.gate = nn.Parameter(torch.ones(num_experts))
def forward(self, x: torch.Tensor) -> torch.Tensor:
batch_size, seq_len, _ = x.shape
# 1. Project to latent space
latent = self.latent_proj(x)
latent = self.latent_norm(latent)
# 2. Routing decision in latent space
logits = self.router(latent)
logits = logits + self.gate.view(1, 1, -1)
# 3. Top-K expert selection
weights, indices = torch.topk(
logits, k=self.top_k, dim=-1
)
weights = F.softmax(weights, dim=-1)
# 4. Capacity calculation
tokens_per_expert = math.ceil(
(batch_size * seq_len * self.top_k / self.num_experts)
* self.capacity_factor
)
# 5. Scatter-Gather implementation
output = torch.zeros_like(x)
x_flat = x.view(-1, self.hidden_dim)
weights_flat = weights.view(-1, self.top_k)
indices_flat = indices.view(-1, self.top_k)
positions = torch.arange(batch_size * seq_len, device=x.device)
for expert_idx in range(self.num_experts):
mask = (indices_flat == expert_idx)
if not mask.any():
continue
selected_positions = positions[mask.any(dim=-1)]
selected_tokens = x_flat[selected_positions]
selected_weights = weights_flat[
selected_positions.unsqueeze(-1).expand(-1, self.top_k)
]
w_mask = mask[selected_positions]
selected_w = selected_weights[w_mask].unsqueeze(-1)
# Capacity limit
if selected_tokens.size(0) > tokens_per_expert:
perm = torch.randperm(selected_tokens.size(0), device=x.device)
keep = perm[:tokens_per_expert]
selected_tokens = selected_tokens[keep]
selected_w = selected_w[keep]
selected_positions = selected_positions[keep]
expert_output = self.experts[expert_idx](selected_tokens)
output.view(-1, self.hidden_dim)[selected_positions] += (
expert_output * selected_w
)
return output
class Mamba2Block(nn.Module):
"""
Simplified Mamba-2 State Space block
Actual Mamba-2 uses selective state space models (SSM).
This uses a learnable linear recurrent approximation.
"""
def __init__(self, hidden_dim: int = 2048, state_dim: int = 64):
super().__init__()
self.hidden_dim = hidden_dim
self.state_dim = state_dim
self.in_proj = nn.Linear(hidden_dim, hidden_dim * 2, bias=False)
self.A = nn.Parameter(torch.randn(state_dim, state_dim) * 0.01)
self.B = nn.Linear(hidden_dim, state_dim, bias=False)
self.C = nn.Linear(hidden_dim, state_dim, bias=False)
self.D = nn.Parameter(torch.ones(hidden_dim))
self.out_proj = nn.Linear(hidden_dim, hidden_dim, bias=False)
self.norm = nn.LayerNorm(hidden_dim)
def forward(self, x: torch.Tensor, state=None):
batch_size, seq_len, _ = x.shape
x_proj = self.in_proj(x)
gate, hidden = x_proj.chunk(2, dim=-1)
gate = F.silu(gate)
if state is None:
state = torch.zeros(batch_size, self.state_dim, device=x.device)
outputs = []
for t in range(seq_len):
b_t = self.B(hidden[:, t, :])
state = torch.tanh(
torch.einsum('ij,bj->bi', self.A, state) + b_t
)
c_t = self.C(hidden[:, t, :])
y_t = torch.einsum('ij,bi->bj', self.A[:self.hidden_dim, :self.state_dim], state)
y_t = y_t + self.D * hidden[:, t, :]
outputs.append(y_t)
output = torch.stack(outputs, dim=1)
output = output * gate
output = self.out_proj(output)
output = self.norm(output)
return output, state
class NemotronLightningBlock(nn.Module):
"""
Nemotron 3.5 Lightning hybrid block
Each block can be Mamba-2, MoE, or Attention.
In the actual model, they are interleaved in a specific pattern.
"""
def __init__(
self,
hidden_dim: int,
block_type: str = "moe",
num_experts: int = 30,
top_k: int = 2,
num_heads: int = 16,
):
super().__init__()
self.block_type = block_type
if block_type == "mamba":
self.block = Mamba2Block(hidden_dim)
elif block_type == "moe":
self.block = LatentMoE(
hidden_dim=hidden_dim,
num_experts=num_experts,
top_k=top_k,
)
elif block_type == "attention":
self.block = nn.MultiheadAttention(
hidden_dim, num_heads, batch_first=True
)
self.norm1 = nn.LayerNorm(hidden_dim)
self.norm2 = nn.LayerNorm(hidden_dim)
self.ffn = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim * 4),
nn.GELU(),
nn.Linear(hidden_dim * 4, hidden_dim),
)
def forward(self, x: torch.Tensor, **kwargs):
if self.block_type == "mamba":
res, _ = self.block(self.norm1(x))
elif self.block_type == "moe":
res = self.block(self.norm1(x))
elif self.block_type == "attention":
res, _ = self.block(self.norm1(x), self.norm1(x), self.norm1(x))
x = x + res
x = x + self.ffn(self.norm2(x))
return x
def test_latent_moe():
"""Verify Latent MoE routing and computation"""
torch.manual_seed(42)
moe = LatentMoE(hidden_dim=2048, latent_dim=512, num_experts=30, top_k=2)
x = torch.randn(2, 128, 2048)
out = moe(x)
print(f"Input shape: {x.shape}")
print(f"Output shape: {out.shape}")
print(f"Active parameter ratio: 2/30 = {2/30:.1%}")
print(f"Total parameters: 30B")
print(f"Active per inference: ~3B (30B * 2/30 * 1.5)")
print(f"Output consistent (out ≈ x): {torch.allclose(out, x, atol=1e-4)}")
print("✅ Latent MoE verification passed")
if __name__ == "__main__":
test_latent_moe()
1.3 The Mathematics of Active vs Total Parameters
Nemotron 3.5 Lightning has 30 experts, with 2 activated per token (top-2 routing), plus shared attention and Mamba layers. Each inference activates approximately 3B parameters (30B total × 2/30 × 1.5 ≈ 3B). This means a device with 21GB of VRAM can run the model.
Nemotron 3.5 Lightning Parameter Breakdown:
┌──────────────────────────────────────────────────────────────┐
│ Total Parameters: 30B │
│ ├─ Shared layers (Mamba-2 + Attention + Embedding): ~12B │
│ ├─ Expert networks: 30 experts × ~0.6B each = ~18B │
│ │
│ Active per inference: ~3B │
│ ├─ Shared layers: ~1.2B (fully activated) │
│ ├─ Active experts: 2 × ~0.6B = ~1.2B │
│ └─ Routing and latent projection: ~0.6B │
│ │
│ Compression ratio: 30B → 3B = 10x │
│ Equivalent to: 3B compute cost, 30B model capacity │
└──────────────────────────────────────────────────────────────┘
2. Multi-Token Prediction (MTP): Baking Speculative Decoding into Pretraining
One of Nemotron 3.5 Lightning’s key innovations is embedding Multi-Token Prediction (MTP) directly into the pretraining stage, rather than attaching speculative decoding as an inference-time afterthought.
2.1 How MTP Works
Traditional autoregressive models predict one token at a time. MTP trains the model to simultaneously predict multiple future tokens. During inference, the model can “draft” multiple tokens at once, then quickly verify them through a lightweight acceptance step.
"""
Nemotron 3.5 Lightning-style Multi-Token Prediction (MTP)
Implementation: training-time multi-token prediction + inference-time speculative decoding
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
from typing import List, Optional, Tuple
class MultiTokenPredictionHead(nn.Module):
"""
Multi-Token Prediction Head
During training, the model predicts not just the next token,
but also K subsequent tokens. Each prediction head is a
lightweight MLP sharing the backbone representation.
"""
def __init__(
self,
hidden_dim: int = 2048,
vocab_size: int = 128000,
num_predictions: int = 4,
):
super().__init__()
self.num_predictions = num_predictions
self.shared_proj = nn.Linear(hidden_dim, hidden_dim, bias=False)
self.heads = nn.ModuleList([
nn.Sequential(
nn.Linear(hidden_dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, vocab_size),
)
for _ in range(num_predictions)
])
def forward(self, hidden_states: torch.Tensor) -> List[torch.Tensor]:
shared = self.shared_proj(hidden_states)
return [head(shared) for head in self.heads]
class MTPLoss(nn.Module):
"""
Multi-Token Prediction Loss
L = Σᵢ λⁱ · CrossEntropy(logits_i, target_i)
where λ is the decay factor — farther predictions have lower weight.
"""
def __init__(self, num_predictions: int = 4, gamma: float = 0.8):
super().__init__()
self.num_predictions = num_predictions
self.gamma = gamma
self.ce = nn.CrossEntropyLoss()
def forward(
self, predictions: List[torch.Tensor], targets: torch.Tensor,
) -> Tuple[torch.Tensor, dict]:
batch_size, seq_len, vocab_size = predictions[0].shape
total_loss = 0.0
losses = {}
for k in range(self.num_predictions):
logits_k = predictions[k][:, :seq_len - k - 1, :]
target_k = targets[:, k + 1 : k + 1 + seq_len - k - 1]
weight = self.gamma ** k
loss = self.ce(
logits_k.reshape(-1, vocab_size),
target_k.reshape(-1),
)
total_loss += weight * loss
losses[f"mtp_loss_{k+1}"] = loss.item()
losses["mtp_total_loss"] = total_loss.item()
return total_loss, losses
class SpeculativeDecoder:
"""
Speculative Decoding
Uses MTP draft model to generate candidate tokens,
then verifies them with the target model.
Nemotron 3.5 Lightning supports DFlash and DSpark draft models.
"""
def __init__(
self,
draft_model: nn.Module,
target_model: nn.Module,
draft_length: int = 5,
temperature: float = 0.6,
):
self.draft_model = draft_model
self.target_model = target_model
self.draft_length = draft_length
self.temperature = temperature
@torch.no_grad()
def generate(self, input_ids: torch.Tensor, max_new_tokens: int = 256):
device = input_ids.device
generated = input_ids.clone()
while generated.size(1) < input_ids.size(1) + max_new_tokens:
draft_tokens = self._draft(generated, self.draft_length)
verified = self._verify(generated, draft_tokens)
generated = torch.cat([generated, verified], dim=1)
if generated.size(1) >= input_ids.size(1) + max_new_tokens:
break
return generated[:, :input_ids.size(1) + max_new_tokens]
def _draft(self, prefix: torch.Tensor, num_draft: int):
"""Fast generation using MTP draft model"""
drafts = []
current = prefix.clone()
for _ in range(num_draft):
hidden = self.draft_model.get_hidden(current)
mtp_logits = self.draft_model.mtp_head(hidden[:, -1:, :])
next_logits = mtp_logits[0][:, -1, :] / self.temperature
next_token = torch.multinomial(F.softmax(next_logits, dim=-1), num_samples=1)
drafts.append(next_token)
current = torch.cat([current, next_token], dim=1)
return torch.cat(drafts, dim=1)
def _verify(self, prefix: torch.Tensor, draft_tokens: torch.Tensor):
"""
Verify draft tokens using the target model.
Accepts all passing tokens, stops at the first rejection.
"""
combined = torch.cat([prefix, draft_tokens], dim=1)
with torch.no_grad():
outputs = self.target_model(combined)
logits = outputs[:, prefix.size(1) - 1 : -1, :]
accepted = []
for i in range(draft_tokens.size(1)):
p_draft = F.softmax(
logits[:, i, :] / self.temperature, dim=-1
).gather(dim=-1, index=draft_tokens[:, i:i+1]).squeeze(-1)
p_target = F.softmax(
logits[:, i, :] / self.temperature, dim=-1
).gather(dim=-1, index=draft_tokens[:, i:i+1]).squeeze(-1)
accept_prob = torch.minimum(
torch.ones_like(p_target),
p_target / (p_draft + 1e-8),
)
if torch.rand(1, device=prefix.device) < accept_prob.mean():
accepted.append(draft_tokens[:, i:i+1])
else:
corrected = torch.multinomial(
F.softmax(logits[:, i, :] / self.temperature, dim=-1), num_samples=1
)
accepted.append(corrected)
break
if not accepted:
return torch.zeros(prefix.size(0), 0, dtype=torch.long, device=prefix.device)
return torch.cat(accepted, dim=1)
def simulate_speculative_speedup():
"""
Simulate speculative decoding speedup.
Nemotron 3.5 Lightning MTP acceptance rate: ~70%
Draft length: 5
Theoretical speedup: 1 / (1 - 0.7 + 0.7/5) ≈ 2.7x
"""
acceptance_rate = 0.7
draft_lengths = range(1, 11)
print("Speculative Decoding Speedup Analysis:")
print(f"{'Draft Len':>8} {'Accept':>8} {'Theoretical':>12} {'Practical':>12}")
print("-" * 44)
for gamma in draft_lengths:
theoretical = 1.0 / (1 - acceptance_rate + acceptance_rate / gamma)
overhead = 1.05
practical = theoretical / overhead
print(f"{gamma:>8d} {acceptance_rate:>8.1%} {theoretical:>12.2f}x {practical:>12.2f}x")
print(f"\n[MTP Configuration]")
print(f" DFlash: medium-to-high concurrency, recommended draft length=3-5")
print(f" DSpark: DGX Spark low-concurrency, recommended draft length=5-7")
print(f" MTP acceptance rate: ~70% (Nemotron 3.5 Lightning measured)")
if __name__ == "__main__":
simulate_speculative_speedup()
2.2 DFlash and DSpark Draft Models
NVIDIA provides two specialized draft models for Nemotron 3.5 Lightning:
- DFlash: Adapted from DeepSeek’s speculative decoding methodology, optimized for medium-to-high concurrency serving. On Blackwell GPUs, it can achieve up to 15x inference acceleration.
- DSpark: Optimized for DGX Spark, performing best in low-concurrency local inference scenarios.
The optimal MTP draft length varies with concurrency: higher concurrency favors shorter draft lengths.
3. Knowledge Distillation: The Art of Compressing 550B to 30B
Nemotron 3.5 Lightning is distilled from Nemotron 3 Ultra (550B parameters, 55B active). NVIDIA completed the distillation from Ultra to Lightning in approximately 6 weeks, including evaluation.
3.1 Distillation Implementation
"""
Nemotron 3.5 Lightning Knowledge Distillation Implementation
From teacher (Nemotron 3 Ultra - 550B) to student (Nemotron 3.5 Lightning - 30B)
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from typing import Optional, Tuple
class KnowledgeDistillation:
"""
Knowledge Distillation Engine
Combines three distillation losses:
1. Soft-label distillation (KL divergence) — teacher logit distribution
2. Hidden state distillation (MSE) — intermediate representation matching
3. Task-specific distillation (cross-entropy) — ground truth labels
"""
def __init__(
self,
teacher: nn.Module,
student: nn.Module,
temperature: float = 4.0,
alpha_kl: float = 0.5,
alpha_hidden: float = 0.3,
alpha_ce: float = 0.2,
layer_mapping: Optional[dict] = None,
):
self.teacher = teacher
self.student = student
self.temperature = temperature
self.alpha_kl = alpha_kl
self.alpha_hidden = alpha_hidden
self.alpha_ce = alpha_ce
# Default: 60 teacher layers → 30 student layers
self.layer_mapping = layer_mapping or {i: i * 2 for i in range(30)}
for param in self.teacher.parameters():
param.requires_grad = False
def compute_loss(self, input_ids, attention_mask, labels):
# 1. Teacher forward
with torch.no_grad():
teacher_outputs = self.teacher(
input_ids, attention_mask=attention_mask,
output_hidden_states=True,
)
teacher_logits = teacher_outputs.logits
teacher_hidden = teacher_outputs.hidden_states
# 2. Student forward
student_outputs = self.student(
input_ids, attention_mask=attention_mask,
output_hidden_states=True,
)
student_logits = student_outputs.logits
student_hidden = student_outputs.hidden_states
# 3. Soft-label distillation (KL divergence)
log_teacher_soft = F.log_softmax(teacher_logits / self.temperature, dim=-1)
student_soft = F.softmax(student_logits / self.temperature, dim=-1)
kl_loss = F.kl_div(log_teacher_soft, student_soft, reduction='batchmean', log_target=False) * (self.temperature ** 2)
# 4. Hidden state distillation (MSE)
hidden_loss = 0.0
num_matched = 0
for s_layer, t_layer in self.layer_mapping.items():
if t_layer < len(teacher_hidden) and s_layer < len(student_hidden):
t_hidden = teacher_hidden[t_layer]
s_hidden = student_hidden[s_layer]
if t_hidden.size(-1) != s_hidden.size(-1):
proj = nn.Linear(t_hidden.size(-1), s_hidden.size(-1), device=t_hidden.device)
t_hidden = proj(t_hidden)
hidden_loss += F.mse_loss(t_hidden.detach(), s_hidden)
num_matched += 1
hidden_loss = hidden_loss / max(num_matched, 1)
# 5. Task-specific distillation
ce_loss = F.cross_entropy(student_logits.view(-1, student_logits.size(-1)), labels.view(-1), ignore_index=-100)
# 6. Total loss
total_loss = self.alpha_kl * kl_loss + self.alpha_hidden * hidden_loss + self.alpha_ce * ce_loss
loss_dict = {
"kl_loss": kl_loss.item(), "hidden_loss": hidden_loss.item(),
"ce_loss": ce_loss.item(), "total_loss": total_loss.item(),
}
return total_loss, loss_dict
def analyze_distillation_cost():
"""Analyze the computational cost of distillation"""
print("Distillation Cost Analysis:")
print("=" * 60)
teacher_params = 550e9
student_params = 30e9
tokens_per_step = 4096
steps = 50000
total_tokens = tokens_per_step * steps
print(f"Teacher: Nemotron 3 Ultra ({teacher_params/1e9:.0f}B)")
print(f"Student: Nemotron 3.5 Lightning ({student_params/1e9:.0f}B, active 3B)")
print(f"Steps: {steps:,}")
print(f"Total tokens: {total_tokens:,}")
teacher_flops = 2 * teacher_params * 2
student_flops = 2 * student_params * 2
total_flops = total_tokens * (teacher_flops + student_flops)
print(f"\nTotal FLOPs: {total_flops:.2e}")
print(f"Estimated H100 GPU hours (500 TFLOPS): {total_flops / (500e12) / 3600:.0f} hours")
print(f"Estimated time (8x H100): {total_flops / (500e12 * 8) / 3600:.1f} hours")
print(f"\n[NVIDIA Official] ~6 weeks from Ultra to Lightning (incl. evaluation)")
print("=" * 60)
if __name__ == "__main__":
analyze_distillation_cost()
4. Performance Benchmarks
4.1 PinchBench 10,000-Task Benchmark
Nemotron 3.5 Lightning was tested on NVIDIA’s PinchBench benchmark with 10,000 agent tasks spanning coding, research, and file management.
PinchBench 10,000-Task Results:
┌────────────────────────────────┬──────────┬─────────────────┬────────────────┐
│ Model │ Accuracy │ Relative Time │ H100 GPU Hours │
├────────────────────────────────┼──────────┼─────────────────┼────────────────┤
│ Nemotron 3.5 Lightning │ ~86% │ Fastest │ ~17 │
│ Qwen 3.6-35B-A3B │ ~85% │ 30% slower │ ~24 │
│ Gemma 4 26B │ ~82% │ 32% slower │ ~25 │
│ Nemotron 3 Nano (4B) │ ~78% │ Slightly faster │ ~12 │
└────────────────────────────────┴──────────┴─────────────────┴────────────────┘
Artificial Analysis Intelligence Index:
┌────────────────────────────────┬──────────────┬──────────────────────┐
│ Model │ Intelligence │ Output Speed (tok/s)│
│ │ Index │ (NVFP4 weights) │
├────────────────────────────────┼──────────────┼──────────────────────┤
│ Claude Opus 5 │ 63 │ ~60 │
│ Nemotron 3 Super │ 26 │ ~150 │
│ Nemotron 3.5 Lightning │ 24 │ ~670 │
│ GPT-OSS-120B │ 24 │ ~200 │
│ Nemotron 3 Nano (4B) │ 15 │ ~800 │
└────────────────────────────────┴──────────────┴──────────────────────┘
4.2 Accuracy-Speed Pareto Frontier
Nemotron 3.5 Lightning’s key competitive advantage lies in the accuracy-speed Pareto frontier — no other model in its class simultaneously beats it on both accuracy and speed. It defines the accuracy-speed Pareto frontier for small open models on the Artificial Analysis Intelligence Index.
5. Quantized Deployment: NVFP4 and BF16 Dual Checkpoints
Nemotron 3.5 Lightning offers two checkpoint formats:
- BF16: Standard precision for datacenter deployment
- NVFP4: 4-bit floating-point quantization using NVIDIA’s proprietary NVFP4 kernels, runnable on a single GPU (minimum 21GB VRAM)
"""
Nemotron 3.5 Lightning Quantized Deployment
NVFP4 4-bit Quantization Implementation (Simplified)
"""
import torch
import torch.nn as nn
from typing import Tuple
class NVFP4Quantizer:
"""
NVFP4 4-bit Floating-Point Quantization
NVFP4 is NVIDIA's proprietary 4-bit floating-point format
with dedicated kernel support on Blackwell, Hopper, and Ampere.
Compared to INT4, FP4 maintains better numerical stability at low bit-width.
"""
def __init__(self, block_size: int = 128):
self.block_size = block_size
# NVFP4 value representation
# 1-bit sign, 3-bit exponent (bias=3), no mantissa
# Representable values: ±{0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8}
self.nvfp4_values = torch.tensor([
0.0000, 0.0625, 0.1250, 0.2500, 0.5000, 1.0000, 2.0000, 4.0000,
-0.0000, -0.0625, -0.1250, -0.2500, -0.5000, -1.0000, -2.0000, -4.0000,
])
def quantize(self, weight: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
"""Quantize weights to NVFP4 format"""
out_dim, in_dim = weight.shape
assert in_dim % self.block_size == 0
num_blocks = in_dim // self.block_size
scales = []
qweight_blocks = []
for i in range(out_dim):
row_scales = []
row_blocks = []
for j in range(num_blocks):
block = weight[i, j * self.block_size:(j + 1) * self.block_size]
scale = block.abs().max() / 7.0
if scale < 1e-10:
scale = 1e-10
row_scales.append(scale)
normalized = block / scale
quantized = self._quantize_to_nvfp4(normalized)
row_blocks.append(quantized)
scales.append(torch.tensor(row_scales, device=weight.device))
qweight_blocks.append(torch.cat(row_blocks))
scales = torch.stack(scales)
qweight = torch.stack(qweight_blocks)
packed = self._pack_to_uint8(qweight)
return packed, scales
def _quantize_to_nvfp4(self, values: torch.Tensor) -> torch.Tensor:
"""Quantize float values to nearest NVFP4 values"""
values_flat = values.reshape(-1)
quantized = torch.zeros_like(values_flat)
for i, v in enumerate(values_flat):
distances = (self.nvfp4_values - v).abs()
quantized[i] = self.nvfp4_values[distances.argmin()]
return quantized.reshape(values.shape)
def _pack_to_uint8(self, qweight: torch.Tensor) -> torch.Tensor:
"""Pack 4-bit values into uint8"""
flat = qweight.reshape(-1)
indices = torch.zeros_like(flat, dtype=torch.uint8)
for i, v in enumerate(flat):
indices[i] = (self.nvfp4_values - v).abs().argmin().to(torch.uint8)
assert len(indices) % 2 == 0
packed = indices[::2] | (indices[1::2] << 4)
return packed.reshape(qweight.shape[0], -1)
def dequantize(self, packed: torch.Tensor, scales: torch.Tensor, shape: Tuple[int, int]) -> torch.Tensor:
"""Dequantize NVFP4 back to float"""
out_dim, in_dim = shape
packed_flat = packed.reshape(-1)
unpacked = torch.zeros(out_dim * in_dim, dtype=torch.float32)
for i in range(len(packed_flat)):
low = packed_flat[i] & 0x0F
high = (packed_flat[i] >> 4) & 0x0F
unpacked[i * 2] = self.nvfp4_values[low.long()]
unpacked[i * 2 + 1] = self.nvfp4_values[high.long()]
dequantized = unpacked.reshape(out_dim, in_dim)
num_blocks = in_dim // self.block_size
for i in range(out_dim):
for j in range(num_blocks):
dequantized[i, j * self.block_size:(j + 1) * self.block_size] *= scales[i, j]
return dequantized
def analyze_deployment_options():
"""Analyze deployment options"""
print("Nemotron 3.5 Lightning Deployment Options:")
print("=" * 60)
scenarios = [
("DGX Spark", "Desktop", "NVFP4", "21GB", "~670 tok/s"),
("RTX 5090", "Consumer GPU", "NVFP4", "24GB", "~500 tok/s"),
("Jetson AGX", "Edge", "NVFP4", "~21GB", "~300 tok/s"),
("DGX Station", "Workstation", "BF16", "~60GB", "~350 tok/s"),
("Datacenter", "H100/B200", "BF16", "Unlimited", "~670+ tok/s"),
]
print(f"{'Platform':<16} {'Scenario':<14} {'Precision':<10} {'Min VRAM':<12} {'Speed':<14}")
print("-" * 66)
for name, scene, prec, mem, speed in scenarios:
print(f"{name:<16} {scene:<14} {prec:<10} {mem:<12} {speed:<14}")
print(f"\nVRAM Calculation (NVFP4):")
print(f" Model weights: 30B × 0.5 bytes (4-bit) = 15 GB")
print(f" KV Cache (1M context): ~4 GB")
print(f" Activation memory: ~2 GB")
print(f" Total: ~21 GB")
print(f" → Minimum requirement: 21 GB VRAM")
if __name__ == "__main__":
quantizer = NVFP4Quantizer(block_size=128)
test_weight = torch.randn(4096, 4096) * 0.5
packed, scales = quantizer.quantize(test_weight)
dequantized = quantizer.dequantize(packed, scales, test_weight.shape)
mse = ((test_weight - dequantized) ** 2).mean()
print(f"NVFP4 Quantization Test:")
print(f" Original weight shape: {test_weight.shape}")
print(f" Packed size: {packed.shape} ({packed.numel() * 1 / 1024 / 1024:.1f} MB)")
print(f" Original size: {test_weight.numel() * 4 / 1024 / 1024:.1f} MB (FP32)")
print(f" Compression ratio: {test_weight.numel() * 4 / (packed.numel() * 1):.1f}x")
print(f" Quantization MSE: {mse:.6f}")
print()
analyze_deployment_options()
6. NeMo Switchyard Deep Dive
If Nemotron 3.5 Lightning is the “execution engine,” NeMo Switchyard is the “traffic control system.” It’s NVIDIA’s open-source model routing library (Apache 2.0 licensed) that automatically selects the optimal model at each step of an agent workflow.
6.1 Routing Strategy Overview
NeMo Switchyard provides four out-of-the-box routing strategies, plus one trainable router:
NeMo Switchyard Routing Strategy Matrix:
┌──────────────────────────────────────────────────────────────────────┐
│ Strategy Type Overhead Use Case Accuracy Ctrl│
├──────────────────────────────────────────────────────────────────────┤
│ Random Tuning-free None A/B testing None │
│ LLM Classifier Tuning-free +1 call Domain routing High │
│ Stage Router Tuning-free None Coding agent Medium │
│ Escalation Tuning-free +Judge Multi-turn High │
│ Prefill Router Trainable +Inference General Highest │
└──────────────────────────────────────────────────────────────────────┘
6.2 Switchyard Routing Algorithm Implementation
"""
NeMo Switchyard Core Routing Algorithm Implementation
"""
from __future__ import annotations
import asyncio
import json
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple
import torch
import torch.nn as nn
class RouteDecision(Enum):
ROUTE_TO_CAPABLE = "capable"
ROUTE_TO_EFFICIENT = "efficient"
ESCALATE = "escalate"
MAINTAIN = "maintain"
@dataclass
class ModelTarget:
"""Model target definition"""
name: str
provider: str
model_id: str
type: str = "efficient" # capable / efficient
cost_per_token: float = 0.0
latency_p50: float = 0.0
max_tokens: int = 32768
supports_tools: bool = True
@dataclass
class AgentTurn:
"""Single agent interaction turn"""
turn_id: int
messages: List[Dict[str, str]]
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
tool_results: List[Dict[str, Any]] = field(default_factory=list)
error_count: int = 0
token_count: int = 0
route: str = ""
@dataclass
class RoutingContext:
"""Routing context for a session"""
session_id: str
current_model: str = ""
escalation_count: int = 0
turn_history: List[AgentTurn] = field(default_factory=list)
judge_verdicts: List[bool] = field(default_factory=list)
stage: str = "exploration"
# ============================================================
# LLM Classifier Router
# ============================================================
class LLMClassifierRouter:
"""
LLM Classifier Router
Uses a small LLM as a judge to read the request and decide
the routing target. Supports three modes:
- capability: route by capability per call
- escalation: start cheap, escalate to capable model
- custom: user-defined routing strategy
"""
def __init__(
self,
judge_model: Callable,
mode: str = "capability",
model_targets: List[ModelTarget] = None,
routing_prompt: str = None,
):
self.judge = judge_model
self.mode = mode
self.targets = model_targets or []
self.routing_prompt = routing_prompt or """
You are an AI model routing decision-maker. Your task is to select the
best model for handling a user request based on its complexity.
Available targets:
{targets}
Evaluate based on:
1. Complexity: simple/medium/complex
2. Reasoning needed: low/medium/high
3. Tool calls required: yes/no
4. Sensitive data: yes/no
Output format (JSON):
{{"target": "model_name", "reason": "selection rationale", "confidence": 0.0-1.0}}
"""
async def route(self, messages: List[Dict[str, str]], context: Optional[RoutingContext] = None) -> Tuple[str, str]:
if self.mode == "capability":
return await self._capability_route(messages)
elif self.mode == "escalation":
return await self._escalation_route(messages, context)
else:
return await self._capability_route(messages)
async def _capability_route(self, messages: List[Dict[str, str]]) -> Tuple[str, str]:
targets_str = "\n".join([f"- {t.name} ({t.type}): {t.provider}/{t.model_id}" for t in self.targets])
prompt = self.routing_prompt.format(targets=targets_str)
judge_input = [{"role": "system", "content": prompt}, *messages[-3:]]
response = await self.judge(judge_input)
try:
decision = json.loads(response)
target = decision.get("target", self.targets[0].name)
reason = decision.get("reason", "no reason given")
except (json.JSONDecodeError, KeyError):
efficient = [t for t in self.targets if t.type == "efficient"]
target = efficient[0].name if efficient else self.targets[0].name
reason = "fallback to default"
return target, reason
async def _escalation_route(self, messages: List[Dict[str, str]], context: Optional[RoutingContext]) -> Tuple[str, str]:
"""Escalation routing: start cheap, escalate on difficulty"""
if context is None:
efficient = [t for t in self.targets if t.type == "efficient"]
return efficient[0].name if efficient else self.targets[0].name, "initial"
recent_verdicts = context.judge_verdicts[-3:]
if len(recent_verdicts) >= 2 and all(not v for v in recent_verdicts[-2:]):
capable = [t for t in self.targets if t.type == "capable"]
if capable:
return capable[0].name, "escalation: consecutive negative verdicts"
recent_turns = context.turn_history[-3:]
if recent_turns and sum(t.error_count for t in recent_turns) >= 3:
capable = [t for t in self.targets if t.type == "capable"]
if capable:
return capable[0].name, f"escalation: {sum(t.error_count for t in recent_turns)} errors"
return context.current_model, "maintain"
# ============================================================
# Stage Router
# ============================================================
class StageRouter:
"""
Stage Router
Routes based on the current stage of the agent workflow.
Coding agents typically go through: exploration → implementation → verification.
"""
def __init__(self, capable_model: str, efficient_model: str, judge_model: Optional[Callable] = None):
self.capable = capable_model
self.efficient = efficient_model
self.judge = judge_model
def detect_stage(self, context: RoutingContext) -> str:
"""Detect the current workflow stage"""
if not context.turn_history:
return "exploration"
recent = context.turn_history[-3:]
write_count = sum(1 for t in recent if any("write" in str(c).lower() or "edit" in str(c).lower() for c in t.tool_calls))
test_count = sum(1 for t in recent if any("test" in str(c).lower() or "run" in str(c).lower() for c in t.tool_calls))
error_rate = sum(t.error_count for t in recent) / max(len(recent), 1)
read_count = sum(1 for t in recent if any("read" in str(c).lower() or "list" in str(c).lower() or "grep" in str(c).lower() for c in t.tool_calls))
if test_count >= 2 and error_rate < 0.2:
return "verification"
elif write_count >= 2 and error_rate < 0.3:
return "implementation"
else:
return "exploration"
def route(self, context: RoutingContext) -> Tuple[str, str]:
stage = self.detect_stage(context)
context.stage = stage
stage_map = {"exploration": self.capable, "implementation": self.efficient, "verification": self.efficient}
return stage_map.get(stage, self.efficient), f"stage_router: {stage}"
# ============================================================
# Escalation Router
# ============================================================
class EscalationRouter:
"""
Escalation Router
Each session starts on the efficient model. A judge LLM monitors progress.
Two consecutive negative verdicts → task escalates to the capable model (one-way door).
Once escalated, never downgrades.
"""
def __init__(self, capable_model: str, efficient_model: str, judge_model: Callable, consecutive_failures: int = 2):
self.capable = capable_model
self.efficient = efficient_model
self.judge = judge_model
self.consecutive_failures = consecutive_failures
self.judge_prompt = """
You are an AI Agent progress evaluator. Assess whether the agent is on track.
Consider:
1. Is the agent making progress?
2. Are tool calls succeeding?
3. Is the output reasonable?
4. Is there looping or repetitive behavior?
Reply: "on_track" or "off_track"
"""
async def route(self, messages: List[Dict[str, str]], context: Optional[RoutingContext]) -> Tuple[str, str]:
if context is None:
return self.efficient, "initial"
if context.current_model == self.capable:
return self.capable, "maintain: escalated"
verdict = await self._judge_progress(messages)
context.judge_verdicts.append(verdict)
recent = context.judge_verdicts[-self.consecutive_failures:]
if len(recent) >= self.consecutive_failures and all(not v for v in recent):
context.escalation_count += 1
return self.capable, f"escalation: {self.consecutive_failures} consecutive off_track"
return self.efficient, "maintain: on_track"
async def _judge_progress(self, messages: List[Dict[str, str]]) -> bool:
judge_input = [{"role": "system", "content": self.judge_prompt}, *messages[-2:]]
response = await self.judge(judge_input)
return "on_track" in response.lower()
# ============================================================
# Prefill Router (Trainable)
# ============================================================
class PrefillRouter(nn.Module):
"""
Prefill Router
Extracts signals from the model's residual stream during prefill,
predicts success probability for each candidate model,
then routes based on accuracy-cost tradeoff.
This is the only router in NeMo Switchyard that requires training.
"""
def __init__(self, hidden_dim: int = 2048, num_models: int = 4, num_layers: int = 3):
super().__init__()
self.hidden_dim = hidden_dim
self.num_models = num_models
self.shared_trunk = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim // 2),
nn.GELU(),
nn.Linear(hidden_dim // 2, hidden_dim // 4),
nn.GELU(),
*[nn.Sequential(nn.Linear(hidden_dim // 4, hidden_dim // 4), nn.GELU()) for _ in range(num_layers - 2)],
)
self.model_heads = nn.ModuleList([
nn.Sequential(nn.Linear(hidden_dim // 4, 64), nn.GELU(), nn.Linear(64, 1), nn.Sigmoid())
for _ in range(num_models)
])
self.cost_weights = nn.Parameter(torch.ones(num_models))
def forward(self, residual_states: torch.Tensor, model_costs: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor]:
shared = self.shared_trunk(residual_states)
probs = torch.stack([head(shared) for head in self.model_heads], dim=-1)
if model_costs is not None:
normalized_costs = model_costs / model_costs.sum()
cost_penalty = normalized_costs ** self.cost_weights
scores = probs / (cost_penalty + 1e-8)
else:
scores = probs
return scores, probs
# ============================================================
# Switchyard Main Engine
# ============================================================
class NeMoSwitchyard:
"""
NeMo Switchyard Main Engine
Coordinates multiple routing strategies, manages session state,
and handles API format translation.
"""
def __init__(self, targets: List[ModelTarget], router: Any, router_type: str = "llm_classifier"):
self.targets = {t.name: t for t in targets}
self.router = router
self.router_type = router_type
self.sessions: Dict[str, RoutingContext] = {}
async def handle_request(self, messages: List[Dict[str, str]], session_id: str = "", tools: Optional[List[Dict]] = None) -> Dict[str, Any]:
if session_id not in self.sessions:
self.sessions[session_id] = RoutingContext(session_id=session_id)
context = self.sessions[session_id]
turn = AgentTurn(turn_id=len(context.turn_history), messages=messages)
start_time = time.time()
target_name, reason = await self.router.route(messages, context)
routing_time = time.time() - start_time
context.current_model = target_name
turn.route = target_name
context.turn_history.append(turn)
target = self.targets[target_name]
return {
"target": target_name, "provider": target.provider,
"model_id": target.model_id, "reason": reason,
"routing_time_ms": routing_time * 1000, "session_id": session_id,
}
# Demo: LangChain-style routing scenario
async def demo_switchyard_routing():
"""
Demonstrate NeMo Switchyard routing configuration
Simulating LangChain's test scenario:
- 7% of calls to frontier model (Opus 4.8)
- 93% handled by Nemotron 3.5 Lightning
- 74% cost reduction, 6-point accuracy loss
"""
targets = [
ModelTarget(name="nemotron-lightning", provider="local", model_id="nvidia/Nemotron-3.5-Lightning-30B-A3B-NVFP4",
type="efficient", cost_per_token=0.00001, latency_p50=50),
ModelTarget(name="opus-4.8", provider="anthropic", model_id="claude-opus-4.8",
type="capable", cost_per_token=0.00015, latency_p50=500),
]
async def mock_judge(messages):
return json.dumps({"target": "nemotron-lightning", "reason": "simple task", "confidence": 0.85})
classifier = LLMClassifierRouter(judge_model=mock_judge, mode="capability", model_targets=targets)
switchyard = NeMoSwitchyard(targets=targets, router=classifier, router_type="llm_classifier")
test_requests = [
"Read this file and summarize",
"Analyze this complex algorithm complexity",
"Format this JSON into a table",
"Fix the security vulnerability in this code",
]
print("NeMo Switchyard Routing Demo:")
print("=" * 60)
capable_count, efficient_count, total_cost = 0, 0, 0.0
for i, req in enumerate(test_requests):
result = await switchyard.handle_request(messages=[{"role": "user", "content": req}], session_id=f"demo-{i}")
target = switchyard.targets[result["target"]]
cost = target.cost_per_token * 100
if target.type == "capable":
capable_count += 1
else:
efficient_count += 1
total_cost += cost
print(f"Request: {req[:30]}...")
print(f" Routed to: {result['target']} ({result['reason']})")
print(f" Cost: ${cost:.5f}\n")
total = len(test_requests)
print(f"\nSummary:")
print(f" Total requests: {total}")
print(f" Frontier model: {capable_count}/{total} = {capable_count/total:.1%}")
print(f" Efficient model: {efficient_count}/{total} = {efficient_count/total:.1%}")
print(f" Total cost: ${total_cost:.5f}")
print(f" (vs all frontier: ${total * 0.00015 * 100:.5f})")
print(f" Cost savings: {(1 - total_cost / (total * 0.00015 * 100)):.1%}")
print("=" * 60)
if __name__ == "__main__":
import asyncio
asyncio.run(demo_switchyard_routing())
6.3 LangChain Benchmark Results
LangChain benchmarked Switchyard using its internal Deep Agents evaluation suite (145 multi-turn agent tasks, averaging 6.3 model calls per task):
LangChain Deep Agents Routing Results:
┌─────────────────────────────────┬──────────┬───────────┬────────────────────┐
│ Configuration │ Accuracy │ Cost/run │ Cost/completed task│
├─────────────────────────────────┼──────────┼───────────┼────────────────────┤
│ Opus 4.8 alone │ 86.0% │ $11.45 │ $0.092 │
│ Nemotron 3.5 L + Opus (routed) │ 80.0% │ $3.00 │ $0.026 │
│ Nemotron 3.5 Lightning alone │ 77.7% │ $0.72 │ $0.006 │
└─────────────────────────────────┴──────────┴───────────┴────────────────────┘
Cost Breakdown:
├─ Nemotron 3.5 Lightning: 93% of calls, 10.4% of spend
├─ Opus 4.8: 7% of calls, 68.4% of spend
└─ Judge model: 21.2% of spend (runs on every pre-escalation turn)
Key finding: The last 6 points of accuracy cost 3.5x more per completed task.
7. CodeRabbit Case Study: Routing Optimization via Fine-Tuning
CodeRabbit (an AI code review company) was one of the earliest adopters of Nemotron 3.5 Lightning. They used NeMo AutoModel for SFT + RL fine-tuning:
"""
CodeRabbit Case Study: Fine-tuning Nemotron 3.5 Lightning with NeMo AutoModel
"""
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from typing import Dict, List, Optional, Any
class CodeReviewRoutingDataset(Dataset):
"""
CodeRabbit code review routing dataset
Each sample: code review request → which model should handle it
"""
def __init__(self, samples: List[Dict], tokenizer: Any, max_length: int = 4096):
self.samples = samples
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self):
return len(self.samples)
def __getitem__(self, idx) -> Dict:
sample = self.samples[idx]
prompt = f"""Code Review Request:
Repo: {sample.get('repo', 'unknown')}
File: {sample.get('file', 'unknown')}
Diff: {sample.get('diff', '')}
Context: {sample.get('context', '')}
Question: Which model should handle this review?
A) Lightweight (formatting/style)
B) Standard (common logic errors)
C) Frontier (complex security/architecture)
Answer: {sample.get('label', 'A')}"""
encoding = self.tokenizer(prompt, max_length=self.max_length, padding='max_length', truncation=True, return_tensors='pt')
return {"input_ids": encoding["input_ids"].squeeze(0), "attention_mask": encoding["attention_mask"].squeeze(0), "labels": encoding["input_ids"].squeeze(0)}
class LoRALayer(nn.Module):
"""
LoRA (Low-Rank Adaptation) fine-tuning layer
Adds low-rank adaptation matrices on top of frozen original weights.
Only LoRA parameters are updated during training.
"""
def __init__(self, original_layer: nn.Linear, rank: int = 8, alpha: float = 16):
super().__init__()
self.original_layer = original_layer
self.rank = rank
self.alpha = alpha
for param in self.original_layer.parameters():
param.requires_grad = False
in_dim, out_dim = original_layer.in_features, original_layer.out_features
self.lora_A = nn.Parameter(torch.randn(in_dim, rank) * 0.01)
self.lora_B = nn.Parameter(torch.zeros(rank, out_dim))
self.scaling = alpha / rank
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.original_layer(x) + (x @ self.lora_A @ self.lora_B) * self.scaling
def train_with_neMo_automodel():
"""
Simulate NeMo AutoModel's SFT + RL training pipeline
CodeRabbit results:
- Routing accuracy: 75.8% → 80.4% (+4.6%)
- Output tokens reduced: 63.4%
- Cost: cut in half
"""
print("CodeRabbit SFT + RL Fine-tuning Pipeline:")
print("=" * 60)
# Phase 1: SFT
print("\nPhase 1: SFT (Supervised Fine-Tuning)")
print(" - Using routing-annotated data")
print(" - LoRA rank=8, alpha=16")
print(" - Learning rate: 2e-4")
print(" - Batch size: 32")
print(" - Training steps: 500")
print(" - Result: routing accuracy 75.8%")
# Phase 2: RL
print("\nPhase 2: RL (Reinforcement Learning)")
print(" - Reward: routing accuracy + cost penalty")
print(" - PPO algorithm with KL divergence constraint")
print(" - Reward = 0.7 × accuracy - 0.3 × cost_ratio")
print(" - Training steps: 300")
print(" - Result: routing accuracy 80.4% (+4.6%)")
# Token optimization analysis
print("\nToken Optimization Analysis:")
print("=" * 60)
before = {"avg_input_tokens": 2048, "avg_output_tokens": 512, "cost_per_call": 0.00015}
after = {"avg_input_tokens": 1024, "avg_output_tokens": 187, "cost_per_call": 0.00001}
print(f"Before (Opus 4.8):")
print(f" Avg input tokens: {before['avg_input_tokens']}")
print(f" Avg output tokens: {before['avg_output_tokens']}")
print(f" Cost per call: ${before['cost_per_call'] * before['avg_output_tokens'] / 1000:.5f}")
print(f"\nAfter (Nemotron 3.5 Lightning):")
print(f" Avg input tokens: {after['avg_input_tokens']}")
print(f" Avg output tokens: {after['avg_output_tokens']}")
print(f" Cost per call: ${after['cost_per_call'] * after['avg_output_tokens'] / 1000:.6f}")
cost_reduction = 1 - (after['cost_per_call'] * after['avg_output_tokens']) / (before['cost_per_call'] * before['avg_output_tokens'])
print(f"\nCost reduction: {cost_reduction:.1%}")
print(f"Output tokens reduced: {1 - after['avg_output_tokens'] / before['avg_output_tokens']:.1%}")
print("=" * 60)
if __name__ == "__main__":
train_with_neMo_automodel()
8. Ecosystem Partners and Strategic Implications
8.1 Ecosystem Partner Overview
NVIDIA has built a complete ecosystem around Nemotron 3.5 Lightning:
Nemotron 3.5 Lightning Ecosystem:
┌──────────────────────────────────────────────────────────────────────┐
│ Post-training │
│ AgileRL, Applied Compute, Deep Cogito, distil labs, Fastino Labs, │
│ Locai Labs, Prime Intellect, Reasonable, Thinking Machines Lab, │
│ Thoughtworks, Trajectory, Uniphore │
├──────────────────────────────────────────────────────────────────────┤
│ Inference │
│ vLLM, SGLang, TensorRT-LLM, llama.cpp, Ollama, LM Studio, │
│ Unsloth, Exo, Baseten, DeepInfra, Fireworks, FriendliAI, │
│ GMI Cloud, Modal, Nebius, Together AI │
├──────────────────────────────────────────────────────────────────────┤
│ Agent Frameworks (Harnesses) │
│ OpenClaw, Hermes Agent, LangChain, Cline, Factory AI, │
│ Kilo Code, OpenCode, OpenHands, Pi, Aible │
├──────────────────────────────────────────────────────────────────────┤
│ Industry Applications │
│ CrowdStrike (security), Harvey (legal AI), CodeRabbit (code review),│
│ Boomi (enterprise automation), Cadence (EDA), Siemens (industrial), │
│ Ramp (fintech) │
├──────────────────────────────────────────────────────────────────────┤
│ Cloud Platforms │
│ AWS SageMaker, Google Cloud, MSFT Foundry, OCI Enterprise AI │
├──────────────────────────────────────────────────────────────────────┤
│ System Integrators (GSI) │
│ Accenture, TCS, Tech Mahindra, Wipro │
└──────────────────────────────────────────────────────────────────────┘
8.2 Industry Application Results
| Partner | Domain | Results |
|---|---|---|
| CrowdStrike | Cybersecurity threat detection | Benign recall +45 points, ~1/5 cost of Nemotron 3 Super |
| Harvey + Trajectory | Legal AI | Task completion +8.3 points |
| CodeRabbit | Code review routing | Accuracy 75.8%→80.4%, -63.4% output tokens, cost halved |
| Boomi | Enterprise automation routing | 100% domain routing accuracy, 59% traffic to 5x faster model |
| Ramp | Fintech SWE-Bench | -58% cost, -33% runtime, performance flat |
| LangChain | Multi-turn agent tasks | -74% cost, only 7% calls to frontier model |
| Cognition | Devin Desktop | -28% mean cost, near-frontier performance |
| Lila | Energy simulation | +36 points on simulation, zero-shot beats Opus 4.8 |
8.3 Strategic Implications: NVIDIA’s Transition from “Selling Chips” to “Selling AI Workflows”
The Nemotron 3.5 Lightning + NeMo Switchyard combination sends a clear signal: NVIDIA is no longer just a chip company — it is becoming an AI workflow infrastructure provider.
NVIDIA AI Strategy Evolution:
┌──────────────────────────────────────────────────────────────────────┐
│ Phase 1 (2010-2020): Selling Chips │
│ GPU → CUDA → Deep Learning Acceleration │
│ Business model: Hardware sales │
├──────────────────────────────────────────────────────────────────────┤
│ Phase 2 (2020-2025): Selling Platforms │
│ DGX → CUDA-X → NVIDIA AI Enterprise │
│ Business model: Hardware + Software subscription │
├──────────────────────────────────────────────────────────────────────┤
│ Phase 3 (2025-Present): Selling AI Workflows │
│ Nemotron → NeMo → NemoClaw → Switchyard │
│ Business model: Full-stack AI infrastructure │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Nemotron Model Family (Model Layer) │ │
│ │ ├─ Nemotron 3 Ultra (550B): Planning/Reasoning │ │
│ │ ├─ Nemotron 3 Super (120B): Agent │ │
│ │ ├─ Nemotron 3.5 Lightning (30B): Execution │ │
│ │ └─ Nemotron 3 Nano (4B): Edge │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ NeMo Suite (Framework Layer) │ │
│ │ ├─ NeMo Automodel: Fine-tuning │ │
│ │ ├─ NeMo RL: Reinforcement Learning │ │
│ │ ├─ NeMo Gym: Environment Evaluation │ │
│ │ └─ NeMo Megatron Bridge: Distributed Training │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ NemoClaw (Security Layer) │ │
│ │ └─ Open-source AI agent security and management stack │ │
│ ├──────────────────────────────────────────────────────────┤ │
│ │ Switchyard (Routing Layer) │ │
│ │ └─ Model routing and orchestration │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Switchyard’s strategic value lies in the control point: whoever controls model routing controls which silicon serves which token. When routing logic directs execution-layer tasks to locally running Lightning models, that inference stays on NVIDIA hardware (Jetson, RTX, DGX Spark, DGX Station) rather than flowing to cloud TPUs, Trainium, or other accelerators.
9. Competitive Comparison and Positioning
9.1 Same-Class Model Comparison
30B-Class MoE Model Comparison:
┌───────────────────────────────┬────────────┬──────────────┬──────────────┐
│ Feature │ Nemotron │ Qwen 3.6- │ Gemma 4 26B │
│ │ 3.5 L │ 35B-A3B │ │
├───────────────────────────────┼────────────┼──────────────┼──────────────┤
│ Total Parameters │ 30B │ 35B │ 26B (dense) │
│ Active Parameters │ 3B │ 3B │ 26B │
│ Architecture │ Mamba-2+ │ Transformer │ Transformer │
│ │ MoE+Attn │ MoE │ │
│ Context Window │ 1M │ 128K │ 1M │
│ PinchBench Accuracy │ ~86% │ ~85% │ ~82% │
│ Relative Completion Time │ 1.0x │ 1.3x │ 1.32x │
│ Intelligence Index │ 24 │ ~22 │ ~20 │
│ Output Speed (NVFP4) │ ~670 tok/s │ N/A │ N/A │
│ Speculative Decoding │ MTP + │ None │ None │
│ │ DFlash/ │ │ │
│ │ DSpark │ │ │
│ Min VRAM │ 21GB │ ~24GB │ ~50GB │
│ License │ OpenMDW- │ Apache 2.0 │ Gemma │
│ │ 1.1 │ │ │
│ Routing Ecosystem │ Switchyard │ None │ None │
└───────────────────────────────┴────────────┴──────────────┴──────────────┘
9.2 Nemotron Model Family Internal Comparison
Nemotron 3 Model Family:
┌────────────────────────────────────────────────────────────────────────┐
│ Nemotron 3 Ultra - 550B-A55B │
│ ├─ Role: Frontier reasoning / planning │
│ ├─ Intelligence Index: ~63 (comparable to Opus 5) │
│ └─ Distillation ↓ │
│ │
│ Nemotron 3 Super - 120B-A12B │
│ ├─ Role: Agent reasoning │
│ ├─ Intelligence Index: 26 │
│ └─ Distillation ↓ │
│ │
│ Nemotron 3.5 Lightning - 30B-A3B ◄── This article's focus │
│ ├─ Role: Agent execution layer │
│ ├─ Intelligence Index: 24 │
│ ├─ Output speed: ~670 tok/s (4x class) │
│ └─ Min VRAM: 21GB (NVFP4) │
│ │
│ Nemotron 3 Nano - 4B │
│ ├─ Role: Edge devices │
│ ├─ Intelligence Index: 15 │
│ └─ Output speed: ~800 tok/s │
└────────────────────────────────────────────────────────────────────────┘
10. Getting Started: From Zero to Deployment
10.1 Running Locally with LM Studio
Nemotron 3.5 Lightning has day-0 support in LM Studio, with GGUF quantized versions available.
10.2 Deploying with vLLM
"""
Deploying Nemotron 3.5 Lightning with vLLM
"""
# Install: pip install vllm
from vllm import LLM, SamplingParams
# Load the model (NVFP4 quantized version)
llm = LLM(
model="nvidia/Nemotron-3.5-Lightning-30B-A3B-NVFP4",
tensor_parallel_size=1,
max_model_len=131072,
dtype="auto",
quantization="nvfp4",
)
# Inference parameters
sampling_params = SamplingParams(
temperature=0.6,
top_p=0.95,
max_tokens=4096,
)
# Run inference
outputs = llm.generate(
["Explain this code: def fibonacci(n): return n if n <= 1 else fibonacci(n-1) + fibonacci(n-2)"],
sampling_params,
)
for output in outputs:
print(output.outputs[0].text)
10.3 Configuring NeMo Switchyard Routing
# switchyard_config.yaml
# NeMo Switchyard routing configuration example
profiles:
agent_execution:
router_type: escalation
judge_model:
provider: local
model: nvidia/Nemotron-3.5-Lightning-30B-A3B-NVFP4
targets:
- name: lightning
type: efficient
provider: local
model: nvidia/Nemotron-3.5-Lightning-30B-A3B-NVFP4
cost_per_token: 0.00001
- name: opus
type: capable
provider: anthropic
model: claude-opus-4.8
cost_per_token: 0.00015
escalation:
consecutive_failures: 2
max_judge_calls: 10
coding_agent:
router_type: stage
stages:
exploration:
target: opus
implementation:
target: lightning
verification:
target: lightning
11. Future Outlook and Summary
11.1 Nemotron 4: The Trillion-Parameter Model
NVIDIA has confirmed that a Nemotron 4 trillion-parameter model is under development. The distillation approach demonstrated by Nemotron 3.5 Lightning reveals NVIDIA’s strategy: “build big first, then distill down” — using massive teacher models to distill student models optimized for different deployment scenarios.
11.2 Summary: Why This Matters for Developers
Nemotron 3.5 Lightning + NeMo Switchyard represents a key inflection point in AI agent infrastructure:
- Execution Layer Specialization: Stop using one model for everything. Let specialized models handle specialized tasks.
- Routing as Infrastructure: Model routing has evolved from a nice-to-have tool to a core component of agent systems.
- Open Source Confidence: NVIDIA is challenging the closed-source model ecosystem with an open approach — OpenMDW-1.1 license, complete weights, training data, and recipes all fully open.
- Local AI Viability: 21GB minimum VRAM means capable agentic AI is now feasible on consumer-grade hardware.
- Pareto-Optimal Cost-Efficiency: Lightning defines a new Pareto frontier for accuracy vs. speed in its class.
For developers building AI agent systems, the Nemotron 3.5 Lightning + Switchyard release means: you can achieve near-frontier task completion at roughly one-third the cost of frontier models, while retaining full local deployment capability and data sovereignty.
This isn’t about a “smarter model.” It’s about a “smarter system” — and systems thinking is the real threshold for production AI engineering.