Downloads · 30 days
0
Chtholly17/ClinSeek-35B-A3B-DeepmedSFT-Think
ClinSeek-35B-A3B-DeepmedSFT-Think is a text generation model from Chtholly17. Use it when you need the model to write or continue text. It is set up for transformers. The card lists the license as other.
Full-parameter SFT checkpoints of Qwen/Qwen3.5-35B-A3B on agentic EHR trajectories (Chtholly17/DeepmedSFT), trained with the ClinSeekAgent SFT recipe (https://github.com/UCSC-VLAA/ClinSeekAgent, vendored verl + Megatr…
Downloads · 30 days
0
Access
Public
Updated Sep 13, 2026
Repo size
281 GB
Likes
0
Public
Click a slice to open those files.
.safetensors281 GB · 100%
From the Hugging Face model README
Full-parameter SFT checkpoints of Qwen/Qwen3.5-35B-A3B on agentic EHR trajectories
(Chtholly17/Deepmed_SFT), trained with the ClinSeekAgent SFT recipe
(https://github.com/UCSC-VLAA/ClinSeekAgent, vendored verl + Megatron/mbridge).
The distinguishing feature of this run: the teacher's forced ehr.think tool calls were
folded into native <think>…</think> reasoning before every action turn, so the student
learns to reason in the Qwen3.5 thinking channel instead of emitting a think tool call.
Three training runs share this repo. They use the same recipe and preprocessing; they differ in
which part of Chtholly17/Deepmed_SFT they train on (runs A/B: AgentEHR-Bench prompts;
run C: EHRBench prompts, a different task mix — see Training data).
| Subfolder | Run | Training data | Global step | Epochs seen | Train loss (last 55 steps, mean) | Val loss | Note |
|---|---|---|---|---|---|---|---|
run12_epoch2_step330/ | B (run1 + run2) | 5,286 trajectories | 330 | 2 | 0.213 | 0.274 (step 325) | best validation loss overall — recommended |
epoch2_step164/ | A (run1 only) | 2,628 trajectories | 164 | 2 | 0.223 | 0.316 (step 150) | best of run A |
epoch3_step246/ | A (run1 only) | 2,628 trajectories | 246 | 3 | 0.132 | 0.331 (step 246) | final checkpoint of run A; mild overfitting |
ehrbench6k_epoch2_step344/ | C (EHRBench6k only) | 5,525 trajectories | 344 | 2 | 0.215 | 0.305 (step 325) | best of run C; the checkpoint to use for EHRBench-style risk-prediction / next-event tasks |
Validation sets differ between the runs (run A: 53 random rows; run B: 108 rows from 56 held-out questions; run C: 112 rows from 112 held-out questions), so the val losses are only roughly comparable across runs. Within each run the third epoch lowers train loss to ~0.12-0.13 while val loss rises (A: 0.316 -> 0.331, B: 0.274 -> 0.291, C: 0.305 -> 0.317), so the epoch-2 checkpoints are the ones to use. The epoch-1 checkpoints of all runs and the epoch-3 checkpoints of runs B and C were not uploaded.
Each subfolder is a complete HF checkpoint (Qwen3_5MoeForConditionalGeneration, bf16,
14 safetensors shards, tokenizer, chat_template.jinja, generation_config.json).
Chtholly17/Deepmed_SFT — think-first agentic EHR trajectories generated by
gpt-5.6-sol (reasoning effort high), EHR tools only (16 ehr.* MCP tools, no browser),
temporal policy strict-ehr-runtime-v8. The dataset covers two prompt sets:
AgentEHR3k_..._run1.jsonl, 2,947 successful trajectories, and
..._run2.jsonl, 2,972): the 3,000 prompts of the AgentEHR-Bench train split, six
task families (diagnoses_ccs, procedures_ccs, labevents, microbiologyevents, prescriptions,
transfers), answered with a list of official candidate names. Two independent sampling
passes over the same prompts.
epoch2_step164, epoch3_step246) used run1 only.run12_epoch2_step330) used run1 + run2 (5,919 trajectories; 2,920 prompts
carry two different trajectories, 79 carry one).EHRBench6k_gpt56sol_thinkfirst_strictv8_run1.jsonl, 5,984 successful
trajectories, one per prompt): 45 EHRBench task types in two families. 18 risk-prediction
tasks answered yes/no (ED hospitalization, critical outcomes, ICU transfer within 12 h,
ED re-attendance, inpatient and ICU mortality at 1/2/3/7/14 days, ICU stay > 7/14 days, ICU
readmission, length of stay > 3/7 days, readmission within 30/60 days; 2,999 trajectories),
and 27 decision-making tasks that pick the next clinical event or code from a
<candidate_answers> list given in the prompt (next diagnosis as ICD/CCS, procedures,
prescriptions/ATC, lab / microbiology / chart / input / output / datetime events,
medication reconciliation, transfers, services, radiology, admissions, POE, Pyxis, OMR;
2,985 trajectories). Trajectories average 13.9 assistant turns (max 26).
ehrbench6k_epoch2_step344) used this file only.prepare_clinseek_data.py --success_only --merge_think --strip_think_protocol):
ehr.think is removed together with its
"Thinking Finish" tool response; the response argument text becomes the
reasoning_content of the next assistant turn, which the Qwen3.5 chat template
renders as <think>…</think>. Consecutive think turns are concatenated
(runs A/B: 26,428 think turns folded; run C: 41,707; 0 ehr.think calls remain).REASONING TRACE PROTOCOL paragraph (which instructed the teacher to call
ehr.think first in every round) is stripped from the system prompt. The
- ehr.think: Record your reasoning process line in the tool list is kept, matching
the deployment prompt.<tool_call> / <tool_response> blocks; the provider's own reasoning_content
summaries are discarded.You are a research assistant with access to clinical EHR tools.
**EHR Tools** (for clinical data analysis):
- ehr.load_ehr: Load patient EHR database (must be called first for clinical tasks)
- ehr.get_table_names: List available patient data tables
- ehr.get_column_names: Get table column information
- ehr.get_records_by_time: Query patient records within time range
- ehr.run_sql_query: Execute SQL queries on patient database
- ehr.get_candidates_by_semantic_similarity: Search medical terminology/diagnosis codes
- ehr.get_candidates_by_keyword: Search diagnosis codes by keyword
- ehr.think: Record your reasoning process
- ehr.finish: Submit your final answer
**Important:** No web browsing is available in this setting. Base every
conclusion on the patient data returned by the EHR tools and on your own
clinical knowledge; do not attempt to search the web.
sources=ehr
This is the teacher's system prompt with the REASONING TRACE PROTOCOL paragraph removed.
Only the seven tools above plus ehr.think/ehr.finish are listed, but the trajectories use
all 16 ehr.* MCP tools (e.g. ehr.get_latest_records, ehr.get_candidates_by_fuzzy_matching,
ehr.get_records_by_keyword, ehr.get_event_counts_by_time, ehr.get_table_description,
ehr.get_unique_values, ehr.get_records_by_value). No tools= JSON schema is passed to the
chat template; the model learned the tool names and argument schemas from the trajectories.
The model never calls ehr.think (0 occurrences in the training targets) — its reasoning goes
into the <think> block instead.
<task_instruction>
Your current task is to act as a <role, e.g. clinical microbiologist / diagnostician>.
Your objective is to determine all <targets> for the patient by analyzing their complete
medical history ...
You must find the most likely official <...> candidates using the **`<task>_candidates`**
reference data or semantic matching tools.
...
Submit your final answer through `ehr.finish` as a **list** containing **multiple plausible
<items>**. Each item in the list must be a string representing an official <...> name, and
**must not contain any codes or other additional information**.
</task_instruction>
<patient_info>
Current Time: 2155-08-29 23:59:00
Patient Subject ID: 19573105
</patient_info>
EHRBench6k prompts (run C) have the same three-part shape, with a shorter instruction and a candidate list:
<task_instruction>
Please give the next ED Diagnoses on International Classification of Diseases suggestion for the patiens.
Analyze the patient's EHR records up to the current time to make your prediction.
Do not assume any unseen future events beyond the current time.
When you need medical knowledge or clinical information, use the `browser.search` tool to find authoritative medical information from reliable sources.
Submit your final answer through `ehr.finish`. Your answer must be chosen only from the candidate list below.
</task_instruction>
<patient_info>
Current Time: 2132-05-28 15:15:01
Patient Subject ID: 18185480
</patient_info>
<candidate_answers>
["Oth transient cerebral ischemic attacks and related synd", "Other epilepsy, not intractable, without status epilepticus", ...]
</candidate_answers>
Risk-prediction prompts ask a yes/no question instead (e.g. whether the patient will be admitted
from the ED, or die in the ICU within 7 days) and the candidate list is ["yes", "no"].
Note: the benchmark task text still mentions browser.search; the teacher ignored it (no
browser tool was available, as the system prompt states) and so does the student.
Every assistant turn produced by the model has the shape below (Qwen3.5 chat template,
content is always empty in the training targets — 0 of 2,659 assistant turns contain
free text outside the think block and tool calls):
<|im_start|>assistant
<think>
The available EHR lacks dedicated diagnosis, procedure, note, radiology, and microbiology
event tables, but includes admissions/ED data, labs, medication administration, orders,
transfers, triage, and vital signs. Therefore infection syndromes must be reconstructed from
... I will inspect schemas for admissions, ED stays, triage, vitals, labs, orders,
medication administration, and transfers in parallel; ...
</think>
<tool_call>
<function=ehr.get_column_names>
<parameter=subject_id>
19573105
</parameter>
<parameter=table_name>
admissions
</parameter>
</function>
</tool_call>
<tool_call>
<function=ehr.get_column_names>
<parameter=subject_id>
19573105
</parameter>
<parameter=table_name>
edstays
</parameter>
</function>
</tool_call><|im_end|>
<think> block is the folded teacher reasoning: what the previous results showed, which
hypotheses they raise or rule out, and what to query next. It is present in every assistant
turn (including the first one and the final answer turn).<tool_call> blocks follow in the same turn (median 1, up to 8 parallel calls),
in Qwen3.5's XML-style function-call syntax (<function=name> / <parameter=key>value</parameter>),
which vLLM's qwen3_xml tool parser converts to OpenAI tool_calls.user turns wrapped in <tool_response> ... </tool_response>
(one block per call, in call order), exactly as the Qwen3.5 template renders role: tool
messages.ehr.finish whose only argument is response,
a JSON list of official candidate names (runs A/B), or, for run C, ["yes"] / ["no"] for
risk-prediction tasks and a list of names taken from <candidate_answers> for
decision-making tasks:<think>
... Given the absence of any clinical infection syndrome, invasive cultures, C. difficile
testing, MRSA screening, and serologies would be unnecessary. I will therefore submit only
exact official names for plausible noninvasive respiratory viral screening ...
</think>
<tool_call>
<function=ehr.finish>
<parameter=response>
["Respiratory Viral Antigen Screen", "Respiratory Virus Identification", "DIRECT INFLUENZA A ANTIGEN TEST", "DIRECT INFLUENZA B ANTIGEN TEST", "DIRECT RSV ANTIGEN TEST"]
</parameter>
</function>
</tool_call>
Because the model reasons in the thinking channel, serve it with a reasoning parser
(vLLM: --reasoning-parser qwen3) so the <think> text is returned as reasoning_content
and not mixed into content; use --tool-call-parser qwen3_xml for the tool calls.
| Setting | Value |
|---|---|
| Base model | Qwen/Qwen3.5-35B-A3B (bf16) |
| Objective | full-parameter SFT, multi-turn, native tool-call format |
| Max sequence length | 52,000 tokens, truncation=left, no padding (one sequence per micro-batch) |
| Global batch size | 32 sequences (paper recipe) |
| Micro batch per GPU | 1 (16 gradient-accumulation steps per GPU) |
| Epochs / steps | 3 epochs; run A: 82 steps/epoch, 246 total; run B: 165 steps/epoch, 495 total; run C: 172 steps/epoch, 516 total (drop_last) |
| Optimizer | Megatron distributed Adam, precision-aware, fp32 master weights and states on CPU |
| LR schedule | peak 2e-5, cosine decay to 2e-6, 10 warmup steps (linear from 0) |
| Weight decay / grad clip | 0.1 / 1.0 |
| Adam betas | (0.9, 0.999) — the recipe requests (0.9, 0.95) but the vendored verl Megatron path does not forward betas; recorded here as actually applied |
| MoE aux / z loss | 0.01 / 0.001 |
| Precision | bf16 params and grads, fp32 optimizer states |
| Parallelism | TP=2, EP=2, PP=1, CP=1, sequence parallel, 2x NVIDIA H200 (141 GB) |
| Memory tricks | full activation recompute (1 layer), bf16 logits with chunked vocab-parallel cross-entropy, CPU-offloaded optimizer (~1 TB host RAM) |
| Seed | 42 |
| Validation | 50 val samples every 25 steps |
| Wall clock | run A ~12 h, run B ~24.5 h (about 2.9 min per step); run C ~22 h of training time (2.3-2.9 min per step) spread over two days because of two interruptions (see Caveats) |
Differences from the ClinSeekAgent paper recipe: 2 GPUs instead of 8 (TP2/EP2 instead of TP2/EP8), a different and smaller dataset (Deepmed_SFT think-first trajectories, EHR tools only, instead of the Claude Opus trajectories with browser tools), and the think-folding preprocessing above. Learning rate, batch size, schedule and sequence length follow the paper.
| Steps | Train loss (mean over 55-step blocks) | Val loss checkpoints |
|---|---|---|
| 1-55 | 0.403 | step 25: 0.345, step 50: 0.313 |
| 56-110 | 0.320 | step 75: 0.303, step 100: 0.296 |
| 111-165 (epoch 1 end) | 0.301 | step 125: 0.290, step 150: 0.283 |
| 166-220 | 0.220 | step 175: 0.286, step 200: 0.286 |
| 221-275 | 0.216 | step 225: 0.286, step 250: 0.282, step 275: 0.279 |
| 276-330 (epoch 2 end) | 0.213 | step 300: 0.276, step 325: 0.274 |
| 331-385 | 0.132 | step 350: 0.291, step 375: 0.292 |
| 386-440 | 0.129 | step 400: 0.291, step 425: 0.293 |
| 441-495 (epoch 3 end) | 0.128 | step 450: 0.291, step 475: 0.291, step 495: 0.291 |
| Steps | Train loss (mean over ~57-step blocks) | Val loss checkpoints |
|---|---|---|
| 1-57 | 0.418 | step 25: 0.379, step 50: 0.351 |
| 58-114 | 0.333 | step 75: 0.336, step 100: 0.328 |
| 115-172 (epoch 1 end) | 0.315 | step 125: 0.320, step 150: 0.315 |
| 173-229 | 0.224 | step 175: 0.310, step 200: 0.326, step 225: 0.318 |
| 230-286 | 0.221 | step 250: 0.315, step 275: 0.312 |
| 287-344 (epoch 2 end) | 0.215 | step 300: 0.310, step 325: 0.305 |
| 345-401 | 0.127 | step 350: 0.308, step 375: 0.325, step 400: 0.319 |
| 402-458 | 0.122 | step 425: 0.317, step 450: 0.318 |
| 459-516 (epoch 3 end) | 0.120 | step 475: 0.317, step 500: 0.316, step 516: 0.317 |
Run C was resumed twice (after step 172 and after step 344, see Caveats); steps 173-344 and 345-516 come from the resumed processes.
Train loss (mean over 41-step blocks) and validation loss:
| Steps | Train loss | Val loss checkpoints |
|---|---|---|
| 1-41 | 0.428 | step 25: 0.373 |
| 42-82 (epoch 1 end) | 0.326 | step 50: 0.342, step 75: 0.326 |
| 83-123 | 0.229 | step 100: 0.330 |
| 124-164 (epoch 2 end) | 0.223 | step 125: 0.323, step 150: 0.316 |
| 165-205 | 0.135 | step 175: 0.329, step 200: 0.331 |
| 206-246 (epoch 3 end) | 0.132 | step 225: 0.331, step 246: 0.331 |
from transformers import AutoTokenizer, AutoModelForCausalLM
repo = "Chtholly17/ClinSeek-35B-A3B-DeepmedSFT-Think"
tok = AutoTokenizer.from_pretrained(repo, subfolder="run12_epoch2_step330")
model = AutoModelForCausalLM.from_pretrained(repo, subfolder="run12_epoch2_step330", dtype="bfloat16", device_map="auto")
For vLLM, download the subfolder and serve it with the Qwen3 reasoning parser so the
<think> block is separated from the tool-call content:
hf download Chtholly17/ClinSeek-35B-A3B-DeepmedSFT-Think --include "run12_epoch2_step330/*" --local-dir ./ckpt
vllm serve ./ckpt/run12_epoch2_step330 --reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser qwen3_xml
See Prompt format and Output format above for the exact system prompt and the turn
structure. Use the model with the ClinSeekAgent EHR MCP tools and the system prompt shown above
(or the standard AgentEHR one); do not add the teacher's "call ehr.think first" instruction.
run12_epoch2_step330 (or epoch2_step164 for the run1-only variant) for
AgentEHR-Bench-style tasks and ehrbench6k_epoch2_step344 for EHRBench-style tasks unless
downstream evaluation says otherwise.