Fiddler Guide: CPU-GPU Orchestration for Fast MoE AI

The Memory-Compute Dichotomy of Mixture-of-Experts Models
In modern enterprise foundation model serving, high-throughput batch generation, and real-time automated workflows powered by AI Fetchly, Mixture-of-Experts (MoE) architectures have emerged as the premier design for frontier intelligence. Foundation models such as Mixtral-8x7B, Mixtral-8x22B, DBRX, and DeepSeek-V3 decouple total parameter capacity from per-token compute cost:
- A 47-billion or 141-billion parameter MoE model activates only a tiny fraction of its total parameters (e.g., 2 out of 8 experts per token, representing only $\sim 13\text{B}$ active parameters).
- This sparse routing grants the model the associative capacity and knowledge density of a massive network while incurring the arithmetic FLOP cost of a compact dense model during inference.
However, while MoE models are computationally efficient, they introduce a severe infrastructure crisis: the physical GPU memory wall.
The Sparse MoE Serving Dilemma (Mixtral-8x7B / Mixtral-8x22B):
┌───────────────────────────────────────┬───────────────────────────────────────┐
│ Computational FLOP Demand │ Physical Memory Capacity Demand │
├───────────────────────────────────────┼───────────────────────────────────────┤
│ • Active parameters per token: ~12.9B │ • Total model weight footprint: ~94 GB│
│ • FLOPs per token: Equivalent to 13B │ • 4-bit Quantized footprint: ~26 GB │
│ • Arithmetic intensity: Very Low │ • KV Cache footprint (32k): ~16 GB │
└───────────────────────────────────────┴───────────────────────────────────────┘
The Crisis: A consumer or workstation GPU (RTX 3090, 4090, or A5000 with 24GB)
has plenty of compute cores to run 13B math, but CANNOT store the 94GB weights! The Failure of Conventional Weight Offloading
When organizations cannot afford multi-GPU datacenter clusters (such as 4x A100 80GB or 8x H100 nodes), they turn to heterogeneous CPU-GPU offloading frameworks—most notably DeepSpeed-Zero-Inference, FlexGen, and llama.cpp.
In traditional offloading, the host system’s RAM (which is cheap and plentiful, often 128 GB to 512 GB) acts as a secondary storage pool for expert weights. During autoregressive token generation:
- The token embedding and self-attention layers execute on the GPU.
- The router gating network evaluates the router logits and selects the top-$k$ active experts (e.g., Expert 3 and Expert 7).
- If Expert 3 and Expert 7 reside in host CPU memory, the runtime fetches the expert weight matrices across the PCIe bus into GPU VRAM.
- The GPU executes the feed-forward projection, and the weights are evicted or overwritten for the next token.
The Fatal PCIe Transfer Bottleneck in Weight Offloading:
┌────────────────────────────────────────────────────────┐
│ Host System Memory (CPU RAM: 128 GB DDR5 - 80 GB/s) │
│ Inactive Expert Weights Stored Here │
└──────────────────────────┬─────────────────────────────┘
│ PCIe Gen4 x16 Bus
│ Bandwidth Ceiling: ~31.5 GB/s (Real-world: ~24 GB/s)
▼
┌────────────────────────────────────────────────────────┐
│ GPU VRAM (RTX 4090: 24 GB GDDR6X - 1,008 GB/s) │
│ Waiting for Expert Weights to Transfer... │
└────────────────────────────────────────────────────────┘
The Latency Equation for Fetching a Single 16-Bit Expert (~6.5 GB):
Transfer Latency = 6.5 GB / 24 GB/s = 270 milliseconds!
Compute Latency on GPU for 1 Token = ~0.5 milliseconds!
Result: GPU Tensor Cores sit completely IDLE 99.8% of the time! In latency-critical single-batch or small-batch conversational serving (such as enterprise search queries or real-time data fetching agents), the arithmetic computation for a single token takes less than 1 millisecond. However, transferring an uncompressed expert across PCIe takes over 200 milliseconds!
Even when weights are aggressively quantized to 4-bit, transferring two 1.5 GB expert weights takes over 120 milliseconds per token, capping generation speed at a dismal 3 to 4 tokens per second. The GPU’s high-throughput Tensor Cores spend over 99% of their time stalled, waiting for memory movement over the narrow PCIe interface.
To shatter this memory-bandwidth bottleneck, researchers from the University of Washington’s SyFI Lab—Keisuke Kamahori, Yile Gu, and Baris Kasikci—introduced Fiddler (arXiv:2402.07033, published at ICLR 2025).
Fiddler challenges the dogmatic assumption that all neural network calculations must execute on the GPU. By proving that in single-batch inference, it is faster to execute cold experts directly on multi-core host CPUs than to transfer their weights across PCIe, Fiddler introduces an orchestrated, heterogeneous runtime that delivers up to 8.2x to 10.1x lower inference latency on commodity hardware with zero accuracy loss.
The Fiddler Counter-Intuitive Principle: Move Activations, Not Weights
To understand Fiddler’s breakthrough, consider the fundamental physics of data transmission versus computational throughput across heterogeneous system interconnects.
The Fundamental Data Movement Asymmetry:
┌────────────────────────────────────────────────────────────────────────┐
│ Option A: Traditional Weight Offloading │
│ • Move Expert Weights to GPU: ~3.0 GB (INT4) to ~13.0 GB (FP16) │
│ • PCIe Transfer Time: ~120 ms to ~540 ms │
│ • GPU Compute Time: ~0.5 ms │
│ • Total Step Latency: ~120.5 ms (Dominated by weight transport!) │
├────────────────────────────────────────────────────────────────────────┤
│ Option B: Fiddler CPU-GPU Orchestration │
│ • Keep Cold Experts Resident in Host CPU RAM │
│ • Move Token Activation Vector to CPU: Shape [1, 4096] = ONLY 8 KB! │
│ • PCIe Transfer Time: ~0.0003 ms (Virtually ZERO!) │
│ • CPU Multi-Threaded Compute Time: ~15 ms to ~22 ms │
│ • Total Step Latency: ~15.0 ms │
│ 🚀 RESULT: Option B is 8x to 10x FASTER than Option A! │
└────────────────────────────────────────────────────────────────────────┘ The Analytical Latency Model
Let $d$ be the model hidden dimension, $d_{\text{ffn}}$ be the intermediate expert dimension, and $r$ be the number of active experts selected per token (typically $r = 2$). For an expert layer in Mixtral-8x7B, $d = 4,096$ and $d_{\text{ffn}} = 14,336$.
Each expert comprises three projection matrices: Gate ($W_{\text{gate}}$), Up ($W_{\text{up}}$), and Down ($W_{\text{down}}$):
$$\text{Expert}(x) = (x W_{\text{gate}} \cdot \text{SiLU}(x W_{\text{up}})) W_{\text{down}}$$
The weight memory footprint for a single 16-bit expert is:
$$M_{\text{expert}} = 3 \times d \times d_{\text{ffn}} \times 2\text{ bytes} \approx 352\text{ MB (or }\sim 88\text{ MB in INT4)}$$
When processing a single token ($B = 1$):
-
Traditional Offloading: Must transfer $2 \times 88\text{ MB} = 176\text{ MB}$ of weights across the PCIe bus. At a realistic PCIe Gen4 x16 throughput of $22\text{ GB/s}$, the transfer time is:
$$T_{\text{transfer}} = \frac{176\text{ MB}}{22\text{ GB/s}} \approx 8.0\text{ ms (per layer!)}$$
Across 32 transformer layers, weight transfer stalls accumulate to over 250 milliseconds per token!
-
Fiddler Orchestration: Fiddler keeps the cold expert weights permanently resident in host system RAM. Instead of transferring weights, Fiddler transfers the intermediate token activation vector $x \in \mathbb{R}^d$:
$$\text{Payload} = d \times 2\text{ bytes} = 4,096 \times 2\text{ bytes} = 8,192\text{ bytes} \approx 8\text{ KB}$$
Transferring an 8 KB buffer across PCIe takes less than 0.4 microseconds ($\approx 0.0004\text{ ms}$)—effectively instantaneous!
While host CPU vector units (such as modern x86 AVX-512 / AMX or ARM NEON cores) have lower peak FLOPS than an NVIDIA Tensor Core, host CPUs are connected to system DDR5 memory channels delivering 60 GB/s to 120 GB/s of direct memory bandwidth. Reading the resident expert weights locally from host RAM and computing the dot product on 16 to 32 CPU cores takes only 12 to 18 milliseconds—drastically faster than waiting for PCIe weight transport!
System Architecture: Hybrid Placement and Heterogeneous Scheduling
Fiddler achieves optimal throughput by partitioning foundation model components across hardware devices according to their execution frequency and computational characteristics.
The Fiddler Heterogeneous Placement Architecture:
┌────────────────────────────────────────────────────────┐
│ GPU VRAM (NVIDIA RTX 4090 / 24 GB Dedicated Memory) │
├────────────────────────────────────────────────────────┤
│ 1. Token Embeddings, LayerNorms & LM Head (Fast) │
│ 2. Multi-Head Self-Attention (MHA / GQA) Layers │
│ 3. Key-Value (KV) Cache (Persistent high-speed access) │
│ 4. Router Gating Networks (Evaluates expert routing) │
│ 5. Hot Expert Cache: Resident top-frequency experts │
└──────────────────────────┬─────────────────────────────┘
│ Lightweight Activation Vectors (8 KB)
│ Bidirectional PCIe Stream
▼
┌────────────────────────────────────────────────────────┐
│ Host System Memory (CPU RAM: 64 GB - 128 GB DDR5) │
├────────────────────────────────────────────────────────┤
│ 1. Cold Un-cached Expert Weights (100% Resident) │
│ 2. Multi-Threaded CPU Compute Workers (AVX-512 / AMX) │
│ 3. Asymmetric Vector Output Accumulators │
└────────────────────────────────────────────────────────┘ 1. Static Non-Expert Layer Placement on GPU
Shared model components are executed on every single token step, regardless of routing decisions:
- Token Embeddings & Unembeddings (LM Head): High arithmetic throughput, compact size.
- Multi-Head Attention (MHA) & KV Cache: Self-attention exhibits quadratic or long-context memory access patterns. Managing the KV cache in GPU VRAM allows the use of high-throughput FlashAttention kernels, eliminating attention memory stalls.
- Router Gating Networks: The lightweight softmax gating projections are executed on the GPU immediately following attention normalization, allowing the system to instantly determine routing paths.
By pinning these shared components in GPU VRAM, Fiddler guarantees that 100% of non-expert operations execute at maximum GPU speed.
2. Frequency-Aware Expert Caching
In Mixture-of-Experts models, expert activation is not uniformly random. Extensive empirical analysis reveals that expert selection exhibits strong temporal locality and power-law skewness:
- A small subset of “hot” experts are selected on 60% to 80% of tokens within a conversation or domain.
- The remaining “cold” experts are activated infrequently.
Fiddler allocates all remaining GPU VRAM (after reserving space for attention weights and the KV cache) to a Dynamic Hot Expert Cache:
- In an RTX 4090 (24 GB VRAM) running Mixtral-8x7B (INT4), after allocating 8 GB for attention and KV cache, approximately 14 GB remains available for expert caching.
- This allows Fiddler to store 16 to 24 complete expert matrices directly in fast GPU VRAM (out of 256 total experts across 32 layers).
- Fiddler tracks runtime expert routing frequencies using a modified Least-Frequently-Used (LFU) cache with exponential decay. When a token routes to a GPU-resident expert, it executes at full Tensor Core speed with zero host interaction.
3. Dynamic Execution Path Decision Engine
For every active token step, the router selects top-$k$ experts. Fiddler evaluates an analytical decision threshold to choose the execution path:
$$ \text{Path}(E_i) = \begin{cases} \text{GPU}, & \text{if } E_i \in \text{GPU Hot Cache} \ \text{CPU-Compute}, & \text{if } E_i \notin \text{GPU Hot Cache} \land B \le B_{\text{threshold}} \ \text{PCIe-Offload}, & \text{if } E_i \notin \text{GPU Hot Cache} \land B > B_{\text{threshold}} \end{cases} $$
- GPU Cache Hit: The expert executes on GPU Tensor Cores.
- GPU Cache Miss (Single-Batch / Small Batch $B \le 4$): Fiddler streams the 8 KB activation vector to the CPU. Dedicated OpenMP CPU threads compute the expert projection in host RAM and return the output vector to the GPU.
- GPU Cache Miss (High-Throughput Batch $B > 16$): When batch sizes are large, the computational workload on the CPU scales linearly with batch size, eventually exceeding host memory bandwidth. At this crossover point, transferring the expert weights once to serve many concurrent tokens becomes profitable. Fiddler dynamically switches to PCIe weight transfer, ensuring optimal throughput across all serving regimes.
Production Implementation: Building a Fiddler Heterogeneous MoE Runtime
To see how Fiddler coordinates CPU and GPU compute in software, let us examine a complete, modular PyTorch implementation of the Fiddler execution engine.
Step 1: Expert Memory Allocator & Router (fiddler_allocator.py)
import torch
import torch.nn as nn
from typing import Dict, List, Tuple
class FiddlerMoEAllocator:
"""
Manages expert parameter placement between GPU VRAM and Host System RAM.
Tracks hot-expert frequencies and routes activation vectors.
"""
def __init__(
self,
num_layers: int,
num_experts: int,
hidden_dim: int,
intermediate_dim: int,
gpu_device: torch.device,
cpu_device: torch.device,
max_gpu_experts: int = 24
):
self.num_layers = num_layers
self.num_experts = num_experts
self.hidden_dim = hidden_dim
self.intermediate_dim = intermediate_dim
self.gpu_device = gpu_device
self.cpu_device = cpu_device
self.max_gpu_experts = max_gpu_experts
# Track expert location: (layer_idx, expert_idx) -> device
self.expert_locations: Dict[Tuple[int, int], torch.device] = {}
self.expert_usage_counter: Dict[Tuple[int, int], int] = {}
# Initialize storage containers
self.gpu_experts: Dict[Tuple[int, int], nn.Module] = {}
self.cpu_experts: Dict[Tuple[int, int], nn.Module] = {}
def register_expert(self, layer_idx: int, expert_idx: int, expert_module: nn.Module, is_hot: bool = False):
key = (layer_idx, expert_idx)
self.expert_usage_counter[key] = 0
if is_hot and len(self.gpu_experts) < self.max_gpu_experts:
# Place in fast GPU VRAM
self.gpu_experts[key] = expert_module.to(self.gpu_device)
self.expert_locations[key] = self.gpu_device
else:
# Place in Host CPU Memory
self.cpu_experts[key] = expert_module.to(self.cpu_device)
self.expert_locations[key] = self.cpu_device Step 2: Asymmetric Heterogeneous MoE Layer (fiddler_layer.py)
class FiddlerMoELayer(nn.Module):
"""
Heterogeneous MoE layer that executes GPU-cached experts on Tensor Cores
and dispatches un-cached experts directly to multi-threaded CPU workers.
"""
def __init__(
self,
layer_idx: int,
hidden_dim: int,
num_experts: int,
top_k: int,
allocator: FiddlerMoEAllocator
):
super().__init__()
self.layer_idx = layer_idx
self.hidden_dim = hidden_dim
self.num_experts = num_experts
self.top_k = top_k
self.allocator = allocator
# Router gating network always resides on GPU
self.gate = nn.Linear(hidden_dim, num_experts, bias=False, device=allocator.gpu_device)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
x: [batch_size, hidden_dim] activation tensor on GPU
"""
batch_size, dim = x.shape
assert x.device == self.allocator.gpu_device
# 1. Evaluate router logits on GPU
router_logits = self.gate(x) # [batch_size, num_experts]
routing_weights = torch.softmax(router_logits, dim=-1)
topk_weights, topk_indices = torch.topk(routing_weights, self.top_k, dim=-1)
# Normalize top-k probabilities
topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)
# Output accumulator on GPU
final_output = torch.zeros_like(x)
# 2. Process routing assignments
for b in range(batch_size):
token_x = x[b : b + 1] # [1, hidden_dim] on GPU
for k in range(self.top_k):
expert_id = topk_indices[b, k].item()
weight = topk_weights[b, k]
key = (self.layer_idx, expert_id)
# Update frequency tracking
self.allocator.expert_usage_counter[key] += 1
location = self.allocator.expert_locations[key]
if location == self.allocator.gpu_device:
# PATH A: Execute on GPU Tensor Cores
expert = self.allocator.gpu_experts[key]
expert_out = expert(token_x)
final_output[b : b + 1] += weight * expert_out
else:
# PATH B: Fiddler Principle -> Move 8KB Activation to CPU!
# Do NOT transfer 88MB weights over PCIe!
token_x_cpu = token_x.to(self.allocator.cpu_device, non_blocking=True)
expert_cpu = self.allocator.cpu_experts[key]
# Compute on multi-threaded CPU cores
expert_out_cpu = expert_cpu(token_x_cpu)
# Return lightweight output vector to GPU
expert_out_gpu = expert_out_cpu.to(self.allocator.gpu_device, non_blocking=True)
final_output[b : b + 1] += weight * expert_out_gpu
return final_output Step 3: End-to-End Simulation and Latency Comparison (benchmark_fiddler.py)
import time
class SimpleExpert(nn.Module):
def __init__(self, hidden_dim: int, intermediate_dim: int):
super().__init__()
self.w_gate = nn.Linear(hidden_dim, intermediate_dim, bias=False)
self.w_up = nn.Linear(hidden_dim, intermediate_dim, bias=False)
self.w_down = nn.Linear(intermediate_dim, hidden_dim, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.w_down(nn.functional.silu(self.w_gate(x)) * self.w_up(x))
def run_fiddler_benchmark():
hidden_dim = 4096
intermediate_dim = 14336
num_experts = 8
top_k = 2
gpu_dev = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
cpu_dev = torch.device("cpu")
print(f"Initializing Fiddler MoE Benchmarking:")
print(f"Hidden Dim: {hidden_dim} | Intermediate Dim: {intermediate_dim}")
print(f"Device: GPU={gpu_dev}, CPU={cpu_dev}")
# Set CPU threads to maximize host memory bandwidth
torch.set_num_threads(16)
allocator = FiddlerMoEAllocator(
num_layers=1, num_experts=num_experts,
hidden_dim=hidden_dim, intermediate_dim=intermediate_dim,
gpu_device=gpu_dev, cpu_device=cpu_dev, max_gpu_experts=2
)
# Register 2 Hot Experts on GPU, 6 Cold Experts on CPU
for e_idx in range(num_experts):
exp = SimpleExpert(hidden_dim, intermediate_dim)
is_hot = (e_idx < 2)
allocator.register_expert(0, e_idx, exp, is_hot=is_hot)
moe_layer = FiddlerMoELayer(0, hidden_dim, num_experts, top_k, allocator)
# Simulate input token activation
x = torch.randn(1, hidden_dim, device=gpu_dev)
# Warmup
for _ in range(5):
_ = moe_layer(x)
if torch.cuda.is_available():
torch.cuda.synchronize()
# Benchmark execution
start = time.perf_counter()
iterations = 20
for _ in range(iterations):
_ = moe_layer(x)
if torch.cuda.is_available():
torch.cuda.synchronize()
elapsed_ms = (time.perf_counter() - start) * 1000 / iterations
print(f"\nFiddler Heterogeneous Step Latency: {elapsed_ms:.2f} ms")
print(f"Successfully avoided all PCIe weight transfer bottlenecks!")
if __name__ == "__main__":
run_fiddler_benchmark() Empirical Benchmarks: Fiddler vs. DeepSpeed and FlexGen
The University of Washington team evaluated Fiddler on real-world hardware testbeds running Mixtral-8x7B (47B total parameters) and Mixtral-8x22B (141B total parameters).
- Hardware Testbed: Workstation equipped with a single NVIDIA GeForce RTX 4090 GPU (24 GB VRAM), an Intel Core i9-13900K CPU (24 cores / 32 threads), and 64 GB DDR5 System Memory (5600 MHz) connected via PCIe Gen4 x16.
- Baselines:
DeepSpeed-Inference(state-of-the-art ZeRO-Inference parameter offloader) andFlexGen(high-throughput linear programming offloader).
Single-Batch Token Generation Latency on Mixtral-8x7B (RTX 4090 24GB):
┌────────────────────────────────────────────────────────────────────────┐
│ DeepSpeed-Inference (PCIe Weight Transfer): │
│ [==================================================] ~280 ms / token │
│ Generation Speed: ~3.5 tok/s (Saturated PCIe bus) │
├────────────────────────────────────────────────────────────────────────┤
│ FlexGen (Throughput Offloading): │
│ [=============================================] ~245 ms / token │
│ Generation Speed: ~4.1 tok/s │
├────────────────────────────────────────────────────────────────────────┤
│ Fiddler (CPU-GPU Orchestration): │
│ [====] ~34 ms / token │
│ Generation Speed: ~29.4 tok/s! │
│ -> 8.2x Lower Latency than DeepSpeed! (10.1x on Mixtral-8x22B) │
└────────────────────────────────────────────────────────────────────────┘ End-to-End Latency Across Batch Sizes
The latency per token comparison reveals how Fiddler’s execution advantage scales across serving workloads:
| Serving Batch Size ($B$) | DeepSpeed-Inference (ms/token) | FlexGen (ms/token) | Fiddler (ms/token) | Speedup Ratio | Primary Bottleneck |
|---|---|---|---|---|---|
| Batch = 1 (Interactive Chat) | 280.4 ms | 245.2 ms | 34.1 ms | 8.22x Faster | DeepSpeed: PCIe weight transfer |
| Batch = 2 (Dual user) | 289.1 ms | 251.8 ms | 41.6 ms | 6.95x Faster | DeepSpeed: PCIe weight transfer |
| Batch = 4 (Team workspace) | 305.6 ms | 268.4 ms | 58.2 ms | 5.25x Faster | DeepSpeed: PCIe weight transfer |
| Batch = 8 (Concurrent API) | 338.2 ms | 295.1 ms | 92.4 ms | 3.66x Faster | Fiddler switches to hybrid mode |
| Batch = 16 (High throughput) | 395.0 ms | 340.5 ms | 165.0 ms | 2.39x Faster | CPU bandwidth shared |
For single-batch conversational inference, Fiddler slashes token generation time from 280.4 ms down to 34.1 ms, boosting generation speed from an agonizing 3.5 tokens/s to an interactive 29.4 tokens per second!
Memory Consumption and Accuracy Verification
Because Fiddler never alters model parameters or approximates floating-point matrices:
- Mathematical Losslessness: Output representations are mathematically identical to running Mixtral on an 8x A100 GPU cluster. The perplexity delta across WikiText-2 and C4 is exactly 0.00.
- Zero VRAM Thrashing: GPU memory utilization remains completely flat during inference. By avoiding continuous memory allocation and deallocation over PCIe, Fiddler eliminates CUDA memory fragmentation and out-of-memory crashes.
Production Best Practices for Heterogeneous MoE Deployment
When deploying Fiddler within enterprise infrastructure, agent runtimes, or on-premises workstations, engineering teams should observe key operational practices:
1. CPU Thread Pinning and NUMA Optimization
Host CPU memory bandwidth directly dictates cold expert execution speed:
- Set
OMP_NUM_THREADSto match physical CPU performance cores (P-cores) rather than logical hyperthreaded cores to prevent cache thrashing. - On dual-socket server systems (e.g., dual AMD EPYC or Intel Xeon), enforce NUMA node binding using
numactl --cpunodebind=0 --membind=0. Ensuring that host RAM holding expert weights resides on the same NUMA node as the PCIe controller attached to the GPU eliminates cross-socket QPI/UPI interconnect latency.
2. Hot-Expert Warmup via Domain Profiling
Rather than starting with an empty hot-expert cache, profile your deployment with domain-specific calibration queries (e.g., legal prompts, Python code snippets, or financial datasets):
- Pre-populate the GPU hot-expert cache with the top-24 most frequently activated experts for your specific workload.
- This achieves immediate 65%+ GPU cache hit rates on the first user query, eliminating cold-start latency spikes.
3. Combining with Context Pipelines
In modern agentic architectures powered by AiFetchly Web Scraping and real-time data fetching, user queries frequently ingest multi-thousand-token document contexts.
- Combine Fiddler with FlashAttention-2 or Flash-Decoding to keep the attention prefill stage compute-bound on the GPU.
- Once the prefill phase finishes, Fiddler seamlessly transitions into its CPU-GPU orchestrated decoding pipeline.
By recognizing that moving kilobytes of activations is orders of magnitude faster than moving gigabytes of weights, Fiddler establishes an indispensable systems paradigm—democratizing frontier Mixture-of-Experts foundation models on accessible, commodity hardware.