File size: 7,445 Bytes
d3d8124
 
 
 
227fa34
de3a711
d3d8124
 
 
 
 
e43df9f
d3d8124
 
 
 
 
e43df9f
 
d3d8124
 
e43df9f
 
227fa34
d3d8124
227fa34
e43df9f
d3d8124
e43df9f
227fa34
e43df9f
d3d8124
 
 
e43df9f
d3d8124
e43df9f
 
227fa34
e43df9f
d3d8124
 
 
e43df9f
d3d8124
 
 
 
 
 
 
 
de3a711
d3d8124
 
227fa34
 
d3d8124
 
e43df9f
d3d8124
227fa34
e43df9f
 
227fa34
e43df9f
 
de3a711
227fa34
d3d8124
 
 
 
 
 
 
e43df9f
d3d8124
 
 
 
 
 
 
 
 
 
 
 
 
 
e43df9f
d3d8124
 
de3a711
e43df9f
227fa34
 
e43df9f
d3d8124
e43df9f
 
 
de3a711
e43df9f
 
de3a711
 
 
e43df9f
 
 
 
 
 
de3a711
e43df9f
 
 
de3a711
 
e43df9f
de3a711
 
d3d8124
 
e43df9f
de3a711
 
d3d8124
 
de3a711
d3d8124
e43df9f
d3d8124
de3a711
d3d8124
e43df9f
de3a711
d3d8124
 
e43df9f
de3a711
e43df9f
227fa34
d3d8124
227fa34
d3d8124
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
# 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" # Directorio donde se copiar谩n los archivos en el contenedor Modal

stub = modal.Stub(APP_NAME)

app_image = (
    modal.Image.debian_slim(python_version=PYTHON_VERSION)
    .pip_install_from_requirements(LOCAL_APP_DIR / "requirements.txt") # Lee desde tu requirements.txt
    .copy_mount( # Copia todos los archivos de la app
        modal.Mount.from_local_dir(LOCAL_APP_DIR, remote_path=REMOTE_APP_DIR)
    )
    .env({ # Configura variables de entorno dentro del contenedor
        "PYTHONPATH": REMOTE_APP_DIR, # Permite importar m贸dulos desde /app
        "HF_HOME": "/cache/huggingface",
        "HF_HUB_CACHE": "/cache/huggingface/hub",
        "TRANSFORMERS_CACHE": "/cache/huggingface/hub",
        "MPLCONFIGDIR": "/tmp/matplotlib_cache" # Evita warnings de matplotlib
    })
    .run_commands( # Comandos a ejecutar durante la construcci贸n de la imagen
        "apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/lists/*",
        "mkdir -p /cache/huggingface/hub /tmp/matplotlib_cache" # Crea directorios de cach茅
    )
)

# --- Funci贸n Modal para LLM (sin cambios significativos respecto a la respuesta completa anterior) ---
@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 
    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
        )
        
        model_context_window = getattr(model.config, 'max_position_embeddings', getattr(model.config, 'sliding_window', 4096))
        if model_context_window is None : model_context_window = 4096
        
        max_prompt_len = model_context_window - max_new_tokens_config - 50 
        if max_prompt_len <=0 : max_prompt_len = model_context_window // 2 

        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)
def serve_gradio_app_asgi():
    import gradio as gr
    
    # El PYTHONPATH ya est谩 configurado en la imagen, pero una verificaci贸n no da帽a.
    if REMOTE_APP_DIR not in sys.path:
        sys.path.insert(0, REMOTE_APP_DIR)
        print(f"INFO (modal_app.py @asgi): A帽adido {REMOTE_APP_DIR} a sys.path")
    
    # --- Neutralizaci贸n del decorador GPU de Spaces, DENTRO del contexto Modal ---
    # Esto asegura que cualquier importaci贸n de `gradio` o `decorators`
    # no cause problemas con el chequeo de `@spaces.GPU`.
    try:
        import decorators # Importa TU decorators.py
        class _GPUNeutralizerInModal:
            def __init__(self, *args, **kwargs): pass
            def __call__(self, func): return func
        
        # Sobrescribir la clase GPU y el decorador en el m贸dulo 'decorators'
        # para esta instancia del servidor ASGI.
        decorators.ActualSpacesGPU = _GPUNeutralizerInModal # Si decorators.py usa este nombre
        decorators._GPU_decorator_target = _GPUNeutralizerInModal # Si decorators.py usa este
        decorators.gpu_decorator = lambda duration=0: lambda func: func # Neutralizar el decorador
        print("INFO (modal_app.py @asgi): Decoradores GPU de 'spaces' neutralizados para el entorno Modal.")
    except ImportError:
        print("ADVERTENCIA (modal_app.py @asgi): M贸dulo 'decorators' no encontrado durante la neutralizaci贸n. Puede ser OK.")
    except Exception as e_neut_modal:
        print(f"ADVERTENCIA (modal_app.py @asgi): Error durante la neutralizaci贸n de decoradores en Modal: {e_neut_modal}")
    # --- Fin de la neutralizaci贸n ---

    # Importar los m贸dulos de la aplicaci贸n DESPU脡S de la neutralizaci贸n y config de path
    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
    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)

    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 @asgi): Runner de LLM Modal inyectado en el m贸dulo 'interface'.")

    gradio_ui_instance = create_interface(process_function_for_button=app_interface_module.process_and_plot)
    
    print("INFO (modal_app.py @asgi): Interfaz Gradio creada y lista para ser servida por Modal.")
    return gr.routes.App.create_app(gradio_ui_instance)

@stub.local_entrypoint()
def test_llm_local_entry():
    print("Probando la generaci贸n de LLM con Modal (local_entrypoint)...")
    if str(LOCAL_APP_DIR) not in sys.path:
        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:
        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()