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()