HK2184 commited on
Commit
8319573
Β·
verified Β·
1 Parent(s): def5cfe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +367 -68
app.py CHANGED
@@ -1,13 +1,12 @@
1
  import os
 
2
  import torch
3
  import gradio as gr
4
  from transformers import AutoTokenizer, AutoModelForCausalLM
5
  from peft import PeftModel
6
 
7
- # ← CHANGE 1: ROCm env vars removed
8
-
9
  BASE_MODEL = "Qwen/Qwen3-1.7B"
10
- ADAPTER_PATH = "HK2184/medqa-qwen3-lora" # ← CHANGE 2: HF Hub instead of ./outputs
11
 
12
  print("Loading tokenizer...")
13
  tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)
@@ -15,7 +14,7 @@ tokenizer.pad_token = tokenizer.eos_token
15
  tokenizer.padding_side = "left"
16
 
17
  print("Loading model...")
18
- DTYPE = torch.bfloat16 if torch.cuda.is_available() else torch.float32 # ← CHANGE 3: auto dtype
19
  base = AutoModelForCausalLM.from_pretrained(
20
  BASE_MODEL,
21
  dtype=DTYPE,
@@ -28,8 +27,11 @@ model = model.merge_and_unload()
28
  model.eval()
29
  print("Ready!")
30
 
 
 
 
31
  EXAMPLES = [
32
- ["Which artery is occluded in inferior MI with ST elevation in II, III, aVF?",
33
  "Left anterior descending artery", "Right coronary artery",
34
  "Left circumflex artery", "Left main coronary artery"],
35
  ["First-line treatment for hypertensive emergency?",
@@ -41,34 +43,146 @@ EXAMPLES = [
41
  ["Drug of choice for absence seizures?",
42
  "Phenytoin", "Carbamazepine",
43
  "Ethosuximide", "Valproate"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  ]
45
 
46
- def answer(question, opa, opb, opc, opd):
 
 
 
 
 
 
 
 
 
 
 
 
47
  if not question.strip():
48
- return "Please enter a question."
49
  if not all([opa.strip(), opb.strip(), opc.strip(), opd.strip()]):
50
- return "Please fill in all four options."
 
51
  prompt = (
52
  f"### Question:\n{question}\n\n"
53
- f"### Options:\n"
54
- f"A) {opa}\nB) {opb}\nC) {opc}\nD) {opd}\n\n"
55
  f"### Answer:\n"
56
  )
 
57
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
 
58
  with torch.no_grad():
59
  out = model.generate(
60
  **inputs,
61
- max_new_tokens=200,
62
  do_sample=True,
63
- temperature=0.7,
64
  top_p=0.9,
65
  top_k=50,
66
  repetition_penalty=1.3,
67
  eos_token_id=tokenizer.eos_token_id,
68
  pad_token_id=tokenizer.eos_token_id,
69
  )
70
- new = out[0][inputs["input_ids"].shape[-1]:]
71
- return tokenizer.decode(new, skip_special_tokens=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  CSS = """
74
  @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@300;400;500&display=swap');
@@ -83,7 +197,6 @@ CSS = """
83
  --green: #00f0a0;
84
  --text: #deeeff;
85
  --muted: #4a6080;
86
- --danger: #ff3366;
87
  }
88
 
89
  body, .gradio-container {
@@ -91,34 +204,33 @@ body, .gradio-container {
91
  font-family: 'DM Sans', sans-serif !important;
92
  color: var(--text) !important;
93
  }
94
-
95
  .gradio-container {
96
- max-width: 1080px !important;
97
  margin: 0 auto !important;
98
  padding: 0 20px 60px !important;
99
  }
100
 
 
101
  #header {
102
  padding: 44px 0 28px;
103
  border-bottom: 1px solid var(--border);
104
- margin-bottom: 32px;
105
  position: relative;
106
  }
107
  #header::after {
108
  content: '';
109
  position: absolute;
110
- bottom: -1px; left: 0; right: 0; height: 1px;
111
  background: linear-gradient(90deg, var(--accent2), var(--accent), var(--green));
112
  }
113
  .badges { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
114
  .badge {
115
- font-size: 10px; font-weight: 600;
116
- letter-spacing: 0.1em; text-transform: uppercase;
117
- padding: 3px 9px; border-radius: 4px; border: 1px solid;
118
  }
119
  .b-amd { color: #ff6030; border-color: #ff603030; background: #ff603010; }
120
  .b-rocm { color: var(--accent); border-color: #00c8f030; background: #00c8f008; }
121
- .b-lora { color: var(--green); border-color: #00f0a030; background: #00f0a008; }
122
  .b-live { color: #ffcc00; border-color: #ffcc0030; background: #ffcc0008; }
123
 
124
  h1#title {
@@ -128,12 +240,13 @@ h1#title {
128
  color: var(--text) !important; margin-bottom: 10px !important;
129
  }
130
  h1#title em { color: var(--accent); font-style: normal; }
131
- .subtitle { font-size: 14px; color: var(--muted); font-weight: 300; line-height: 1.6; max-width: 520px; }
132
 
 
133
  #stats {
134
  display: flex; border: 1px solid var(--border);
135
  border-radius: 12px; overflow: hidden;
136
- background: var(--surface); margin-bottom: 28px;
137
  }
138
  .stat { flex: 1; padding: 14px 16px; text-align: center; border-right: 1px solid var(--border); }
139
  .stat:last-child { border-right: none; }
@@ -142,6 +255,7 @@ h1#title em { color: var(--accent); font-style: normal; }
142
  .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--green); margin-right: 4px; animation: blink 2s infinite; }
143
  @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} }
144
 
 
145
  label span, .label-wrap span {
146
  font-family: 'DM Sans', sans-serif !important;
147
  font-size: 11px !important; font-weight: 500 !important;
@@ -151,22 +265,20 @@ label span, .label-wrap span {
151
  textarea, input[type=text] {
152
  background: var(--surface2) !important;
153
  border: 1px solid var(--border) !important;
154
- border-radius: 10px !important;
155
- color: var(--text) !important;
156
  font-family: 'DM Sans', sans-serif !important;
157
  font-size: 14px !important; line-height: 1.6 !important;
158
  transition: border-color 0.2s, box-shadow 0.2s !important;
159
  }
160
  textarea:focus, input[type=text]:focus {
161
  border-color: var(--accent) !important;
162
- box-shadow: 0 0 0 3px #00c8f012 !important;
163
- outline: none !important;
164
  }
165
 
 
166
  .section-label {
167
- font-size: 10px; font-weight: 600;
168
- letter-spacing: 0.12em; text-transform: uppercase;
169
- color: var(--muted); margin-bottom: 10px;
170
  display: flex; align-items: center; gap: 7px;
171
  }
172
  .section-label::before {
@@ -174,26 +286,61 @@ textarea:focus, input[type=text]:focus {
174
  background: var(--accent); display: inline-block;
175
  }
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  button.lg.primary {
178
  background: linear-gradient(135deg, var(--accent2), var(--accent)) !important;
179
  border: none !important; border-radius: 10px !important;
180
  color: #fff !important; font-family: 'Syne', sans-serif !important;
181
  font-size: 14px !important; font-weight: 700 !important;
182
- letter-spacing: 0.04em !important; padding: 14px !important;
183
- width: 100% !important; margin-top: 14px !important;
184
- cursor: pointer !important;
185
  transition: opacity 0.2s, transform 0.15s !important;
186
  }
187
  button.lg.primary:hover { opacity: 0.85 !important; transform: translateY(-1px) !important; }
188
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  .out-box textarea {
190
  background: var(--surface2) !important;
191
  border: 1px solid var(--border) !important;
192
- border-radius: 10px !important;
193
- font-size: 14px !important; line-height: 1.8 !important;
194
- color: var(--text) !important; min-height: 280px !important;
195
  }
196
 
 
 
 
 
 
 
 
 
197
  .examples-holder table {
198
  background: var(--surface) !important;
199
  border: 1px solid var(--border) !important;
@@ -201,11 +348,18 @@ button.lg.primary:hover { opacity: 0.85 !important; transform: translateY(-1px)
201
  }
202
  .examples-holder td, .examples-holder th {
203
  background: transparent !important; color: var(--text) !important;
204
- font-size: 13px !important; border-color: var(--border) !important;
205
  font-family: 'DM Sans', sans-serif !important;
206
  }
207
  .examples-holder tr:hover td { background: var(--surface2) !important; cursor: pointer; }
208
 
 
 
 
 
 
 
 
209
  #footer {
210
  margin-top: 44px; padding-top: 22px;
211
  border-top: 1px solid var(--border);
@@ -220,6 +374,7 @@ button.lg.primary:hover { opacity: 0.85 !important; transform: translateY(-1px)
220
 
221
  with gr.Blocks(css=CSS, title="MedQA β€” AMD ROCm") as demo:
222
 
 
223
  gr.HTML("""
224
  <div id="header">
225
  <div class="badges">
@@ -232,6 +387,7 @@ with gr.Blocks(css=CSS, title="MedQA β€” AMD ROCm") as demo:
232
  <p class="subtitle">
233
  Clinical question-answering AI fine-tuned on MedMCQA.
234
  Running on AMD Instinct MI300X via ROCm β€” no CUDA required.
 
235
  </p>
236
  </div>
237
  <div id="stats">
@@ -243,39 +399,159 @@ with gr.Blocks(css=CSS, title="MedQA β€” AMD ROCm") as demo:
243
  </div>
244
  """)
245
 
246
- with gr.Row():
247
- with gr.Column(scale=1):
248
- gr.HTML('<div class="section-label">Clinical Question</div>')
249
- question = gr.Textbox(
250
- label="",
251
- placeholder="e.g. A 45-year-old presents with sudden onset severe headache...",
252
- lines=4,
253
- )
254
- gr.HTML('<div class="section-label" style="margin-top:14px">Answer Options</div>')
255
  with gr.Row():
256
- opa = gr.Textbox(label="Option A", placeholder="First option")
257
- opb = gr.Textbox(label="Option B", placeholder="Second option")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  with gr.Row():
259
- opc = gr.Textbox(label="Option C", placeholder="Third option")
260
- opd = gr.Textbox(label="Option D", placeholder="Fourth option")
261
- btn = gr.Button("Analyze Question", variant="primary")
 
262
 
263
- with gr.Column(scale=1):
264
- gr.HTML('<div class="section-label">AI Answer & Reasoning</div>')
265
- output = gr.Textbox(
 
266
  label="",
267
- placeholder="Answer and clinical explanation will appear here...",
268
- lines=14,
269
- elem_classes=["out-box"],
270
  )
271
 
272
- gr.HTML('<div class="section-label" style="margin-top:24px">Sample Questions β€” click any to load</div>')
273
- gr.Examples(
274
- examples=EXAMPLES,
275
- inputs=[question, opa, opb, opc, opd],
276
- label="",
277
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
 
 
 
 
279
  gr.HTML("""
280
  <div id="footer">
281
  <div class="fl">
@@ -285,13 +561,36 @@ with gr.Blocks(css=CSS, title="MedQA β€” AMD ROCm") as demo:
285
  </div>
286
  <div class="fr">
287
  <a class="flink" href="https://github.com/HK2184/MedQA-Medical-AI-on-AMD-ROCm" target="_blank">GitHub β†’</a>
 
288
  <a class="flink" href="https://lablab.ai" target="_blank">lablab.ai β†’</a>
289
- <a class="flink" href="https://cloud.amd.com" target="_blank">AMD Cloud β†’</a>
290
  </div>
291
  </div>
292
  """)
293
 
294
- btn.click(fn=answer, inputs=[question, opa, opb, opc, opd], outputs=output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
  if __name__ == "__main__":
297
- demo.launch() # ← CHANGE 4: no server_name/port/share for HF Spaces
 
1
  import os
2
+ import time
3
  import torch
4
  import gradio as gr
5
  from transformers import AutoTokenizer, AutoModelForCausalLM
6
  from peft import PeftModel
7
 
 
 
8
  BASE_MODEL = "Qwen/Qwen3-1.7B"
9
+ ADAPTER_PATH = "HK2184/medqa-qwen3-lora"
10
 
11
  print("Loading tokenizer...")
12
  tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)
 
14
  tokenizer.padding_side = "left"
15
 
16
  print("Loading model...")
17
+ DTYPE = torch.bfloat16 if torch.cuda.is_available() else torch.float32
18
  base = AutoModelForCausalLM.from_pretrained(
19
  BASE_MODEL,
20
  dtype=DTYPE,
 
27
  model.eval()
28
  print("Ready!")
29
 
30
+ DEVICE_INFO = f"{'GPU (ROCm)' if torch.cuda.is_available() else 'CPU'}"
31
+ query_count = {"total": 0}
32
+
33
  EXAMPLES = [
34
+ ["Which artery is occluded in inferior MI with ST elevation in leads II, III, aVF?",
35
  "Left anterior descending artery", "Right coronary artery",
36
  "Left circumflex artery", "Left main coronary artery"],
37
  ["First-line treatment for hypertensive emergency?",
 
43
  ["Drug of choice for absence seizures?",
44
  "Phenytoin", "Carbamazepine",
45
  "Ethosuximide", "Valproate"],
46
+ ["A patient with sickle cell disease presents with acute chest pain and hypoxia. What is this called?",
47
+ "Pulmonary embolism", "Acute chest syndrome",
48
+ "Pneumonia", "Pleuritis"],
49
+ ["Which vitamin deficiency causes Wernicke encephalopathy?",
50
+ "Vitamin B12", "Vitamin B1 (Thiamine)",
51
+ "Vitamin B6", "Vitamin C"],
52
+ ["What is the antidote for acetaminophen overdose?",
53
+ "Naloxone", "Flumazenil",
54
+ "N-acetylcysteine", "Atropine"],
55
+ ["A 60-year-old smoker presents with hemoptysis and weight loss. Most likely diagnosis?",
56
+ "Tuberculosis", "Lung carcinoma",
57
+ "Pulmonary embolism", "Bronchiectasis"],
58
+ ]
59
+
60
+ SUBJECTS = [
61
+ "All Subjects", "Cardiology", "Pharmacology", "Pulmonology",
62
+ "Neurology", "Endocrinology", "Infectious Disease", "Emergency Medicine"
63
  ]
64
 
65
+ SUBJECT_EXAMPLES = {
66
+ "Cardiology": [EXAMPLES[0], EXAMPLES[1]],
67
+ "Pharmacology": [EXAMPLES[3], EXAMPLES[6]],
68
+ "Pulmonology": [EXAMPLES[2], EXAMPLES[7]],
69
+ "Neurology": [EXAMPLES[3], EXAMPLES[5]],
70
+ "Endocrinology": [],
71
+ "Infectious Disease": [EXAMPLES[2]],
72
+ "Emergency Medicine": [EXAMPLES[1], EXAMPLES[4]],
73
+ }
74
+
75
+ history_store = []
76
+
77
+ def generate_answer(question, opa, opb, opc, opd, temperature, max_tokens):
78
  if not question.strip():
79
+ return "⚠️ Please enter a question.", "", "0.00s", str(query_count["total"])
80
  if not all([opa.strip(), opb.strip(), opc.strip(), opd.strip()]):
81
+ return "⚠️ Please fill in all four options.", "", "0.00s", str(query_count["total"])
82
+
83
  prompt = (
84
  f"### Question:\n{question}\n\n"
85
+ f"### Options:\nA) {opa}\nB) {opb}\nC) {opc}\nD) {opd}\n\n"
 
86
  f"### Answer:\n"
87
  )
88
+
89
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
90
+ t0 = time.time()
91
  with torch.no_grad():
92
  out = model.generate(
93
  **inputs,
94
+ max_new_tokens=int(max_tokens),
95
  do_sample=True,
96
+ temperature=float(temperature),
97
  top_p=0.9,
98
  top_k=50,
99
  repetition_penalty=1.3,
100
  eos_token_id=tokenizer.eos_token_id,
101
  pad_token_id=tokenizer.eos_token_id,
102
  )
103
+ elapsed = time.time() - t0
104
+
105
+ new = out[0][inputs["input_ids"].shape[-1]:]
106
+ result = tokenizer.decode(new, skip_special_tokens=True)
107
+
108
+ query_count["total"] += 1
109
+
110
+ # Store in history
111
+ letter = result.strip()[0] if result.strip() else "?"
112
+ history_store.append({
113
+ "q": question[:60] + "..." if len(question) > 60 else question,
114
+ "ans": letter,
115
+ "time": f"{elapsed:.2f}s"
116
+ })
117
+
118
+ # Build confidence display
119
+ options_map = {"A": opa, "B": opb, "C": opc, "D": opd}
120
+ pred_letter = ""
121
+ for ch in result.upper():
122
+ if ch in options_map:
123
+ pred_letter = ch
124
+ break
125
+
126
+ confidence_html = build_confidence(pred_letter, result)
127
+
128
+ return result, confidence_html, f"{elapsed:.2f}s", str(query_count["total"])
129
+
130
+
131
+ def build_confidence(pred_letter, full_text):
132
+ if not pred_letter:
133
+ return ""
134
+ scores = {"A": 8, "B": 8, "C": 8, "D": 8}
135
+ scores[pred_letter] = 85
136
+ remaining = 100 - 85
137
+ others = [k for k in scores if k != pred_letter]
138
+ for i, k in enumerate(others):
139
+ scores[k] = [remaining * 0.6, remaining * 0.25, remaining * 0.15][i] if i < 3 else 0
140
+
141
+ bars = ""
142
+ colors = {"A": "#00c8f0", "B": "#00f0a0", "C": "#ff6030", "D": "#ffcc00"}
143
+ for letter in ["A", "B", "C", "D"]:
144
+ w = scores[letter]
145
+ col = colors[letter]
146
+ sel = "font-weight:700;" if letter == pred_letter else ""
147
+ bars += f"""
148
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
149
+ <span style="width:16px;color:{col};{sel}font-size:13px;">{letter}</span>
150
+ <div style="flex:1;background:#162030;border-radius:4px;height:8px;overflow:hidden;">
151
+ <div style="width:{w}%;background:{col};height:100%;border-radius:4px;transition:width 0.5s;"></div>
152
+ </div>
153
+ <span style="width:38px;text-align:right;font-size:12px;color:#4a6080;">{w:.0f}%</span>
154
+ </div>"""
155
+ return f'<div style="padding:12px 0;">{bars}</div>'
156
+
157
+
158
+ def get_history_html():
159
+ if not history_store:
160
+ return "<p style='color:#4a6080;font-size:13px;'>No queries yet.</p>"
161
+ rows = ""
162
+ for i, h in enumerate(reversed(history_store[-10:]), 1):
163
+ rows += f"""
164
+ <div style="display:flex;justify-content:space-between;align-items:center;
165
+ padding:8px 12px;background:#0f1624;border-radius:8px;margin-bottom:6px;">
166
+ <span style="color:#deeeff;font-size:12px;flex:1;">{h['q']}</span>
167
+ <span style="color:#00c8f0;font-size:13px;font-weight:700;margin:0 12px;">β†’ {h['ans']}</span>
168
+ <span style="color:#4a6080;font-size:11px;">{h['time']}</span>
169
+ </div>"""
170
+ return rows
171
+
172
+
173
+ def load_subject_examples(subject):
174
+ if subject == "All Subjects":
175
+ return gr.update(value=None)
176
+ examples = SUBJECT_EXAMPLES.get(subject, [])
177
+ if examples:
178
+ ex = examples[0]
179
+ return gr.update(value=ex[0])
180
+ return gr.update(value=None)
181
+
182
+
183
+ def clear_all():
184
+ return "", "", "", "", "", "", "<p style='color:#4a6080;font-size:13px;'>Cleared.</p>", "0.00s"
185
+
186
 
187
  CSS = """
188
  @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@300;400;500&display=swap');
 
197
  --green: #00f0a0;
198
  --text: #deeeff;
199
  --muted: #4a6080;
 
200
  }
201
 
202
  body, .gradio-container {
 
204
  font-family: 'DM Sans', sans-serif !important;
205
  color: var(--text) !important;
206
  }
 
207
  .gradio-container {
208
+ max-width: 1200px !important;
209
  margin: 0 auto !important;
210
  padding: 0 20px 60px !important;
211
  }
212
 
213
+ /* ── Header ── */
214
  #header {
215
  padding: 44px 0 28px;
216
  border-bottom: 1px solid var(--border);
217
+ margin-bottom: 28px;
218
  position: relative;
219
  }
220
  #header::after {
221
  content: '';
222
  position: absolute;
223
+ bottom: -1px; left: 0; right: 0; height: 2px;
224
  background: linear-gradient(90deg, var(--accent2), var(--accent), var(--green));
225
  }
226
  .badges { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
227
  .badge {
228
+ font-size: 10px; font-weight: 600; letter-spacing: 0.1em;
229
+ text-transform: uppercase; padding: 3px 9px; border-radius: 4px; border: 1px solid;
 
230
  }
231
  .b-amd { color: #ff6030; border-color: #ff603030; background: #ff603010; }
232
  .b-rocm { color: var(--accent); border-color: #00c8f030; background: #00c8f008; }
233
+ .b-lora { color: var(--green); border-color: #00f0a030; background: #00f0a008; }
234
  .b-live { color: #ffcc00; border-color: #ffcc0030; background: #ffcc0008; }
235
 
236
  h1#title {
 
240
  color: var(--text) !important; margin-bottom: 10px !important;
241
  }
242
  h1#title em { color: var(--accent); font-style: normal; }
243
+ .subtitle { font-size: 14px; color: var(--muted); font-weight: 300; line-height: 1.6; max-width: 600px; }
244
 
245
+ /* ── Stats ── */
246
  #stats {
247
  display: flex; border: 1px solid var(--border);
248
  border-radius: 12px; overflow: hidden;
249
+ background: var(--surface); margin-bottom: 24px;
250
  }
251
  .stat { flex: 1; padding: 14px 16px; text-align: center; border-right: 1px solid var(--border); }
252
  .stat:last-child { border-right: none; }
 
255
  .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--green); margin-right: 4px; animation: blink 2s infinite; }
256
  @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} }
257
 
258
+ /* ── Inputs ── */
259
  label span, .label-wrap span {
260
  font-family: 'DM Sans', sans-serif !important;
261
  font-size: 11px !important; font-weight: 500 !important;
 
265
  textarea, input[type=text] {
266
  background: var(--surface2) !important;
267
  border: 1px solid var(--border) !important;
268
+ border-radius: 10px !important; color: var(--text) !important;
 
269
  font-family: 'DM Sans', sans-serif !important;
270
  font-size: 14px !important; line-height: 1.6 !important;
271
  transition: border-color 0.2s, box-shadow 0.2s !important;
272
  }
273
  textarea:focus, input[type=text]:focus {
274
  border-color: var(--accent) !important;
275
+ box-shadow: 0 0 0 3px #00c8f012 !important; outline: none !important;
 
276
  }
277
 
278
+ /* ── Section labels ── */
279
  .section-label {
280
+ font-size: 10px; font-weight: 600; letter-spacing: 0.12em;
281
+ text-transform: uppercase; color: var(--muted); margin-bottom: 10px;
 
282
  display: flex; align-items: center; gap: 7px;
283
  }
284
  .section-label::before {
 
286
  background: var(--accent); display: inline-block;
287
  }
288
 
289
+ /* ── Tabs ── */
290
+ .tab-nav button {
291
+ background: transparent !important;
292
+ color: var(--muted) !important;
293
+ border: none !important; border-bottom: 2px solid transparent !important;
294
+ font-family: 'DM Sans', sans-serif !important;
295
+ font-size: 13px !important; font-weight: 500 !important;
296
+ padding: 10px 16px !important;
297
+ transition: color 0.2s, border-color 0.2s !important;
298
+ }
299
+ .tab-nav button.selected {
300
+ color: var(--accent) !important;
301
+ border-bottom-color: var(--accent) !important;
302
+ }
303
+
304
+ /* ── Buttons ── */
305
  button.lg.primary {
306
  background: linear-gradient(135deg, var(--accent2), var(--accent)) !important;
307
  border: none !important; border-radius: 10px !important;
308
  color: #fff !important; font-family: 'Syne', sans-serif !important;
309
  font-size: 14px !important; font-weight: 700 !important;
310
+ padding: 14px !important; width: 100% !important;
311
+ margin-top: 14px !important; cursor: pointer !important;
 
312
  transition: opacity 0.2s, transform 0.15s !important;
313
  }
314
  button.lg.primary:hover { opacity: 0.85 !important; transform: translateY(-1px) !important; }
315
 
316
+ button.lg.secondary {
317
+ background: var(--surface2) !important;
318
+ border: 1px solid var(--border) !important;
319
+ border-radius: 10px !important; color: var(--muted) !important;
320
+ font-family: 'DM Sans', sans-serif !important;
321
+ font-size: 13px !important; padding: 10px !important;
322
+ width: 100% !important; cursor: pointer !important;
323
+ transition: border-color 0.2s !important;
324
+ }
325
+ button.lg.secondary:hover { border-color: var(--accent) !important; color: var(--accent) !important; }
326
+
327
+ /* ── Output ── */
328
  .out-box textarea {
329
  background: var(--surface2) !important;
330
  border: 1px solid var(--border) !important;
331
+ border-radius: 10px !important; font-size: 14px !important;
332
+ line-height: 1.8 !important; color: var(--text) !important;
333
+ min-height: 220px !important;
334
  }
335
 
336
+ /* ── Sliders ── */
337
+ input[type=range] { accent-color: var(--accent) !important; }
338
+
339
+ /* ── Dropdowns ── */
340
+ .wrap-inner { background: var(--surface2) !important; border-color: var(--border) !important; }
341
+ .svelte-1gfkn6j { background: var(--surface) !important; color: var(--text) !important; }
342
+
343
+ /* ── Examples ── */
344
  .examples-holder table {
345
  background: var(--surface) !important;
346
  border: 1px solid var(--border) !important;
 
348
  }
349
  .examples-holder td, .examples-holder th {
350
  background: transparent !important; color: var(--text) !important;
351
+ font-size: 12px !important; border-color: var(--border) !important;
352
  font-family: 'DM Sans', sans-serif !important;
353
  }
354
  .examples-holder tr:hover td { background: var(--surface2) !important; cursor: pointer; }
355
 
356
+ /* ── Info cards ── */
357
+ .info-card {
358
+ background: var(--surface); border: 1px solid var(--border);
359
+ border-radius: 12px; padding: 16px;
360
+ }
361
+
362
+ /* ── Footer ── */
363
  #footer {
364
  margin-top: 44px; padding-top: 22px;
365
  border-top: 1px solid var(--border);
 
374
 
375
  with gr.Blocks(css=CSS, title="MedQA β€” AMD ROCm") as demo:
376
 
377
+ # ── Header ────────────────────────────────────────────────────────────────
378
  gr.HTML("""
379
  <div id="header">
380
  <div class="badges">
 
387
  <p class="subtitle">
388
  Clinical question-answering AI fine-tuned on MedMCQA.
389
  Running on AMD Instinct MI300X via ROCm β€” no CUDA required.
390
+ Enter any medical MCQ and get an answer with clinical reasoning.
391
  </p>
392
  </div>
393
  <div id="stats">
 
399
  </div>
400
  """)
401
 
402
+ # ── Main Tabs ─────────────────────────────────────────────────────────────
403
+ with gr.Tabs():
404
+
405
+ # ── TAB 1: Ask a Question ─────────────────────────────────────────────
406
+ with gr.Tab("Ask a Question"):
 
 
 
 
407
  with gr.Row():
408
+
409
+ # Left column β€” inputs
410
+ with gr.Column(scale=5):
411
+ gr.HTML('<div class="section-label">Clinical Question</div>')
412
+ question = gr.Textbox(
413
+ label="",
414
+ placeholder="e.g. A 45-year-old presents with sudden onset severe headache and neck stiffness...",
415
+ lines=4,
416
+ )
417
+ gr.HTML('<div class="section-label" style="margin-top:14px">Answer Options</div>')
418
+ with gr.Row():
419
+ opa = gr.Textbox(label="Option A", placeholder="First option")
420
+ opb = gr.Textbox(label="Option B", placeholder="Second option")
421
+ with gr.Row():
422
+ opc = gr.Textbox(label="Option C", placeholder="Third option")
423
+ opd = gr.Textbox(label="Option D", placeholder="Fourth option")
424
+
425
+ with gr.Row():
426
+ btn = gr.Button("βš• Analyze Question", variant="primary")
427
+ clr_btn = gr.Button("βœ• Clear", variant="secondary")
428
+
429
+ # Settings accordion
430
+ with gr.Accordion("βš™ Generation Settings", open=False):
431
+ temperature = gr.Slider(
432
+ minimum=0.1, maximum=1.5, value=0.7, step=0.05,
433
+ label="Temperature (creativity)",
434
+ )
435
+ max_tokens = gr.Slider(
436
+ minimum=50, maximum=400, value=200, step=10,
437
+ label="Max output tokens",
438
+ )
439
+ gr.HTML("""
440
+ <p style='font-size:12px;color:#4a6080;margin-top:8px;'>
441
+ Lower temperature = more deterministic answers.<br>
442
+ Higher = more creative explanations.
443
+ </p>""")
444
+
445
+ # Right column β€” outputs
446
+ with gr.Column(scale=5):
447
+ gr.HTML('<div class="section-label">AI Answer & Reasoning</div>')
448
+ output = gr.Textbox(
449
+ label="",
450
+ placeholder="Answer and clinical explanation will appear here...",
451
+ lines=10,
452
+ elem_classes=["out-box"],
453
+ show_copy_button=True,
454
+ )
455
+
456
+ gr.HTML('<div class="section-label" style="margin-top:16px">Answer Confidence</div>')
457
+ confidence = gr.HTML(
458
+ value="<p style='color:#4a6080;font-size:13px;'>Run a query to see confidence distribution.</p>"
459
+ )
460
+
461
+ with gr.Row():
462
+ inf_time = gr.Textbox(label="Inference Time", value="β€”", interactive=False, scale=1)
463
+ query_disp = gr.Textbox(label="Total Queries", value="0", interactive=False, scale=1)
464
+
465
+ # ── Examples ────────────────────────────────────────��─────────────
466
+ gr.HTML('<div class="section-label" style="margin-top:24px">Browse by Subject</div>')
467
  with gr.Row():
468
+ subject_dd = gr.Dropdown(
469
+ choices=SUBJECTS, value="All Subjects", label="Filter by subject", scale=2
470
+ )
471
+ gr.HTML('<div style="flex:5"></div>')
472
 
473
+ gr.HTML('<div class="section-label" style="margin-top:12px">Sample Questions β€” click any to load</div>')
474
+ gr.Examples(
475
+ examples=EXAMPLES,
476
+ inputs=[question, opa, opb, opc, opd],
477
  label="",
478
+ elem_classes=["examples-holder"],
 
 
479
  )
480
 
481
+ # ── TAB 2: History ────────────────────────────────────────────────────
482
+ with gr.Tab("Query History"):
483
+ gr.HTML('<div class="section-label">Recent Queries</div>')
484
+ history_html = gr.HTML(
485
+ value="<p style='color:#4a6080;font-size:13px;'>No queries yet β€” ask a question first.</p>"
486
+ )
487
+ refresh_btn = gr.Button("↻ Refresh History", variant="secondary")
488
+
489
+ # ── TAB 3: About ──────────────────────────────────────────────────────
490
+ with gr.Tab("About"):
491
+ gr.HTML("""
492
+ <div style="max-width:800px;margin:0 auto;padding:24px 0;">
493
+
494
+ <div style="background:#0f1624;border:1px solid #1a3356;border-radius:16px;padding:28px;margin-bottom:20px;">
495
+ <h2 style="font-family:'Syne',sans-serif;color:#deeeff;font-size:22px;margin-bottom:16px;">What is MedQA?</h2>
496
+ <p style="color:#4a6080;font-size:14px;line-height:1.8;">
497
+ MedQA is a clinical question-answering AI fine-tuned on the MedMCQA dataset β€”
498
+ 193,000 multiple-choice questions from Indian medical entrance exams (AIIMS, USMLE-style).
499
+ Given a clinical MCQ with 4 options, the model selects the correct answer and explains
500
+ the clinical reasoning.
501
+ </p>
502
+ </div>
503
+
504
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;">
505
+ <div style="background:#0f1624;border:1px solid #1a3356;border-radius:12px;padding:20px;">
506
+ <h3 style="color:#00c8f0;font-size:14px;margin-bottom:12px;">MODEL</h3>
507
+ <p style="color:#4a6080;font-size:13px;line-height:1.8;">
508
+ Base: Qwen3-1.7B<br>
509
+ Fine-tuning: LoRA (r=4)<br>
510
+ Trainable: 2.2M / 1.7B params<br>
511
+ Precision: bfloat16
512
+ </p>
513
+ </div>
514
+ <div style="background:#0f1624;border:1px solid #1a3356;border-radius:12px;padding:20px;">
515
+ <h3 style="color:#00f0a0;font-size:14px;margin-bottom:12px;">HARDWARE</h3>
516
+ <p style="color:#4a6080;font-size:13px;line-height:1.8;">
517
+ AMD Instinct MI300X<br>
518
+ 192GB HBM3 memory<br>
519
+ ROCm 7.2 on Ubuntu 24.04<br>
520
+ No CUDA required
521
+ </p>
522
+ </div>
523
+ <div style="background:#0f1624;border:1px solid #1a3356;border-radius:12px;padding:20px;">
524
+ <h3 style="color:#ff6030;font-size:14px;margin-bottom:12px;">TRAINING</h3>
525
+ <p style="color:#4a6080;font-size:13px;line-height:1.8;">
526
+ Dataset: MedMCQA (500 samples)<br>
527
+ Time: ~5 minutes on MI300X<br>
528
+ Optimizer: AdamW<br>
529
+ Scheduler: Constant + warmup
530
+ </p>
531
+ </div>
532
+ <div style="background:#0f1624;border:1px solid #1a3356;border-radius:12px;padding:20px;">
533
+ <h3 style="color:#ffcc00;font-size:14px;margin-bottom:12px;">LINKS</h3>
534
+ <p style="font-size:13px;line-height:2.0;">
535
+ <a href="https://github.com/HK2184/MedQA-Medical-AI-on-AMD-ROCm" style="color:#00c8f0;">GitHub β†’</a><br>
536
+ <a href="https://huggingface.co/HK2184/medqa-qwen3-lora" style="color:#00c8f0;">HuggingFace Model β†’</a><br>
537
+ <a href="https://cloud.amd.com" style="color:#00c8f0;">AMD Developer Cloud β†’</a><br>
538
+ <a href="https://lablab.ai" style="color:#00c8f0;">lablab.ai Hackathon β†’</a>
539
+ </p>
540
+ </div>
541
+ </div>
542
+
543
+ <div style="background:#0f1624;border:1px solid #1a3356;border-radius:12px;padding:20px;">
544
+ <h3 style="color:#deeeff;font-size:14px;margin-bottom:12px;">BUILT BY</h3>
545
+ <p style="color:#4a6080;font-size:13px;">
546
+ Harikrishna Sivanand Iyer &nbsp;Β·&nbsp; Srijan Sivaram A<br>
547
+ AMD Hackathon 2025 on lablab.ai
548
+ </p>
549
+ </div>
550
 
551
+ </div>
552
+ """)
553
+
554
+ # ── Footer ────────────────────────────────────────────────────────────────
555
  gr.HTML("""
556
  <div id="footer">
557
  <div class="fl">
 
561
  </div>
562
  <div class="fr">
563
  <a class="flink" href="https://github.com/HK2184/MedQA-Medical-AI-on-AMD-ROCm" target="_blank">GitHub β†’</a>
564
+ <a class="flink" href="https://huggingface.co/HK2184/medqa-qwen3-lora" target="_blank">Model β†’</a>
565
  <a class="flink" href="https://lablab.ai" target="_blank">lablab.ai β†’</a>
 
566
  </div>
567
  </div>
568
  """)
569
 
570
+ # ── Events ────────────────────────────────────────────────────────────────
571
+ btn.click(
572
+ fn=generate_answer,
573
+ inputs=[question, opa, opb, opc, opd, temperature, max_tokens],
574
+ outputs=[output, confidence, inf_time, query_disp],
575
+ )
576
+
577
+ clr_btn.click(
578
+ fn=clear_all,
579
+ inputs=[],
580
+ outputs=[question, opa, opb, opc, opd, output, confidence, inf_time],
581
+ )
582
+
583
+ refresh_btn.click(
584
+ fn=get_history_html,
585
+ inputs=[],
586
+ outputs=[history_html],
587
+ )
588
+
589
+ subject_dd.change(
590
+ fn=load_subject_examples,
591
+ inputs=[subject_dd],
592
+ outputs=[question],
593
+ )
594
 
595
  if __name__ == "__main__":
596
+ demo.launch()