File size: 6,328 Bytes
d3d8124
 
 
 
227fa34
 
d3d8124
 
 
 
 
227fa34
d3d8124
 
 
 
 
 
 
 
 
 
 
227fa34
d3d8124
227fa34
 
d3d8124
227fa34
 
 
d3d8124
 
 
227fa34
d3d8124
 
227fa34
 
 
d3d8124
 
 
227fa34
d3d8124
 
 
 
 
 
 
 
 
 
 
227fa34
 
d3d8124
 
 
227fa34
 
 
 
 
 
 
 
d3d8124
 
 
 
 
 
 
 
227fa34
d3d8124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227fa34
d3d8124
 
 
227fa34
 
 
d3d8124
227fa34
 
d3d8124
 
227fa34
d3d8124
 
 
 
 
 
 
 
227fa34
 
 
d3d8124
227fa34
d3d8124
 
 
 
227fa34
 
 
 
 
d3d8124
227fa34
d3d8124
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
# modal_app.py
import modal
import sys
from pathlib import Path
import os
import traceback # Para imprimir traceback detallado

# --- 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) ---
@stub.function(
    image=app_image,
    gpu="any",
    secrets=[modal.Secret.from_name("huggingface-read-token", optional=True)],
    timeout=600,
    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 # Mover 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)
        model = AutoModelForCausalLM.from_pretrained(
            model_path_config,
            torch_dtype="auto", 
            device_map="auto",
            cache_dir="/cache/huggingface/hub",
            token=hf_token,
        )
        
        # Ajustar longitud de truncamiento para el prompt para dejar espacio a max_new_tokens
        # La longitud total (prompt + generado) no debe exceder el context window del modelo.
        # Asumamos un context window conservador de 4096 si no se conoce.
        model_context_window = getattr(model.config, 'max_position_embeddings', 4096)
        max_prompt_len = model_context_window - max_new_tokens_config - 50 # 50 tokens de buffer
        
        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()
def serve_gradio_app_asgi():
    # Estas importaciones ocurren DENTRO del contenedor Modal
    import gradio as gr
    # sys.path ya est谩 configurado por la imagen, pero por si acaso:
    if REMOTE_APP_DIR not in sys.path:
        sys.path.insert(0, REMOTE_APP_DIR)
    
    from UI import create_interface # De tu UI.py
    import interface as app_interface_module # El m贸dulo interface.py
    from config import MODEL_PATH as cfg_MODEL_PATH, MAX_LENGTH as cfg_MAX_LENGTH

    # Wrapper para llamar a la funci贸n Modal remota
    def analysis_func_wrapper_for_interface(prompt: str) -> str:
        print("Gradio Backend: 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`
    app_interface_module.generate_analysis_from_modal = analysis_func_wrapper_for_interface
    app_interface_module.USE_MODAL_FOR_LLM_ANALYSIS = True

    # Crear la app Gradio, pas谩ndole la funci贸n de procesamiento que ahora est谩 en app_interface_module
    # create_interface ahora toma la funci贸n de callback como argumento.
    gradio_ui = create_interface(process_function_for_button=app_interface_module.process_and_plot)
    
    return gr.routes.App.create_app(gradio_ui)

@stub.local_entrypoint()
def test_llm():
    print("Probando la generaci贸n de LLM con Modal (localmente)...")
    # Necesitas importar config para que MODEL_PATH y MAX_LENGTH est茅n definidos
    # Esto debe hacerse dentro de un contexto donde los m贸dulos de la app sean accesibles.
    # Es mejor llamar a la funci贸n stub desde aqu铆.
    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."
    # Ejecuta la funci贸n Modal directamente (no .remote() para prueba local de l贸gica)
    # Para probar la ejecuci贸n remota real, necesitar铆as `modal run modal_app.py test_llm`
    # o que test_llm llame a .remote().
    # Aqu铆 vamos a probar la llamada remota.
    try:
        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: {e}")
        traceback.print_exc()