Running Large AI Models on Consumer Hardware
Listen to the summary
Uses a voice available on your device
Audio options
On this page 5 sections
Related concepts 4 concepts
Key Takeaways
- FreeToken allows consumer-grade hardware like the RTX 4060 and 5090 to serve high-parameter models previously limited to datacenter-class clusters.
- The system achieves 77 to 83 tokens per second on Qwen3.6-35B-A3B and 39.3 tokens per second on a 35B model using an 8GB RTX 4060 laptop.
- A bandwidth-adaptive execution policy dynamically shifts expert loading between PCIe transfer and direct CPU processing to handle memory constraints.
- Expert double buffering and semantic-aware state caching optimize latency by overlapping computation with data transfer and enabling prefix reuse.
Summary & Methodology Analysis
FreeToken addresses the gap in deploying Mixture of Experts (MoE) models, which utilize a specific architecture that activates only a subset of parameters per token, on consumer hardware. To manage the high demand for memory and bandwidth, it employs full-layer expert double buffering. This technique hides latency by streaming the next layer of experts over the PCIe bus while the GPU computes the current layer. Additionally, the system uses semantic-aware state caching, which pins recurrent-state checkpoints at specific boundaries like tool calls or thinking segments, allowing for partial reuse when context is modified. For long-term residency, a shared LRU expert cache maintains active experts based on temporal routing locality, resizing according to current memory availability.
Interactive System Flowchart
Illustrative Implementation
A short sketch of the paper's core idea, not the authors' own code.
# Illustrative sketch (not from the paper)
import torch
from collections import OrderedDict
# Double‑buffer for two consecutive expert layers
class DoubleBuffer:
def __init__(self, layer_fn):
self.current = None # GPU resident experts for current layer
self.next = None # Prefetched experts for next layer
self.layer_fn = layer_fn
def load_next(self, expert_ids):
# Simulate async PCIe transfer (placeholder)
self.next = torch.nn.ModuleList([self.layer_fn(eid) for eid in expert_ids])
def swap(self):
self.current, self.next = self.next, None
# Simple LRU cache for experts on GPU
class ExpertLRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, eid):
if eid in self.cache:
self.cache.move_to_end(eid)
return self.cache[eid]
return None
def put(self, eid, expert):
self.cache[eid] = expert
self.cache.move_to_end(eid)
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # evict LRU
# Bandwidth‑adaptive execution (q* policy)
def execute_token(token, cache, bandwidth_pcie, bandwidth_cpu):
# Decide where to fetch missing expert
if bandwidth_pcie >= bandwidth_cpu:
# Prefer PCIe fill then GPU exec
expert = cache.get(token.expert_id) or load_expert_via_pcie(token.expert_id)
else:
# Direct CPU execution fallback
expert = load_expert_via_cpu(token.expert_id)
# Run expert (placeholder)
return expert(token.input)
# Placeholder loaders
def load_expert_via_pcie(eid):
# In real system this streams over PCIe into GPU memory
return torch.nn.Linear(1024, 1024)
def load_expert_via_cpu(eid):
# CPU‑only execution path
return lambda x: x # no‑op for illustration
# Example workflow for a single decode step
def decode_step(tokens, double_buf, lru_cache, bw_pcie, bw_cpu):
# Prefetch next layer experts while current layer runs
double_buf.load_next([t.expert_id for t in tokens])
outputs = []
for token in tokens:
expert = lru_cache.get(token.expert_id)
if expert is None:
expert = execute_token(token, lru_cache, bw_pcie, bw_cpu)
lru_cache.put(token.expert_id, expert)
outputs.append(expert(token.input))
double_buf.swap() # hide latency for next layer
return outputs
// Illustrative sketch (not from the paper)
const { EventEmitter } = require('events');
// Double‑buffer for two consecutive expert layers
class DoubleBuffer extends EventEmitter {
constructor(layerFactory) {
super();
this.current = null; // GPU resident experts for current layer
this.next = null; // Prefetched experts for next layer
this.layerFactory = layerFactory;
}
loadNext(expertIds) {
// Simulate async PCIe transfer (placeholder)
this.next = expertIds.map(id => this.layerFactory(id));
this.emit('prefetchDone');
}
swap() {
this.current = this.next;
this.next = null;
}
}
// Simple LRU cache for experts on GPU
class ExpertLRUCache {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map(); // preserves insertion order
}
get(eid) {
if (!this.map.has(eid)) return null;
const val = this.map.get(eid);
this.map.delete(eid);
this.map.set(eid, val); // move to recent
return val;
}
put(eid, expert) {
if (this.map.has(eid)) this.map.delete(eid);
this.map.set(eid, expert);
if (this.map.size > this.capacity) {
// evict LRU (first entry)
const lruKey = this.map.keys().next().value;
this.map.delete(lruKey);
}
}
}
// Bandwidth‑adaptive execution (q* policy)
function executeToken(token, cache, bwPcie, bwCpu) {
let expert;
if (bwPcie >= bwCpu) {
expert = cache.get(token.expertId) || loadExpertViaPCIe(token.expertId);
} else {
expert = loadExpertViaCPU(token.expertId);
}
// Run expert (placeholder)
return expert(token.input);
}
function loadExpertViaPCIe(eid) {
// In real system this streams over PCIe into GPU memory
return input => input; // no‑op placeholder
}
function loadExpertViaCPU(eid) {
// CPU‑only execution path
return input => input; // no‑op placeholder
}
// Example workflow for a single decode step
function decodeStep(tokens, doubleBuf, lruCache, bwPcie, bwCpu) {
doubleBuf.loadNext(tokens.map(t => t.expertId));
const outputs = [];
for (const token of tokens) {
let expert = lruCache.get(token.expertId);
if (!expert) {
expert = executeToken(token, lruCache, bwPcie, bwCpu);
lruCache.put(token.expertId, expert);
}
outputs.push(expert(token.input));
}
doubleBuf.swap(); // hide latency for next layer
return outputs;
}
Cross-Examination & FAQs
A deeper dive clarifying mechanics, constraints, and baseline evaluations.
Q1. What is the primary problem FreeToken solves?
It addresses the accessibility gap for users who want to run large Mixture of Experts models but lack expensive datacenter-grade GPU clusters.
Q2. Can I run this on consumer hardware?
Yes, it is designed for consumer hardware and has been tested on platforms like an 8GB RTX 4060 laptop and an RTX 5090.
Q3. Does this system work for all model sizes?
It is optimized for large models like Qwen3.6-35B-A3B and DeepSeek-V4-Flash, though effectiveness depends on available hardware capacity.
Q4. What happens if my GPU memory is limited?
If the GPU memory cannot accommodate two full-layer buffers, the system reverts to on-demand loading of experts.
Q5. How does the q* policy manage bandwidth bottlenecks?
The bandwidth-adaptive execution policy partitions decode-time cache misses by choosing between PCIe-based cache fills and direct CPU-based execution based on real-time bandwidth measurements.
Q6. What is the role of the shared LRU expert cache?
It manages which experts stay resident on the GPU based on how often they are used, adjusting its size dynamically to fit the current memory budget.
Q7. Are there scenarios where the system performs poorly?
On platforms where a fast pinned DMA path cannot be established, the system falls back to a slower pure-CPU backend.
Q8. Which specific models were used in testing?
Testing included Kimi-K3, GLM-5.2, DeepSeek-V4-Flash-0731, and Qwen3.6-35B-A3B.
Q9. Does this paper compare against other serving frameworks?
The paper references systems like llama.cpp, KTransformers, Ollama, and MoE-Infinity, though it focuses on its own architectural contributions.