File size: 7,749 Bytes
d3d8124 227fa34 de3a711 d3d8124 227fa34 d3d8124 227fa34 d3d8124 227fa34 d3d8124 227fa34 d3d8124 de3a711 d3d8124 de3a711 227fa34 de3a711 d3d8124 de3a711 d3d8124 de3a711 d3d8124 227fa34 d3d8124 de3a711 d3d8124 227fa34 de3a711 227fa34 de3a711 227fa34 d3d8124 227fa34 d3d8124 de3a711 d3d8124 de3a711 227fa34 de3a711 d3d8124 de3a711 d3d8124 de3a711 d3d8124 de3a711 d3d8124 de3a711 d3d8124 de3a711 d3d8124 de3a711 d3d8124 de3a711 227fa34 d3d8124 227fa34 d3d8124 227fa34 de3a711 227fa34 de3a711 227fa34 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# modal_app.py
import modal
import sys
from pathlib import Path
import os
import traceback
# --- Configuraci贸n ---
PYTHON_VERSION = "3.10"
APP_NAME = "bioprocess-custom-eq-agent-modal"
LOCAL_APP_DIR = Path(__file__).parent
REMOTE_APP_DIR = "/app"
stub = modal.Stub(APP_NAME)
app_image = (
modal.Image.debian_slim(python_version=PYTHON_VERSION)
.pip_install_from_requirements(LOCAL_APP_DIR / "requirements.txt")
.copy_mount(
modal.Mount.from_local_dir(LOCAL_APP_DIR, remote_path=REMOTE_APP_DIR)
)
.env({
"PYTHONPATH": REMOTE_APP_DIR,
"HF_HOME": "/cache/huggingface",
"HF_HUB_CACHE": "/cache/huggingface/hub",
"TRANSFORMERS_CACHE": "/cache/huggingface/hub",
"MPLCONFIGDIR": "/tmp/matplotlib_cache"
})
.run_commands(
"apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/lists/*",
"mkdir -p /cache/huggingface/hub /tmp/matplotlib_cache"
)
)
# --- Funci贸n Modal para LLM (sin cambios respecto a la anterior respuesta completa) ---
@stub.function(
image=app_image, # Hereda la imagen base del stub si est谩 definida, o usa esta.
gpu="any",
secrets=[modal.Secret.from_name("huggingface-read-token", optional=True)],
timeout=600, # 10 minutos
volumes={"/cache/huggingface": modal.Volume.persisted(f"{APP_NAME}-hf-cache-vol")}
)
def generate_analysis_llm_modal_remote(prompt: str, model_path_config: str, max_new_tokens_config: int) -> str:
import torch # Importaciones pesadas dentro de la funci贸n Modal
from transformers import AutoTokenizer, AutoModelForCausalLM
hf_token = os.environ.get("HUGGING_FACE_TOKEN")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"LLM Modal Func: Usando dispositivo: {device}")
print(f"LLM Modal Func: Cargando modelo: {model_path_config} con token: {'S铆' if hf_token else 'No'}")
try:
tokenizer = AutoTokenizer.from_pretrained(model_path_config, cache_dir="/cache/huggingface/hub", token=hf_token, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_path_config,
torch_dtype="auto",
device_map="auto",
cache_dir="/cache/huggingface/hub",
token=hf_token,
trust_remote_code=True # Necesario para algunos modelos como Qwen
)
model_context_window = getattr(model.config, 'max_position_embeddings', getattr(model.config, 'sliding_window', 4096)) # Para Qwen2 sliding_window
if model_context_window is None : model_context_window = 4096 # Fallback
max_prompt_len = model_context_window - max_new_tokens_config - 50 # Buffer
if max_prompt_len <=0 : max_prompt_len = model_context_window // 2 # Si max_new_tokens es muy grande
inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=max_prompt_len).to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens_config,
eos_token_id=tokenizer.eos_token_id,
pad_token_id=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else tokenizer.eos_token_id,
do_sample=True,
temperature=0.6,
top_p=0.9,
)
input_length = inputs.input_ids.shape[1]
generated_ids = outputs[0][input_length:]
analysis = tokenizer.decode(generated_ids, skip_special_tokens=True)
print(f"LLM Modal Func: Longitud del an谩lisis generado: {len(analysis)} caracteres.")
return analysis.strip()
except Exception as e:
error_traceback = traceback.format_exc()
print(f"Error en generate_analysis_llm_modal_remote: {e}\n{error_traceback}")
return f"Error al generar an谩lisis con el modelo LLM: {str(e)}"
# --- Servidor Gradio ---
@stub.asgi_app(image=app_image) # Especificar la imagen para el endpoint ASGI tambi茅n
def serve_gradio_app_asgi():
import gradio as gr
# sys.path ya est谩 configurado por la imagen de Modal debido a .env({"PYTHONPATH": REMOTE_APP_DIR})
# y .copy_mount(... remote_path=REMOTE_APP_DIR)
# No obstante, una comprobaci贸n o inserci贸n expl铆cita no da帽a:
if REMOTE_APP_DIR not in sys.path:
sys.path.insert(0, REMOTE_APP_DIR)
print(f"INFO (modal_app.py): A帽adido {REMOTE_APP_DIR} a sys.path")
# --- Intento de Neutralizar el decorador problem谩tico ANTES de las importaciones de la app ---
# Esto es para evitar el error "No @spaces.GPU function detected" si Gradio lo busca.
try:
import decorators # Intenta importar TU decorators.py
class _GPUNeutralizerModal:
def __init__(self, *args, **kwargs): pass
def __call__(self, func): return func
if hasattr(decorators, 'GPU'):
decorators.GPU = _GPUNeutralizerModal
print("INFO (modal_app.py): 'decorators.GPU' neutralizado para el entorno Modal.")
if hasattr(decorators, 'gpu_decorator'):
decorators.gpu_decorator = lambda duration=0: lambda func: func
print("INFO (modal_app.py): 'decorators.gpu_decorator' neutralizado para el entorno Modal.")
except ImportError:
print("ADVERTENCIA (modal_app.py): M贸dulo 'decorators' no encontrado durante la neutralizaci贸n. Esto puede ser OK.")
except Exception as e_neut:
print(f"ADVERTENCIA (modal_app.py): Error durante la neutralizaci贸n de decoradores: {e_neut}")
# --- Fin de la neutralizaci贸n ---
# Importar los m贸dulos de la aplicaci贸n AHORA
from UI import create_interface
import interface as app_interface_module
from config import MODEL_PATH as cfg_MODEL_PATH, MAX_LENGTH as cfg_MAX_LENGTH
# Wrapper para llamar a la funci贸n Modal remota desde tu interface.py
def analysis_func_wrapper_for_interface_modal(prompt: str) -> str:
print("Gradio Backend (Modal): Llamando a generate_analysis_llm_modal_remote.remote...")
return generate_analysis_llm_modal_remote.remote(prompt, cfg_MODEL_PATH, cfg_MAX_LENGTH)
# Inyectar esta funci贸n wrapper en el m贸dulo `interface` que usa Gradio
app_interface_module.generate_analysis_from_modal = analysis_func_wrapper_for_interface_modal
app_interface_module.USE_MODAL_FOR_LLM_ANALYSIS = True
print("INFO (modal_app.py): Runner de LLM Modal inyectado en el m贸dulo 'interface'.")
# Crear la app Gradio, pas谩ndole la funci贸n de procesamiento real
gradio_ui_instance = create_interface(process_function_for_button=app_interface_module.process_and_plot)
print("INFO (modal_app.py): Interfaz Gradio creada y lista para ser servida.")
return gr.routes.App.create_app(gradio_ui_instance)
@stub.local_entrypoint()
def test_llm_local_entry(): # Renombrado para evitar conflicto con el `test_llm` de la respuesta anterior
print("Probando la generaci贸n de LLM con Modal (local_entrypoint)...")
if REMOTE_APP_DIR not in sys.path: # Asegurar path para pruebas locales tambi茅n
sys.path.insert(0, str(LOCAL_APP_DIR))
from config import MODEL_PATH, MAX_LENGTH
sample_prompt = "Explica brevemente el concepto de R cuadrado (R虏) en el ajuste de modelos."
try:
# Para ejecutar esto, necesitar铆as que el stub est茅 activo.
# `modal run modal_app.py test_llm_local_entry`
analysis = generate_analysis_llm_modal_remote.remote(sample_prompt, MODEL_PATH, MAX_LENGTH)
print("\nRespuesta del LLM:")
print(analysis)
except Exception as e:
print(f"Error durante test_llm_local_entry: {e}")
traceback.print_exc() |