Text Classification
Transformers
Safetensors
Chinese
spam-detection
advertising-detection
ad-classification
chinese
modernbert
mmbert
multi-task
four-head
Instructions to use x6nux/laya-ad with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use x6nux/laya-ad with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="x6nux/laya-ad")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("x6nux/laya-ad", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 8,097 Bytes
dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 f8d2531 dd18e84 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | #!/usr/bin/env python3
"""单编码器双头模型:共享 ModernBERT encoder,两个任务头各自保留自己的 prediction head。
- binary branch: head + classifier 来自 v1.1(768 → 2)
- kind branch: head + classifier 来自已有 7 类模型(768 → 7)
- 一次前向共享 encoder,两个 head 只多约 0.6M 参数;总参数 ≈ 一个 322M 编码器,
而不是两个完整模型(约 644M)。
"""
import copy
import json
from pathlib import Path
import torch
import torch.nn as nn
from safetensors import safe_open
from safetensors.torch import load_file
from transformers import ModernBertConfig, ModernBertForSequenceClassification
INT4_GROUP = 64
class DualHeadModernBert(nn.Module):
def __init__(self, base_bin, base_kind, kind_num_labels=7, use_kind_adapter=False,
kind_adapter_scope="pooled", use_scope_head=False, use_severity_head=False):
super().__init__()
self.config = base_bin.config
self.model = base_bin.model
self.drop = base_bin.drop
self.binary_head = base_bin.head
self.binary_classifier = base_bin.classifier
self.kind_head = base_kind.head
self.kind_classifier = base_kind.classifier
assert self.kind_classifier.out_features == kind_num_labels
self.kind_adapter_scope = kind_adapter_scope
h = self.config.hidden_size
if use_kind_adapter:
self.kind_adapter = nn.Sequential(
nn.Linear(h, h), nn.GELU(), nn.Dropout(0.1), nn.Linear(h, h))
nn.init.zeros_(self.kind_adapter[-1].weight)
nn.init.zeros_(self.kind_adapter[-1].bias)
else:
self.kind_adapter = None
if use_scope_head:
self.scope_head = copy.deepcopy(base_kind.head)
self.scope_classifier = nn.Linear(h, 2)
else:
self.scope_head = None
self.scope_classifier = None
if use_severity_head:
self.severity_head = copy.deepcopy(base_kind.head)
self.severity_classifier = nn.Linear(h, 4)
else:
self.severity_head = None
self.severity_classifier = None
def _pool(self, h, attention_mask):
if self.config.classifier_pooling == "cls":
return h[:, 0]
if attention_mask is None:
attention_mask = torch.ones(h.shape[:2], device=h.device, dtype=torch.bool)
return (h * attention_mask.unsqueeze(-1)).sum(1) / attention_mask.sum(1, keepdim=True)
def forward_all(self, input_ids, attention_mask=None, position_ids=None):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask,
position_ids=position_ids)
tokens = outputs[0]
h = self._pool(tokens, attention_mask)
bin_logits = self.binary_classifier(self.drop(self.binary_head(h)))
if self.kind_adapter is None:
hk = h
elif self.kind_adapter_scope == "token":
hk = self._pool(tokens + self.kind_adapter(tokens), attention_mask)
else:
hk = h + self.kind_adapter(h)
kind_logits = self.kind_classifier(self.drop(self.kind_head(hk)))
scope_logits = (self.scope_classifier(self.drop(self.scope_head(h)))
if self.scope_head is not None else None)
severity_logits = (self.severity_classifier(self.drop(self.severity_head(h)))
if self.severity_head is not None else None)
return bin_logits, kind_logits, scope_logits, severity_logits, h
def forward(self, input_ids, attention_mask=None, position_ids=None):
bin_logits, kind_logits, _, _, _ = self.forward_all(
input_ids, attention_mask=attention_mask, position_ids=position_ids)
return bin_logits, kind_logits
def load_dual_head(model_dir, device="cpu", dtype=torch.bfloat16):
model_dir = Path(model_dir)
dc = json.loads((model_dir / "dual_config.json").read_text(encoding="utf-8"))
heads = dc.get("heads") or []
config = ModernBertConfig.from_pretrained(str(model_dir))
cfg_kind = copy.deepcopy(config)
cfg_kind.num_labels = len(dc["kind_labels"])
base_bin = ModernBertForSequenceClassification(config)
base_kind = ModernBertForSequenceClassification(cfg_kind)
model = DualHeadModernBert(base_bin, base_kind, len(dc["kind_labels"]),
use_kind_adapter=bool(dc.get("use_kind_adapter", False)),
kind_adapter_scope=dc.get("kind_adapter_scope", "pooled"),
use_scope_head=("scope" in heads) or bool(dc.get("use_scope_head", False)),
use_severity_head=("severity" in heads) or bool(dc.get("use_severity_head", False)))
state = load_file(str(model_dir / "model.safetensors"))
model.load_state_dict(state)
return model.to(device=device, dtype=dtype).eval()
def _dual_skeleton(model_dir):
model_dir = Path(model_dir)
dc = json.loads((model_dir / "dual_config.json").read_text(encoding="utf-8"))
heads = dc.get("heads") or []
config = ModernBertConfig.from_pretrained(str(model_dir))
cfg_kind = copy.deepcopy(config)
cfg_kind.num_labels = len(dc["kind_labels"])
base_bin = ModernBertForSequenceClassification(config)
base_kind = ModernBertForSequenceClassification(cfg_kind)
model = DualHeadModernBert(base_bin, base_kind, len(dc["kind_labels"]),
use_kind_adapter=bool(dc.get("use_kind_adapter", False)),
kind_adapter_scope=dc.get("kind_adapter_scope", "pooled"),
use_scope_head=("scope" in heads) or bool(dc.get("use_scope_head", False)),
use_severity_head=("severity" in heads) or bool(dc.get("use_severity_head", False)))
return model, dc
def _dtypes(path):
with safe_open(str(path), framework="pt") as f:
return {f.get_slice(k).get_dtype() for k in f.keys()}
def load_dual_head_quantized(model_dir, device="cpu", dtype=torch.bfloat16):
"""加载 fp8 / int8 / int4 变体:反量化成双头模型。"""
model_dir = Path(model_dir)
model, _ = _dual_skeleton(model_dir)
param_shapes = {k: v.shape for k, v in model.state_dict().items()}
raw = load_file(str(model_dir / "model.safetensors"))
state = {}
for k, v in raw.items():
if k.endswith(".__scale__") or k.endswith(".__pad__"):
continue
if v.dtype == torch.float8_e4m3fn:
state[k] = v.to(dtype)
elif v.dtype == torch.int8 and k + ".__scale__" in raw:
scale = raw[k + ".__scale__"].float().view(-1, *([1] * (v.dim() - 1)))
state[k] = (v.float() * scale).to(dtype)
elif v.dtype == torch.uint8 and k + ".__scale__" in raw:
hi = (v >> 4).to(torch.int16) - 8
lo = (v & 0xF).to(torch.int16) - 8
q = torch.stack([hi, lo], dim=-1).reshape(-1).float()
scale = raw[k + ".__scale__"].float()
s_exp = scale.repeat_interleave(INT4_GROUP)[:q.numel()]
w = q * s_exp
pad = int(raw[k + ".__pad__"].item()) if k + ".__pad__" in raw else 0
if pad:
w = w[:-pad]
state[k] = w.reshape(param_shapes[k]).to(dtype)
else:
state[k] = v.to(dtype)
model.load_state_dict(state, strict=True)
return model.to(device=device, dtype=dtype).eval()
def load_dual_head_any(model_dir, device="cpu", dtype=torch.bfloat16):
"""按 safetensors 存储类型自动选择普通/量化加载器。"""
dts = _dtypes(Path(model_dir) / "model.safetensors")
if {"I8", "U8", "F8_E4M3"} & dts:
return load_dual_head_quantized(model_dir, device=device, dtype=dtype)
return load_dual_head(model_dir, device=device, dtype=dtype)
if __name__ == "__main__":
import sys
m = load_dual_head_any(sys.argv[1] if len(sys.argv) > 1 else "outputs/ad-dual/best")
print("加载成功 | 参数", sum(p.numel() for p in m.parameters()) / 1e6, "M")
|