Bc-AI commited on
Commit
9f1c50c
Β·
verified Β·
1 Parent(s): b8302d9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -50
app.py CHANGED
@@ -21,6 +21,63 @@ model = AutoModelForCausalLM.from_pretrained(
21
  )
22
  model.eval()
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # ─────────────────────────────────────────────
25
  # 2. INFERENCE FUNCTION (ZeroGPU decorated)
26
  # ─────────────────────────────────────────────
@@ -34,32 +91,10 @@ def generate(
34
  do_sample: bool = True,
35
  ) -> str:
36
  """
37
- Generate a text response from Mira-1-Large.
38
-
39
- Args:
40
- prompt: The user message / prompt to send to the model.
41
- system_prompt: System-level instruction for the model.
42
- max_new_tokens: Maximum number of tokens to generate.
43
- temperature: Sampling temperature (higher = more creative).
44
- top_p: Nucleus sampling probability mass.
45
- do_sample: Whether to use sampling (True) or greedy decoding (False).
46
-
47
- Returns:
48
- The model's text response as a string.
49
  """
50
- # Build chat-style messages (Qwen uses apply_chat_template)
51
- messages = [
52
- {"role": "system", "content": system_prompt},
53
- {"role": "user", "content": prompt},
54
- ]
55
-
56
- # Qwen / Mira chat template
57
- text = tokenizer.apply_chat_template(
58
- messages,
59
- tokenize=False,
60
- add_generation_prompt=True,
61
- )
62
-
63
  inputs = tokenizer(text, return_tensors="pt").to(model.device)
64
 
65
  with torch.no_grad():
@@ -72,9 +107,12 @@ def generate(
72
  pad_token_id=tokenizer.eos_token_id,
73
  )
74
 
75
- # Decode only the newly generated tokens
76
  new_tokens = output_ids[0][inputs["input_ids"].shape[1]:]
77
  response = tokenizer.decode(new_tokens, skip_special_tokens=True)
 
 
 
 
78
  return response
79
 
80
 
@@ -88,30 +126,15 @@ def generate_stream(
88
  max_new_tokens: int = 512,
89
  temperature: float = 0.7,
90
  top_p: float = 0.9,
91
- ) -> str:
92
  """
93
- Stream a text response token-by-token from Mira-1-Large via SSE.
94
-
95
- Args:
96
- prompt: The user message / prompt.
97
- system_prompt: System-level instruction for the model.
98
- max_new_tokens: Maximum number of tokens to generate.
99
- temperature: Sampling temperature.
100
- top_p: Nucleus sampling probability mass.
101
-
102
- Yields:
103
- Partial response strings, growing with each new token.
104
  """
105
  from transformers import TextIteratorStreamer
106
  from threading import Thread
107
 
108
- messages = [
109
- {"role": "system", "content": system_prompt},
110
- {"role": "user", "content": prompt},
111
- ]
112
- text = tokenizer.apply_chat_template(
113
- messages, tokenize=False, add_generation_prompt=True
114
- )
115
  inputs = tokenizer(text, return_tensors="pt").to(model.device)
116
 
