--- language: - th - en base_model: - google/medgemma-27b-text-it --- # ThaiLLM-27B-Prescreen ThaiLLM-27B-Prescreen is a reinforcement learning fine-tuned version of google/medgemma-27b-text-it, trained specifically for patient pre-screening. Given a patient profile and current symptoms, the model predicts the likely disease, recommends the appropriate hospital department, and estimates clinical severity. ## Training Details The model was trained using Prime-Intellect's [prime-rl](https://github.com/PrimeIntellect-ai/prime-rl) framework ### Data The model was trained using https://huggingface.co/datasets/ThaiLLM/med-prescreen dataset with Prime Intellect's verifier framework. ### Training Configuration This was the prime-rl configuration used to train the model ```toml max_steps = 500 seq_len = 16384 [deployment] type = "single_node" num_train_gpus = 2 num_infer_gpus = 6 [inference.parallel] dp = 6 [trainer.model] attn = "flash_attention_3" optimization_dtype = "bfloat16" reduce_dtype = "bfloat16" [trainer.model.lora] rank = 64 alpha = 128 [trainer.model.ac] [trainer.optim] lr = 5e-5 [orchestrator] batch_size = 512 rollouts_per_example = 16 num_train_workers = 2 [orchestrator.wandb.log_extras] samples = true interval = 1 [orchestrator.sampling] max_tokens = 8192 [[orchestrator.env]] id = "prescreen_classification" name = "prescreen_classification" [ckpt] interval = 50 keep_interval = 50 ``` ### Reward Functions The environment was developed following the `verifiers` framework with the following reward functions with the following weights for each reward `[2.0, 1.0, 1.0, 0.3]` ```python async def disease_reward(completion, answer): response = completion[-1]["content"] predicted = _extract_tag(response, "disease") if predicted is None: return 0.0 predicted = predicted.lower() true_disease = answer.get("disease", "").lower() if predicted == true_disease: return 1.0 return 0.0 async def department_reward(completion, answer): response = completion[-1]["content"] predicted = _extract_tag(response, "department").lower() if predicted is None: return 0.0 answer = answer.get("department", "").lower() return 1.0 if predicted == answer else 0.0 async def severity_reward(completion, answer): response = completion[-1]["content"] predicted = _extract_tag(response, "severity").lower() if predicted is None: return 0.0 answer = answer.get("severity", "").lower() return 1.0 if predicted == answer else 0.0 async def format_reward(completion, answer) -> float: response = completion[-1]["content"] text_without_think = re.sub(r".*?", "", response, flags=re.DOTALL | re.IGNORECASE) # medgemma uses the the token instead of tags = ["disease", "department", "severity"] present = sum(1 for t in tags if f"<{t}>" in text_without_think.lower() and f"" in text_without_think.lower()) return present / len(tags) ``` ## Performance We benchmark against four baselines spanning general-purpose reasoning models (`Qwen3-30B-A3B-Thinking-2507`, `Qwen3-8B`) and medical-domain models (`medgemma-27b-text-it`, `medgemma1.5-4b-it`). ThaiLLM-27B-Prescreen improves disease F1 by +0.448 over its base model (0.287 → 0.735) and outperforms Qwen3-30B-A3B-Thinking-2507 at 0.515. Department routing also improves meaningfully (+0.048 F1 over the base, +0.077 over Qwen3-30B-A3B-Thinking-2507), with the largest gain appearing in accuracy (0.436 → 0.677), suggesting the model is substantially better at picking the single correct department rather than hedging across plausible ones. There is however a severity trade-off, severity F1 is slightly below the base MedGemma-27B (0.571 vs 0.601) and noticeably below Qwen3-30B-A3B-Thinking (0.659). However, ThaiLLM-27B-Prescreen achieves the highest severity accuracy of any model tested (0.799), and the per-class breakdown below shows why the two metrics diverge: the model is strong on the two clinically consequential classes (Emergency and Visit Hospital / Clinic) and fails entirely on Observe at Home. #### Overall Performance (F1) | Model | Disease | Department | Severity | |---|---:|---:|---:| | Qwen3-30B-A3B-Thinking-2507 | 0.515 | 0.464 | **0.659** | | Qwen3-8B | 0.157 | 0.449 | 0.574 | | medgemma1.5-4b-it | 0.095 | 0.424 | 0.525 | | medgemma-27b-text-it | 0.287 | 0.493 | 0.601 | | ThaiLLM-27B-Prescreen | **0.735** | **0.541** | 0.571 | #### Disease Classification | Model | F1 | Precision | Recall | Accuracy | |---|---:|---:|---:|---:| | Qwen3-30B-A3B-Thinking-2507 | 0.515 | 0.562 | 0.509 | 0.510 | | Qwen3-8B | 0.157 | 0.215 | 0.148 | 0.149 | | medgemma1.5-4b-it | 0.095 | 0.131 | 0.082 | 0.076 | | medgemma-27b-text-it | 0.287 | 0.336 | 0.266 | 0.286 | | ThaiLLM-27B-Prescreen | **0.735** | **0.776** | **0.730** | **0.729** | #### Department Classification | Model | F1 | Precision | Recall | Accuracy | |---|---:|---:|---:|---:| | Qwen3-30B-A3B-Thinking-2507 | 0.464 | 0.466 | 0.677 | 0.420 | | Qwen3-8B | 0.449 | 0.419 | 0.648 | 0.358 | | medgemma1.5-4b-it | 0.424 | 0.394 | 0.541 | 0.358 | | medgemma-27b-text-it | 0.493 | 0.469 | **0.678** | 0.436 | | ThaiLLM-27B-Prescreen | **0.541** | **0.606** | 0.518 | **0.677** | #### Severity Classification | Model | F1 | Precision | Recall | Accuracy | |---|---:|---:|---:|---:| | Qwen3-30B-A3B-Thinking-2507 | **0.659** | 0.722 | **0.639** | 0.774 | | Qwen3-8B | 0.574 | **0.858** | 0.601 | 0.771 | | medgemma1.5-4b-it | 0.525 | 0.616 | 0.529 | 0.715 | | medgemma-27b-text-it | 0.601 | 0.835 | 0.609 | 0.755 | | ThaiLLM-27B-Prescreen | 0.571 | 0.548 | 0.599 | **0.799** | | Class | Precision | Recall | F1 | Support | |---|---:|---:|---:|---:| | Emergency | 0.878 | 0.857 | 0.867 | 84 | | Observe At Home | 0.000 | 0.000 | 0.000 | 36 | | Visit Hospital / Clinic | 0.767 | 0.940 | 0.845 | 168 | The model never predicts Observe at Home — those 36 cases are being absorbed into Visit Hospital / Clinic instead. The collapse of the Observe at Home class is a real limitation of the system and should be taken into account when deploying the model. ## Usage The model expects a specific system prompt (specified in `system_prompt.py`) where the list of possible diseases and department can be retrieved from https://github.com/vistec-AI/thaillm-prescreen-rulesets/blob/main/v1/const/diseases.yaml and https://github.com/vistec-AI/thaillm-prescreen-rulesets/blob/main/v1/const/departments.yaml respectively. ### vLLM ```bash uv run --with vllm vllm serve google/medgemma-27b-text-it \ --enable-lora \ --lora-modules prescreen=ThaiLLM/ThaiLLM-27B-Prescreen \ --max-lora-rank 64 ```