codybum commited on
Commit
7e7cd29
·
verified ·
1 Parent(s): 3939c53

kos-v4 initial release (private, under review)

Browse files
README.md ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # kos-v4-instruct
2
+
3
+ **A 3B general instruction-following + tool-calling model, trained from scratch.** The flagship of the kos-v4
4
+ family. Its instruction ability comes from GRPO reinforcement learning on top of a from-scratch clinical base —
5
+ landing **within ~4 points of Qwen2.5-3B-Instruct on IFEval** while holding strong tool-calling.
6
+
7
+ - **Artifact:** `sft/models/v4_prod2_rl_step1120_hf` (~3.015B params, ~6.4 GB, safetensors)
8
+ - **Base lineage:** kos-v4 from-scratch base → instruction SFT → GRPO RL (prod2 lineage, **step 1120 = the peak**)
9
+ - **Chat template:** ChatML (`<|im_start|>`/`<|im_end|>`, eos = `<|im_end|>`); template bundled in tokenizer_config
10
+ - **Context length:** 24k (whole-document pretraining)
11
+
12
+ ## Evaluations (public benchmarks)
13
+ | benchmark | kos-v4-instruct (step 1120) | Qwen2.5-3B-It | Qwen3-4B-It | kos-v4 base |
14
+ |---|---|---|---|---|
15
+ | **IFEval** AVG4 (EleutherAI lm-eval, `--apply_chat_template`) | **62.7** | 65.4 | 86.6 | 47.7 |
16
+ | — prompt-strict / inst-strict / prompt-loose / inst-loose | 55.8 / 67.0 / 58.4 / 69.7 | — | — | 39.4/54.0/41.2/56.2 |
17
+ | **BFCL** simple / multiple / parallel | **0.81 / 0.73 / 0.62** | — | — | 0.84/0.76/0.65 |
18
+
19
+ **Reading:** IFEval **62.7** is ~2.7 pts below Qwen2.5-3B (65.4) and far above our base (47.7, **+15.0 from RL**).
20
+ Tool-calling stays strong (81/73/62 vs base 84/76/65) — only ~2 points of tax for the instruction gain.
21
+
22
+ ## How it was trained
23
+ 1. **Base** (from scratch): ~180.3B tokens, custom 3B decoder, 32k medical BBPE tokenizer (see family README §1).
24
+ 2. **Instruction SFT** on a curated instruction corpus (ChatML).
25
+ 3. **GRPO RL (verl)** with **verifiable IFEval-style constraint rewards** (programmatic length/format/keyword/
26
+ case/section/JSON checks). Official IFEval by GRPO step: base 47.7 → 400 **58.6** → 520 **59.4** → 800 **59.7**
27
+ → 1040 **61.5** → **1120 62.7 (peak)** → 1160 61.8. We ship the argmax (step 1120); the curve peaks and then
28
+ softens, so further RL isn't warranted.
29
+
30
+ ## Intended use
31
+ General instruction following, structured output, and function/tool calling in clinical-adjacent workflows.
32
+ For grounded record/guideline reading with abstention, use **kos-v4-grounded**; for retrieval, **kos-v4-retriever**.
33
+
34
+ ## Usage
35
+ ```python
36
+ from transformers import AutoModelForCausalLM, AutoTokenizer
37
+ tok = AutoTokenizer.from_pretrained("v4_prod2_rl_step1120_hf")
38
+ model = AutoModelForCausalLM.from_pretrained("v4_prod2_rl_step1120_hf", torch_dtype="bfloat16", device_map="auto")
39
+ msgs = [{"role":"user","content":"List three contraindications for ibuprofen. Answer in exactly 3 bullet points."}]
40
+ ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt").to(model.device)
41
+ print(tok.decode(model.generate(ids, max_new_tokens=256)[0][ids.shape[1]:], skip_special_tokens=True))
42
+ ```
43
+ Serve with vLLM/TGI as a standard `Qwen`-arch causal LM (bf16). Pin **RoPE θ = 25000** on any GGUF/quantized export.
44
+
45
+ ## Limitations
46
+ - **Not a medical-knowledge QA model** (MedQA/MMLU-Med ≈ 30). It follows instructions and calls tools; it does not
47
+ reliably recall parametric medical facts. Ground it with retrieval.
48
+ - IFEval 62.7 trails Qwen2.5-3B (65.4) and Qwen3-4B (86.6); the GRPO curve peaked at step 1120 (1160 softened to 61.8), so 62.7 is this RL run's ceiling.
49
+ - This checkpoint's clinical **grounding/abstention regressed** under IF-RL (RGB neg-reject ~0.7) — use kos-v4-grounded
50
+ when non-fabrication on unanswerable questions is required.
51
+ - Public-benchmark performance is not a substitute for validation on real clinical data.
bad_words_ids.json ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "note": "v4 sink tokens orphaned in this surgical tokenizer; suppress at generation as belt-and-suspenders (surgical tokenizer already stops them appearing as input keys)",
3
+ "bad_words_ids": [
4
+ [
5
+ 509
6
+ ],
7
+ [
8
+ 1082
9
+ ],
10
+ [
11
+ 1093
12
+ ],
13
+ [
14
+ 1641
15
+ ],
16
+ [
17
+ 2965
18
+ ],
19
+ [
20
+ 3275
21
+ ],
22
+ [
23
+ 4126
24
+ ],
25
+ [
26
+ 4835
27
+ ],
28
+ [
29
+ 5301
30
+ ],
31
+ [
32
+ 7666
33
+ ],
34
+ [
35
+ 7980
36
+ ],
37
+ [
38
+ 8129
39
+ ],
40
+ [
41
+ 8208
42
+ ],
43
+ [
44
+ 8477
45
+ ],
46
+ [
47
+ 10191
48
+ ],
49
+ [
50
+ 10851
51
+ ],
52
+ [
53
+ 11470
54
+ ],
55
+ [
56
+ 15211
57
+ ],
58
+ [
59
+ 16321
60
+ ],
61
+ [
62
+ 17796
63
+ ],
64
+ [
65
+ 19001
66
+ ],
67
+ [
68
+ 24821
69
+ ],
70
+ [
71
+ 25066
72
+ ],
73
+ [
74
+ 25080
75
+ ],
76
+ [
77
+ 25401
78
+ ],
79
+ [
80
+ 25790
81
+ ],
82
+ [
83
+ 25998
84
+ ],
85
+ [
86
+ 26515
87
+ ],
88
+ [
89
+ 27144
90
+ ],
91
+ [
92
+ 27202
93
+ ],
94
+ [
95
+ 27821
96
+ ],
97
+ [
98
+ 27885
99
+ ],
100
+ [
101
+ 28028
102
+ ],
103
+ [
104
+ 28432
105
+ ],
106
+ [
107
+ 28659
108
+ ],
109
+ [
110
+ 29294
111
+ ],
112
+ [
113
+ 31270
114
+ ],
115
+ [
116
+ 31294
117
+ ]
118
+ ],
119
+ "ids": [
120
+ 509,
121
+ 1082,
122
+ 1093,
123
+ 1641,
124
+ 2965,
125
+ 3275,
126
+ 4126,
127
+ 4835,
128
+ 5301,
129
+ 7666,
130
+ 7980,
131
+ 8129,
132
+ 8208,
133
+ 8477,
134
+ 10191,
135
+ 10851,
136
+ 11470,
137
+ 15211,
138
+ 16321,
139
+ 17796,
140
+ 19001,
141
+ 24821,
142
+ 25066,
143
+ 25080,
144
+ 25401,
145
+ 25790,
146
+ 25998,
147
+ 26515,
148
+ 27144,
149
+ 27202,
150
+ 27821,
151
+ 27885,
152
+ 28028,
153
+ 28432,
154
+ 28659,
155
+ 29294,
156
+ 31270,
157
+ 31294
158
+ ]
159
+ }
chat_template.jinja ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if tools %}
2
+ {{- '<|im_start|>system\n' }}
3
+ {%- if messages[0].role == 'system' %}
4
+ {{- messages[0].content + '\n\n' }}
5
+ {%- endif %}
6
+ {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
7
+ {%- for tool in tools %}
8
+ {{- "\n" }}
9
+ {{- tool | tojson }}
10
+ {%- endfor %}
11
+ {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
12
+ {%- else %}
13
+ {%- if messages[0].role == 'system' %}
14
+ {{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
15
+ {%- endif %}
16
+ {%- endif %}
17
+ {%- for message in messages %}
18
+ {%- if message.content is string %}
19
+ {%- set content = message.content %}
20
+ {%- else %}
21
+ {%- set content = '' %}
22
+ {%- endif %}
23
+ {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
24
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
25
+ {%- elif message.role == "assistant" %}
26
+ {{- '<|im_start|>' + message.role + '\n' + content }}
27
+ {%- if message.tool_calls %}
28
+ {%- for tool_call in message.tool_calls %}
29
+ {%- if (loop.first and content) or (not loop.first) %}
30
+ {{- '\n' }}
31
+ {%- endif %}
32
+ {%- if tool_call.function %}
33
+ {%- set tool_call = tool_call.function %}
34
+ {%- endif %}
35
+ {{- '<tool_call>\n{"name": "' }}
36
+ {{- tool_call.name }}
37
+ {{- '", "arguments": ' }}
38
+ {%- if tool_call.arguments is string %}
39
+ {{- tool_call.arguments }}
40
+ {%- else %}
41
+ {{- tool_call.arguments | tojson }}
42
+ {%- endif %}
43
+ {{- '}\n</tool_call>' }}
44
+ {%- endfor %}
45
+ {%- endif %}
46
+ {{- '<|im_end|>\n' }}
47
+ {%- elif message.role == "tool" %}
48
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
49
+ {{- '<|im_start|>user' }}
50
+ {%- endif %}
51
+ {{- '\n<tool_response>\n' }}
52
+ {{- content }}
53
+ {{- '\n</tool_response>' }}
54
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
55
+ {{- '<|im_end|>\n' }}
56
+ {%- endif %}
57
+ {%- endif %}
58
+ {%- endfor %}
59
+ {%- if add_generation_prompt %}
60
+ {{- '<|im_start|>assistant\n' }}
61
+ {%- endif %}
config.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3ForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 0,
8
+ "dtype": "bfloat16",
9
+ "eos_token_id": 0,
10
+ "head_dim": 128,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 3072,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 8192,
15
+ "layer_types": [
16
+ "full_attention",
17
+ "full_attention",
18
+ "full_attention",
19
+ "full_attention",
20
+ "full_attention",
21
+ "full_attention",
22
+ "full_attention",
23
+ "full_attention",
24
+ "full_attention",
25
+ "full_attention",
26
+ "full_attention",
27
+ "full_attention",
28
+ "full_attention",
29
+ "full_attention",
30
+ "full_attention",
31
+ "full_attention",
32
+ "full_attention",
33
+ "full_attention",
34
+ "full_attention",
35
+ "full_attention",
36
+ "full_attention",
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention",
42
+ "full_attention",
43
+ "full_attention"
44
+ ],
45
+ "max_position_embeddings": 65536,
46
+ "max_window_layers": 28,
47
+ "model_type": "qwen3",
48
+ "num_attention_heads": 24,
49
+ "num_hidden_layers": 28,
50
+ "num_key_value_heads": 8,
51
+ "pad_token_id": 0,
52
+ "rms_norm_eps": 1e-05,
53
+ "rope_parameters": {
54
+ "rope_theta": 25000.0,
55
+ "rope_type": "default"
56
+ },
57
+ "sliding_window": null,
58
+ "tie_word_embeddings": false,
59
+ "transformers_version": "5.3.0",
60
+ "use_cache": false,
61
+ "use_sliding_window": false,
62
+ "vocab_size": 32000,
63
+ "rope_theta": 25000.0
64
+ }
generation_config.json ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bad_words_ids": [
4
+ [
5
+ 509
6
+ ],
7
+ [
8
+ 1082
9
+ ],
10
+ [
11
+ 1093
12
+ ],
13
+ [
14
+ 1641
15
+ ],
16
+ [
17
+ 2965
18
+ ],
19
+ [
20
+ 3275
21
+ ],
22
+ [
23
+ 4126
24
+ ],
25
+ [
26
+ 4835
27
+ ],
28
+ [
29
+ 5301
30
+ ],
31
+ [
32
+ 7666
33
+ ],
34
+ [
35
+ 7980
36
+ ],
37
+ [
38
+ 8129
39
+ ],
40
+ [
41
+ 8208
42
+ ],
43
+ [
44
+ 8477
45
+ ],
46
+ [
47
+ 10191
48
+ ],
49
+ [
50
+ 10851
51
+ ],
52
+ [
53
+ 11470
54
+ ],
55
+ [
56
+ 15211
57
+ ],
58
+ [
59
+ 16321
60
+ ],
61
+ [
62
+ 17796
63
+ ],
64
+ [
65
+ 19001
66
+ ],
67
+ [
68
+ 24821
69
+ ],
70
+ [
71
+ 25066
72
+ ],
73
+ [
74
+ 25080
75
+ ],
76
+ [
77
+ 25401
78
+ ],
79
+ [
80
+ 25790
81
+ ],
82
+ [
83
+ 25998
84
+ ],
85
+ [
86
+ 26515
87
+ ],
88
+ [
89
+ 27144
90
+ ],
91
+ [
92
+ 27202
93
+ ],
94
+ [
95
+ 27821
96
+ ],
97
+ [
98
+ 27885
99
+ ],
100
+ [
101
+ 28028
102
+ ],
103
+ [
104
+ 28432
105
+ ],
106
+ [
107
+ 28659
108
+ ],
109
+ [
110
+ 29294
111
+ ],
112
+ [
113
+ 31270
114
+ ],
115
+ [
116
+ 31294
117
+ ]
118
+ ],
119
+ "bos_token_id": 0,
120
+ "eos_token_id": 0,
121
+ "output_attentions": false,
122
+ "output_hidden_states": false,
123
+ "pad_token_id": 0,
124
+ "transformers_version": "5.3.0",
125
+ "use_cache": false
126
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2a975d1d43b17a172e69b066d705b7c99d71b9d0953ff50e6abdc64c2d93a4c0
3
+ size 6030760880
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"bos_token": "<|im_end|>", "clean_up_tokenization_spaces": false, "eos_token": "<|im_end|>", "model_max_length": 1000000000000000019884624838656, "pad_token": "<|im_end|>", "tokenizer_class": "PreTrainedTokenizerFast", "unk_token": "<|im_end|>", "add_bos_token": false, "add_eos_token": false, "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {{- messages[0].content + '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0].content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if message.content is string %}\n {%- set content = message.content %}\n {%- else %}\n {%- set content = '' %}\n {%- endif %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}"}