CoolFace
Apppublic

lablab-ai-amd-developer-hackathon/MedQA-Medical-AI-on-AMD-ROCm

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