Skip to content

English · Español

Lab 01 — GPTQ sobre una única capa Linear

Objetivo: implementar GPTQ para un Linear y mostrar que supera a RTN en INT4 por-grupo.

Tiempo estimado: 6–10 horas (es la combinación de matemáticas+código más difícil de la Fase 26).

Prerrequisito: lab 00 commiteado; theory/03 leído; sección GPTQ de src/miniquant/BLUEPRINT.md leída.


Lo que produces

Un directorio experiments/26-gptq-toy/ que contiene:

  • gptq_toy.py — script que cuantiza un Linear(768, 768) sintético tanto con RTN como con GPTQ, imprime el tensor de pérdida y la proxy de PPL en el peor caso por fila.
  • results.json — mediciones.
  • loss_curve.png — MSE de cuantización por índice de fila para RTN vs GPTQ.
  • manifest.json.
  • README.md — interpretación.

Commiteas src/miniquant/gptq.py implementando el algoritmo.

El kernel

Implementa GPTQ para una matriz de pesos W ∈ ℝ^(out × in) con una distribución de calibration X ∈ ℝ^(in × n).

Bosquejo del algoritmo (el archivo de teoría 03 tiene la matemática):

H = X @ X.T / n              # (in, in) Hessiana
H += eps * I                 # ridge para estabilidad, eps = 1e-2 * mean(diag(H))
Hinv_chol = cholesky(inverse(H))   # triangular superior L = chol(H^-1)

for row r in 0..out-1:
    w = W[r, :].clone()                # vector de longitud in
    err = zeros(in)
    for i in 0..in-1:
        q_i = round_to_grid(w[i], scale=s_per_group[r, i // group_size])
        delta_i = w[i] - dequant(q_i)
        err[i] = delta_i
        # actualizar columnas restantes i+1.. vía la fila de Cholesky
        for j in i+1..in-1:
            w[j] -= delta_i * Hinv_chol[i, j] / Hinv_chol[i, i]
        store q_i into Q[r, i]

El coeficiente del bucle interno es exactamente lo que te dio el archivo de teoría 03. Puedes organizar la implementación por bloques (procesar columnas en chunks de 128) por velocidad; la salida del algoritmo es idéntica.

TODOs

Bloque A — montaje sintético

  • W ~ N(0, 0.02²) aleatoria de shape (768, 768) — coincide con un peso típico de capa intermedia.
  • Calibration X ~ N(0, 1) de shape (768, 128) — 128 vectores aleatorios.
  • Grid objetivo: INT4 por-grupo, group size 64. (Escalas elegidas por grupo como max(|w|)/7 para INT4 simétrico.)
  • Calcula H = X @ X.T / 128 y H += eps * I.

Bloque B — implementa baseline RTN

  • Cuantización independiente por elemento usando las escalas por-grupo.
  • Calcula la pérdida tr((W - W_rtn).T @ H @ (W - W_rtn)). Esta es tu métrica escalar de calidad.

Bloque C — implementa GPTQ

  • Cholesky de H^-1. PyTorch: torch.linalg.cholesky(torch.linalg.inv(H)).
  • Bucle sobre filas (vectoriza dentro de cada fila a lo largo de columnas), aplica la actualización.
  • Cuantiza. Almacena como INT4 denso (puedes empacar dos-por-byte al final; para este lab basta con un tensor[int8] que almacene valores simétricos en [-7, 7] — el registro de almacenamiento es complemento a dos [-8, 7], pero la cuantización simétrica con scale = max(|w|)/7 solo emite códigos en [-7, 7]).

Bloque D — compara

  • Calcula la pérdida para ambos esquemas.
  • Pérdida por fila: ¿qué filas se beneficiaron más de GPTQ?
  • Calcula n_grid_changes: ¿cuántos pesos redondeó GPTQ a un punto de grid distinto al que habría redondeado RTN? (Habitualmente un porcentaje de un dígito.)

Bloque E — interpreta en README.md

Tres preguntas:

  1. ¿Cuál es la razón de pérdida GPTQ vs RTN? Espera loss_gptq / loss_rtn ∈ [0.3, 0.7] para este montaje. Si es > 0.9, tu Cholesky está mal o la dirección de actualización está al revés.
  2. ¿Qué filas se benefician más? Dibuja la pérdida por fila para ambos esquemas. Las filas con mayor pérdida en RTN deberían ver la mayor mejora absoluta. ¿Por qué?
  3. ¿Qué pasa si reemplazas X ~ N(0, 1) por X ~ Cauchy(0, 1)? (Distribución con muchos outliers.) Re-ejecuta y reporta. GPTQ debería beneficiarse más porque el acoplamiento fuera-de-diagonal de la Hessiana es más fuerte.

Condiciones de parada

Terminado cuando:

  1. src/miniquant/gptq.py implementa el algoritmo; los tests pasan.
  2. loss_gptq / loss_rtn < 0.7 en el montaje estándar.
  3. Los cinco archivos commiteados.
  4. README.md responde las tres preguntas.

Restricciones

  • Nada de librería GPTQ de referencia. Estás escribiendo el algoritmo. Existe el paquete auto-gptq; no lo importes.
  • Solo CPU. Cholesky en (768, 768) tarda menos de un segundo; el bucle de filas con actualizaciones explícitas es el cuello de botella. Cronométralo; apunta a < 30 segundos por capa.
  • Reproducibilidad: seed_everything(42). Entonces torch.linalg.cholesky es determinista para una entrada dada.

Trampas

  • Cholesky falla con "matrix not positive-definite". Tu ridge eps es demasiado pequeño. Prueba eps = 1e-1 * mean(diag(H)).
  • loss_gptq > loss_rtn. Bugs más comunes: (i) estás restando delta * Hinv_chol[j, i] en vez de Hinv_chol[i, j] (triángulo equivocado); (ii) tu redondeo a grid calcula delta como dequant(q) - w en vez de w - dequant(q); (iii) estás aplicando las actualizaciones después de cuantizar el resto de la fila, no antes.
  • La pérdida de GPTQ es exactamente igual a la de RTN. La actualización es silenciosamente un no-op porque delta redondea a cero (tu escala es demasiado gruesa). Sanity-check: las magnitudes de delta no son triviales.

Cuándo consultar solutions/

Tras cumplir todas las condiciones de parada. Referencia en solutions/01-gptq-toy-ref.md (apertura de fase) recorre la derivación paso a paso y muestra los números esperados dentro de tolerancia.


Siguiente lab: lab/02-quant-curve.md.