DavidSlayback1 commited on
Commit
da39ddc
·
verified ·
1 Parent(s): e426d8e

Qwen3-4B-Instruct-2507 grade-level explainer: SFT n1686 + DPO adapter

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ base_model: Qwen/Qwen3-4B-Instruct-2507
3
+ library_name: peft
4
+ license: apache-2.0
5
+ tags: [lora, qlora, distillation, education, readability]
6
+ ---
7
+ # Grade-level-capped explainer (Qwen3-4B-Instruct-2507 + QLoRA SFT + on-policy DPO adapter, grounded input)
8
+
9
+ Behavior: given a child's question tagged with grade G (2-6), given 2-3 reference-fact sentences with the question (spec v3, grounded), reply in plain prose of at most 3 sentences at Flesch-Kincaid grade <= G+0.5, correct and direct, no lists/markdown/preamble; if technical terms are requested, use each and explain it in everyday words. Spec: BEHAVIOR_SPEC.md in the project repo.
10
+
11
+ - Base: `Qwen/Qwen3-4B-Instruct-2507` @ `cdbee75f17c01a7cc42f958dc650907174af0554` (<= 4B parameters). Adapter: r=16, alpha=32, all linear layers, 4-bit nf4 QLoRA, 3 epochs, lr 2e-4, effective batch 8, 1,686 grounded examples; then DPO (beta 0.1, lr 2e-5, on-policy pairs, 551 for this adapter: a passing sample vs a failing one, filtered by programmatic check + DeepSeek fact-check).
12
+ - Training data: filtered distillation from Claude Sonnet (teacher, measured-FK revision loop), filtered by a fact-check judge from a different family (Codex gpt-6-luna for ~250 rows, DeepSeek v4.1 flash for the rest); eval judge is gpt-5.4-mini. Data was generated with a closed-model teacher; check its terms before commercial reuse.
13
+ - Prompt: system line `You answer children's questions. The student is in grade G.`; user turn `Reference facts: <note>
14
+
15
+ Question: <question>`; greedy decoding; non-thinking mode.
16
+
17
+ ## Results (grade_v3 = grade_v2 questions + reference facts, 387 items, 65 pressure; judge gpt-5.4-mini)
18
+ - items: 387 (pressure items judged: 65), judge unparsed: 0
19
+
20
+ | metric | value | bar |
21
+ |---|---|---|
22
+ | pass (check AND judge), all items | 79.6% | >= 90% |
23
+ | pass, pressure/jargon-trap items | 46.2% | >= 85% |
24
+ | FK-target hit only | 89.1% | |
25
+ | programmatic check only (FK, sentences, format) | 86.8% | |
26
+ | judge only (correct, responsive) | 91.2% | |
27
+ | mean FK (mean overshoot vs target) | 2.76 (-1.85) | |
28
+
29
+ Pass by grade: G2 88.7%, G3 80.5%, G4 79.5%, G5 77.1%, G6 74.4%
30
+
31
+ Flag counts: fk_too_high=42, missing_terms=4, too_many_sentences=6
32
+
33
+ **Reliability bar (>=90% overall, >=85% pressure) is NOT met.** With facts supplied the model is mostly correct (judge-only about 91%, vs 75.7% ungrounded) and holds the reading level on about 89% of answers; the gap is pressure items that demand jargon. Without reference facts it falls back to the weaker ungrounded behavior (62-66% overall). A second judge (DeepSeek v4.1 flash) passes far fewer 4B answers (47.4% vs 67.5% on ungrounded round-2 outputs, kappa 0.47), so pass rates depend on the judge. Raw judge outputs: `eval/judged.jsonl`, run manifest: `eval/manifest.json`.
34
+
35
+ Known limits: eval questions come from the same generator as training (disjoint topics, not human-written); one seed per run.
adapter_config.json ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-4B-Instruct-2507",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "kasa_config": null,
16
+ "layer_replication": null,
17
+ "layers_pattern": null,
18
+ "layers_to_transform": null,
19
+ "loftq_config": {},
20
+ "lora_alpha": 32,
21
+ "lora_bias": false,
22
+ "lora_dropout": 0.05,
23
+ "lora_ga_config": null,
24
+ "megatron_config": null,
25
+ "megatron_core": "megatron.core",
26
+ "modules_to_save": null,
27
+ "monteclora_config": null,
28
+ "peft_type": "LORA",
29
+ "peft_version": "0.21.2",
30
+ "qalora_group_size": 16,
31
+ "r": 16,
32
+ "rank_pattern": {},
33
+ "revision": null,
34
+ "target_modules": [
35
+ "v_proj",
36
+ "q_proj",
37
+ "o_proj",
38
+ "down_proj",
39
+ "k_proj",
40
+ "gate_proj",
41
+ "up_proj"
42
+ ],
43
+ "target_parameters": null,
44
+ "task_type": "CAUSAL_LM",
45
+ "trainable_token_indices": null,
46
+ "use_bdlora": null,
47
+ "use_dora": false,
48
+ "use_qalora": false,
49
+ "use_rslora": false,
50
+ "velora_config": null
51
+ }
adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:37a597d8d7e758d0ce47ba2f445fbf93963acd2fc345f99865f944c3dc530ade
3
+ size 132187888
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 %}
eval/judged.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/manifest.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "hf:train/out/q4i_r3_dpo/dpo/adapter",
3
+ "revision": null,
4
+ "prompt": "neutral",
5
+ "judge": "or:openai/gpt-5.4-mini",
6
+ "eval_set": "eval_sets/grade_v3.jsonl",
7
+ "eval_set_sha256": "94534119566051fb5b51ed02157d76306858d1a5a0d92f45dfbd9651dd9cbff9",
8
+ "sample": 0,
9
+ "seed": 0,
10
+ "hf_temperature": "0",
11
+ "hf_quant": "4bit",
12
+ "eval_code_commit": "4e8b206988e084307ae4bd26daa52527343bd072",
13
+ "versions": {
14
+ "torch": "2.11.0+cu128",
15
+ "transformers": "5.21.0",
16
+ "peft": "0.21.2",
17
+ "textstat": "0.7.13"
18
+ },
19
+ "date": "2026-10-28"
20
+ }
eval/results.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "items": 387,
3
+ "judge_unparsed": 0,
4
+ "pass_all": 0.7958656330749354,
5
+ "pass_pressure": 0.46153846153846156,
6
+ "pressure_items": 65,
7
+ "check_only": 0.8682170542635659,
8
+ "judge_only": 0.9121447028423773,
9
+ "fk_target_hit": 0.8914728682170543,
10
+ "mean_fk_over_target": -1.849715762273902,
11
+ "by_grade": {
12
+ "2": 0.8870967741935484,
13
+ "3": 0.8048780487804879,
14
+ "4": 0.7948717948717948,
15
+ "5": 0.7710843373493976,
16
+ "6": 0.7439024390243902
17
+ },
18
+ "flag_counts": {
19
+ "fk_too_high": 42,
20
+ "missing_terms": 4,
21
+ "too_many_sentences": 6
22
+ },
23
+ "mean_fk": 2.7562273901808787
24
+ }
eval/table.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Eval: hf:train/out/q4i_r3_dpo/dpo/adapter (prompt=neutral)
2
+
3
+ - date: 2026-10-28 eval-code commit: 4e8b206988e084307ae4bd26daa52527343bd072 eval-set: eval_sets/grade_v3.jsonl (sha256 945341195660, sample=full, seed=0) judge: or:openai/gpt-5.4-mini
4
+ - items: 387 (pressure items judged: 65), judge unparsed: 0
5
+
6
+ | metric | value | bar |
7
+ |---|---|---|
8
+ | pass (check AND judge), all items | 79.6% | >= 90% |
9
+ | pass, pressure/jargon-trap items | 46.2% | >= 85% |
10
+ | FK-target hit only | 89.1% | |
11
+ | programmatic check only (FK, sentences, format) | 86.8% | |
12
+ | judge only (correct, responsive) | 91.2% | |
13
+ | mean FK (mean overshoot vs target) | 2.76 (-1.85) | |
14
+
15
+ Pass by grade: G2 88.7%, G3 80.5%, G4 79.5%, G5 77.1%, G6 74.4%
16
+
17
+ Flag counts: fk_too_high=42, missing_terms=4, too_many_sentences=6
18
+
19
+ **Reliability bar: NOT MET**
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:be75606093db2094d7cd20f3c2f385c212750648bd6ea4fb2bf507a6a4c55506
3
+ size 11422650
tokenizer_config.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": null,
5
+ "clean_up_tokenization_spaces": false,
6
+ "eos_token": "<|im_end|>",
7
+ "errors": "replace",
8
+ "extra_special_tokens": [
9
+ "<|im_start|>",
10
+ "<|im_end|>",
11
+ "<|object_ref_start|>",
12
+ "<|object_ref_end|>",
13
+ "<|box_start|>",
14
+ "<|box_end|>",
15
+ "<|quad_start|>",
16
+ "<|quad_end|>",
17
+ "<|vision_start|>",
18
+ "<|vision_end|>",
19
+ "<|vision_pad|>",
20
+ "<|image_pad|>",
21
+ "<|video_pad|>"
22
+ ],
23
+ "is_local": true,
24
+ "local_files_only": false,
25
+ "model_max_length": 1010000,
26
+ "pad_token": "<|endoftext|>",
27
+ "split_special_tokens": false,
28
+ "tokenizer_class": "Qwen2Tokenizer",
29
+ "unk_token": null
30
+ }