Volver al inicio

MLOps en GPUs gratuitas: procesamiento de archivos PDF

El artículo cubre dos proyectos prácticos: cribado veterinario basado en CV y generación de imágenes al estilo del Imperio Ruso. El enfoque principal son soluciones técnicas para trabajar con recursos limitados: ZeroGPU, PyMuPDF, filtrado por varianza de brillo y gestión forzada de memoria.

MLOps sin presupuesto: cómo procesamos 9 GB de revistas pre-revolucionarias
Advertisement 728x90

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 + accelerate con 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.

Google AdInline article slot

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:

Google AdInline article slot
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.

Google AdInline article slot

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 pdf2image al 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() y empty_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

Advertisement 728x90

Leer después