117
  streamer = TextIteratorStreamer(
@@ -131,7 +154,12 @@ def generate_stream(
131
  thread = Thread(target=model.generate, kwargs=gen_kwargs)
132
  thread.start()
133
 
134
- partial = ""
 
 
 
 
 
135
  for new_text in streamer:
136
  partial += new_text
137
  yield partial
@@ -146,15 +174,15 @@ app = Server(
146
  version="1.0.0",
147
  )
148
 
149
- # Register as Gradio API endpoints (queued, SSE-streaming capable)
150
- app.api(generate, name="generate") # POST /gradio_api/call/generate
151
- app.api(generate_stream, name="generate_stream") # POST /gradio_api/call/generate_stream
152
 
153
- # Optional: plain FastAPI GET health-check route
154
  @app.get("/health")
155
  def health():
156
  return {"status": "ok", "model": MODEL_ID}
157
 
 
158
  # ─────────────────────────────────────────────
159
  # 5. LAUNCH
160
  # ─────────────────────────────────────────────
 
21
  )
22
  model.eval()
23
 
24
+ # ─────────────────────────────────────────────
25
+ # 1b. THINKING ENFORCEMENT
26
+ #
27
+ # This model isn't guaranteed to emit <think>...</think> on its own
28
+ # (especially as an abliterated finetune). To make it 100% reliable:
29
+ #
30
+ # 1. Try passing enable_thinking=True to apply_chat_template
31
+ # (native support on Qwen3-style templates, if present).
32
+ # 2. ALWAYS force-append "<think>\n" onto the templated prompt
33
+ # so generation is physically forced to begin inside a think
34
+ # block, regardless of whether enable_thinking worked.
35
+ # 3. Append a short mandatory instruction onto whatever system
36
+ # prompt is passed in, telling the model to close the tag.
37
+ # 4. Manually re-prepend "<think>\n" onto the decoded output,
38
+ # since it was part of the forced prompt and gets stripped
39
+ # out by skip_prompt / prompt-slicing.
40
+ # ─────────────────────────────────────────────
41
+ THINK_INSTRUCTION = (
42
+ "\n\nAlways reason through the problem step by step inside <think> "
43
+ "and </think> tags first. After the closing </think> tag, give your "
44
+ "final answer. Never skip the opening or closing think tags."
45
+ )
46
+
47
+
48
+ def build_prompt(prompt: str, system_prompt: str) -> str:
49
+ full_system = (system_prompt or "You are a helpful assistant.") + THINK_INSTRUCTION
50
+
51
+ messages = [
52
+ {"role": "system", "content": full_system},
53
+ {"role": "user", "content": prompt},
54
+ ]
55
+
56
+ try:
57
+ # Native Qwen3-style thinking toggle, if the tokenizer supports it
58
+ text = tokenizer.apply_chat_template(
59
+ messages,
60
+ tokenize=False,
61
+ add_generation_prompt=True,
62
+ enable_thinking=True,
63
+ )
64
+ except TypeError:
65
+ # Tokenizer/template doesn't accept enable_thinking β€” fall back
66
+ text = tokenizer.apply_chat_template(
67
+ messages,
68
+ tokenize=False,
69
+ add_generation_prompt=True,
70
+ )
71
+
72
+ # Force the assistant turn to start inside a <think> block,
73
+ # guaranteeing the tag appears no matter what the model would
74
+ # have done on its own.
75
+ if not text.rstrip().endswith("<think>"):
76
+ text = text + "<think>\n"
77
+
78
+ return text
79
+
80
+
81
  # ─────────────────────────────────────────────
82
  # 2. INFERENCE FUNCTION (ZeroGPU decorated)
83
  # ─────────────────────────────────────────────
 
91
  do_sample: bool = True,
92
  ) -> str:
93
  """
94
+ Generate a text response from the model.
95
+ Response is guaranteed to start with a <think> block.
 
 
 
 
 
 
 
 
 
 
96
  """
97
+ text = build_prompt(prompt, system_prompt)
 
 
 
 
 
 
 
 
 
 
 
 
98
  inputs = tokenizer(text, return_tensors="pt").to(model.device)
99
 
100
  with torch.no_grad():
 
107
  pad_token_id=tokenizer.eos_token_id,
108
  )
109
 
 
110
  new_tokens = output_ids[0][inputs["input_ids"].shape[1]:]
111
  response = tokenizer.decode(new_tokens, skip_special_tokens=True)
112
+
113
+ # Re-add the <think> tag we force-injected into the prompt β€”
114
+ # it was stripped out because it's technically part of the input.
115
+ response = "<think>\n" + response
116
  return response
117
 
118
 
 
126
  max_new_tokens: int = 512,
127
  temperature: float = 0.7,
128
  top_p: float = 0.9,
129
+ ):
130
  """
131
+ Stream a text response token-by-token via SSE.
132
+ Guaranteed to start with a <think> block.
 
 
 
 
 
 
 
 
 
133
  """
134
  from transformers import TextIteratorStreamer
135
  from threading import Thread
136
 
137
+ text = build_prompt(prompt, system_prompt)
 
 
 
 
 
 
138
  inputs = tokenizer(text, return_tensors="pt").to(model.device)
139
 
140
  streamer = TextIteratorStreamer(
 
154
  thread = Thread(target=model.generate, kwargs=gen_kwargs)
155
  thread.start()
156
 
157
+ # Re-inject the forced <think> prefix as the very first chunk,
158
+ # since it was part of the prompt and streamer.skip_prompt=True
159
+ # will not emit it on its own.
160
+ partial = "<think>\n"
161
+ yield partial
162
+
163
  for new_text in streamer:
164
  partial += new_text
165
  yield partial
 
174
  version="1.0.0",
175
  )
176
 
177
+ app.api(generate, name="generate")
178
+ app.api(generate_stream, name="generate_stream")
179
+
180
 
 
181
  @app.get("/health")
182
  def health():
183
  return {"status": "ok", "model": MODEL_ID}
184
 
185
+
186
  # ─────────────────────────────────────────────
187
  # 5. LAUNCH
188
  # ─────────────────────────────────────────────