1La tubería de una predicción
El modelo recibe texto ya convertido en token ids (enteros). Cada id se transforma en un vector denso mediante una tabla de embeddings $E \in \mathbb{R}^{V \times d}$, donde $V$ es el tamaño del vocabulario y $d$ la dimensión oculta. La representación inicial de la secuencia es simplemente la fila correspondiente de $E$ para cada token:
Esos vectores pasan por una pila de bloques Transformer. Cada bloque hace dos cosas: una capa de atención que mezcla información entre posiciones (cada token mira a los anteriores), y un MLP (feed-forward) que transforma cada posición por separado. El MLP es donde vive la mayor parte de los parámetros del modelo, y ambas subcapas se envuelven con conexiones residuales y normalización para que el entrenamiento sea estable en decenas de capas. Al final, la representación de la última posición se proyecta al vocabulario para producir los logits, un número por cada token posible.
token_ids → embeddings → [ bloque × N: atención causal + MLP + residual/norm ] → logits → softmax → p(siguiente token)
2Atención causal — el corazón del Transformer
Cada posición construye tres vectores a partir de su representación $h$: una consulta (query), una clave (key) y un valor (value), mediante matrices aprendidas:
La atención compara cada query con todas las keys (producto punto), normaliza con softmax y usa esos pesos para promediar los values. El escalado por $\sqrt{d_k}$ evita que los productos punto crezcan demasiado (donde $d_k$ es la dimensión por cabeza, que veremos abajo). La máscara causal $M$ pone $-\infty$ en las posiciones futuras, de modo que el token en la posición $i$ solo puede mirar hacia atrás ($j \le i$):
Se usan varias cabezas en paralelo (multi-head): con $H$ cabezas, cada una opera en dimensión $d_k = d/H$ sobre su propia proyección, y sus salidas se concatenan y se proyectan con $W_O$:
Qwen3 no suma un vector de posición al embedding: usa RoPE (rotary position embedding), que rota los pares $(q,k)$ por un ángulo dependiente de la posición dentro de la atención. Así el producto punto codifica la posición relativa. Lo verás de nuevo en el KV cache y en el runtime on-device.
3De logits a probabilidades
La representación final de la última posición, $h_L$, se proyecta con la matriz de "des-embedding" $W_U \in \mathbb{R}^{d \times V}$ para obtener el vector de logits $z$. El softmax lo convierte en una distribución de probabilidad válida (suma 1) sobre todo el vocabulario $\mathcal{V} = \{0,\dots,V-1\}$:
Aquí está la frontera conceptual más importante del curso: el entrenamiento mueve estos logits (ajustando los pesos para que el token correcto reciba más probabilidad); la inferencia solo elige entre las probabilidades que ya existen.
4Muestreo: temperature, top-k, top-p
Con la distribución en mano hay que elegir un token. La opción más simple es greedy: tomar el más probable.
La temperatura $T$ reescala los logits antes del softmax: $T \lt 1$ agudiza la distribución (más determinista), $T \gt 1$ la aplana (más creativa/aleatoria):
- top-k: se conservan solo los $k$ tokens más probables y se re-normaliza.
- top-p (nucleus): se conserva el conjunto más pequeño cuya probabilidad acumulada alcanza $p$, es decir $\sum p \ge p_{\text{top}}$.
Estos parámetros no son parte del modelo entrenado: son ajustes de generación. Cambiarlos nunca "reentrena" nada. Si el modelo dice tonterías con temperature=1.2, bájala antes de culpar al fine-tuning.
5KV cache: por qué la generación es viable
Generar token a token repetiría el cálculo de la atención sobre toda la secuencia en cada paso — costo cuadrático. El KV cache guarda las claves y valores ya calculados $K_{\le t}, V_{\le t}$; en el paso siguiente solo se computa la query nueva $q_{t}$ y se atiende contra el caché. El costo por token pasa de recomputar $O(t^{2}\,d)$ a $O(t\,d)$:
A cambio, el caché ocupa memoria que crece con la longitud del contexto — un tema central cuando llevemos el modelo a un dispositivo (Módulo 10).
6Tres cosas que no son lo mismo
| Concepto | Qué es | Lo cambia… |
|---|---|---|
| Pesos | Los parámetros aprendidos ($E, W_Q, W_U$…). Fijos tras entrenar. | El entrenamiento / fine-tuning |
| Activaciones | Los vectores intermedios ($h$) que aparecen al procesar una entrada. | Cada entrada distinta |
| Contexto | Los tokens que le pasas (prompt + lo generado) dentro de la ventana. | Lo que tú escribes / RAG |
Fine-tuning cambia pesos. RAG (Módulo 8) cambia contexto. Confundirlos es la causa número uno de meter en el modelo algo que debía ir en el prompt.
Todo este ciclo — embeddings, atención, softmax, muestreo — es exactamente lo que corre en el dispositivo cuando el modelo es on-device: no hay servidor. El modelo va cuantizado (Módulo 9) y un runtime como llama.cpp hace el forward, el softmax y el muestreo en el CPU/GPU del teléfono. Cada concepto de este módulo (el KV cache que come RAM, la temperatura, greedy vs muestreo) es una perilla que controlas desde la app.
- Instala el entorno:
pip install transformers torch(o usa un notebook en Colab, gratis). - Carga el modelo y su tokenizer:
AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")yAutoTokenizer.from_pretrained(...). - Tokeniza un prompt y mira los
input_ids— son los enteros del §1. - Haz un forward y extrae
logits[0, -1](la última posición). Aplícale softmax y mira los 10 tokens más probables. - Juega con la temperatura: divide los logits por
T ∈ {0.5, 1.0, 2.0}antes del softmax y observa cómo la distribución se agudiza o se aplana.
La tokenización que usas en Python ya es Rust por debajo: la librería tokenizers de Hugging Face está escrita en Rust. Y el forward completo puedes correrlo en Rust con candle (el framework de ML de Hugging Face), que carga pesos de Qwen y calcula logits sin tocar Python — ideal para empaquetarlo en una app.
// candle: cargar Qwen y sacar los logits del último token let logits = model.forward(&input_ids, 0)?; // [batch, seq, vocab] let last = logits.i((0, seq_len - 1))?; // logits del siguiente token let probs = softmax(&last, D::Minus1)?; // distribución
En este curso el entrenamiento lo haremos en Python (es donde está el ecosistema maduro), pero la inferencia on-device es territorio de Rust — y a eso volvemos en serio en el Módulo 10.
Lecturas y recursos
✦Ejercicios
De menor a mayor complejidad. El último es el que hace un practicante de verdad.
Tokeniza y cuenta
Carga el tokenizer de Qwen3-0.6B y tokeniza tres frases. Imprime los token_ids y su longitud.
Entrega: tabla frase → nº de tokens. Pista: AutoTokenizer.from_pretrained + tokenizer(text)["input_ids"].
Softmax a mano
Implementa softmax(z) en NumPy con estabilidad numérica (resta el máximo) y verifica que suma 1 para un vector de logits aleatorio.
Entrega: función + assert de que $\sum p = 1$. Pista: $\mathrm{softmax}(z) = e^{z-\max z}/\sum e^{z-\max z}$.
Efecto de la temperatura
Toma un vector de logits fijo y grafica la distribución resultante para $T \in \{0.5, 1.0, 2.0\}$. Observa cómo se agudiza o aplana.
Entrega: una figura con tres distribuciones. Pista: aplica $z/T$ antes del softmax.
Los logits reales del modelo
Haz un forward de Qwen3-0.6B sobre un prompt, extrae los logits de la última posición y muestra los 10 tokens más probables con su probabilidad.
Entrega: top-10 (token decodificado, prob). Pista: model(**inputs).logits[0, -1] → softmax → topk(10).
Instrumenta un forward completo con KV cache
Para un prompt fijo: (1) extrae los logits de la última posición y grafica la distribución
del siguiente token para T ∈ {0.0, 0.7, 1.0} y top_p ∈ {1.0, 0.9},
lado a lado. (2) Implementa a mano un KV cache y demuestra que la generación es
idéntica a la ruta sin caché, midiendo la latencia por token frente a la longitud de
secuencia (mostrando el paso de $O(t^2)$ a $O(t)$).
Entrega: una figura con las 4 distribuciones de muestreo + un gráfico latencia-vs-longitud. Pista: usa use_cache=True y guarda past_key_values entre pasos; compara salida token a token con use_cache=False.
- Un LLM predice el siguiente token: tokens → logits → softmax → distribución.
- La atención causal deja que cada posición mire solo hacia atrás (máscara $-\infty$).
- El entrenamiento mueve los logits;
temperature/top_p/top_ksolo eligen entre ellos. - El KV cache vuelve la generación lineal por token, a costa de memoria que crece con el contexto.
- Pesos ≠ activaciones ≠ contexto. Fine-tuning toca pesos; RAG toca contexto.
- El mismo ciclo corre on-device; en Rust lo mueven
candleytokenizers.