File size: 5,968 Bytes
2da4631
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import gradio as gr
import subprocess
from huggingface_hub import hf_hub_download

# 1. Install llama-cpp-python i runtime (inte via requirements.txt)
#    Viktigt: ta bort `llama-cpp-python` från requirements.txt,
#    annars försöker Spaces bygga från källkod och fastnar.
subprocess.run("pip install -q 'llama_cpp_python==0.3.15'", shell=True, check=False)
from llama_cpp import Llama


# 2. Ladda din GGUF-modell från Hugging Face
MODEL_REPO = "Jeppcode/ScalableLab2"
GGUF_FILENAME = "model-q4_k_m.gguf"  # eller "model-f16.gguf" om du vill ha fp16-varianten

print(f"Downloading GGUF model {MODEL_REPO}/{GGUF_FILENAME} ...")
model_path = hf_hub_download(
    repo_id=MODEL_REPO,
    filename=GGUF_FILENAME,
)

print("Initializing llama.cpp LLM ...")
llm = Llama(
    model_path=model_path,
    n_ctx=2048,      # kontextlängd
    n_threads=2,     # trådar (Spaces CPU är begränsad)
    n_batch=64,      # batchstorlek för generation
    use_mmap=True,
    use_mlock=False,
)


# 3. Några stil-lägen som "system prompts"
STYLE_SYSTEM_PROMPTS = {
    "Default": "You are a helpful, polite assistant.",
    "Short answer": (
        "You are a helpful assistant. Answer as concisely as possible, usually in 1–3 sentences."
    ),
    "Detailed explanation": (
        "You are a helpful teaching assistant. Give clear, structured and detailed explanations, "
        "often with bullet points or numbered steps when useful."
    ),
    "Step-by-step reasoning": (
        "You are a careful problem solver. Think step by step and explain your reasoning clearly "
        "before giving the final answer."
    ),
}


def _extract_text_from_content(content):
    """
    Gradio 6 ChatInterface använder 'messages'-format.
    content kan vara:
      - en sträng
      - en lista av blocks: [{"type": "text", "text": "..."} , ...]
    Vi konverterar det till en enkel sträng.
    """
    if isinstance(content, list):
        texts = []
        for block in content:
            if isinstance(block, dict) and block.get("type") == "text":
                texts.append(block.get("text", ""))
            else:
                texts.append(str(block))
        return "\n".join(t for t in texts if t)
    else:
        return str(content)


def build_prompt(message, history, style):
    """
    Bygger en enkel textprompt för llama.cpp baserat på:
      - vald stil (system prompt)
      - konversationshistorik
      - senaste user-meddelandet
    Vi använder ett simpelt format:
        System: ...
        Conversation:
        User: ...
        Assistant: ...
        ...
        User: <current message>
        Assistant:
    """
    system_prompt = STYLE_SYSTEM_PROMPTS.get(style, STYLE_SYSTEM_PROMPTS["Default"])

    prompt_parts = []
    prompt_parts.append(f"System: {system_prompt}\n")
    prompt_parts.append("Conversation:\n")

    # history är en lista av dicts: {"role": "...", "content": ...}
    for msg in history or []:
        role = msg.get("role")
        content = _extract_text_from_content(msg.get("content", ""))

        if not content:
            continue

        if role == "user":
            prompt_parts.append(f"User: {content}\n")
        elif role == "assistant":
            prompt_parts.append(f"Assistant: {content}\n")
        elif role == "system":
            prompt_parts.append(f"System (previous): {content}\n")

    # Nuvarande användarmeddelande
    prompt_parts.append(f"User: {message}\n")
    prompt_parts.append("Assistant:")

    full_prompt = "".join(prompt_parts)
    return full_prompt


def chat_fn(message, history, max_new_tokens, temperature, top_p, repetition_penalty, style):
    """
    Huvudfunktionen som Gradio ChatInterface anropar.
    - message: senaste user input
    - history: tidigare meddelanden (messages-format)
    - övriga parametrar: sliders / radio-knappar
    """
    prompt = build_prompt(message, history, style)

    # Hantera deterministiskt läge om temperature == 0
    temp = float(temperature)
    top_p_val = float(top_p)
    repeat_pen = float(repetition_penalty)

    if temp <= 0.0:
        temp = 0.0
        top_p_val = 1.0  # spelar mindre roll när temp=0

    output = llm(
        prompt,
        max_tokens=int(max_new_tokens),
        temperature=temp,
        top_p=top_p_val,
        repeat_penalty=repeat_pen,
        stop=["User:", "Assistant:", "System:", "Conversation:"],
    )

    reply = output["choices"][0]["text"].strip()
    return reply


# 4. DJ-reglagen (extra inputs till ChatInterface)
max_new_tokens_slider = gr.Slider(
    minimum=16,
    maximum=256,
    value=64,
    step=8,
    label="Max new tokens (response length)",
)

temperature_slider = gr.Slider(
    minimum=0.0,
    maximum=1.5,
    value=0.0,
    step=0.1,
    label="Temperature (0 = deterministic, higher = more random)",
)

top_p_slider = gr.Slider(
    minimum=0.1,
    maximum=1.0,
    value=0.9,
    step=0.05,
    label="Top-p (nucleus sampling)",
)

repetition_penalty_slider = gr.Slider(
    minimum=0.8,
    maximum=1.3,
    value=1.0,
    step=0.05,
    label="Repetition penalty",
)

style_radio = gr.Radio(
    choices=[
        "Default",
        "Short answer",
        "Detailed explanation",
        "Step-by-step reasoning",
    ],
    value="Detailed explanation",
    label="Answer style",
)

demo = gr.ChatInterface(
    fn=chat_fn,
    title="Lab 2 – Fine-tuned GGUF model",
    description=(
        "Chat with our fine-tuned Llama-based model, converted to GGUF and "
        "loaded via llama.cpp from Jeppcode/ScalableLab2.\n\n"
        "Use the controls in the accordion below like a DJ board to tweak "
        "response length, randomness and style."
    ),
    additional_inputs=[
        max_new_tokens_slider,
        temperature_slider,
        top_p_slider,
        repetition_penalty_slider,
        style_radio,
    ],
    additional_inputs_accordion="Generation controls",
)

if __name__ == "__main__":
    demo.launch()