luguoshan commited on
Commit
4bb52fb
·
1 Parent(s): 250a08e

First model version

Browse files
README_CN.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ling-3.0-flash-singprobe
2
+
3
+ <p align="center"><a href="./README.md">English</a> | <b>中文</b></p>
4
+
5
+ ## 模型简介
6
+
7
+ SingProbe 是基于 `inclusionAI/Ling-3.0-flash` 构建的**内生式流式安全护栏**。它不需要额外运行一个安全模型,而是复用基座模型生成时已计算的隐藏状态,在每个 token 上同时评估**用户意图**、**响应不安全风险**和**幻觉风险**;Decode 阶段额外开销小于 0.5%。
8
+
9
+ | 基座模型 | 探测头参数量 | 采样层 | 输出 |
10
+ | --- | ---: | --- | --- |
11
+ | `inclusionAI/Ling-3.0-flash-singprobe` | 5.18M | `[13, 26, 40]` | 8 类意图 + 不安全 + 幻觉 |
12
+
13
+ 方法与完整实验结果见[技术报告](https://arxiv.org/abs/2608.30703),实现代码见 [inclusionAI/SingProbe](https://github.com/inclusionAI/SingProbe)。
14
+
15
+ ## 核心评测结果
16
+
17
+ 所有指标均为越高越好;结果为下述 benchmark 集的平均值。
18
+
19
+ | 任务 | 指标 | Ling-3.0-flash-singprobe | 参考基线 |
20
+ | --- | --- | ---: | ---: |
21
+ | 用户意图分类(6 个 benchmark) | F1 | **0.8674** | YuFeng-XGuard-Reason-8B:0.8714 |
22
+ | 响应安全分类(8 个 benchmark) | F1 | **0.8728** | Qwen3Guard-Gen-8B-strict:0.8604 |
23
+ | 流式安全检测(3 个 benchmark) | R-AUC / T-AUC | **0.9887 / 0.9481** | Qwen3Guard-Stream-8B-strict:0.9640 / 0.8893 |
24
+ | 幻觉检测(6 个 benchmark) | AUC | **0.8012** | DRIFT:0.8000 |
25
+
26
+ | 部署特性 | 结果 |
27
+ | --- | --- |
28
+ | 良性响应误报率 | 5 个数据集平均 0.03% |
29
+ | 在线自由生成 | leave-one-out 协议下 Accuracy 0.9641 / F1 0.6452 |
30
+ | Decode 开销 | < 0.5% |
31
+
32
+ ## Quick Start
33
+
34
+ SingProbe 仅支持通过 [SGLang 集成分支](https://github.com/jinzhen-lin/sglang/tree/token-probe-ling3-flash-main) 或 [vLLM 集成分支](https://github.com/jinzhen-lin/vllm/tree/bailing-v3-token-probe) 使用。启动服务时通过 Hugging Face ID 加载探测头:
35
+
36
+ ```bash
37
+ python -m sglang.launch_server \
38
+ --model-path inclusionAI/Ling-3.0-flash \
39
+ --probe-ckpt inclusionAI/Ling-3.0-flash-singprobe \
40
+ --port 30000
41
+ ```
42
+
43
+ 集成会为每个生成 token 返回一份分数字典(`label_0`–`label_9`),目前仅支持 Ling-3.0 的 `BailingMoeV3ForCausalLM`。请严格匹配基座模型与探测头:本检查点仅用于 `inclusionAI/Ling-3.0-flash`。
44
+
45
+ ## Citation
46
+
47
+ ```bibtex
48
+ @article{singteam2026singprobe,
49
+ title = {SingProbe Technical Report},
50
+ author = {Sing Team},
51
+ journal = {arXiv preprint arXiv:2608.30703},
52
+ year = {2026},
53
+ }
54
+ ```
config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SingProbeAttnModel"
4
+ ],
5
+ "model_type": "sing_probe_attn",
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_sing_probe.SingProbeAttnConfig",
8
+ "AutoModel": "modeling_sing_probe.SingProbeAttnModel"
9
+ },
10
+ "hidden_size": 2560,
11
+ "base_model_layer_ids": [
12
+ 13,
13
+ 26,
14
+ 40
15
+ ],
16
+ "num_attention_heads": 8,
17
+ "head_dim": 64,
18
+ "sliding_window": 2048,
19
+ "num_labels": 10,
20
+ "base_model_name": "inclusionAI/Ling-3.0-flash",
21
+ "torch_dtype": "bfloat16"
22
+ }
configuration_sing_probe.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+
4
+ class SingProbeMlpConfig(PretrainedConfig):
5
+ model_type = "sing_probe_mlp"
6
+
7
+ def __init__(
8
+ self,
9
+ hidden_size: int = 2560,
10
+ base_model_layer_ids: list[int] | None = None,
11
+ intermediate_size: int = 1024,
12
+ num_labels: int = 10,
13
+ hidden_act: str = "gelu",
14
+ base_model_name: str | None = None,
15
+ **kwargs,
16
+ ) -> None:
17
+ super().__init__(**kwargs)
18
+ self.hidden_size = int(hidden_size)
19
+ self.base_model_layer_ids = base_model_layer_ids or []
20
+ self.intermediate_size = int(intermediate_size)
21
+ self.num_labels = int(num_labels)
22
+ self.hidden_act = hidden_act
23
+ self.base_model_name = base_model_name
24
+
25
+ @property
26
+ def input_size(self) -> int:
27
+ return self.hidden_size * len(self.base_model_layer_ids)
28
+
29
+
30
+ class SingProbeAttnConfig(PretrainedConfig):
31
+ model_type = "sing_probe_attn"
32
+
33
+ def __init__(
34
+ self,
35
+ hidden_size: int = 2560,
36
+ base_model_layer_ids: list[int] | None = None,
37
+ num_attention_heads: int = 4,
38
+ head_dim: int = 64,
39
+ sliding_window: int | None = None,
40
+ num_labels: int = 10,
41
+ base_model_name: str | None = None,
42
+ **kwargs,
43
+ ) -> None:
44
+ super().__init__(**kwargs)
45
+ self.hidden_size = int(hidden_size)
46
+ self.base_model_layer_ids = base_model_layer_ids or []
47
+ self.num_attention_heads = int(num_attention_heads)
48
+ self.head_dim = int(head_dim)
49
+ self.sliding_window = None if sliding_window is None else int(sliding_window)
50
+ self.num_labels = int(num_labels)
51
+ self.base_model_name = base_model_name
52
+
53
+ @property
54
+ def input_size(self) -> int:
55
+ return self.hidden_size * len(self.base_model_layer_ids)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ba29c8895a29b2715519a2b9c6c784da0fa420e8b1bc1e21ec3bf8567060bcf
3
+ size 10366572
modeling_sing_probe.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+ from transformers import PreTrainedModel
7
+ from transformers.activations import ACT2FN
8
+ from transformers.modeling_outputs import TokenClassifierOutput
9
+
10
+ from .configuration_sing_probe import SingProbeAttnConfig, SingProbeMlpConfig
11
+
12
+
13
+ class SingProbePreTrainedModel(PreTrainedModel):
14
+ base_model_prefix = ""
15
+ main_input_name = "hidden_states"
16
+
17
+ def _init_weights(self, module: nn.Module) -> None:
18
+ if isinstance(module, nn.Linear):
19
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
20
+ if module.bias is not None:
21
+ nn.init.zeros_(module.bias)
22
+ elif isinstance(module, nn.RMSNorm):
23
+ nn.init.ones_(module.weight)
24
+
25
+ @staticmethod
26
+ def _validate_input(config: Any, hidden_states: torch.Tensor) -> None:
27
+ if hidden_states.shape[-1] != config.input_size:
28
+ raise ValueError(
29
+ f"input feature size {hidden_states.shape[-1]} does not match "
30
+ f"hidden_size * number of tapped layers ({config.input_size})"
31
+ )
32
+
33
+
34
+ class SingProbeMlpModel(SingProbePreTrainedModel):
35
+ config_class = SingProbeMlpConfig
36
+
37
+ def __init__(self, config: SingProbeMlpConfig) -> None:
38
+ super().__init__(config)
39
+ self.fc1 = nn.Linear(config.input_size, config.intermediate_size)
40
+ self.fc2 = nn.Linear(config.intermediate_size, config.num_labels)
41
+ self.act_fn = ACT2FN[config.hidden_act]
42
+ self.post_init()
43
+
44
+ def forward(
45
+ self,
46
+ hidden_states: torch.Tensor,
47
+ return_dict: bool | None = None,
48
+ **_: Any,
49
+ ) -> TokenClassifierOutput | tuple[torch.Tensor]:
50
+ self._validate_input(self.config, hidden_states)
51
+ hidden_states = hidden_states.to(self.fc1.weight.dtype)
52
+ hidden_states = self.fc1(hidden_states)
53
+ hidden_states = self.act_fn(hidden_states)
54
+ logits = self.fc2(hidden_states)
55
+ if return_dict is False:
56
+ return (logits,)
57
+ return TokenClassifierOutput(logits=logits)
58
+
59
+
60
+ class SingProbeAttnModel(SingProbePreTrainedModel):
61
+ config_class = SingProbeAttnConfig
62
+
63
+ def __init__(self, config: SingProbeAttnConfig) -> None:
64
+ super().__init__(config)
65
+ if config.num_attention_heads < 1 or config.head_dim < 1:
66
+ raise ValueError("num_attention_heads and head_dim must be positive")
67
+ if config.sliding_window is not None and config.sliding_window <= 0:
68
+ raise ValueError("sliding_window must be positive")
69
+ self.num_attention_heads = config.num_attention_heads
70
+ self.head_dim = config.head_dim
71
+ self.projection_size = self.num_attention_heads * self.head_dim
72
+ self.proj_q = nn.Linear(config.input_size, self.projection_size, bias=False)
73
+ self.proj_k = nn.Linear(config.input_size, self.head_dim, bias=False)
74
+ self.proj_v = nn.Linear(config.input_size, self.head_dim, bias=False)
75
+ self.o_proj = nn.Linear(self.projection_size, self.projection_size, bias=False)
76
+ self.norm = nn.RMSNorm(self.projection_size, eps=1e-6)
77
+ self.classifier = nn.Linear(self.projection_size, config.num_labels)
78
+ self.post_init()
79
+
80
+ def _sliding_window_attention(
81
+ self,
82
+ query: torch.Tensor,
83
+ key: torch.Tensor,
84
+ value: torch.Tensor,
85
+ ) -> torch.Tensor:
86
+ seq_len = query.shape[2]
87
+ window = self.config.sliding_window
88
+ assert window is not None
89
+ outputs = []
90
+ for start in range(0, seq_len, window):
91
+ end = min(start + window, seq_len)
92
+ key_start = max(0, start - window + 1)
93
+ query_positions = torch.arange(start, end, device=query.device)
94
+ key_positions = torch.arange(key_start, end, device=query.device)
95
+ relative_positions = query_positions[:, None] - key_positions
96
+ attention_mask = (relative_positions >= 0) & (relative_positions < window)
97
+ attention_mask = attention_mask[None, None]
98
+ query_block = query[:, :, start:end]
99
+ key_block = key[:, :, key_start:end]
100
+ value_block = value[:, :, key_start:end]
101
+ attention_output = F.scaled_dot_product_attention(
102
+ query_block,
103
+ key_block,
104
+ value_block,
105
+ attn_mask=attention_mask,
106
+ enable_gqa=self.num_attention_heads > 1,
107
+ )
108
+ outputs.append(attention_output)
109
+ return torch.cat(outputs, dim=2)
110
+
111
+ def forward(
112
+ self,
113
+ hidden_states: torch.Tensor,
114
+ return_dict: bool | None = None,
115
+ **_: Any,
116
+ ) -> TokenClassifierOutput | tuple[torch.Tensor]:
117
+ self._validate_input(self.config, hidden_states)
118
+ hidden_states = hidden_states.to(self.classifier.weight.dtype)
119
+ batch_size, seq_len, _ = hidden_states.shape
120
+ query_features = self.proj_q(hidden_states)
121
+ query_shape = (batch_size, seq_len, self.num_attention_heads, self.head_dim)
122
+ kv_shape = (batch_size, seq_len, 1, self.head_dim)
123
+ query = query_features.view(query_shape).transpose(1, 2)
124
+ key = self.proj_k(hidden_states).view(kv_shape).transpose(1, 2)
125
+ value = self.proj_v(hidden_states).view(kv_shape).transpose(1, 2)
126
+ if self.config.sliding_window and self.config.sliding_window < seq_len:
127
+ context = self._sliding_window_attention(query, key, value)
128
+ else:
129
+ context = F.scaled_dot_product_attention(
130
+ query=query,
131
+ key=key,
132
+ value=value,
133
+ is_causal=True,
134
+ enable_gqa=self.num_attention_heads > 1,
135
+ )
136
+ context = context.transpose(1, 2).contiguous()
137
+ context = context.view(batch_size, seq_len, self.projection_size)
138
+ hidden_states = self.o_proj(context) + query_features
139
+ logits = self.classifier(self.norm(hidden_states))
140
+ if return_dict is False:
141
+ return (logits,)
142
+ return TokenClassifierOutput(logits=logits)