ichetandhembre commited on
Commit
253271d
·
verified ·
1 Parent(s): 7fba164

Add PII-masking SFT LoRA adapter (rank-8, extracted) + model card

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,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ base_model: Qwen/Qwen3-4B-Instruct-2507
3
+ library_name: peft
4
+ license: apache-2.0
5
+ tags:
6
+ - lora
7
+ - peft
8
+ - pii
9
+ - pii-masking
10
+ - qwen3
11
+ ---
12
+
13
+ # Qwen3-4B-Instruct-2507 — PII Masking (SFT LoRA)
14
+
15
+ LoRA adapter that fine-tunes [Qwen/Qwen3-4B-Instruct-2507](https://huggingface.co/Qwen/Qwen3-4B-Instruct-2507)
16
+ to detect personally identifiable information (PII) in text, replace it with `[PII]` tags, and
17
+ return the masked text wrapped in `<masked_output>...</masked_output>`.
18
+
19
+ ## Results
20
+
21
+ Greedy exact-match on a 5,914-row held-out split (provably disjoint from training by input):
22
+
23
+ | Model | Exact-match | Relaxed-match | mean_reward\* |
24
+ |---|---|---|---|
25
+ | Base Qwen3-4B-Instruct-2507 | 0.409 | 0.577 | 0.472 |
26
+ | Prior GRPO (RL) reference | 0.784 | — | — |
27
+ | **This adapter (SFT)** | **0.932** | 0.976 | 0.939 |
28
+
29
+ \*`mean_reward` is the normalized reward `(exact·1.0 + pii_count·0.5 + format·0.1) / 1.6` (max 1.0).
30
+ The Prime Intellect verifier for this task reports the same three components **un-normalized**
31
+ (max 1.6), so e.g. a Prime reward of ~1.50 corresponds to ~0.94 here.
32
+
33
+ Format compliance is already ~1.00 on the base model; the adapter's gain is almost entirely
34
+ learning the exact `[PII]` segmentation convention (it cuts the relaxed-minus-exact gap from
35
+ 0.168 to 0.045).
36
+
37
+ ## Training
38
+
39
+ - **Method:** supervised fine-tuning, completion-only loss (loss on the gold masked answer + EOS only).
40
+ - **LoRA:** r=8, α=16, dropout 0, all-linear (q/k/v/o/gate/up/down projections).
41
+ - **Data:** 5,000 examples from [`AdamLucek/open-pii-masking-en-us-30k`](https://huggingface.co/datasets/AdamLucek/open-pii-masking-en-us-30k) (train split).
42
+ - **Schedule:** 2 epochs, lr 2e-4 cosine, bf16, gradient checkpointing.
43
+
44
+ The published adapter was extracted from the merged SFT checkpoint by a per-layer rank-8 SVD of
45
+ `(W_merged − W_base)`. Base + this adapter reproduces the merged model's held-out exact-match to
46
+ within bf16 rounding (0.9319 vs 0.9325).
47
+
48
+ ## Usage
49
+
50
+ ```python
51
+ from peft import PeftModel
52
+ from transformers import AutoModelForCausalLM, AutoTokenizer
53
+
54
+ base = AutoModelForCausalLM.from_pretrained(
55
+ "Qwen/Qwen3-4B-Instruct-2507", torch_dtype="bfloat16", device_map="auto")
56
+ model = PeftModel.from_pretrained(base, "ichetandhembre/Qwen3-4B-Instruct-2507-PII-SFT-LoRA")
57
+ tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-4B-Instruct-2507")
58
+
59
+ SYSTEM = ("Replace all personally identifiable information (PII) in the text with [PII] tags. "
60
+ "PII includes: names, dates, phone numbers, SSNs, account numbers, addresses, "
61
+ "email addresses. Wrap the masked text in <masked_output>...</masked_output>.")
62
+ msgs = [{"role": "system", "content": SYSTEM},
63
+ {"role": "user", "content": "Hi, this is John Smith, call me at 555-0123."}]
64
+ ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt").to(model.device)
65
+ print(tok.decode(model.generate(ids, max_new_tokens=384, do_sample=False)[0][ids.shape[1]:],
66
+ skip_special_tokens=True))
67
+ ```
68
+
69
+ Or serve base + adapter with vLLM (`enable_lora=True`, `max_lora_rank>=8`).
adapter_config.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 16,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.0,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 8,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "k_proj",
34
+ "q_proj",
35
+ "gate_proj",
36
+ "down_proj",
37
+ "o_proj",
38
+ "up_proj",
39
+ "v_proj"
40
+ ],
41
+ "target_parameters": null,
42
+ "task_type": "CAUSAL_LM",
43
+ "trainable_token_indices": null,
44
+ "use_bdlora": null,
45
+ "use_dora": false,
46
+ "use_qalora": false,
47
+ "use_rslora": false
48
+ }
adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7f839e1ddb6ef2776f02c1f2b13cb2a797a79972622f3b84cf60e8571be67a39
3
+ size 66126768
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 %}
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
+ }