Desarreglo y perfil
Type: Build
Language:Python
Prerequisites: Lesson 1 (Dev Environment), basic PyTorch familiarity
Time: ~60 minutes
Objetivos de aprendizaje
- Utilice condicional
breakpoint()ydebug_printpara inspeccionar las formas, los tipos y los valores de tensor en medio del entrenamiento - Perfil de los bucles de entrenamiento con
cProfile¿ Qué ?line_profiler, ytracemallocpara encontrar cuellos de botella - Detectar errores comunes de IA: desajustes de forma, pérdida de NaN, fuga de datos y tensores de dispositivo equivocado
- Configure TensorBoard para visualizar curvas de pérdida, histogramas de peso y distribuciones de gradientes
El problema
El código de IA falla de manera diferente al código normal. Una aplicación web se estrella con un rastro de pila. Un bucle de entrenamiento mal configurado se ejecuta durante 8 horas, quema $ 200 en tiempo de GPU, y produce un modelo que predice la media de cada entrada. El código nunca cometió error. El error fue un tensor en el dispositivo equivocado, un olvidado..detach(), o etiquetas que se filtran en las características.
Necesitas herramientas de depuración que capten estos fallos silenciosos antes de que pierdan tu tiempo y computación.
El concepto
La debugging de IA funciona en tres niveles:
graph TD
L3["3. Training Dynamics<br/>Loss curves, gradient norms, activations"] --> L2
L2["2. Tensor Operations<br/>Shapes, dtypes, devices, NaN/Inf values"] --> L1
L1["1. Standard Python<br/>Breakpoints, logging, profiling, memory"]La mayoría de la gente salta directamente al nivel 3 (mirando TensorBoard). Pero el 80% de los errores de IA viven en los niveles 1 y 2.
Construye el mismo
Parte 1: Descargar los errores de impresión (sí, funciona)
Para el código tensor, una declaración de impresión dirigida es mejor que pasar por un depurador porque necesitas ver formas, tipos y rangos de valores a la vez.
pythondef debug_print(name, tensor):
print(f"{name}: shape={tensor.shape}, dtype={tensor.dtype}, "
f"device={tensor.device}, "
f"min={tensor.min().item():.4f}, max={tensor.max().item():.4f}, "
f"mean={tensor.mean().item():.4f}, "
f"has_nan={tensor.isnan().any().item()}")Llame esto después de cada operación sospechosa.
Parte 2: Descomposición de Python (pdb y punto de ruptura)
El depurador incorporado está subestimado por el trabajo de IA.breakpoint()En su bucle de entrenamiento e inspeccionar los tensores de forma interactiva.
pythondef training_step(model, batch, criterion, optimizer):
inputs, labels = batch
outputs = model(inputs)
loss = criterion(outputs, labels)
if loss.item() > 100 or torch.isnan(loss):
breakpoint()
loss.backward()
optimizer.step()Cuando el desembolso te deja entrar, comandos útiles:
p outputs.shapepara comprobar las formasp loss.item()para ver el valor de pérdidap torch.isnan(outputs).sum()para contar las NaNp model.fc1.weight.gradpara comprobar los gradientescpara continuar,qdejar de fumar
Esto es depuración condicional, sólo se detiene cuando algo parece mal para una carrera de entrenamiento de 10.000 pasos, eso importa.
Parte 3: registro de Python
Sustituye las declaraciones de impresión con registro cuando el depuración va más allá de una verificación rápida.
pythonimport logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("training.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
logger.info("Starting training: lr=%.4f, batch_size=%d", lr, batch_size)
logger.warning("Loss spike detected: %.4f at step %d", loss.item(), step)
logger.error("NaN loss at step %d, stopping", step)Cuando una carrera de entrenamiento falla a las 3 AM, se quiere un archivo de registro, no una salida terminal que se desplace fuera de la pantalla.
Parte 4: Secciones de código de tiempo
Saber dónde va el tiempo es el primer paso para la optimización.
pythonimport time
class Timer:
def __init__(self, name=""):
self.name = name
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, *args):
elapsed = time.perf_counter() - self.start
print(f"[{self.name}] {elapsed:.4f}s")
with Timer("data loading"):
batch = next(dataloader_iter)
with Timer("forward pass"):
outputs = model(batch)
with Timer("backward pass"):
loss.backward()El resultado común es que la carga de datos toma el 60% del tiempo de formación.num_workers > 0en su DataLoader, no una GPU más rápida.
Parte 5: cProfil y line_profiiler
Cuando necesite más que temporizadores manuales:
bashpython -m cProfile -s cumtime train.pyEsto muestra cada llamada de función ordenada por tiempo acumulado.
bashpip install line_profilerpython@profile
def train_step(model, data, target):
output = model(data)
loss = F.cross_entropy(output, target)
loss.backward()
return loss
# Run with: kernprof -l -v train.pyParte 6: Profiles de memoria
#### Memoria de CPU con tracemalloc
pythonimport tracemalloc
tracemalloc.start()
# your code here
model = build_model()
data = load_dataset()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)#### CPU memoria con memoria_profiler
bashpip install memory_profilerpythonfrom memory_profiler import profile
@profile
def load_data():
raw = read_csv("data.csv") # watch memory jump here
processed = preprocess(raw) # and here
return processedCorra conpython -m memory_profiler your_script.pypara ver el uso de memoria línea por línea.
#### Memoria de GPU con PyTorch
pythonimport torch
if torch.cuda.is_available():
print(torch.cuda.memory_summary())
print(f"Allocated: {torch.cuda.memory_allocated() / 1e9:.2f} GB")
print(f"Cached: {torch.cuda.memory_reserved() / 1e9:.2f} GB")Cuando pulsamos OOM (Desde memoria):
- Reducir el tamaño del lote (lo primero que se intenta, siempre)
- Usar
torch.cuda.empty_cache()para liberar la memoria almacenada en caché - Usar
del tensorseguido portorch.cuda.empty_cache()para grandes intermediarios - Utilice una precisión mixta (
torch.cuda.amp) para reducir a la mitad el uso de memoria - Utilice el control de gradientes para modelos muy profundos
Parte 7: Los insectos comunes de IA y cómo atraparlos
#### Desajuste de forma
El problema más frecuente es que el tensor tiene forma.[batch, features]cuando el modelo espera [batch, channels, height, width]¿ Qué ?
pythondef check_shapes(model, sample_input):
print(f"Input: {sample_input.shape}")
hooks = []
def make_hook(name):
def hook(module, inp, out):
in_shape = inp[0].shape if isinstance(inp, tuple) else inp.shape
out_shape = out.shape if hasattr(out, "shape") else type(out)
print(f" {name}: {in_shape} -> {out_shape}")
return hook
for name, module in model.named_modules():
hooks.append(module.register_forward_hook(make_hook(name)))
with torch.no_grad():
model(sample_input)
for h in hooks:
h.remove()Ejecutar esto una vez con un lote de muestras.
#### Pérdida de
La pérdida de NaN significa algo explotado.
- Taxa de aprendizaje demasiado alta
- División por cero en pérdidas de aduana
- Registro de cero o número negativo
- Gradientes explosivos en las RNN
pythondef detect_nan(model, loss, step):
if torch.isnan(loss):
print(f"NaN loss at step {step}")
for name, param in model.named_parameters():
if param.grad is not None:
if torch.isnan(param.grad).any():
print(f" NaN gradient in {name}")
if torch.isinf(param.grad).any():
print(f" Inf gradient in {name}")
return True
return False#### Fugas de datos
Su modelo tiene una precisión del 99% en el set de pruebas.
pythondef check_data_leakage(train_set, test_set, id_column="id"):
train_ids = set(train_set[id_column].tolist())
test_ids = set(test_set[id_column].tolist())
overlap = train_ids & test_ids
if overlap:
print(f"DATA LEAKAGE: {len(overlap)} samples in both train and test")
return True
return FalseTambién compruebe la fuga temporal: usando datos futuros para predecir el pasado.
#### Dispositivo equivocado
Los tensores en diferentes dispositivos (CPU vs GPU) causan errores de tiempo de ejecución. Pero a veces un tensor permanece silenciosamente en la CPU mientras todo lo demás está en la GPU, y el entrenamiento solo se ejecuta lentamente.
pythondef check_devices(model, *tensors):
model_device = next(model.parameters()).device
print(f"Model device: {model_device}")
for i, t in enumerate(tensors):
if t.device != model_device:
print(f" WARNING: tensor {i} on {t.device}, model on {model_device}")Parte 8: Fundamentos de la tabla de tensores
TensorBoard te muestra lo que está sucediendo dentro del entrenamiento con el tiempo.
bashpip install tensorboardpythonfrom torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("runs/experiment_1")
for step in range(num_steps):
loss = train_step(model, batch)
writer.add_scalar("loss/train", loss.item(), step)
writer.add_scalar("lr", optimizer.param_groups[0]["lr"], step)
if step % 100 == 0:
for name, param in model.named_parameters():
writer.add_histogram(f"weights/{name}", param, step)
if param.grad is not None:
writer.add_histogram(f"grads/{name}", param.grad, step)
writer.close()Lanza el juego:
bashtensorboard --logdir=runsQué buscar:
- Loss not decreasing: Taxa de aprendizaje demasiado baja o problema de arquitectura de modelo
- Loss oscillating wildly: Taxa de aprendizaje demasiado alta
- Loss goes to NaN: Inestabilidad numérica (véase la sección NaN anterior)
- Train loss decreasing, val loss increasing: Superajuste
- Weight histograms collapsing to zero: Gradientes que se desvanecen
- Gradient histograms exploding: Necesita recorte de gradiente
Parte 9: Descargador de código VS
Para el depuración interactiva, configure el código VS con un launch.json¿Qué es esto ?
json{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Training",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
}
]
}Establezca puntos de ruptura haciendo clic en la barranca. Utilice el panel de variables para inspeccionar las propiedades del tensor. La consola de descomposición le permite ejecutar expresiones de Python arbitrarias en medio de la ejecución.
Útil para pasar por las líneas de procesamiento previo de datos donde quieres ver cada transformación.
Usalo
Aquí está el flujo de trabajo de depuración que capta la mayoría de los errores de IA:
- Before training- ¿ Qué ?
check_shapesCon un lote de muestra, comprobar que las dimensiones de entrada y salida coinciden con las expectativas. - First 10 stepsUso:
debug_printConfirmar que nada es NaN y los valores están en rangos razonables. - During training: pérdida de registro, tasa de aprendizaje y normas de gradiente. Utilice TensorBoard para la visualización.
- When something breaks- ¡ No !
breakpoint()Inspeccionar los tensores de forma interactiva. - For performanceTiempo de carga de datos frente a paso hacia adelante frente a paso hacia atrás. memoria de perfil si estás cerca de OOM.
Envío
Ejecutar el guión de depuración del kit de herramientas:
bashpython phases/00-setup-and-tooling/12-debugging-and-profiling/code/debug_tools.py¿ Qué ?outputs/prompt-debug-ai-code.mdpara una llamada que ayuda a diagnosticar errores específicos de IA.
Los ejercicios
- - ¿ Qué ?
debug_tools.pyModifique el modelo de maniobra para introducir un NaN (sentido: dividir por cero en el pase hacia adelante) y vea que el detector lo capte. - Perfila un ciclo de entrenamiento con
cProfiley identificar la función más lenta. - Usar
tracemallocpara encontrar qué línea en su línea de carga de datos asigna la mayor cantidad de memoria. - Configure TensorBoard para una simple carrera de entrenamiento y identifique si el modelo está sobreajustado.
- Usar
breakpoint()En el interior de un bucle de entrenamiento.
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.