Model Details
- Base model: Qwen/Qwen3-8B (8.2B parameters), loaded in 4-bit NF4
- Adapter: LoRA (r=16, alpha=32, dropout=0.05) on all attention and MLP projections (
q/k/v/o_proj, gate/up/down_proj), 43.6M trainable parameters (0.53% of the model)
- Task: Headline → predicted engagement percentile, framed as instruction-following text generation
- Input: A news headline / title (plain text, no additional context required)
- Output:
{"engagement_score": <float from 0 to 1>}. Higher values indicate the model expects stronger engagement.
- Thinking mode: disabled (
enable_thinking=False) during both training and inference
How to Use
This adapter was built to help biotech communicators, PR teams, and researchers gauge in advance how a headline is likely to land with an audience, before publishing.
The prompt must match the training format exactly (same system prompt and user template).
import reimport torchfrom transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfigfrom peft import PeftModel BASE_MODEL = "Qwen/Qwen3-8B"ADAPTER_ID = "Sliva322/Qwen3-8B-biotech-news-virality-QLoRA" SYSTEM_PROMPT = ( "You predict the engagement percentile of a biotechnology news article. " "Return ONLY valid JSON with one key named engagement_score. " "The value must be a decimal number between 0 and 1. " "Do not add explanations, markdown, or extra keys.") tokenizer = AutoTokenizer.from_pretrained(ADAPTER_ID) base = AutoModelForCausalLM.from_pretrained( BASE_MODEL, quantization_config=BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=torch.float16, ), device_map="auto",)model = PeftModel.from_pretrained(base, ADAPTER_ID).eval() def predict_score(title: str) -> float | None: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"Predict the engagement score for this article title.\n\nTitle: {title}"}, ] prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=False ) inputs = tokenizer(prompt, return_tensors="pt").to(model.device) with torch.inference_mode(): out = model.generate( **inputs, max_new_tokens=24, do_sample=False, pad_token_id=tokenizer.pad_token_id ) text = tokenizer.decode(out[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True) m = re.search(r'"engagement_score"\s*:\s*([0-9]*\.?[0-9]+)', text) return float(m.group(1)) if m else None sample_title = "With $263M, LifeMine unearths a new drug for organ transplants"print(f"Title: {sample_title}")print(f"Predicted Engagement Score: {predict_score(sample_title):.4f}")
Requirements: transformers, peft, bitsandbytes>=0.46.1, accelerate, and a CUDA GPU with ~8 GB+ VRAM for 4-bit inference.
Training Details
The training data was built from posts in the r/biotech subreddit, filtered to the "Biotech News 📰" flair. For each post, the title text was paired with a target engagement score derived from the post's upvotes and comment counts.
- Source: r/biotech, "Biotech News 📰" flair
- Features used: Post title only
- Target: Engagement score derived from upvotes and number of comments, rounded to 4 decimals in the JSON answer
- Dataset file: Final training dataset
- Split: chronological, first 80% for training (2051 titles) and last 20% for validation (513 titles)
Supervised fine-tuning setup:
Table with columns: Parameter, Value| Parameter | Value |
|---|
| Quantization | 4-bit NF4, double quantization, fp16 compute |
| LoRA | r=16, alpha=32, dropout=0.05, all linear projections |
| Loss | Causal LM loss on the assistant answer only (prompt tokens masked) |
| Optimizer | paged AdamW 8-bit, lr 2e-4, cosine schedule, 19 warmup steps |
| Batch | micro-batch 1 × gradient accumulation 16 |
| Epochs | 2 (258 steps) |
| Max sequence length | 512 |
| Hardware | 1× NVIDIA T4 (15 GB), ~67 min |
|
Evaluation
Validated on 513 recent biotech news headlines (from February 2026 onward, unseen during training). Predictions were produced with greedy decoding; 100% of generations were valid, parseable JSON.
Table with columns: Model, RMSE, MAE, R², Pearson, Spearman, Accuracy @0.05| Model | RMSE | MAE | R² | Pearson | Spearman | Accuracy @0.05 |
|---|
| Qwen3-8B + this QLoRA adapter | 0.3367 | 0.2463 | -0.2885 | 0.4409 | 0.4515 | 0.2378 |
| Qwen3-8B (zero-shot, same prompt) | 0.3900 | 0.3134 | — | 0.1207 | — |
ROC AUC at threshold = 0.8 (classifying a headline as high-virality):
Table with columns: Model, ROC AUC| Model | ROC AUC |
|---|
| Qwen3-8B + this QLoRA adapter | 0.766 |
| Qwen3-8B (zero-shot) | 0.555 |
| Claude Sonnet 5 (prompt example) | 0.697 |
| bartowski/gemma-2-9b-it-GGUF | 0.565 |
At the 0.8 threshold the adapter reaches precision 0.47, recall 0.49 and F1 0.48 on the high-virality class (97 of 513 headlines).
Fine-tuning raised ROC AUC from 0.555 to 0.766 and Pearson correlation from 0.12 to 0.44 over the same base model. It ranks headlines slightly better than the encoder-based BioModernBERT-news-virality and clearly better than zero-shot general-purpose LLMs.
For questions or issues, please open a discussion on the model's Hugging Face repository page.