Nanochat de Karpathy: Entiende Cómo Funcionan los LLMs por Dentro
Tiempo de lectura: 10 minutos
Andrej Karpathy acaba de lanzar nanochat: un mini-ChatGPT funcional en un solo archivo Python de ~800 líneas. Pero aquí lo interesante no es que cueste $100 entrenar (eso es marketing). Lo interesante es que puedes entender EXACTAMENTE cómo funciona un LLM leyendo su código.
Este artículo no va de entrenar tu propio ChatGPT (para eso ya tienes Ollama y modelos pre-entrenados). Va de usar nanochat como herramienta educativa para entender transformers, attention, embeddings, tokenización… y todo lo que pasa entre el prompt y la respuesta.
- Nanochat: LLM educacional de 15M parámetros en 800 líneas de código
- Stack completo: Desde tokenización hasta generación, todo visible
- Arquitectura moderna: RoPE, GQA, RMS Norm (igual que Llama)
- Entrenable en casa: 4h en NVIDIA V100 ($100) o 24h en RTX 4090 ($0)
- Código didáctico: Comentado paso a paso por Karpathy
Tabla de Contenidos
- Qué es Nanochat y Por Qué Importa
- Arquitectura Explicada: De Input a Output
- Instalación y Primer Inference Local
- Disección del Código: Cómo Funciona Cada Parte
- Entrenamiento Local con tu GPU
- Nanochat vs Llama: Qué Comparten y Qué Difiere
- Experimentos para Aprender
- Limitaciones y Qué No Esperar
- Preguntas Frecuentes
Qué es Nanochat y Por Qué Importa
Andrej Karpathy (ex-director de IA en Tesla, co-fundador de OpenAI) es conocido por hacer IA accesible. Su proyecto nanoGPT ya era referencia educativa para entender GPT-2. Ahora llega nanochat, la evolución con arquitectura moderna.
Especificaciones Técnicas
- 15 millones de parámetros (vs 70B de Llama 3.3)
- Arquitectura transformer con mejoras modernas:
- RoPE (Rotary Position Embeddings)
- GQA (Grouped Query Attention)
- RMS Normalization
- SwiGLU activation
- Vocabulario 4096 tokens (tiny, para rapidez)
- Contexto 512 tokens (1-2 párrafos)
- Dataset: FineWeb-Edu (educación high-quality filtrada)
Por Qué es Importante para Homelabbers
No vas a usar nanochat en producción. Lo vas a usar para aprender cómo funcionan LLMs desde cero:
- Código readable: 800 líneas vs 50,000+ de Llama
- Todo en un archivo: No dependencies hell, no abstracciones mágicas
- Entrenable en casa: Con RTX 4090 puedes entrenar un modelo funcional
- Arquitectura moderna: Mismas técnicas que Llama/GPT-4
- Documentación Karpathy: Video de 90 minutos explicando línea por línea
Use case real: Antes de intentar fine-tunear Llama 70B, entrena nanochat para entender qué hace cada hiperparámetro (learning rate, batch size, warmup steps, etc.).
Arquitectura Explicada: De Input a Output
Vamos a descomponer qué pasa cuando le das un prompt a nanochat.
Paso 1: Tokenización
Tu prompt en texto se convierte en números:
Input text: "Hello, how are you?"
↓ Tokenizer
Token IDs: [245, 891, 1423, 392, 1156, 67]
Nanochat usa un tokenizer BPE simple de 4096 tokens. Llama usa 128,000 tokens para mejor cobertura multilingüe, pero el concepto es idéntico.
Paso 2: Embeddings
Cada token ID se convierte en un vector de 512 dimensiones:
Token ID 245 → Vector [0.23, -0.45, 0.67, ..., 0.12] (512 dims)
Este vector representa el «significado» del token en un espacio latente.
Paso 3: Positional Encoding (RoPE)
Los embeddings no saben su posición en la frase. RoPE añade información posicional:
Token "how" en posición 2 → Vector con frecuencias rotadas
Token "how" en posición 5 → Vector con rotación diferente
RoPE permite que el modelo entienda que «how are you» ≠ «are you how».
Paso 4: Attention Mechanism (Multi-Head + GQA)
Aquí es donde la magia ocurre. El modelo calcula qué tokens deben «prestarse atención» entre sí:
Query: "you" busca contexto
Keys: ["Hello", "how", "are"]
Values: Representaciones semánticas
Attention weights:
"you" ← 90% "are" (alta correlación)
"you" ← 5% "how" (correlación media)
"you" ← 5% "Hello" (baja correlación)
Output: Vector que combina información relevante
GQA (Grouped Query Attention) es una optimización: en lugar de calcular Q/K/V para cada head independientemente, agrupa queries para reducir cómputo (técnica de Llama 2/3).
Paso 5: Feed-Forward Network (SwiGLU)
Después de attention, cada token pasa por una red neuronal:
Vector de 512 dims
↓ Linear (512 → 2048)
↓ SwiGLU activation
↓ Linear (2048 → 512)
Vector transformado de 512 dims
SwiGLU es una función de activación más efectiva que ReLU (usada en Llama/GPT-4).
Paso 6: Stacking (Múltiples Capas)
Nanochat tiene 12 capas de transformer. Cada capa refina la representación:
Input tokens
↓ Layer 1: Attention + FFN
↓ Layer 2: Attention + FFN
↓ Layer 3: Attention + FFN
...
↓ Layer 12: Attention + FFN
Output logits
Paso 7: Predicción del Siguiente Token
La última capa produce probabilidades para cada token del vocabulario:
Logits: [0.001, 0.003, ..., 0.45, ..., 0.002] (4096 valores)
↓ Softmax
Probabilities: Token 2891 ("fine") → 45% probabilidad
Token 1034 ("good") → 30% probabilidad
Token 892 ("ok") → 15% probabilidad
↓ Sampling
Next token: "fine"
Repite el proceso con el nuevo token añadido hasta generar la respuesta completa.
Instalación y Primer Inference Local
Vamos a ejecutar nanochat en local para ver un LLM «desde dentro».
Requisitos
- GPU: Cualquier NVIDIA con 2GB+ VRAM (hasta GTX 1060 funciona)
- Python 3.10+
- PyTorch con CUDA
Instalación
# Clonar repositorio
git clone https://github.com/karpathy/nanochat
cd nanochat
# Crear entorno virtual
python3 -m venv venv
source venv/bin/activate
# Instalar dependencias (solo PyTorch + numpy)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install numpy requests tiktoken
# Verificar CUDA
python -c "import torch; print(torch.cuda.is_available())"
# Debe devolver: True
Descargar Modelo Pre-entrenado
Karpathy proporciona un checkpoint entrenado:
# Descargar modelo (~60MB)
wget https://huggingface.co/karpathy/nanochat/resolve/main/nanochat.pt -O nanochat.pt
# Verificar descarga
ls -lh nanochat.pt
# Debe mostrar: 60M nanochat.pt
Primera Inferencia
# Ejecutar inference interactivo
python sample.py --checkpoint nanochat.pt --device cuda
# Prompt:
# > What is machine learning?
# Respuesta (ejemplo):
# Machine learning is when computers learn from data to make predictions
# or decisions without being explicitly programmed.
Nota: Las respuestas serán simples y a veces incorrectas (es un modelo de 15M parámetros). El objetivo es educativo, no producción.
Disección del Código: Cómo Funciona Cada Parte
El código de nanochat está en model.py
. Vamos a recorrer las partes clave.
1. Transformer Block
class TransformerBlock(nn.Module):
def __init__(self, config):
super().__init__()
# Attention layer
self.attn = MultiHeadAttention(config)
# Feed-forward layer
self.ffn = FeedForward(config)
# Normalization
self.norm1 = RMSNorm(config.dim)
self.norm2 = RMSNorm(config.dim)
def forward(self, x):
# Residual connection + attention
x = x + self.attn(self.norm1(x))
# Residual connection + FFN
x = x + self.ffn(self.norm2(x))
return x
Residual connections (x + self.attn(...)
) son críticas: permiten que gradientes fluyan durante entrenamiento sin desaparecer.
2. Rotary Position Embeddings (RoPE)
def apply_rope(x, cos, sin):
# x shape: (batch, seq_len, n_heads, head_dim)
# Divide en pares de dimensiones
x1, x2 = x[..., ::2], x[..., 1::2]
# Aplica rotación
rotated = torch.cat([
x1 * cos - x2 * sin,
x1 * sin + x2 * cos
], dim=-1)
return rotated
RoPE rota vectores según su posición. Tokens cercanos tienen rotaciones similares, tokens lejanos se «alejan» en el espacio vectorial.
3. Grouped Query Attention
class MultiHeadAttention(nn.Module):
def __init__(self, config):
self.n_heads = config.n_heads
self.n_kv_heads = config.n_kv_heads # Menos KV heads que Q heads
self.head_dim = config.dim // config.n_heads
# Proyecciones
self.q_proj = nn.Linear(config.dim, config.dim)
self.k_proj = nn.Linear(config.dim, self.n_kv_heads * self.head_dim)
self.v_proj = nn.Linear(config.dim, self.n_kv_heads * self.head_dim)
self.o_proj = nn.Linear(config.dim, config.dim)
GQA ahorra memoria: Si tienes 8 query heads pero solo 2 KV heads, reduces el KV cache un 75% (crucial para modelos grandes).
4. Generación Autoregresiva
@torch.no_grad()
def generate(model, prompt_tokens, max_new_tokens=50):
tokens = prompt_tokens
for _ in range(max_new_tokens):
# Forward pass
logits = model(tokens)
# Tomar solo el último token
next_token_logits = logits[-1, :]
# Sampling (temperature, top-k, top-p)
probs = F.softmax(next_token_logits / temperature, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
# Añadir a secuencia
tokens = torch.cat([tokens, next_token])
# Parar si es token EOS
if next_token == eos_token:
break
return tokens
Este loop es el corazón de la generación: predice 1 token, lo añade al prompt, repite.
Entrenamiento Local con tu GPU
Ahora la parte divertida: entrenar tu propio nanochat desde cero.
Preparar Dataset
# Descargar FineWeb-Edu sample (10GB de texto educativo)
python prepare_dataset.py --dataset fineweb-edu --size 10GB
# Tokenizar dataset
python tokenize.py --input data/fineweb-edu --output data/tokens
# Resultado: ~5B tokens tokenizados listos para entrenar
Configuración de Entrenamiento
Edita config.py
:
# Hardware
batch_size = 64 # Ajusta según tu VRAM
gradient_accum = 4 # Batch efectivo = 64*4 = 256
# Modelo (15M params)
n_layers = 12
n_heads = 8
n_kv_heads = 2 # GQA
dim = 512
vocab_size = 4096
# Entrenamiento
max_iters = 100000 # ~4h en RTX 4090
learning_rate = 3e-4
warmup_iters = 1000
lr_decay = True
# Optimizador
optimizer = "AdamW"
weight_decay = 0.1
beta1 = 0.9
beta2 = 0.95
Lanzar Entrenamiento
# Entrenar en single GPU
python train.py --config config.py --device cuda
# Output esperado:
# iter 0: loss 10.234
# iter 100: loss 8.456
# iter 1000: loss 5.123
# ...
# iter 100000: loss 2.345 (convergencia)
Tiempos estimados:
- RTX 4090: 20-24h para 100k iters
- RTX 4070 Ti: 30-36h
- NVIDIA V100 (cloud): 4-5h ($100 en Lambda Labs)
Monitorizar Entrenamiento
# En otra terminal, monitorizar GPU
watch -n 1 nvidia-smi
# Deberías ver:
# GPU Utilization: 95-100%
# Memory Used: ~10GB / 24GB (RTX 4090)
# Power: 350-400W
Evaluar Modelo Entrenado
# Después de entrenar, probar tu modelo
python sample.py --checkpoint out/model_100000.pt --device cuda
# Prompt test:
# > Explain what a neural network is.
# Respuesta de TU modelo entrenado:
# A neural network is a type of computer program that learns patterns
# from examples to make predictions about new data.
Nanochat vs Llama: Qué Comparten y Qué Difiere
Característica | Nanochat | Llama 3.3 70B |
---|---|---|
Parámetros | 15 millones | 70 mil millones |
Arquitectura | Transformer + RoPE + GQA | Transformer + RoPE + GQA |
Normalización | RMS Norm | RMS Norm |
Activación FFN | SwiGLU | SwiGLU |
Contexto | 512 tokens | 128,000 tokens |
Vocabulario | 4,096 tokens | 128,000 tokens |
Capas | 12 | 80 |
Dimensión embeddings | 512 | 8,192 |
Tamaño modelo | 60 MB | 140 GB (FP16) |
VRAM inference | 2 GB | 40-160 GB (según quant) |
Velocidad inference | 500-1000 tokens/seg (RTX 4090) | 40-60 tokens/seg (RTX 4090) |
Calidad respuestas | Básica, errores frecuentes | Estado del arte |
Código fuente | 800 líneas, 1 archivo | 50,000+ líneas, complejo |
Objetivo | Educación + experimentación | Producción + investigación |
Conclusión: Nanochat usa las mismas técnicas que Llama, solo que a escala tiny para que puedas entender y experimentar sin cluster de GPUs.
Experimentos para Aprender
Una vez que tienes nanochat corriendo, estos experimentos te enseñarán mucho:
Experimento 1: Ablation Studies
Elimina componentes para ver su impacto:
# Entrenar sin RoPE (comentar apply_rope en model.py)
# Resultado: Modelo no entiende orden de palabras
# Entrenar sin residual connections (quitar x + en forward)
# Resultado: No converge, loss se queda alto
# Reducir n_layers de 12 a 6
# Resultado: Converge más rápido pero calidad peor
Experimento 2: Temperatura en Sampling
# Temperature = 0.1 (casi determinista)
python sample.py --temp 0.1
# Respuestas: Repetitivas pero coherentes
# Temperature = 1.0 (balanced)
python sample.py --temp 1.0
# Respuestas: Variadas y creativas
# Temperature = 2.0 (muy aleatorio)
python sample.py --temp 2.0
# Respuestas: Caóticas, poco coherentes
Experimento 3: Fine-tuning en tu Dataset
# Crea dataset personalizado (ej: tus notas técnicas)
cat mis_notas.txt > data/custom.txt
# Tokeniza
python tokenize.py --input data/custom.txt --output data/custom_tokens
# Fine-tune desde checkpoint pre-entrenado
python train.py \
--init_from nanochat.pt \
--data_dir data/custom_tokens \
--max_iters 5000
# Resultado: Modelo habla como tus notas
Experimento 4: Quantización Post-Training
# Quantizar modelo a int8 (reduce tamaño 4x)
python quantize.py --model nanochat.pt --dtype int8 --output nanochat_int8.pt
# Comparar calidad
python sample.py --checkpoint nanochat.pt # FP32
python sample.py --checkpoint nanochat_int8.pt # INT8
# Medir degradación de calidad
python eval.py --model1 nanochat.pt --model2 nanochat_int8.pt
Limitaciones y Qué No Esperar
Nanochat es educativo, no producción. Sus limitaciones son importantes:
1. Contexto Corto (512 tokens)
512 tokens = ~400 palabras = 1-2 párrafos. No puedes darle documentos largos como a Llama (128k tokens).
2. Vocabulario Tiny (4,096 tokens)
Muchas palabras se fragmentan en subtokens:
"Nanotechnology" → ["Nano", "tech", "nology"] (3 tokens)
"Electroencephalography" → 6+ subtokens
Llama tokenizer:
"Electroencephalography" → 1 token
3. Sin Instruction Following
Nanochat solo predice siguiente token, no sigue instrucciones como ChatGPT. Para eso necesitarías:
- Instrucción tuning (entrenar con pares prompt-respuesta)
- RLHF (Reinforcement Learning from Human Feedback)
4. Calidad de Respuestas
15M parámetros vs 70,000M de Llama = 4,600x menos capacidad. Espera:
- Errores factuales frecuentes
- Respuestas cortas y simples
- Falta de razonamiento complejo
- Vocabulario limitado
5. Solo Inglés
El dataset FineWeb-Edu es principalmente inglés. Para otros idiomas necesitarías re-entrenar con dataset multilingüe.
Preguntas Frecuentes
¿Vale la pena entrenar nanochat si ya tengo Ollama con Llama?
Si tu objetivo es tener el mejor LLM, no. Usa Llama. Si tu objetivo es entender cómo funcionan LLMs por dentro, sí. Entrenar nanochat te enseña qué hacen learning rate, batch size, gradient accumulation, warmup… conceptos que luego aplicarás al fine-tunear Llama.
¿Puedo usar nanochat en producción?
No es recomendable. Es demasiado pequeño y dará respuestas mediocres comparado con Llama 7B o modelos similares. Su valor es educativo.
¿Cuánto cuesta entrenar nanochat desde cero?
Depende del hardware:
- Cloud (Lambda Labs V100): $24/hora × 4h = $96
- Local RTX 4090: 24h × 400W = 9.6 kWh × €0.15 = €1.44
- Local RTX 4070 Ti: 36h × 300W = 10.8 kWh × €0.15 = €1.62
Si ya tienes GPU, entrenar local es casi gratis.
¿Qué GPU mínima necesito para entrenar nanochat?
Con las configuraciones default necesitas ~10GB VRAM. GPUs viables:
- RTX 3080 (10GB) → Justo, batch_size=32
- RTX 4070 (12GB) → Cómodo, batch_size=48
- RTX 4090 (24GB) → Sobrado, batch_size=64+
Si tienes menos VRAM, reduce batch_size
y aumenta gradient_accum
para compensar.
¿Puedo escalar nanochat a 1B parámetros?
Sí, técnicamente. Aumenta n_layers
, dim
, n_heads
en config. Pero:
- Necesitas más VRAM (probablemente 2x RTX 4090)
- Entrenamiento tarda 10-20x más
- Dataset necesita ser 10x más grande
Para modelos 1B+, mejor usa frameworks como Axolotl o LLaMA-Factory que ya están optimizados.
¿Nanochat soporta fine-tuning con LoRA/QLoRA?
El código base no, pero es sencillo añadirlo. El repo tiene un PR con implementación LoRA de ejemplo. Para un modelo tan pequeño (15M), full fine-tuning es más directo.
¿Puedo entrenar nanochat con mis propios datos privados?
Sí, totalmente. Prepara tu dataset en archivos .txt, tokeniza con tokenize.py
y entrena. Es una buena forma de crear un LLM que conoce tu documentación interna sin subir datos a la nube.
¿Qué diferencia hay entre nanochat y nanoGPT?
NanoGPT (2022) replica GPT-2 con arquitectura antigua. Nanochat (2025) usa arquitectura moderna (RoPE, GQA, RMS Norm, SwiGLU) como Llama 3. Si estás aprendiendo en 2025, nanochat es más relevante.
¿Karpathy tiene un video explicando nanochat?
Sí, hay un video de 90 minutos en YouTube donde explica el código línea por línea: youtube.com/watch?v=EFpDHdsITrg. Muy recomendable si quieres entender cada detalle.
¿Puedo usar nanochat para aprender antes de hacer RAG?
Absolutamente. RAG (Retrieval-Augmented Generation) asume que entiendes cómo funciona el LLM base. Nanochat te da esa base sólida. Después de entender nanochat, implementar RAG con Llama será mucho más claro.
Conclusión: Abre la Caja Negra de los LLMs
Nanochat no es el LLM más potente. Es el LLM más entendible. En 800 líneas de código puedes ver exactamente:
- Cómo se tokenizan los textos
- Cómo funcionan embeddings y RoPE
- Qué hace el attention mechanism
- Por qué existen residual connections
- Cómo se entrena con backpropagation
- Qué controlan los hiperparámetros
Roadmap de aprendizaje recomendado:
- Ejecuta inference con modelo pre-entrenado (30 min)
- Lee el código de model.py completo (2-3 horas)
- Mira el video de Karpathy (90 min)
- Entrena tu propio modelo desde cero (24h GPU time)
- Experimenta con modificaciones (ablations, fine-tuning, etc.)
- Aplica lo aprendido a Llama (fine-tuning con LoRA/QLoRA)
Después de este proceso, cuando uses Ollama con Llama 3.3, ya no será magia. Sabrás exactamente qué pasa entre tu prompt y la respuesta.
Recursos relacionados:
- Monta tu servidor IA casero para entrenar modelos
- RAG: Dale memoria a tus LLMs (guía completa)
- n8n: Integra LLMs en workflows automatizados
- Repositorio oficial de nanochat
¿Has entrenado nanochat? ¿Qué experimentos te han enseñado más? Comparte en comentarios.