Training Pipeline
Trained on the cometadata/funding-extraction-artifact-data-mix-grpo-mixed-reward dataset using its pre-split sft / rl / test separations on the Tinker training service.
Stage 1: Supervised Fine-Tuning (SFT)
- Base model:
Qwen/Qwen3.5-9B
- Data (
data/sft/): 3,528 real + 7,240 synthetic funding statements with gold-standard funder/award labels (synthetic upsampled 2×)
- Data augmentation: 50% of training examples augmented with synthetic noise (OCR-like case errors, digit/letter swaps, Unicode artifacts, XML/HTML tags, LaTeX markup)
- Renderer:
qwen3_5_disable_thinking (no chain-of-thought; keep thinking disabled at inference, see Usage)
- LoRA rank: 128
- Epochs: 2
- Result: eval NLL 0.116 → 0.0035 over 252 steps
Stage 2: Reinforcement Learning (GRPO)
- Algorithm: Group Relative Policy Optimization (GRPO) with importance sampling loss
- Data (
data/rl/): 1,160 real + 1,916 synthetic (train); 576 real + 968 synthetic (eval)
- Reward: Hierarchical F0.5 scoring with binary funder/award-ID matching + flat award-ID association bonus
reward = 0.50 * funder_F0.5 + 0.40 * hierarchical_award_id_F0.5 + 0.10 * flat_award_id_F0.5
- Funder matching — fuzzy (token_sort_ratio ≥ 0.80 threshold, Hungarian optimal assignment)
- Award ID matching — binary exact after normalization (strip whitespace/hyphens/slashes, uppercase), with soft (edit-distance-1) partial credit during training
- Flat award-ID term — awards partial credit when the correct award ID is extracted under the wrong funder, providing gradient on funder-award association errors
- KL penalty: 0.03 (anchored to SFT checkpoint)
- Group size: 8 rollouts per prompt
- Temperature: 0.8
- 3e-5
Evaluation Results
arxiv_test.jsonl (300 held-out examples)
Permissive (partial_ratio + token_set, no damping)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9384 | 0.9362 | 0.9373 | 0.9379 | 0.9369 |
| Award ID | 0.9069 | 0.8909 | 0.8988 | 0.9037 | 0.8957 |
| Scheme | 0.7407 | 0.8264 |
Balanced (length-damped + acronym detection)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.8882 | 0.8960 | 0.8921 | 0.8897 | 0.8936 |
| Award ID | 0.8889 | 0.8732 | 0.8810 | 0.8857 | 0.8779 |
| Scheme | 0.6889 | 0.7686 |
Strict (token_sort_ratio only)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.8796 | 0.8874 | 0.8835 | 0.8812 | 0.8850 |
| Award ID | 0.8859 | 0.8702 | 0.8780 | 0.8827 | 0.8750 |
| Scheme | 0.6667 | 0.7438 |
All 300 outputs were valid JSON.
Evaluated on the held-out test sets from cometadata/funding-entity-extraction-dataset-mix with the same evaluation harness. test_with_context uses the full_text field (the funding statement with its surrounding document text) as the model input.
test.jsonl (347 examples)
Permissive (partial_ratio + token_set, no damping)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9376 | 0.8923 | 0.9144 | 0.9282 | 0.9058 |
| Award ID | 0.8407 | 0.8339 | 0.8373 | 0.8394 | 0.8360 |
| Scheme | 0.4118 | 0.5927 |
Balanced (length-damped + acronym detection)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9008 | 0.8555 | 0.8776 | 0.8913 | 0.8689 |
| Award ID | 0.8138 | 0.8072 | 0.8105 | 0.8125 | 0.8092 |
| Scheme | 0.3725 | 0.5363 |
Strict (token_sort_ratio only)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.8722 | 0.8276 | 0.8493 | 0.8629 | 0.8408 |
| Award ID | 0.7963 | 0.7898 | 0.7930 | 0.7949 | 0.7918 |
| Scheme | 0.3333 | 0.4798 |
test_degraded.jsonl (1,288 examples)
Permissive (partial_ratio + token_set, no damping)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9285 | 0.9216 | 0.9250 | 0.9271 | 0.9237 |
| Award ID | 0.8586 | 0.8560 | 0.8573 | 0.8581 | 0.8568 |
| Scheme | 0.7413 | 0.6704 |
Balanced (length-damped + acronym detection)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9001 | 0.8906 | 0.8953 | 0.8981 | 0.8935 |
| Award ID | 0.8416 | 0.8390 | 0.8403 | 0.8411 | 0.8398 |
| Scheme | 0.6757 | 0.6110 |
Strict (token_sort_ratio only)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.8801 | 0.8690 | 0.8745 | 0.8778 | 0.8724 |
| Award ID | 0.8317 | 0.8291 | 0.8304 | 0.8312 | 0.8299 |
| Scheme | 0.6039 | 0.5461 |
test_with_context.jsonl (322 examples)
Permissive (partial_ratio + token_set, no damping)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9348 | 0.9383 | 0.9365 | 0.9355 | 0.9372 |
| Award ID | 0.8711 | 0.8690 | 0.8700 | 0.8707 | 0.8696 |
| Scheme | 0.7515 | 0.6844 |
Balanced (length-damped + acronym detection)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.9072 | 0.9061 | 0.9066 | 0.9070 | 0.9064 |
| Award ID | 0.8538 | 0.8517 | 0.8527 | 0.8534 | 0.8523 |
| Scheme | 0.6871 | 0.6257 |
Strict (token_sort_ratio only)
Table with columns: Field, P, R, F1, F0.5, F1.5| Field | P | R | F1 | F0.5 | F1.5 |
|---|
| Funder | 0.8863 | 0.8842 | 0.8852 | 0.8859 | 0.8848 |
| Award ID | 0.8439 | 0.8418 | 0.8428 | 0.8434 | 0.8424 |
| Scheme | 0.6074 | 0.5531 |
Comparison to the Llama 3.1 8B baseline
Balanced-mode F1 on the two test sets reported by the Llama baseline card:
arxiv_test (300 examples)
Table with columns: Field, Llama 3.1 8B, Qwen3.5-9B, Δ| Field | Llama 3.1 8B | Qwen3.5-9B | Δ |
|---|
| Funder | 0.9001 | 0.8921 | −0.008 |
| Award ID | 0.8780 | 0.8810 | +0.003 |
| Scheme | 0.6466 | 0.7266 | +0.080 |
| Title | 0.5316 | 0.5507 | +0.019 |
test_degraded (1,288 examples)
Table with columns: Field, Llama 3.1 8B, Qwen3.5-9B, Δ| Field | Llama 3.1 8B | Qwen3.5-9B | Δ |
|---|
| Funder | 0.8999 | 0.8953 | −0.005 |
| Award ID | 0.8477 | 0.8403 | −0.007 |
| Scheme | 0.6370 | 0.6417 | +0.005 |
| Title | 0.4110 | 0.3011 | −0.110 |
Funder and award ID (the reward-weighted fields) are within 0.008 F1 of the Llama baseline on both sets. Scheme and title carry zero reward weight.
Usage
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-9B")
model = PeftModel.from_pretrained(base_model, "cometadata/funding-extraction-qwen3.5-9B-artifact-data-mix-grpo-mixed-reward")
tokenizer = AutoTokenizer.from_pretrained("cometadata/funding-extraction-qwen3.5-9B-artifact-data-mix-grpo-mixed-reward")
prompt = """Extract funding information from the following statement:
This work was supported by the National Science Foundation under grant DMS-1613002 and by the NIH (R01-AI123456)."""
messages = [
{"role": "system", "content": "You are an expert at extracting structured funding metadata from academic papers. Given a funding statement, extract all funders and their associated awards. Return a JSON array of funder objects. Each funder has:\n- \"funder_name\": string or null\n- \"awards\": array of objects with \"award_ids\" (array of strings), \"funding_scheme\" (array of strings), and \"award_title\" (array of strings)\nReturn ONLY the JSON array, no other text."},
{"role": "user", "content": prompt},
]
inputs = tokenizer.apply_chat_template(
messages, return_tensors="pt", add_generation_prompt=True, enable_thinking=False
)
outputs = model.generate(inputs, max_new_tokens=512, do_sample=False)
print(tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True))
[
{
"funder_name": "National Science Foundation",
"awards": [
{
"award_ids": ["DMS-1613002"],
"funding_scheme": [],
"award_title": []
}
]
},
{
"funder_name": "NIH",
"awards": [
{
"award_ids": ["R01-AI123456"],
"funding_scheme": [],
"award_title": []
}
]
}
]