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.
