Anatomía del bucle de entrenamiento en PyTorch: cada línea y sus errores

Fuentes: What each line does and what breaks if you move it

Construir un bucle de entrenamiento en PyTorch parece sencillo, pero colocar cada instrucción en el orden correcto resulta sorprendentemente frágil: los entrenamientos fallan al converger, arrojan resultados incorrectos o consumen memoria excesiva cuando una línea está mal ubicada. Este artículo desglosa, una por una, todas las operaciones del bucle y enumera los errores silenciosos más habituales que conviene memorizar.

El recorrido empieza por el pipeline de datos: la clase Dataset, que implementa len y getitem, y el DataLoader, que la envuelve para producir lotes. Se explican parámetros clave como batch_size, shuffle, num_workers (procesos en segundo plano que prefetchan lotes en paralelo con el cómputo en GPU), pin_memory (memoria fija del host para transferencias DMA más rápidas a CUDA), persistent_workers (evita el coste de re-fork entre épocas) y drop_last (descarta el último lote incompleto para estabilizar BatchNorm). También se discute el tamaño de lote como forma de regularización implícita y la conveniencia de alinearlo a múltiplos de 8 o 16 para coincidir con los tiles de los tensor cores.

A continuación se aborda el movimiento a dispositivo con .to(device), aclarando que para tensores no es in-place, y por qué hay que llamarlo antes de instanciar el optimizador para que las referencias a los parámetros no apunten a tensores descartados. Después se detalla la gestión de semillas para reproducibilidad: torch.manual_seed, torch.cuda.manual_seed_all, las semillas de NumPy y random, y los flags cudnn.deterministic y cudnn.benchmark, junto con la necesidad de pasar un generator y un worker_init_fn al DataLoader cuando hay varios workers.

Por último, la guía repasa las trampas más comunes en una tabla de referencia rápida: olvidar optimiser.zero_grad() antes de backward, colocar clip_grad_norm_ antes de backward o después de step, llamar a scheduler.step() dentro del bucle de lotes, omitir model.train() tras model.eval(), saltarse torch.no_grad() en validación (que dispara el consumo de memoria hasta OOM) y registrar loss en lugar de loss.item(), lo que mantiene vivo el grafo de cómputo. Distributed training, FSDP y configuraciones multi-GPU quedan fuera del alcance y se reservan para un ensayo futuro.