404 lines
9.4 KiB
Markdown
404 lines
9.4 KiB
Markdown
|
|
# GRACE - Servicio de IA Cognitiva
|
||
|
|
|
||
|
|
**Versión:** 5.0
|
||
|
|
**Fecha:** 2024-12-24
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Estado Actual
|
||
|
|
|
||
|
|
**BLOQUEADO:** RunPod no inicia workers.
|
||
|
|
|
||
|
|
| Aspecto | Valor |
|
||
|
|
|---------|-------|
|
||
|
|
| Plataforma | RunPod Serverless |
|
||
|
|
| Endpoint ID | r00x4g3rrwkbyh |
|
||
|
|
| Balance | ~$72 USD |
|
||
|
|
| Workers activos | 0 |
|
||
|
|
| Estado | Inoperativo |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Descripción
|
||
|
|
|
||
|
|
GRACE es el servicio de extracción de información mediante IA. Procesa contenido multimedia para extraer datos estructurados.
|
||
|
|
|
||
|
|
```
|
||
|
|
Contenido Raw
|
||
|
|
(audio, imagen, video, documento)
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ GRACE (GPU) │
|
||
|
|
│ │
|
||
|
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||
|
|
│ │ ASR │ │ OCR │ │ TTS │ │
|
||
|
|
│ └─────────┘ └─────────┘ └─────────┘ │
|
||
|
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||
|
|
│ │ Face │ │Embeddings│ │ Avatar │ │
|
||
|
|
│ └─────────┘ └─────────┘ └─────────┘ │
|
||
|
|
│ │
|
||
|
|
│ + 12 módulos pendientes │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
Datos Estructurados
|
||
|
|
(JSON, vectores, metadatos)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Módulos Implementados (6 de 18)
|
||
|
|
|
||
|
|
### ASR - Automatic Speech Recognition
|
||
|
|
|
||
|
|
| Campo | Valor |
|
||
|
|
|-------|-------|
|
||
|
|
| Modelo | Whisper Large v3 |
|
||
|
|
| Input | Audio (mp3, wav, m4a) |
|
||
|
|
| Output | Texto + timestamps |
|
||
|
|
| GPU | A10G recomendado |
|
||
|
|
|
||
|
|
```python
|
||
|
|
@grace.module("asr")
|
||
|
|
def process_asr(audio_bytes: bytes) -> dict:
|
||
|
|
"""Transcribe audio a texto."""
|
||
|
|
model = whisper.load_model("large-v3")
|
||
|
|
result = model.transcribe(audio_bytes)
|
||
|
|
return {
|
||
|
|
"text": result["text"],
|
||
|
|
"segments": result["segments"],
|
||
|
|
"language": result["language"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### OCR - Optical Character Recognition
|
||
|
|
|
||
|
|
| Campo | Valor |
|
||
|
|
|-------|-------|
|
||
|
|
| Modelo | EasyOCR + Tesseract |
|
||
|
|
| Input | Imagen (jpg, png) |
|
||
|
|
| Output | Texto + bounding boxes |
|
||
|
|
| GPU | A10G recomendado |
|
||
|
|
|
||
|
|
```python
|
||
|
|
@grace.module("ocr")
|
||
|
|
def process_ocr(image_bytes: bytes) -> dict:
|
||
|
|
"""Extrae texto de imagen."""
|
||
|
|
reader = easyocr.Reader(['es', 'en'])
|
||
|
|
result = reader.readtext(image_bytes)
|
||
|
|
return {
|
||
|
|
"text": " ".join([r[1] for r in result]),
|
||
|
|
"boxes": [{"text": r[1], "bbox": r[0], "confidence": r[2]} for r in result]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### TTS - Text to Speech
|
||
|
|
|
||
|
|
| Campo | Valor |
|
||
|
|
|-------|-------|
|
||
|
|
| Modelo | Coqui TTS |
|
||
|
|
| Input | Texto |
|
||
|
|
| Output | Audio (wav) |
|
||
|
|
| GPU | A10G recomendado |
|
||
|
|
|
||
|
|
### Face - Reconocimiento Facial
|
||
|
|
|
||
|
|
| Campo | Valor |
|
||
|
|
|-------|-------|
|
||
|
|
| Modelo | InsightFace |
|
||
|
|
| Input | Imagen |
|
||
|
|
| Output | Embeddings faciales |
|
||
|
|
| GPU | A10G recomendado |
|
||
|
|
|
||
|
|
### Embeddings - Vectores Semánticos
|
||
|
|
|
||
|
|
| Campo | Valor |
|
||
|
|
|-------|-------|
|
||
|
|
| Modelo | Sentence Transformers |
|
||
|
|
| Input | Texto |
|
||
|
|
| Output | Vector 384/768 dims |
|
||
|
|
| GPU | A10G recomendado |
|
||
|
|
|
||
|
|
### Avatar - Generación de Avatares
|
||
|
|
|
||
|
|
| Campo | Valor |
|
||
|
|
|-------|-------|
|
||
|
|
| Modelo | StyleGAN |
|
||
|
|
| Input | Imagen facial |
|
||
|
|
| Output | Avatar estilizado |
|
||
|
|
| GPU | A10G recomendado |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Módulos Pendientes (12)
|
||
|
|
|
||
|
|
| Módulo | Descripción | Prioridad |
|
||
|
|
|--------|-------------|-----------|
|
||
|
|
| Document parsing | Extracción de PDFs | Alta |
|
||
|
|
| Image classification | Clasificación de imágenes | Media |
|
||
|
|
| Object detection | Detección de objetos | Media |
|
||
|
|
| Sentiment analysis | Análisis de sentimiento | Media |
|
||
|
|
| Named entity recognition | Extracción de entidades | Alta |
|
||
|
|
| Translation | Traducción de texto | Media |
|
||
|
|
| Summarization | Resumen de texto | Media |
|
||
|
|
| Question answering | Respuestas a preguntas | Baja |
|
||
|
|
| Code generation | Generación de código | Baja |
|
||
|
|
| Audio classification | Clasificación de audio | Baja |
|
||
|
|
| Video analysis | Análisis de video | Baja |
|
||
|
|
| Multimodal fusion | Fusión multimodal | Baja |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Código en R2
|
||
|
|
|
||
|
|
El código está listo y disponible:
|
||
|
|
|
||
|
|
```
|
||
|
|
s3://architect/gpu-services/
|
||
|
|
├── base/
|
||
|
|
│ └── bootstrap.sh # Script de inicialización
|
||
|
|
├── grace/
|
||
|
|
│ └── code/
|
||
|
|
│ └── handler.py # Handler principal
|
||
|
|
├── penny/
|
||
|
|
│ └── code/
|
||
|
|
│ └── handler.py
|
||
|
|
└── factory/
|
||
|
|
└── code/
|
||
|
|
└── handler.py
|
||
|
|
```
|
||
|
|
|
||
|
|
### Descargar Código
|
||
|
|
|
||
|
|
```bash
|
||
|
|
source /home/orchestrator/orchestrator/.env
|
||
|
|
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY"
|
||
|
|
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_KEY"
|
||
|
|
|
||
|
|
aws s3 sync s3://architect/gpu-services/grace/ ./grace/ \
|
||
|
|
--endpoint-url https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Handler RunPod
|
||
|
|
|
||
|
|
```python
|
||
|
|
import runpod
|
||
|
|
|
||
|
|
def handler(event):
|
||
|
|
"""
|
||
|
|
Handler principal de GRACE para RunPod Serverless.
|
||
|
|
|
||
|
|
Input:
|
||
|
|
{
|
||
|
|
"input": {
|
||
|
|
"module": "asr|ocr|tts|face|embeddings|avatar",
|
||
|
|
"data": "base64 encoded content",
|
||
|
|
"options": {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Output:
|
||
|
|
{
|
||
|
|
"output": { ... resultado del módulo ... },
|
||
|
|
"error": null
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
module = event["input"]["module"]
|
||
|
|
data = base64.b64decode(event["input"]["data"])
|
||
|
|
options = event["input"].get("options", {})
|
||
|
|
|
||
|
|
if module == "asr":
|
||
|
|
result = process_asr(data, **options)
|
||
|
|
elif module == "ocr":
|
||
|
|
result = process_ocr(data, **options)
|
||
|
|
elif module == "tts":
|
||
|
|
result = process_tts(data, **options)
|
||
|
|
elif module == "face":
|
||
|
|
result = process_face(data, **options)
|
||
|
|
elif module == "embeddings":
|
||
|
|
result = process_embeddings(data, **options)
|
||
|
|
elif module == "avatar":
|
||
|
|
result = process_avatar(data, **options)
|
||
|
|
else:
|
||
|
|
return {"error": f"Módulo desconocido: {module}"}
|
||
|
|
|
||
|
|
return {"output": result, "error": None}
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
return {"error": str(e)}
|
||
|
|
|
||
|
|
runpod.serverless.start({"handler": handler})
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Uso desde MASON
|
||
|
|
|
||
|
|
```python
|
||
|
|
import runpod
|
||
|
|
import base64
|
||
|
|
|
||
|
|
runpod.api_key = "..." # Desde Infisical
|
||
|
|
|
||
|
|
def call_grace(module: str, content: bytes, options: dict = None) -> dict:
|
||
|
|
"""Llama a GRACE para procesar contenido."""
|
||
|
|
|
||
|
|
job = runpod.run(
|
||
|
|
endpoint_id="r00x4g3rrwkbyh",
|
||
|
|
input={
|
||
|
|
"module": module,
|
||
|
|
"data": base64.b64encode(content).decode(),
|
||
|
|
"options": options or {}
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Polling hasta completar
|
||
|
|
while True:
|
||
|
|
status = runpod.status(job["id"])
|
||
|
|
if status["status"] == "COMPLETED":
|
||
|
|
return status["output"]
|
||
|
|
elif status["status"] == "FAILED":
|
||
|
|
raise Exception(status.get("error", "Job failed"))
|
||
|
|
time.sleep(1)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Problema Actual: Workers No Inician
|
||
|
|
|
||
|
|
### Incidente 2024-12-24
|
||
|
|
|
||
|
|
- **Síntoma:** 0 workers activos a pesar de jobs en cola
|
||
|
|
- **Balance:** $72+ (suficiente)
|
||
|
|
- **Configuración:** Correcta
|
||
|
|
- **Causa probable:** Problema de capacidad RunPod
|
||
|
|
|
||
|
|
### Intentos de Diagnóstico
|
||
|
|
|
||
|
|
1. Verificado endpoint activo en dashboard
|
||
|
|
2. Verificado balance suficiente
|
||
|
|
3. Verificado configuración de scaling
|
||
|
|
4. Jobs quedan en estado "IN_QUEUE" indefinidamente
|
||
|
|
|
||
|
|
### Log de Errores
|
||
|
|
|
||
|
|
```
|
||
|
|
No hay logs disponibles - workers nunca inician
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Alternativas Evaluadas
|
||
|
|
|
||
|
|
### 1. Modal (Recomendado)
|
||
|
|
|
||
|
|
```python
|
||
|
|
import modal
|
||
|
|
|
||
|
|
stub = modal.Stub("grace")
|
||
|
|
image = modal.Image.debian_slim().pip_install("whisper", "easyocr")
|
||
|
|
|
||
|
|
@stub.function(gpu="A10G", image=image)
|
||
|
|
def process_asr(audio_bytes: bytes) -> dict:
|
||
|
|
import whisper
|
||
|
|
model = whisper.load_model("large-v3")
|
||
|
|
result = model.transcribe(audio_bytes)
|
||
|
|
return {"text": result["text"]}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Serverless real
|
||
|
|
- Buen DX (developer experience)
|
||
|
|
- Python-native
|
||
|
|
- Cold start rápido
|
||
|
|
|
||
|
|
**Contras:**
|
||
|
|
- Menos GPUs disponibles que RunPod
|
||
|
|
- Pricing puede ser mayor
|
||
|
|
|
||
|
|
### 2. Replicate
|
||
|
|
|
||
|
|
```python
|
||
|
|
import replicate
|
||
|
|
|
||
|
|
output = replicate.run(
|
||
|
|
"openai/whisper:large-v3",
|
||
|
|
input={"audio": audio_url}
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Modelos pre-entrenados
|
||
|
|
- API simple
|
||
|
|
- Sin gestión de infraestructura
|
||
|
|
|
||
|
|
**Contras:**
|
||
|
|
- Menos control
|
||
|
|
- Más caro a escala
|
||
|
|
- Dependencia de modelos de terceros
|
||
|
|
|
||
|
|
### 3. Lambda Labs
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Hardware dedicado
|
||
|
|
- GPUs disponibles
|
||
|
|
|
||
|
|
**Contras:**
|
||
|
|
- Menos flexible
|
||
|
|
- Reserva manual
|
||
|
|
- No serverless
|
||
|
|
|
||
|
|
### 4. Self-Hosted
|
||
|
|
|
||
|
|
**Pros:**
|
||
|
|
- Control total
|
||
|
|
- Sin dependencias externas
|
||
|
|
- Costo fijo a largo plazo
|
||
|
|
|
||
|
|
**Contras:**
|
||
|
|
- CapEx alto
|
||
|
|
- Mantenimiento
|
||
|
|
- Requiere expertise
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plan de Migración
|
||
|
|
|
||
|
|
### Fase 1: Evaluación
|
||
|
|
|
||
|
|
- [ ] Crear cuenta Modal
|
||
|
|
- [ ] Portar módulo ASR a Modal
|
||
|
|
- [ ] Comparar latencia con RunPod (cuando funcione)
|
||
|
|
- [ ] Comparar costos
|
||
|
|
|
||
|
|
### Fase 2: Migración
|
||
|
|
|
||
|
|
- [ ] Portar 6 handlers a Modal
|
||
|
|
- [ ] Actualizar endpoints en MASON
|
||
|
|
- [ ] Actualizar documentación
|
||
|
|
- [ ] Testing end-to-end
|
||
|
|
|
||
|
|
### Fase 3: Producción
|
||
|
|
|
||
|
|
- [ ] Desplegar en Modal
|
||
|
|
- [ ] Monitorear costos y performance
|
||
|
|
- [ ] Deprecar RunPod endpoints
|
||
|
|
- [ ] Cancelar cuenta RunPod
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Principio Fundamental
|
||
|
|
|
||
|
|
**GRACE nunca modifica datos.**
|
||
|
|
|
||
|
|
GRACE es read-only: extrae información pero no puede modificar el contenido original ni los datos del sistema. Las extracciones se almacenan como metadatos asociados al contenido original.
|
||
|
|
|
||
|
|
```
|
||
|
|
Contenido Original ────► GRACE ────► Metadatos Extraídos
|
||
|
|
(inmutable) (nuevos datos)
|
||
|
|
```
|