Ihor commited on
Commit
0ffff7f
·
verified ·
1 Parent(s): e82af15

Upload folder using huggingface_hub

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,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: gliner
3
+ pipeline_tag: token-classification
4
+ base_model: Qwen/Qwen3-0.6B
5
+ tags:
6
+ - gliner
7
+ - gliner-streaming
8
+ - named-entity-recognition
9
+ - zero-shot-ner
10
+ - pii
11
+ - privacy
12
+ - multilingual
13
+ language:
14
+ - en
15
+ metrics:
16
+ - precision
17
+ - recall
18
+ - f1
19
+ ---
20
+
21
+ # GLiNER Streaming PII — Qwen3 0.6B
22
+
23
+ An open-label PII detector built with the GLiNER streaming-span architecture and a Qwen3-0.6B causal backbone. It supports regular full-text inference, cached incremental streams, and full-session recomputation.
24
+
25
+ ## Architecture
26
+
27
+ Following the component schema in the [GLiNER architecture docs](https://github.com/urchade/GLiNER/blob/main/docs/add_custom_architecture.md), this checkpoint is composed of:
28
+
29
+ [![GLiNER Streaming Span architecture](assets/gliner-streaming-architecture.svg)](assets/gliner-streaming-architecture.svg)
30
+
31
+ [Open the full-size architecture diagram](assets/gliner-streaming-architecture.svg).
32
+
33
+ ## Install and load
34
+
35
+ ```bash
36
+ pip install "gliner>=0.2.27"
37
+ ```
38
+
39
+ ```python
40
+ import torch
41
+ from gliner import GLiNER
42
+
43
+ device = "cuda" if torch.cuda.is_available() else "cpu"
44
+ dtype = "bf16" if device == "cuda" else "fp32"
45
+
46
+ model = GLiNER.from_pretrained(
47
+ "/path/to/qwen-pii-final", # or the Hugging Face model ID
48
+ load_tokenizer=True,
49
+ map_location=device,
50
+ dtype=dtype,
51
+ ).eval()
52
+
53
+ labels = [
54
+ "person",
55
+ "email address",
56
+ "phone number",
57
+ "street address",
58
+ "credit card number",
59
+ "passport number",
60
+ ]
61
+ ```
62
+
63
+ ## Three inference strategies
64
+
65
+ | Strategy | API | What is computed | Best for |
66
+ |---|---|---|---|
67
+ | **1. Stateless full text** | No `session_id` | Complete input and label prompt | Documents, batches, independent requests |
68
+ | **2. Cached incremental** | `session_id=[id]` | New chunk only; decoder KV, labels, words, and span history are reused | Live chat, ASR, logs, token streams |
69
+ | **3. Full recompute** | `session_id=[id], recompute=True` | Accumulated session plus new chunk; cache and all spans are rebuilt | Final pass, changed labels, correction after drift |
70
+
71
+ ### 1. Stateless full text
72
+
73
+ ```python
74
+ entities = model.predict_entities(
75
+ "Jane Doe can be reached at jane.doe@example.com.",
76
+ labels,
77
+ threshold=0.5,
78
+ )
79
+ ```
80
+
81
+ ### 2. Cached incremental session
82
+
83
+ ```python
84
+ session_id = "call-42"
85
+
86
+ for chunk in [
87
+ "Customer Jane",
88
+ " Doe asked us to call",
89
+ " +1 (415) 555-0132.",
90
+ ]:
91
+ snapshot = model.inference(
92
+ [chunk],
93
+ labels,
94
+ session_id=[session_id],
95
+ threshold=0.5,
96
+ )[0]
97
+ print(snapshot)
98
+ ```
99
+
100
+ ### 3. Full-session recompute
101
+
102
+ ```python
103
+ # The next chunk must be non-empty. This reruns all accumulated text
104
+ # and also permits a changed label set.
105
+ final_labels = labels + ["account number"]
106
+ final_snapshot = model.inference(
107
+ [" Account 12345678 was also mentioned."],
108
+ final_labels,
109
+ session_id=[session_id],
110
+ recompute=True,
111
+ threshold=0.5,
112
+ )[0]
113
+
114
+ model.clear_session(session_id)
115
+ ```
116
+
117
+ Streaming details:
118
+
119
+ - Each call returns the **complete current session snapshot**, not only new entities.
120
+ - Chunks are concatenated verbatim; preserve boundary spaces and punctuation.
121
+ - Offsets refer to the complete accumulated text.
122
+ - Keep labels fixed unless using `recompute=True`; always clear finished sessions.
123
+
124
+ ## Evaluation
125
+
126
+ PIIMB ranking scores are label-agnostic, character-level masking metrics. They are micro-averaged within each task; group rows are unweighted averages across tasks. F2 is primary because it weights recall more heavily. Strict NER F1 also requires matching entity boundaries and type.
127
+
128
+ | Scope / task | Precision | Recall | Masking F1 | Masking F2 | FPR | Strict NER F1 |
129
+ |---|---:|---:|---:|---:|---:|---:|
130
+ | **English average** | 87.36% | 91.55% | 89.18% | **90.53%** | 3.01% | — |
131
+ | **Multilingual average** | 53.84% | 78.32% | 60.45% | **68.21%** | 5.85% | — |
132
+ | `ai4privacy-en` | 94.69% | 95.16% | 94.92% | **95.06%** | 1.52% | 67.99% |
133
+ | `ai4privacy-multi` | 87.34% | 92.96% | 90.07% | **91.78%** | 3.82% | 55.39% |
134
+ | `gretel` | 86.95% | 95.58% | 91.06% | **93.72%** | 5.20% | 67.38% |
135
+ | `mapa-eur-lex` | 20.34% | 63.67% | 30.83% | **44.65%** | 7.88% | 10.91% |
136
+ | `nemotron-pii` | 72.75% | 88.07% | 79.68% | **84.51%** | 4.98% | 68.93% |
137
+ | `privy` | 95.07% | 87.39% | 91.07% | **88.83%** | 0.33% | 81.68% |
138
+
139
+ Configuration: PIIMB v0.3.0, dataset revision `4a13e9ffe6fd0d275efbde8afd4d8d8f1ffc2133`, `sentences` subset, threshold 0.5, bfloat16, evaluated 2026-07-24.
140
+
141
+ The main weakness is multilingual legal and administrative text: `mapa-eur-lex` reaches only 44.65% masking F2. Validate on the target languages, domains, labels, and threshold before deployment.
142
+
143
+ ## References
144
+ - [Blog](https://medium.com/p/11aefa0e4ad8/)
145
+ - [GLiNER paper](https://arxiv.org/abs/2311.08526)
146
+ - [Qwen3 Technical Report](https://arxiv.org/abs/2505.09388)
147
+ - [PIIMB benchmark](https://huggingface.co/datasets/piimb/pii-masking-benchmark)
148
+ - [GLiNER repository](https://github.com/urchade/GLiNER)
assets/gliner-streaming-architecture.svg ADDED
chat_template.jinja ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
18
+ {%- for message in messages[::-1] %}
19
+ {%- set index = (messages|length - 1) - loop.index0 %}
20
+ {%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
21
+ {%- set ns.multi_step_tool = false %}
22
+ {%- set ns.last_query_index = index %}
23
+ {%- endif %}
24
+ {%- endfor %}
25
+ {%- for message in messages %}
26
+ {%- if message.content is string %}
27
+ {%- set content = message.content %}
28
+ {%- else %}
29
+ {%- set content = '' %}
30
+ {%- endif %}
31
+ {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
32
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
33
+ {%- elif message.role == "assistant" %}
34
+ {%- set reasoning_content = '' %}
35
+ {%- if message.reasoning_content is string %}
36
+ {%- set reasoning_content = message.reasoning_content %}
37
+ {%- else %}
38
+ {%- if '</think>' in content %}
39
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
40
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
41
+ {%- endif %}
42
+ {%- endif %}
43
+ {%- if loop.index0 > ns.last_query_index %}
44
+ {%- if loop.last or (not loop.last and reasoning_content) %}
45
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
46
+ {%- else %}
47
+ {{- '<|im_start|>' + message.role + '\n' + content }}
48
+ {%- endif %}
49
+ {%- else %}
50
+ {{- '<|im_start|>' + message.role + '\n' + content }}
51
+ {%- endif %}
52
+ {%- if message.tool_calls %}
53
+ {%- for tool_call in message.tool_calls %}
54
+ {%- if (loop.first and content) or (not loop.first) %}
55
+ {{- '\n' }}
56
+ {%- endif %}
57
+ {%- if tool_call.function %}
58
+ {%- set tool_call = tool_call.function %}
59
+ {%- endif %}
60
+ {{- '<tool_call>\n{"name": "' }}
61
+ {{- tool_call.name }}
62
+ {{- '", "arguments": ' }}
63
+ {%- if tool_call.arguments is string %}
64
+ {{- tool_call.arguments }}
65
+ {%- else %}
66
+ {{- tool_call.arguments | tojson }}
67
+ {%- endif %}
68
+ {{- '}\n</tool_call>' }}
69
+ {%- endfor %}
70
+ {%- endif %}
71
+ {{- '<|im_end|>\n' }}
72
+ {%- elif message.role == "tool" %}
73
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
74
+ {{- '<|im_start|>user' }}
75
+ {%- endif %}
76
+ {{- '\n<tool_response>\n' }}
77
+ {{- content }}
78
+ {{- '\n</tool_response>' }}
79
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
80
+ {{- '<|im_end|>\n' }}
81
+ {%- endif %}
82
+ {%- endif %}
83
+ {%- endfor %}
84
+ {%- if add_generation_prompt %}
85
+ {{- '<|im_start|>assistant\n' }}
86
+ {%- if enable_thinking is defined and enable_thinking is false %}
87
+ {{- '<think>\n\n</think>\n\n' }}
88
+ {%- endif %}
89
+ {%- endif %}
gliner_config.json ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "class_token_index": 151669,
3
+ "decoder_config": {
4
+ "_name_or_path": "Qwen/Qwen3-0.6B",
5
+ "architectures": [
6
+ "Qwen3ForCausalLM"
7
+ ],
8
+ "attention_bias": false,
9
+ "attention_dropout": 0.0,
10
+ "bos_token_id": 151643,
11
+ "chunk_size_feed_forward": 0,
12
+ "dtype": "bfloat16",
13
+ "eos_token_id": 151645,
14
+ "head_dim": 128,
15
+ "hidden_act": "silu",
16
+ "hidden_size": 1024,
17
+ "id2label": {
18
+ "0": "LABEL_0",
19
+ "1": "LABEL_1"
20
+ },
21
+ "initializer_range": 0.02,
22
+ "intermediate_size": 3072,
23
+ "is_encoder_decoder": false,
24
+ "label2id": {
25
+ "LABEL_0": 0,
26
+ "LABEL_1": 1
27
+ },
28
+ "layer_types": [
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
+ "full_attention",
45
+ "full_attention",
46
+ "full_attention",
47
+ "full_attention",
48
+ "full_attention",
49
+ "full_attention",
50
+ "full_attention",
51
+ "full_attention",
52
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention"
57
+ ],
58
+ "max_position_embeddings": 40960,
59
+ "max_window_layers": 28,
60
+ "model_type": "qwen3",
61
+ "num_attention_heads": 16,
62
+ "num_hidden_layers": 28,
63
+ "num_key_value_heads": 8,
64
+ "output_attentions": false,
65
+ "output_hidden_states": false,
66
+ "pad_token_id": null,
67
+ "problem_type": null,
68
+ "return_dict": true,
69
+ "rms_norm_eps": 1e-06,
70
+ "rope_parameters": {
71
+ "rope_theta": 1000000,
72
+ "rope_type": "default"
73
+ },
74
+ "sliding_window": null,
75
+ "tie_word_embeddings": true,
76
+ "use_cache": true,
77
+ "use_sliding_window": false,
78
+ "vocab_size": 151671
79
+ },
80
+ "dropout": 0.3,
81
+ "embed_ent_token": true,
82
+ "encoder_config": null,
83
+ "ent_token": "<<ENT>>",
84
+ "eos_token_id": 151645,
85
+ "fine_tune": true,
86
+ "fuse_layers": false,
87
+ "hidden_size": 1024,
88
+ "id_to_classes": null,
89
+ "label_token": "<<LABEL>>",
90
+ "labels_encoder_config": {
91
+ "attention_probs_dropout_prob": 0.1,
92
+ "bos_token_id": null,
93
+ "eos_token_id": null,
94
+ "hidden_act": "gelu",
95
+ "hidden_dropout_prob": 0.1,
96
+ "hidden_size": 1024,
97
+ "initializer_range": 0.02,
98
+ "intermediate_size": 4096,
99
+ "layer_norm_eps": 1e-07,
100
+ "legacy": true,
101
+ "max_position_embeddings": 512,
102
+ "max_relative_positions": 512,
103
+ "model_type": "deberta-v2",
104
+ "num_attention_heads": 16,
105
+ "num_hidden_layers": 2,
106
+ "pad_token_id": 0,
107
+ "pooler_dropout": 0.0,
108
+ "pooler_hidden_act": "gelu",
109
+ "pooler_hidden_size": 1024,
110
+ "pos_att_type": [
111
+ "p2c",
112
+ "c2p"
113
+ ],
114
+ "position_biased_input": true,
115
+ "relative_attention": true,
116
+ "tie_word_embeddings": true,
117
+ "type_vocab_size": 0,
118
+ "vocab_size": 128100
119
+ },
120
+ "max_cache_length": null,
121
+ "max_len": 8192,
122
+ "max_neg_type_ratio": 1,
123
+ "max_types": 100,
124
+ "max_width": 12,
125
+ "model_name": "Qwen/Qwen3-0.6B",
126
+ "model_type": "gliner_streaming_span",
127
+ "name": "streaming span gliner",
128
+ "neg_spans_ratio": 1.0,
129
+ "num_post_fusion_layers": 1,
130
+ "num_rnn_layers": 0,
131
+ "pad_token_id": 151643,
132
+ "post_fusion_schema": "",
133
+ "precomputed_prompts_mode": null,
134
+ "represent_spans": false,
135
+ "right_context_width": 12,
136
+ "sep_token": "<<SEP>>",
137
+ "sep_token_index": 151670,
138
+ "span_encoder_config": null,
139
+ "span_loss_coef": 1.0,
140
+ "span_mode": "markerV2",
141
+ "subtoken_pooling": "first",
142
+ "token_loss_coef": 1.0,
143
+ "transformers_version": "5.6.2",
144
+ "use_cache": false,
145
+ "vocab_size": 151671,
146
+ "words_splitter_type": "whitespace"
147
+ }
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a7f37c8aa4317954ceaf2c7e83bfa45dd6279d04b589d163f1ecf6f30c3df36f
3
+ size 2706436847
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:293208942fa5cad773139afda2d0c2819f2442d5c8fca7f2ce9d22c326b0881b
3
+ size 11423773
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": 131072,
26
+ "pad_token": "<|endoftext|>",
27
+ "split_special_tokens": false,
28
+ "tokenizer_class": "Qwen2Tokenizer",
29
+ "unk_token": null
30
+ }