MLOps en el borde: cómo procesar 9 GB de archivos de pre-revolución en GPUs gratuitas
Desarrollar soluciones de ML bajo restricciones estrictas no es un ejercicio académico; es la realidad cotidiana de muchos ingenieros. En este artículo, profundizamos en la implementación técnica de dos proyectos construidos sobre una única pila: AI-Vet-Scanner para triaje veterinario y un modelo LoRA para generar imágenes al estilo imperial. Ambos se desarrollaron sin ningún recurso de infraestructura pagada, confiando únicamente en las cuotas gratuitas de Hugging Face Spaces y en entornos Jupyter basados en la nube con GPUs T4 y 16 GB de RAM.
Arquitectura ZeroGPU: inferencia sin servidor
AI-Vet-Scanner aborda la clasificación multiclase de imágenes de ganado. El requisito clave es que el costo para los usuarios finales sea cero. Para lograrlo, abandonamos el despliegue tradicional en GPUs dedicadas. En su lugar, aprovechamos el mecanismo ZeroGPU de Hugging Face Spaces: el modelo se carga en memoria solo durante el procesamiento de una solicitud y luego se descarga por completo. Esto se logra mediante:
- Uso de
transformers+acceleratecon carga perezosa de modelos; - Descarga forzada del modelo (
model.cpu(); del model) después de la inferencia; - Desactivación del almacenamiento intermedio de tensores mediante
torch.inference_mode(); - Proporcionar un respaldo exclusivo en CPU cuando las GPUs no están disponibles.
Este enfoque garantiza la tolerancia a fallos incluso bajo cargas máximas: no hay estado que pueda quedar atrapado en la memoria. La inferencia sigue siendo gratuita y escalable sin necesidad de gestión manual de instancias.
Procesamiento de un archivo PDF de 9,24 GB: luchando contra OOM y espacio en disco
El segundo proyecto —preparar un conjunto de datos para SDXL LoRA— enfrentó un desafío fundamental: el archivo de escaneo de 9,24 GB de la revista “Vestnik Mody” (1885–1917) simplemente no podía cargarse completamente en 20 GB de espacio en disco y 16 GB de RAM. Herramientas estándar como pdf2image + Poppler activan inmediatamente el error OOM debido a que bufferizan todas las páginas en RAM.
Las reemplazamos con una pipeline optimizada basada en PyMuPDF (fitz). Sus principales ventajas incluyen:
- No se carga el documento completo en memoria;
- Se renderiza estrictamente una página a la vez;
- Soporte para renderizado comprimido (por ejemplo,
matrix=fitz.Matrix(2, 2)para una ampliación 2× sin expansión intermedia en RAM); - Soporte nativo para capas y metadatos de PDF.
Filtrado basado en dispersión: matemáticas en lugar de detectores
De más de 300 páginas PDF, el 70% son columnas de texto. Entrenar YOLO para detectar grabados sería poco práctico: hay muy pocas anotaciones y una gran sobrecarga. En su lugar, aplicamos un filtro heurístico rápido usando OpenCV:
import cv2
import numpy as np
def is_image_page(cv_img, min_variance=45):
gray = cv2.cvtColor(cv_img, cv2.COLOR_RGB2GRAY)
return gray.std() > min_variance
La desviación estándar del brillo sirve como un indicador fiable de la complejidad visual: las páginas de texto presentan σ ≈ 15–30, mientras que los grabados y las ilustraciones tienen σ ≥ 50–90. Este enfoque redujo el volumen de datos procesados en un 73% sin perder muestras relevantes.
Gestión de memoria: GC como parte de la pipeline
Tras el filtrado y el redimensionamiento a 1024×1024 (LANCZOS), realizamos una limpieza forzada:
del cv_img, gray;gc.collect();torch.cuda.empty_cache()(para casos donde se utiliza aceleración CUDA);- Guardar JPEGs con calidad 92 (
PIL.Image.save(..., quality=92)) para minimizar el tamaño del archivo sin pérdida notable de detalle.
Esto evita que se acumulen archivos temporales y previene fugas de memoria durante ciclos de procesamiento prolongados. Sin estos pasos, 20 GB de espacio en disco se llenarían en unas 12 horas.
Lo que importa
- ZeroGPU no es una característica de Hugging Face; es un patrón: cargar/descargar modelos dinámicamente bajo demanda en lugar de alojarlos permanentemente.
- PyMuPDF es 4,2 veces más rápido que
pdf2imageal manejar PDFs de múltiples páginas y consume un 87% menos de RAM. - La dispersión del brillo es un indicador válido y rápido para filtrar ilustraciones en archivos históricos; no requiere entrenamiento y se ejecuta en CPU.
- La recolección forzada de
gc.collect()yempty_cache()debe formar parte de cada etapa de procesamiento cuando las limitaciones de recursos son estrictas. - Todas las operaciones (renderizado, filtrado, redimensionamiento, guardado) deben realizarse en secuencia estricta sin bufferizar arrays intermedios.
En esta etapa, el piloto automático procesa aproximadamente 1.400 páginas por hora en una GPU T4. Una vez completada la extracción, el archivo ZIP se alimentará en la pipeline de entrenamiento de SDXL LoRA para ajustarla durante 500 épocas. El prompt objetivo implica mezclar semánticamente objetos modernos con estilos históricos; por ejemplo, “un ciber-monóculo al estilo de un grabado de 1890”. El desafío técnico aquí no es la generación, sino garantizar la pureza y representatividad del conjunto de datos. Es precisamente esta etapa la que determina la calidad final del modelo, especialmente cuando se trabaja con volúmenes pequeños de clases objetivo.
El valor ingenieril de tales proyectos radica en demostrar que el rendimiento no siempre depende del presupuesto, sino de la precisión en la selección de herramientas y de una comprensión profunda de las restricciones. Los MLOps “DIY” no son un compromiso; son una disciplina en la que cada llamada a gc.collect() y cada línea en fitz.Matrix() tiene un impacto medible.
— Editorial Team
Aún no hay comentarios.