ClinAge-4B

ClinAge-4B estimates biological age in years from a text description of a person's routine health data: cohort, chronological age, sex and a list of variable = value measurements. It is a LoRA adapter with a scalar regression head on top of Qwen/Qwen3.5-4B. It outputs one number. It does not generate text.

The model was trained by distillation. For each of four population cohorts and two measurement budgets, a cohort-specific mortality model (the teacher) was first built, and its risk score was mapped onto an age scale. ClinAge-4B learned to reproduce these teacher ages with a single model that reads any of the cohorts' variable lists as text. A fifth cohort (MHAS, Mexico) was held out from training and used to test transfer.

Research use only. ClinAge is not a medical device and has not been validated for diagnosis, prognosis or treatment decisions in individual patients.

Developed by Jinxiang Xie and collaborators
Model type LoRA adapter + linear regression head (SEQ_CLS, num_labels=1)
Base model Qwen/Qwen3.5-4B, revision 851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a
Input Plain-text record (template below), up to 8,192 tokens
Output One scalar, converted to biological age in years
Training cohorts UK Biobank, NHANES, SHARE, CHARLS
Held-out cohort MHAS
License Apache-2.0 (adapter). The base model has its own Apache-2.0 license.
Paper In preparation

Model family

Model Base Adapter size When to use
ClinAge-4B Qwen3.5-4B 170 MB Lower memory. Matches 9B within 0.002 C-index on the four training cohorts.
ClinAge-9B Qwen3.5-9B 233 MB Better transfer to the held-out cohort (+0.017 to +0.018 C-index on MHAS).

Both models share the training data, prompt format, hyperparameters and output scaling. Only the base model differs.

How to use

Prompt format

The model expects the exact text layout used in training. Do not apply a chat template.

Cohort: NHANES
Cost tier: low
Chronological age: 63.00
Sex (1=female): 1
Available variable count: 3
Variables:
BMXWT = 0.4127
BMXHT = -1.032
LBXSAL = 0.2511
Predict the biological age as a single numeric value.
Biological age:

Rules:

  • Cohort is one of UKBB, NHANES, SHARE, CHARLS, MHAS. Cost tier is low or high (see Variable panels).
  • Chronological age uses two decimals. Sex is 1 for female and 0 for male.
  • List the variables of the chosen cohort and tier in the order given in variable_panels.json. Omit missing values. Keep zeros.
  • Numbers are written as integers when integral (below 1e7), otherwise with Python's .4g format.
  • Available variable count is the number of variable lines. Age and sex are not counted.
  • The text ends with \nBiological age: exactly once, with no trailing newline.

Variable values must be preprocessed in the same way as the training data for that cohort. For example, NHANES variables are z-scores computed with fixed training statistics, not raw clinical units. The preprocessing code and statistics are not part of this repository. Values in the example above are synthetic.

Loading and inference

Software versions used for training and evaluation: transformers==5.4.0, peft==0.18.1, torch==2.8.0, accelerate==1.15.0. Qwen3.5 checkpoints are multimodal, so the text backbone must be loaded into a sequence-classification model with a key mapping.

import torch
from peft import PeftModel
from transformers import AutoConfig, AutoModelForSequenceClassification, AutoTokenizer

BASE = "Qwen/Qwen3.5-4B"
BASE_REVISION = "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a"
ADAPTER = "jxtse/ClinAge-4B"
TARGET_MEAN, TARGET_SD = 58.51037624664744, 11.975914444702799

tokenizer = AutoTokenizer.from_pretrained(BASE, revision=BASE_REVISION)
tokenizer.padding_side = "right"
if tokenizer.pad_token_id is None:
    tokenizer.pad_token = tokenizer.eos_token

config = AutoConfig.from_pretrained(BASE, revision=BASE_REVISION)
config = getattr(config, "text_config", config)  # use the text backbone only
config.num_labels = 1
config.problem_type = "regression"
config.pad_token_id = tokenizer.pad_token_id
config.use_cache = False

model = AutoModelForSequenceClassification.from_pretrained(
    BASE,
    revision=BASE_REVISION,
    config=config,
    dtype=torch.bfloat16,
    attn_implementation="sdpa",
    key_mapping={r"^model\.language_model\.": "model."},
).to("cuda")
model.config.pad_token_id = tokenizer.pad_token_id
model = PeftModel.from_pretrained(model, ADAPTER).eval()


def fmt(x):
    x = float(x)
    return str(int(x)) if x.is_integer() and abs(x) < 1e7 else f"{x:.4g}"


def render(cohort, tier, age, sex_female, variables):
    """variables: list of (name, value) in panel order, missing values removed."""
    lines = [
        f"Cohort: {cohort}",
        f"Cost tier: {tier}",
        f"Chronological age: {age:.2f}",
        f"Sex (1=female): {sex_female}",
        f"Available variable count: {len(variables)}",
        "Variables:",
        *[f"{name} = {fmt(value)}" for name, value in variables],
        "Predict the biological age as a single numeric value.",
    ]
    return "\n".join(lines) + "\nBiological age:"


