| """Build Small hackathon Space — Robe Iniesta persona chat (Qwen 7B + LoRA, ≤32B).""" |
|
|
| from __future__ import annotations |
|
|
| import gradio as gr |
| import torch |
| from peft import PeftModel |
| from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig |
|
|
| try: |
| import spaces |
| except ImportError: |
|
|
| class _SpacesFallback: |
| @staticmethod |
| def GPU(*args, **kwargs): |
| def decorator(fn): |
| return fn |
|
|
| return decorator |
|
|
| spaces = _SpacesFallback() |
|
|
| BASE_MODEL = "Qwen/Qwen2.5-7B-Instruct" |
| ADAPTER = "kabesaml/robe-iniesta-lora" |
| MODEL_PARAMS_B = 7 |
|
|
| SYSTEM_PROMPT = """\ |
| Eres una simulación del estilo conversacional de Robe Iniesta, cantante de Extremoduro \ |
| y artista en solitario, nacido en Plasencia (Extremadura) en 1963. |
| Hablas con franqueza en español coloquial con acento y giros extremeños. |
| Eres directo e irreverente. Una respuesta de dos o tres frases es mejor que un discurso. |
| Usas muletillas naturales: "¿no?", "joder", "tío", "macho", "la verdad es que...". |
| No eres un asistente. No das listas ni explicaciones académicas. |
| |
| No inventes fechas, discos, colaboradores ni anécdotas. Si no lo recuerdas, di \ |
| "no me acuerdo" o "ni idea". No finjas ser el Robe real — eres una simulación basada \ |
| en entrevistas públicas.""" |
|
|
| GENERATION_KWARGS = { |
| "max_new_tokens": 120, |
| "temperature": 0.6, |
| "top_p": 0.85, |
| "repetition_penalty": 1.2, |
| "do_sample": True, |
| } |
|
|
| MAX_HISTORY_TURNS = 6 |
|
|
| _model = None |
| _tokenizer = None |
|
|
|
|
| def _load_model(): |
| global _model, _tokenizer |
| if _model is not None: |
| return _model, _tokenizer |
|
|
| bnb = BitsAndBytesConfig( |
| load_in_4bit=True, |
| bnb_4bit_compute_dtype=torch.bfloat16, |
| bnb_4bit_quant_type="nf4", |
| bnb_4bit_use_double_quant=True, |
| ) |
| tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL) |
| base = AutoModelForCausalLM.from_pretrained( |
| BASE_MODEL, |
| quantization_config=bnb, |
| torch_dtype=torch.bfloat16, |
| device_map="auto", |
| attn_implementation="sdpa", |
| ) |
| model = PeftModel.from_pretrained(base, ADAPTER) |
| model.eval() |
| _model, _tokenizer = model, tokenizer |
| return model, tokenizer |
|
|
|
|
| @spaces.GPU(duration=120) |
| def chat(message: str, history: list[tuple[str, str]]) -> str: |
| model, tokenizer = _load_model() |
|
|
| messages = [{"role": "system", "content": SYSTEM_PROMPT}] |
| trimmed = history[-MAX_HISTORY_TURNS:] |
| for user, assistant in trimmed: |
| messages.append({"role": "user", "content": user}) |
| messages.append({"role": "assistant", "content": assistant}) |
| messages.append({"role": "user", "content": message}) |
|
|
| text = tokenizer.apply_chat_template( |
| messages, tokenize=False, add_generation_prompt=True |
| ) |
| inputs = tokenizer(text, return_tensors="pt").to(model.device) |
| input_len = inputs["input_ids"].shape[1] |
|
|
| with torch.no_grad(): |
| output_ids = model.generate( |
| **inputs, |
| pad_token_id=tokenizer.eos_token_id, |
| **GENERATION_KWARGS, |
| ) |
|
|
| new_ids = output_ids[0][input_len:] |
| return tokenizer.decode(new_ids, skip_special_tokens=True).strip() |
|
|
|
|
| def _model_info() -> str: |
| return ( |
| f"**Model:** `{BASE_MODEL}` + LoRA `{ADAPTER}` " |
| f"({MODEL_PARAMS_B}B base, ≤32B hackathon cap) · **Hardware:** ZeroGPU" |
| ) |
|
|
|
|
| with gr.Blocks(title="Robe — simulación de estilo") as demo: |
| gr.Markdown( |
| "# 🎸 Robe — simulación de estilo\n\n" |
| "⚠️ **Proyecto fan.** No es Robe Iniesta. " |
| "Simulación entrenada con entrevistas públicas. Puede inventar hechos.\n\n" |
| + _model_info() |
| ) |
| gr.ChatInterface( |
| fn=chat, |
| examples=[ |
| "¿Qué significa Extremoduro para ti?", |
| "¿Cómo escribes una canción?", |
| "¿Qué opinas de la industria musical?", |
| ], |
| retry_btn=None, |
| undo_btn="Deshacer", |
| clear_btn="Nueva conversación", |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|