File size: 4,118 Bytes
b8dfa81
 
 
44980a4
 
 
 
 
 
b8dfa81
 
 
 
 
 
 
 
 
 
 
 
 
 
44980a4
 
b8dfa81
44980a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8dfa81
 
 
 
 
 
 
 
44980a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8dfa81
44980a4
 
 
b8dfa81
44980a4
b8dfa81
44980a4
b8dfa81
44980a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8dfa81
 
 
 
 
 
 
 
 
 
44980a4
b8dfa81
 
 
 
 
 
 
 
 
 
 
 
 
 
44980a4
 
 
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
"""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()  # type: ignore[misc, assignment]

BASE_MODEL = "Qwen/Qwen2.5-7B-Instruct"
ADAPTER = "kabesaml/robe-iniesta-lora"
MODEL_PARAMS_B = 7  # well under the 32B hackathon cap

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