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

PartnerDomainResults
CrowdStrikeCybersecurity threat detectionBenign recall +45 points, ~1/5 cost of Nemotron 3 Super
Harvey + TrajectoryLegal AITask completion +8.3 points
CodeRabbitCode review routingAccuracy 75.8%→80.4%, -63.4% output tokens, cost halved
BoomiEnterprise automation routing100% domain routing accuracy, 59% traffic to 5x faster model
RampFintech SWE-Bench-58% cost, -33% runtime, performance flat
LangChainMulti-turn agent tasks-74% cost, only 7% calls to frontier model
CognitionDevin Desktop-28% mean cost, near-frontier performance
LilaEnergy 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:

  1. Execution Layer Specialization: Stop using one model for everything. Let specialized models handle specialized tasks.
  2. Routing as Infrastructure: Model routing has evolved from a nice-to-have tool to a core component of agent systems.
  3. 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.
  4. Local AI Viability: 21GB minimum VRAM means capable agentic AI is now feasible on consumer-grade hardware.
  5. 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.