Spaces:
Running
Running
| """Inferencia local con el GGUF Qwen3-4B-Instruct mediante llama.cpp.""" | |
| from __future__ import annotations | |
| import os | |
| import threading | |
| import time | |
| from pathlib import Path | |
| from typing import Iterator | |
| from dotenv import load_dotenv | |
| from llama_cpp import Llama | |
| load_dotenv() | |
| DEFAULT_MODEL = "AlbertiTechnology/qwen3-4b-instruct-gguf" | |
| DEFAULT_MODEL_PATH = ( | |
| Path(__file__).resolve().parent | |
| / "MODELS" | |
| / "qwen3-4b-instruct-gguf" | |
| / "model.gguf" | |
| ) | |
| MODEL_ALIASES = { | |
| "Qwen/Qwen3-4B": DEFAULT_MODEL, | |
| "Qwen/Qwen3-4B-Instruct-2507": DEFAULT_MODEL, | |
| "Qwen/Qwen2.5-7B-Instruct": DEFAULT_MODEL, | |
| } | |
| _load_lock = threading.Lock() | |
| _generation_lock = threading.Lock() | |
| _model: Llama | None = None | |
| def local_model_path() -> Path: | |
| return Path(os.getenv("LOCAL_MODEL_PATH", str(DEFAULT_MODEL_PATH))).resolve() | |
| def _load_local_model() -> Llama: | |
| global _model | |
| if _model is not None: | |
| return _model | |
| with _load_lock: | |
| if _model is not None: | |
| return _model | |
| model_path = local_model_path() | |
| if not model_path.is_file(): | |
| raise RuntimeError(f"Local GGUF model was not found at {model_path}") | |
| print(f"Loading local GGUF model from {model_path}...", flush=True) | |
| _model = Llama( | |
| model_path=str(model_path), | |
| n_ctx=int(os.getenv("LOCAL_MODEL_CONTEXT_SIZE", "2048")), | |
| n_threads=int(os.getenv("LOCAL_MODEL_THREADS", "2")), | |
| n_threads_batch=int(os.getenv("LOCAL_MODEL_BATCH_THREADS", "2")), | |
| n_batch=int(os.getenv("LOCAL_MODEL_BATCH_SIZE", "256")), | |
| n_gpu_layers=int(os.getenv("LOCAL_MODEL_GPU_LAYERS", "0")), | |
| verbose=os.getenv("LLAMA_CPP_VERBOSE", "false").lower() == "true", | |
| ) | |
| print("Local GGUF model loaded.", flush=True) | |
| return _model | |
| def generate_response( | |
| text: str, | |
| model: str = DEFAULT_MODEL, | |
| max_new_tokens: int | None = None, | |
| ) -> str: | |
| """Genera una respuesta usando exclusivamente el GGUF local.""" | |
| return "".join(generate_response_stream(text, model, max_new_tokens)).strip() | |
| def generate_response_stream( | |
| text: str, | |
| model: str = DEFAULT_MODEL, | |
| max_new_tokens: int | None = None, | |
| ) -> Iterator[str]: | |
| """Genera la respuesta local y entrega cada fragmento apenas esta disponible.""" | |
| del model | |
| local_model = _load_local_model() | |
| token_limit = max_new_tokens or int( | |
| os.getenv("LOCAL_MODEL_MAX_NEW_TOKENS", "256") | |
| ) | |
| context_size = int(os.getenv("LOCAL_MODEL_CONTEXT_SIZE", "2048")) | |
| prompt_budget = max(256, context_size - token_limit - 192) | |
| prompt_tokens = local_model.tokenize( | |
| text.encode("utf-8"), | |
| add_bos=False, | |
| special=True, | |
| ) | |
| if len(prompt_tokens) > prompt_budget: | |
| marker = "\n\n[Contexto intermedio recortado por límite de tokens]\n\n" | |
| marker_tokens = local_model.tokenize( | |
| marker.encode("utf-8"), | |
| add_bos=False, | |
| special=True, | |
| ) | |
| available = max(1, prompt_budget - len(marker_tokens)) | |
| start_count = int(available * 0.6) | |
| end_count = available - start_count | |
| kept_tokens = ( | |
| prompt_tokens[:start_count] | |
| + marker_tokens | |
| + prompt_tokens[-end_count:] | |
| ) | |
| text = local_model.detokenize(kept_tokens).decode( | |
| "utf-8", errors="ignore" | |
| ) | |
| print( | |
| "Local prompt truncated | " | |
| f"original_tokens={len(prompt_tokens)} | kept_tokens={len(kept_tokens)} " | |
| f"| budget={prompt_budget}", | |
| flush=True, | |
| ) | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "Responde en español de forma directa y concisa. Usa el contexto " | |
| "recuperado de Chroma como fuente principal. Si el contexto no " | |
| "alcanza, dilo claramente. No muestres razonamiento interno." | |
| ), | |
| }, | |
| {"role": "user", "content": f"/no_think\n{text}"}, | |
| ] | |
| with _generation_lock: | |
| started_at = time.monotonic() | |
| first_chunk = True | |
| print( | |
| f"Local generation prompt submitted | max_tokens={token_limit}", | |
| flush=True, | |
| ) | |
| response = local_model.create_chat_completion( | |
| messages=messages, | |
| max_tokens=token_limit, | |
| temperature=0.7, | |
| top_p=0.8, | |
| top_k=20, | |
| repeat_penalty=1.05, | |
| stream=True, | |
| ) | |
| for chunk in response: | |
| delta = chunk["choices"][0].get("delta", {}) | |
| content = delta.get("content") or delta.get("reasoning_content") | |
| if content: | |
| if first_chunk: | |
| print( | |
| "Local model produced a chunk | " | |
| f"elapsed={time.monotonic() - started_at:.1f}s", | |
| flush=True, | |
| ) | |
| first_chunk = False | |
| yield str(content) | |
| if __name__ == "__main__": | |
| print(generate_response("Responde solamente: Qwen GGUF local OK")) | |