aciang commited on
Commit
6efc50c
·
verified ·
1 Parent(s): 81dfb3b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +224 -75
app.py CHANGED
@@ -1,77 +1,226 @@
 
 
 
 
1
 
2
- import os, torch, gradio as gr
3
- from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, TextIteratorStreamer
4
- os.environ.setdefault("HF_HOME", "/data/.cache")
5
- os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
6
- MODEL_ID = os.getenv("MODEL_ID", "aciang/mistral7b-tk-sft-20251019-merged")
7
- TITLE = "LanguageBridge — Multimodal Chatbot (Mistral-7B)"
8
- SYSTEM_PROMPT = "你是教學助教。先讀【任務】,按【格式】作答;資料不足先列缺口,勿猜測。"
9
-
10
- def load_llm():
11
- has_cuda = torch.cuda.is_available()
12
- kwargs = dict(trust_remote_code=False, low_cpu_mem_usage=True)
13
- try:
14
- if has_cuda:
15
- bnb = BitsAndBytesConfig(
16
- load_in_4bit=True, bnb_4bit_use_double_quant=True,
17
- bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16
18
- )
19
- model = AutoModelForCausalLM.from_pretrained(MODEL_ID, device_map="auto", quantization_config=bnb, **kwargs)
20
- else:
21
- print("[no CUDA] using CPU fp32")
22
- model = AutoModelForCausalLM.from_pretrained(MODEL_ID, device_map="cpu", torch_dtype=torch.float32, **kwargs)
23
- except Exception as e:
24
- print("[loader fallback fp16/cpu]:", e)
25
- model = AutoModelForCausalLM.from_pretrained(
26
- MODEL_ID,
27
- device_map="auto" if has_cuda else "cpu",
28
- torch_dtype=torch.float16 if has_cuda else torch.float32,
29
- **kwargs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  )
31
- tok = AutoTokenizer.from_pretrained(MODEL_ID, use_fast=True)
32
- if tok.pad_token is None: tok.pad_token = tok.eos_token
33
- tok.padding_side = "left"
34
- model.config.use_cache = True
35
- return tok, model
36
-
37
- tokenizer, llm = load_llm(); llm.eval()
38
-
39
- def build_prompt(task, ctx=None):
40
- head = "你是教學助教。先讀任務,依:1) 摘要要點;2) 逐步推理;3) 結論條列。\n\n"
41
- if ctx:
42
- ctx = ctx[-6000:]
43
- return f"{head}【參考上下文】\n{ctx}\n\n【使用者問題】\n{task}\n\n【回答】"
44
- return f"{head}【使用者問題】\n{task}\n\n【回答】"
45
-
46
- @torch.inference_mode()
47
- def stream_answer(task, context, mx=256, temp=0.15, top_p=0.9):
48
- prompt = build_prompt(task, context.strip() or None)
49
- inputs = tokenizer(prompt, return_tensors="pt").to(llm.device)
50
- streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
51
- kwargs = dict(**inputs, streamer=streamer, max_new_tokens=int(mx),
52
- temperature=float(temp), top_p=float(top_p),
53
- do_sample=True if float(temp)>0 else False,
54
- eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.pad_token_id)
55
- import threading; threading.Thread(target=llm.generate, kwargs=kwargs).start()
56
- buf=""
57
- for tok in streamer:
58
- buf += tok
59
- yield buf
60
-
61
- with gr.Blocks(title=TITLE, theme="soft") as demo:
62
- gr.Markdown(f"## {TITLE}|模型:`{MODEL_ID}`(流式)")
63
- q = gr.Textbox(label="你的問題 / 指令", lines=5, placeholder="可貼長文;我會先摘要→推理→結論")
64
- ctx = gr.Textbox(label="(可選)上下文", lines=6)
65
- mx = gr.Slider(64, 512, value=256, step=32, label="max_new_tokens")
66
- temp = gr.Slider(0.0, 0.8, value=0.15, step=0.05, label="temperature")
67
- top = gr.Slider(0.6, 1.0, value=0.9, step=0.01, label="top_p")
68
- go = gr.Button("送出 🚀", variant="primary")
69
- out = gr.Textbox(label="輸出(流式)", lines=14)
70
- clr = gr.Button("清除")
71
- go.click(stream_answer, inputs=[q, ctx, mx, temp, top], outputs=out)
72
- clr.click(lambda:"", outputs=out)
73
- # ← 修正:不要用舊參數 concurrency_count
74
- demo.queue(max_size=32, status_update_rate=1, api_open=False)
75
-
76
- if __name__ == "__main__":
77
- demo.launch(share=False, server_name="0.0.0.0", server_port=7860, show_error=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import gradio as gr
4
+ from transformers import AutoTokenizer, AutoModelForCausalLM
5
 
6
+ # ========= 基本設定 =========
7
+ # 你的模型 repo id(目前是 private 也沒關係)
8
+ MODEL_ID = "aciang/mistral7b-tk-sft-20251019-merged"
9
+
10
+ # 若模型是 private,建議在 Space 的「Settings → Repository secrets」加上 HF_TOKEN
11
+ HF_TOKEN = os.getenv("HF_TOKEN", None)
12
+
13
+ # 建議預設的「傳統知識」系統提示,可以在介面中修改
14
+ DEFAULT_SYSTEM_PROMPT = """你是一位熟悉台灣與國際原住民族傳統知識的學者,
15
+ 擅長用淺顯但尊重文化脈絡的繁體中文說明各族的夢境、儀式、宇宙觀、傳統醫療與環境知識。
16
+
17
+ 回答原則:
18
+ 1. 先簡短摘要重點(3–5 點條列)。
19
+ 2. 儘量說明「族名、場域、情境」與「知識來源背景」,避免抽象空話。
20
+ 3. 若是推論或類比,要清楚標註「推測」而不是說成唯一正解。
21
+ 4. 若資料不足或超出目前教材範圍,請誠實說明,並給出安全的延伸建議。
22
+ 5. 全程使用繁體中文。"""
23
+
24
+ # ========= 載入模型 =========
25
+
26
+ print(f"載入模型:{MODEL_ID} ...")
27
+
28
+ tokenizer = AutoTokenizer.from_pretrained(
29
+ MODEL_ID,
30
+ use_auth_token=HF_TOKEN,
31
+ )
32
+
33
+ # 保險起見,若沒有 pad_token 就沿用 eos_token
34
+ if tokenizer.pad_token is None:
35
+ tokenizer.pad_token = tokenizer.eos_token
36
+
37
+ model = AutoModelForCausalLM.from_pretrained(
38
+ MODEL_ID,
39
+ torch_dtype=torch.float16,
40
+ device_map="auto", # 自動分配到 GPU
41
+ use_auth_token=HF_TOKEN,
42
+ )
43
+
44
+ model.eval()
45
+ print("模型載入完成。")
46
+
47
+
48
+ # ========= 建立提示詞 =========
49
+
50
+ def build_prompt(system_prompt: str, history: list[tuple[str, str]], user_message: str) -> str:
51
+ """
52
+ 將 system_prompt + 歷史對話 + 新問題 組成一段文字 prompt。
53
+ 這裡用簡單的「使用者 / 助手」格式,對傳統知識生成已經很足夠。
54
+ """
55
+ system_prompt = system_prompt.strip()
56
+ prompt = f"[系統提示]\n{system_prompt}\n\n"
57
+
58
+ # 過去對話(若有)
59
+ if history:
60
+ prompt += "[對話紀錄]\n"
61
+ for i, (user, bot) in enumerate(history, start=1):
62
+ prompt += f"輪次 {i}:\n使用者:{user}\n助手:{bot}\n\n"
63
+
64
+ # 最新一輪問題
65
+ prompt += "[目前問題]\n"
66
+ prompt += f"使用者:{user_message}\n助手:"
67
+
68
+ return prompt
69
+
70
+
71
+ # ========= 生成函式 =========
72
+
73
+ def generate_reply(
74
+ user_message: str,
75
+ chat_history: list[tuple[str, str]],
76
+ system_prompt: str,
77
+ temperature: float,
78
+ max_new_tokens: int,
79
+ ):
80
+ if not user_message.strip():
81
+ return chat_history, gr.update(value="")
82
+
83
+ # 組合成一個大 prompt
84
+ prompt_text = build_prompt(system_prompt, chat_history, user_message)
85
+
86
+ inputs = tokenizer(
87
+ prompt_text,
88
+ return_tensors="pt",
89
+ add_special_tokens=True,
90
+ ).to(model.device)
91
+
92
+ with torch.no_grad():
93
+ outputs = model.generate(
94
+ **inputs,
95
+ max_new_tokens=int(max_new_tokens),
96
+ do_sample=True,
97
+ temperature=float(temperature),
98
+ top_p=0.9,
99
+ pad_token_id=tokenizer.eos_token_id,
100
  )
101
+
102
+ # 只取新生成的部分
103
+ input_len = inputs["input_ids"].shape[-1]
104
+ generated_tokens = outputs[0, input_len:]
105
+ answer = tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()
106
+
107
+ chat_history = chat_history + [(user_message, answer)]
108
+ return chat_history, "" # 清空輸入框
109
+
110
+
111
+ def clear_history():
112
+ return [], ""
113
+
114
+
115
+ # ========= Gradio 介面 =========
116
+
117
+ with gr.Blocks(title="語言橋傳統知識聊天機器人 Mistral7B TK", theme=gr.themes.Soft()) as demo:
118
+ gr.Markdown(
119
+ """
120
+ # 語言橋傳統知識聊天機器人 Mistral7B TK
121
+ 使用你自訓練的 **mistral7b-tk-sft-20251019-merged** 模型,離線在 Hugging Face Space 上回答與傳統知識相關的問題。
122
+
123
+ 建議題材舉例:
124
+ - 不同族群對「夢境」的五種層次與詮釋差異
125
+ - 布農族狩獵儀式與祖靈信仰
126
+ - 排灣族階級制度與紋面、圖騰的意義
127
+ - 阿美族年齡階層制與植物分類知識
128
+ - 海外原住民(如 Inuit)對身體、疾病與療癒的理解
129
+ """
130
+ )
131
+
132
+ with gr.Row():
133
+ # 左側:設定區
134
+ with gr.Column(scale=1):
135
+ system_prompt_box = gr.Textbox(
136
+ label="系統提示(模型角色與回答風格)",
137
+ value=DEFAULT_SYSTEM_PROMPT,
138
+ lines=16,
139
+ )
140
+ temperature_slider = gr.Slider(
141
+ label="溫度(創造性)",
142
+ minimum=0.1,
143
+ maximum=1.5,
144
+ value=0.7,
145
+ step=0.05,
146
+ )
147
+ max_tokens_slider = gr.Slider(
148
+ label="最大回覆長度(token 數,大約字數的 1.5–2 倍)",
149
+ minimum=64,
150
+ maximum=1024,
151
+ value=512,
152
+ step=16,
153
+ )
154
+ gr.Markdown(
155
+ """
156
+ **小提醒:**
157
+ - 回答太發散 → 降低溫度(0.4–0.7)。
158
+ - 回答太短 → 拉高「最大回覆長度」。
159
+ - Space 若常 timeout,可以稍微降低最大回覆長度。
160
+ """
161
+ )
162
+
163
+ # 右側:聊天區
164
+ with gr.Column(scale=2):
165
+ chatbot = gr.Chatbot(
166
+ label="傳統知識 Chatbot",
167
+ height=480,
168
+ show_copy_button=True,
169
+ )
170
+ user_input = gr.Textbox(
171
+ label="輸入你的問題(可多輪對話)",
172
+ placeholder="例如:請比較布農族、排灣族和阿美族對治療疾病與夢境預兆的不同理解方式。",
173
+ lines=4,
174
+ )
175
+ with gr.Row():
176
+ send_btn = gr.Button("送出問題", variant="primary")
177
+ clear_btn = gr.Button("清除對話")
178
+
179
+ # 狀態:對話歷史
180
+ state = gr.State([]) # list[tuple[user, bot]]
181
+
182
+ # 綁定互動
183
+ send_btn.click(
184
+ fn=generate_reply,
185
+ inputs=[
186
+ user_input,
187
+ state,
188
+ system_prompt_box,
189
+ temperature_slider,
190
+ max_tokens_slider,
191
+ ],
192
+ outputs=[chatbot, user_input],
193
+ ).then(
194
+ fn=lambda h: h,
195
+ inputs=[chatbot],
196
+ outputs=[state],
197
+ )
198
+
199
+ user_input.submit(
200
+ fn=generate_reply,
201
+ inputs=[
202
+ user_input,
203
+ state,
204
+ system_prompt_box,
205
+ temperature_slider,
206
+ max_tokens_slider,
207
+ ],
208
+ outputs=[chatbot, user_input],
209
+ ).then(
210
+ fn=lambda h: h,
211
+ inputs=[chatbot],
212
+ outputs=[state],
213
+ )
214
+
215
+ clear_btn.click(
216
+ fn=clear_history,
217
+ inputs=[],
218
+ outputs=[chatbot, user_input],
219
+ ).then(
220
+ fn=lambda: [],
221
+ inputs=[],
222
+ outputs=[state],
223
+ )
224
+
225
+ # 在 HF Space 中不需要 demo.launch(),平台會自動呼叫 demo
226
+