text = render("NHANES", "low", 63.0, 1, [("BMXWT", 0.4127), ("BMXHT", -1.032), ("LBXSAL", 0.2511)])
inputs = tokenizer([text], return_tensors="pt", padding=True, truncation=False).to("cuda")
with torch.inference_mode():
    logit = model(**inputs).logits.float().item()
biological_age = logit * TARGET_SD + TARGET_MEAN
print(f"{biological_age:.1f} years")

The head predicts a standardized target. Always convert with logit * 11.975914444702799 + 58.51037624664744, which is also stored in run_config.json. For batches, keep right padding: the model pools the last non-padding token.

The longest training record was 3,708 tokens (median 914).

Inputs and outputs

Input. One record per sequence, in the template above. Up to 8,192 tokens. Do not truncate. A truncated record silently drops variables.

Output. A single real number in years. It is not clipped. It carries no uncertainty estimate, percentile or mortality probability.

Interpretation. The output approximates the teacher's biological age: the chronological age at which a person's estimated mortality risk would be typical in the training population. A value above chronological age means higher-than-typical estimated risk for that age. The scale was fitted on the training cohorts. In new populations, the ranking of people transfers better than the absolute age values (see Performance).

Variable panels

The teachers were developed under two measurement budgets per cohort. Low-cost panels use inexpensive routine measurements. High-cost panels may include more expensive laboratory and functional tests. Variable names are the cohorts' own field names (for example NHANES LBXSAL is serum albumin). variable_panels.json gives the exact names and training order.

Cohort Country / region Low-cost variables High-cost variables
UKBB United Kingdom 91 230
NHANES United States 41 73
SHARE Europe and Israel 89 95
CHARLS China 30 48
MHAS (held out) Mexico 12 20

The model accepts other variable names as text. Their effect is not validated in general. One test with new MHAS variables is reported below.

Training

Teachers. For each cohort and cost tier, the ClinAge multi-agent system developed a mortality model on the development split, most often an ensemble of survival models. Its risk score was converted to an age scale with a strictly monotone isotonic regression of chronological age on risk, then rescaled to the training age distribution. Training labels are 5-fold out-of-fold teacher scores on the development data.

Data. 405,968 training records from 202,984 participants (UK Biobank 100,000, SHARE 71,805, NHANES 24,542, CHARLS 6,637). Each participant appears twice, once with the low-cost panel and once with the high-cost panel. Validation, test and training splits are disjoint by participant. MHAS was not used for training. No participant-level data are included in this repository. Each cohort must be obtained through its own access process.

Procedure. LoRA on all attention and MLP projections (q,k,v,o,gate,up,down), rank 32, alpha 64, dropout 0.05, plus a fully trained linear head. Mean squared error on the standardized target (mean 58.51 y, SD 11.98 y). One epoch, 25,373 steps, learning rate 1e-4 with cosine decay and 3% warm-up, batch size 16 (4 per GPU on 4 GPUs), bf16, gradient checkpointing, length-grouped sampling, seed 42. The released adapter is the final step. No checkpoint selection was made on test data. Full settings are in run_config.json.

Performance

All numbers below are for the final ClinAge-4B checkpoint on sealed test splits that were not used for training, teacher development or model selection.

All-cause mortality discrimination

Harrell's C-index for all-cause mortality over full follow-up, with 95% bootstrap confidence intervals (500 participant resamples). All scores are computed on the same participants.

Cohort Inputs n (deaths) ClinAge-4B Teacher Pooled XGBoost Chronological age
UKBB low-cost 84,359 (6,884) 0.806 (0.801–0.811) 0.808 (0.803–0.813) 0.792 (0.787–0.797) 0.702 (0.696–0.708)
UKBB high-cost 84,359 (6,884) 0.808 (0.803–0.813) 0.812 (0.807–0.817) 0.797 (0.792–0.803) 0.702 (0.696–0.708)
SHARE low-cost 23,935 (4,301) 0.874 (0.868–0.879) 0.875 (0.870–0.880) 0.854 (0.847–0.860) 0.777 (0.769–0.785)
SHARE high-cost 23,935 (4,301) 0.892 (0.887–0.896) 0.895 (0.890–0.899) 0.868 (0.863–0.873) 0.777 (0.769–0.785)
NHANES low-cost 5,260 (489) 0.878 (0.863–0.894) 0.881 (0.865–0.896) 0.866 (0.849–0.883) 0.833 (0.813–0.851)
NHANES high-cost 5,260 (489) 0.884 (0.867–0.899) 0.885 (0.869–0.900) 0.870 (0.853–0.887) 0.833 (0.814–0.851)
CHARLS low-cost 2,193 (164) 0.807 (0.770–0.844) 0.806 (0.767–0.842) 0.807 (0.772–0.841) 0.775 (0.737–0.808)
CHARLS high-cost 2,193 (164) 0.811 (0.770–0.845) 0.810 (0.771–0.844) 0.808 (0.770–0.839) 0.775 (0.733–0.813)
MHAS (held out) low-cost 402 (40) 0.770 (0.699–0.835) 0.765 (0.683–0.834) 0.740 (0.657–0.813) 0.741 (0.654–0.819)
MHAS (held out) high-cost 402 (40) 0.781 (0.709–0.841) 0.786 (0.708–0.852) 0.740 (0.660–0.813) 0.741 (0.652–0.815)
  • Teacher: the cohort-specific model the student was distilled from. It is cohort- and tier-specific, so it cannot be applied across cohorts.
  • Pooled XGBoost: a single gradient-boosted tree regressor trained on the same inputs, teacher targets and training cohorts as ClinAge.
  • Chronological age: C-index of age alone.

On the four training cohorts, ClinAge-4B stays within 0.005 C-index of its teachers and is above the pooled XGBoost baseline in UKBB, SHARE and NHANES. On held-out MHAS, its C-index is similar to the MHAS teacher (which was developed on MHAS itself) and above the pooled XGBoost baseline. MHAS has only 40 deaths in the test split, so these intervals are wide.

Agreement with the teacher age

Mean absolute difference between ClinAge-4B and teacher biological age on test participants:

  • Training cohorts: 0.50 to 1.24 years across the eight cohort-tier settings.
  • Held-out MHAS: 5.72 to 6.35 years.

The ranking of people transfers to MHAS, but the absolute age scale does not. Re-calibrate the scale before comparing absolute biological ages in a new population.

ClinAge-4B versus ClinAge-9B

Paired difference in C-index (9B minus 4B), same participants and bootstrap:

  • Training cohorts: between -0.002 and +0.001 across the eight settings.
  • MHAS low-cost: +0.018 (95% CI +0.002 to +0.035). MHAS high-cost: +0.017 (+0.0004 to +0.032).

Unseen variables in a held-out cohort

MHAS test participants were scored again after adding 20 MHAS variables (self-rated health, medication use, disease history, falls, mobility and anthropometry), 17 of which never appear in any training record. No retraining was done. As a control, the same variables were added with values shuffled across participants.

MHAS tier C-index, base C-index, + 20 variables Change Change vs shuffled control
low-cost 0.770 0.800 +0.030 (+0.009 to +0.051) +0.029 (+0.001 to +0.059)
high-cost 0.781 0.802 +0.022 (+0.005 to +0.038) +0.018 (-0.006 to +0.044)

Change values are paired bootstrap estimates with 95% CIs (2,000 resamples). This is one cohort with 40 deaths. It shows that the model can use some new variables with descriptive English names. It does not show that arbitrary new variables will help.

Limitations

  • Research use only. Not evaluated prospectively or in clinical settings. Do not use for individual medical decisions.
  • The target is a learned surrogate. Biological age here is a re-expression of an estimated mortality risk. It is not a direct measurement of ageing biology.
  • Preprocessing matters. Values must match each cohort's training preprocessing. Raw units, different units or different coding will produce unreliable outputs without an error.
  • Population coverage. Training data come from four ageing and population cohorts (UK, US, Europe, China). Transfer was tested on one additional cohort (Mexico, 402 test participants). Performance in other populations, clinical cohorts, younger people or other data sources is unknown.
  • Absolute scale. Absolute biological ages shifted by several years in the held-out cohort. Use within-population rankings or re-calibrate.
  • Free-text variables. The model accepts any variable name. Unknown names, units and combinations are not validated.
  • No uncertainty. The model returns a point estimate only.
  • Subgroups. This card does not report performance by sex, age group, ethnicity or socioeconomic status.

Files

File Content
adapter_model.safetensors LoRA weights and the regression head (score, hidden size 2560 β†’ 1), fp32
adapter_config.json PEFT configuration, pinned base revision
run_config.json Training hyperparameters and the output scaling constants
variable_panels.json Ordered variable names for each cohort and cost tier
tokenizer.json, tokenizer_config.json Tokenizer of the base model, unchanged
SHA256SUMS SHA-256 checksums of the files above

Citation

A paper describing ClinAge is in preparation. Citation details will be added here.

Contact

Jinxiang Xie, via the Hugging Face profile or https://jxtse.github.io.

Downloads last month
14
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for jxtse/ClinAge-4B

Finetuned
Qwen/Qwen3.5-4B
Adapter
(819)
this model

Space using jxtse/ClinAge-4B 1

Collection including jxtse/ClinAge-4B