Instructions to use OsaurusAI/rampart-mlx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use OsaurusAI/rampart-mlx with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir rampart-mlx OsaurusAI/rampart-mlx
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
initial mlx conversion of rampart pii ner
Browse files- README.md +87 -0
- config.json +99 -0
- demo.py +55 -0
- model.safetensors +3 -0
- rampart_mlx.py +132 -0
- special_tokens_map.json +37 -0
- tokenizer.json +0 -0
- tokenizer_config.json +58 -0
- vocab.txt +0 -0
README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-4.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
library_name: mlx
|
| 6 |
+
pipeline_tag: token-classification
|
| 7 |
+
tags:
|
| 8 |
+
- mlx
|
| 9 |
+
- bert
|
| 10 |
+
- token-classification
|
| 11 |
+
- pii
|
| 12 |
+
- ner
|
| 13 |
+
- privacy
|
| 14 |
+
base_model: nationaldesignstudio/rampart
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# Rampart PII NER — MLX
|
| 18 |
+
|
| 19 |
+
An MLX build of **Rampart**, a compact encoder-only BERT (MiniLM-L6, hidden 384,
|
| 20 |
+
6 layers, ~18.5M params) with a 35-label BIO token-classification head for
|
| 21 |
+
detecting personally identifiable information (PII). Intended for on-device,
|
| 22 |
+
client-side PII redaction on Apple Silicon.
|
| 23 |
+
|
| 24 |
+
This repository ships **float (fp16) MLX weights** in `model.safetensors` plus a
|
| 25 |
+
small self-contained MLX implementation (`rampart_mlx.py`).
|
| 26 |
+
|
| 27 |
+
## Provenance
|
| 28 |
+
|
| 29 |
+
This is an **independent MLX conversion** of the original
|
| 30 |
+
[`nationaldesignstudio/rampart`](https://huggingface.co/nationaldesignstudio/rampart).
|
| 31 |
+
The original is distributed as a 4-bit quantized ONNX export; the float weights
|
| 32 |
+
here were recovered directly from that export (4-bit `MatMulNBits` linears and
|
| 33 |
+
INT8 embeddings dequantized to float) and then stored in MLX `safetensors`.
|
| 34 |
+
|
| 35 |
+
The conversion was verified to reproduce the original ONNX model **exactly**:
|
| 36 |
+
on the validation prompts, MLX vs. ONNX Runtime token-label agreement is
|
| 37 |
+
**100%** with a maximum logit difference of ~1e-5 (floating-point rounding).
|
| 38 |
+
|
| 39 |
+
No third-party MLX port was used in producing these weights.
|
| 40 |
+
|
| 41 |
+
## Labels
|
| 42 |
+
|
| 43 |
+
17 entity types in BIO format (35 classes incl. `O`): `GIVEN_NAME`, `SURNAME`,
|
| 44 |
+
`EMAIL`, `PHONE`, `URL`, `TAX_ID`, `BANK_ACCOUNT`, `ROUTING_NUMBER`,
|
| 45 |
+
`GOVERNMENT_ID`, `PASSPORT`, `DRIVERS_LICENSE`, `BUILDING_NUMBER`, `STREET_NAME`,
|
| 46 |
+
`SECONDARY_ADDRESS`, `CITY`, `STATE`, `ZIP_CODE`.
|
| 47 |
+
|
| 48 |
+
## Usage
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
pip install mlx tokenizers
|
| 52 |
+
python demo.py "My name is John Smith, email john.smith@example.com"
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
```python
|
| 56 |
+
import mlx.core as mx
|
| 57 |
+
from tokenizers import Tokenizer
|
| 58 |
+
from rampart_mlx import load
|
| 59 |
+
|
| 60 |
+
model, cfg = load(".")
|
| 61 |
+
tok = Tokenizer.from_file("tokenizer.json")
|
| 62 |
+
enc = tok.encode("Call me at (555) 123-4567")
|
| 63 |
+
logits = model(mx.array([enc.ids]), mx.array([enc.attention_mask]))
|
| 64 |
+
label_ids = mx.argmax(logits[0], axis=-1).tolist()
|
| 65 |
+
labels = [cfg.id2label[i] for i in label_ids]
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
See `demo.py` for BIO span decoding using the tokenizer's char offsets (needed to
|
| 69 |
+
map predicted labels back onto the original text for redaction).
|
| 70 |
+
|
| 71 |
+
## Files
|
| 72 |
+
|
| 73 |
+
| File | Purpose |
|
| 74 |
+
|------|---------|
|
| 75 |
+
| `model.safetensors` | fp16 MLX weights (HuggingFace-style key names) |
|
| 76 |
+
| `config.json` | model architecture + `id2label` |
|
| 77 |
+
| `rampart_mlx.py` | self-contained MLX model + loader |
|
| 78 |
+
| `demo.py` | tokenize → infer → decode spans |
|
| 79 |
+
| `tokenizer.json`, `vocab.txt`, `tokenizer_config.json`, `special_tokens_map.json` | WordPiece tokenizer |
|
| 80 |
+
|
| 81 |
+
## License & attribution
|
| 82 |
+
|
| 83 |
+
Released under **CC-BY-4.0**, the same license as the upstream model. Attribution:
|
| 84 |
+
|
| 85 |
+
- Original model: [`nationaldesignstudio/rampart`](https://huggingface.co/nationaldesignstudio/rampart) (CC-BY-4.0)
|
| 86 |
+
- Base encoder: [`nreimers/MiniLM-L6-H384-uncased`](https://huggingface.co/nreimers/MiniLM-L6-H384-uncased)
|
| 87 |
+
- Training data: [`ai4privacy/pii-masking-openpii-1.5m`](https://huggingface.co/datasets/ai4privacy/pii-masking-openpii-1.5m) (CC-BY-4.0)
|
config.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"BertForTokenClassification"
|
| 4 |
+
],
|
| 5 |
+
"attention_probs_dropout_prob": 0.1,
|
| 6 |
+
"classifier_dropout": null,
|
| 7 |
+
"dtype": "float32",
|
| 8 |
+
"gradient_checkpointing": false,
|
| 9 |
+
"hidden_act": "gelu",
|
| 10 |
+
"hidden_dropout_prob": 0.1,
|
| 11 |
+
"hidden_size": 384,
|
| 12 |
+
"id2label": {
|
| 13 |
+
"0": "O",
|
| 14 |
+
"1": "B-GIVEN_NAME",
|
| 15 |
+
"2": "I-GIVEN_NAME",
|
| 16 |
+
"3": "B-SURNAME",
|
| 17 |
+
"4": "I-SURNAME",
|
| 18 |
+
"5": "B-EMAIL",
|
| 19 |
+
"6": "I-EMAIL",
|
| 20 |
+
"7": "B-PHONE",
|
| 21 |
+
"8": "I-PHONE",
|
| 22 |
+
"9": "B-URL",
|
| 23 |
+
"10": "I-URL",
|
| 24 |
+
"11": "B-TAX_ID",
|
| 25 |
+
"12": "I-TAX_ID",
|
| 26 |
+
"13": "B-BANK_ACCOUNT",
|
| 27 |
+
"14": "I-BANK_ACCOUNT",
|
| 28 |
+
"15": "B-ROUTING_NUMBER",
|
| 29 |
+
"16": "I-ROUTING_NUMBER",
|
| 30 |
+
"17": "B-GOVERNMENT_ID",
|
| 31 |
+
"18": "I-GOVERNMENT_ID",
|
| 32 |
+
"19": "B-PASSPORT",
|
| 33 |
+
"20": "I-PASSPORT",
|
| 34 |
+
"21": "B-DRIVERS_LICENSE",
|
| 35 |
+
"22": "I-DRIVERS_LICENSE",
|
| 36 |
+
"23": "B-BUILDING_NUMBER",
|
| 37 |
+
"24": "I-BUILDING_NUMBER",
|
| 38 |
+
"25": "B-STREET_NAME",
|
| 39 |
+
"26": "I-STREET_NAME",
|
| 40 |
+
"27": "B-SECONDARY_ADDRESS",
|
| 41 |
+
"28": "I-SECONDARY_ADDRESS",
|
| 42 |
+
"29": "B-CITY",
|
| 43 |
+
"30": "I-CITY",
|
| 44 |
+
"31": "B-STATE",
|
| 45 |
+
"32": "I-STATE",
|
| 46 |
+
"33": "B-ZIP_CODE",
|
| 47 |
+
"34": "I-ZIP_CODE"
|
| 48 |
+
},
|
| 49 |
+
"initializer_range": 0.02,
|
| 50 |
+
"intermediate_size": 1536,
|
| 51 |
+
"label2id": {
|
| 52 |
+
"B-BANK_ACCOUNT": 13,
|
| 53 |
+
"B-BUILDING_NUMBER": 23,
|
| 54 |
+
"B-CITY": 29,
|
| 55 |
+
"B-DRIVERS_LICENSE": 21,
|
| 56 |
+
"B-EMAIL": 5,
|
| 57 |
+
"B-GIVEN_NAME": 1,
|
| 58 |
+
"B-GOVERNMENT_ID": 17,
|
| 59 |
+
"B-PASSPORT": 19,
|
| 60 |
+
"B-PHONE": 7,
|
| 61 |
+
"B-ROUTING_NUMBER": 15,
|
| 62 |
+
"B-SECONDARY_ADDRESS": 27,
|
| 63 |
+
"B-STATE": 31,
|
| 64 |
+
"B-STREET_NAME": 25,
|
| 65 |
+
"B-SURNAME": 3,
|
| 66 |
+
"B-TAX_ID": 11,
|
| 67 |
+
"B-URL": 9,
|
| 68 |
+
"B-ZIP_CODE": 33,
|
| 69 |
+
"I-BANK_ACCOUNT": 14,
|
| 70 |
+
"I-BUILDING_NUMBER": 24,
|
| 71 |
+
"I-CITY": 30,
|
| 72 |
+
"I-DRIVERS_LICENSE": 22,
|
| 73 |
+
"I-EMAIL": 6,
|
| 74 |
+
"I-GIVEN_NAME": 2,
|
| 75 |
+
"I-GOVERNMENT_ID": 18,
|
| 76 |
+
"I-PASSPORT": 20,
|
| 77 |
+
"I-PHONE": 8,
|
| 78 |
+
"I-ROUTING_NUMBER": 16,
|
| 79 |
+
"I-SECONDARY_ADDRESS": 28,
|
| 80 |
+
"I-STATE": 32,
|
| 81 |
+
"I-STREET_NAME": 26,
|
| 82 |
+
"I-SURNAME": 4,
|
| 83 |
+
"I-TAX_ID": 12,
|
| 84 |
+
"I-URL": 10,
|
| 85 |
+
"I-ZIP_CODE": 34,
|
| 86 |
+
"O": 0
|
| 87 |
+
},
|
| 88 |
+
"layer_norm_eps": 1e-12,
|
| 89 |
+
"max_position_embeddings": 512,
|
| 90 |
+
"model_type": "bert",
|
| 91 |
+
"num_attention_heads": 12,
|
| 92 |
+
"num_hidden_layers": 6,
|
| 93 |
+
"pad_token_id": 0,
|
| 94 |
+
"position_embedding_type": "absolute",
|
| 95 |
+
"transformers_version": "4.57.6",
|
| 96 |
+
"type_vocab_size": 2,
|
| 97 |
+
"use_cache": true,
|
| 98 |
+
"vocab_size": 19730
|
| 99 |
+
}
|
demo.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Minimal Rampart-MLX demo: tokenize, run the model, decode BIO spans.
|
| 3 |
+
|
| 4 |
+
python demo.py "My name is John Smith, email john@example.com"
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
import mlx.core as mx
|
| 10 |
+
from tokenizers import Tokenizer
|
| 11 |
+
|
| 12 |
+
from rampart_mlx import load
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def decode_spans(tokens, offsets, label_ids, id2label):
|
| 16 |
+
"""Merge B-/I- runs into (entity_type, text, start, end) spans."""
|
| 17 |
+
spans, cur = [], None
|
| 18 |
+
for (start, end), lid in zip(offsets, label_ids):
|
| 19 |
+
if start == end: # special token
|
| 20 |
+
continue
|
| 21 |
+
label = id2label[lid]
|
| 22 |
+
if label == "O":
|
| 23 |
+
cur = None
|
| 24 |
+
continue
|
| 25 |
+
tag, etype = label[0], label[2:]
|
| 26 |
+
if tag == "B" or cur is None or cur["type"] != etype:
|
| 27 |
+
cur = {"type": etype, "start": start, "end": end}
|
| 28 |
+
spans.append(cur)
|
| 29 |
+
else:
|
| 30 |
+
cur["end"] = end
|
| 31 |
+
return spans
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def main():
|
| 35 |
+
text = sys.argv[1] if len(sys.argv) > 1 else \
|
| 36 |
+
"My name is John Smith and my email is john.smith@example.com"
|
| 37 |
+
model, cfg = load(".")
|
| 38 |
+
tok = Tokenizer.from_file("tokenizer.json")
|
| 39 |
+
enc = tok.encode(text)
|
| 40 |
+
|
| 41 |
+
input_ids = mx.array([enc.ids])
|
| 42 |
+
attention_mask = mx.array([enc.attention_mask])
|
| 43 |
+
logits = model(input_ids, attention_mask)
|
| 44 |
+
label_ids = mx.argmax(logits[0], axis=-1).tolist()
|
| 45 |
+
|
| 46 |
+
spans = decode_spans(enc.tokens, enc.offsets, label_ids, cfg.id2label)
|
| 47 |
+
print(f"\nText: {text}\n")
|
| 48 |
+
if not spans:
|
| 49 |
+
print("No PII detected.")
|
| 50 |
+
for s in spans:
|
| 51 |
+
print(f" {s['type']:16s} {text[s['start']:s['end']]!r} [{s['start']}:{s['end']}]")
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
main()
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5a2120db5b6789b9c4bd68dd3bc133567a4cb80835cad8bb6feae83edb43bbcf
|
| 3 |
+
size 36881270
|
rampart_mlx.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Rampart PII NER — MLX implementation.
|
| 3 |
+
|
| 4 |
+
A standard encoder-only BERT (MiniLM-L6, hidden 384) with a 35-label BIO
|
| 5 |
+
token-classification head for PII detection. Weights are float (fp16) and load
|
| 6 |
+
directly from `model.safetensors`.
|
| 7 |
+
|
| 8 |
+
Independent MLX conversion: float weights were recovered 1:1 from the original
|
| 9 |
+
`nationaldesignstudio/rampart` 4-bit ONNX export (verified to match the ONNX
|
| 10 |
+
runtime output exactly). See README for attribution.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
import math
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import mlx.core as mx
|
| 18 |
+
import mlx.nn as nn
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class BertConfig:
|
| 22 |
+
def __init__(self, path):
|
| 23 |
+
d = json.loads(Path(path).read_text())
|
| 24 |
+
self.hidden_size = d["hidden_size"]
|
| 25 |
+
self.num_hidden_layers = d["num_hidden_layers"]
|
| 26 |
+
self.num_attention_heads = d["num_attention_heads"]
|
| 27 |
+
self.intermediate_size = d["intermediate_size"]
|
| 28 |
+
self.vocab_size = d["vocab_size"]
|
| 29 |
+
self.max_position_embeddings = d["max_position_embeddings"]
|
| 30 |
+
self.type_vocab_size = d["type_vocab_size"]
|
| 31 |
+
self.layer_norm_eps = d["layer_norm_eps"]
|
| 32 |
+
self.id2label = {int(k): v for k, v in d["id2label"].items()}
|
| 33 |
+
self.num_labels = len(self.id2label)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class BertLayer(nn.Module):
|
| 37 |
+
def __init__(self, c):
|
| 38 |
+
super().__init__()
|
| 39 |
+
H, I = c.hidden_size, c.intermediate_size
|
| 40 |
+
self.query = nn.Linear(H, H)
|
| 41 |
+
self.key = nn.Linear(H, H)
|
| 42 |
+
self.value = nn.Linear(H, H)
|
| 43 |
+
self.attn_out = nn.Linear(H, H)
|
| 44 |
+
self.attn_ln = nn.LayerNorm(H, eps=c.layer_norm_eps)
|
| 45 |
+
self.intermediate = nn.Linear(H, I)
|
| 46 |
+
self.output = nn.Linear(I, H)
|
| 47 |
+
self.output_ln = nn.LayerNorm(H, eps=c.layer_norm_eps)
|
| 48 |
+
self.n_heads = c.num_attention_heads
|
| 49 |
+
self.d_head = H // c.num_attention_heads
|
| 50 |
+
|
| 51 |
+
def __call__(self, x, add_mask):
|
| 52 |
+
B, S, _ = x.shape
|
| 53 |
+
|
| 54 |
+
def split(t):
|
| 55 |
+
return t.reshape(B, S, self.n_heads, self.d_head).transpose(0, 2, 1, 3)
|
| 56 |
+
|
| 57 |
+
q, k, v = split(self.query(x)), split(self.key(x)), split(self.value(x))
|
| 58 |
+
scores = (q @ k.transpose(0, 1, 3, 2)) / math.sqrt(self.d_head) + add_mask
|
| 59 |
+
ctx = (mx.softmax(scores, axis=-1) @ v).transpose(0, 2, 1, 3).reshape(B, S, -1)
|
| 60 |
+
x = self.attn_ln(self.attn_out(ctx) + x)
|
| 61 |
+
h = nn.gelu(self.intermediate(x))
|
| 62 |
+
return self.output_ln(self.output(h) + x)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class RampartForTokenClassification(nn.Module):
|
| 66 |
+
def __init__(self, c):
|
| 67 |
+
super().__init__()
|
| 68 |
+
self.config = c
|
| 69 |
+
self.word_embeddings = nn.Embedding(c.vocab_size, c.hidden_size)
|
| 70 |
+
self.position_embeddings = nn.Embedding(c.max_position_embeddings, c.hidden_size)
|
| 71 |
+
self.token_type_embeddings = nn.Embedding(c.type_vocab_size, c.hidden_size)
|
| 72 |
+
self.embeddings_ln = nn.LayerNorm(c.hidden_size, eps=c.layer_norm_eps)
|
| 73 |
+
self.layers = [BertLayer(c) for _ in range(c.num_hidden_layers)]
|
| 74 |
+
self.classifier = nn.Linear(c.hidden_size, c.num_labels)
|
| 75 |
+
|
| 76 |
+
def __call__(self, input_ids, attention_mask, token_type_ids=None):
|
| 77 |
+
B, S = input_ids.shape
|
| 78 |
+
if token_type_ids is None:
|
| 79 |
+
token_type_ids = mx.zeros((B, S), dtype=input_ids.dtype)
|
| 80 |
+
pos_ids = mx.arange(S)[None, :]
|
| 81 |
+
x = self.embeddings_ln(
|
| 82 |
+
self.word_embeddings(input_ids)
|
| 83 |
+
+ self.position_embeddings(pos_ids)
|
| 84 |
+
+ self.token_type_embeddings(token_type_ids)
|
| 85 |
+
)
|
| 86 |
+
add_mask = (1.0 - attention_mask.astype(mx.float32))[:, None, None, :] * -1e9
|
| 87 |
+
for layer in self.layers:
|
| 88 |
+
x = layer(x, add_mask)
|
| 89 |
+
return self.classifier(x)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# Maps the HuggingFace-style safetensors keys onto this module tree.
|
| 93 |
+
def _remap(weights, n_layers):
|
| 94 |
+
out = {}
|
| 95 |
+
direct = {
|
| 96 |
+
"bert.embeddings.word_embeddings.weight": "word_embeddings.weight",
|
| 97 |
+
"bert.embeddings.position_embeddings.weight": "position_embeddings.weight",
|
| 98 |
+
"bert.embeddings.token_type_embeddings.weight": "token_type_embeddings.weight",
|
| 99 |
+
"bert.embeddings.LayerNorm.weight": "embeddings_ln.weight",
|
| 100 |
+
"bert.embeddings.LayerNorm.bias": "embeddings_ln.bias",
|
| 101 |
+
"classifier.weight": "classifier.weight",
|
| 102 |
+
"classifier.bias": "classifier.bias",
|
| 103 |
+
}
|
| 104 |
+
for src, dst in direct.items():
|
| 105 |
+
out[dst] = weights[src]
|
| 106 |
+
for i in range(n_layers):
|
| 107 |
+
p = f"bert.encoder.layer.{i}"
|
| 108 |
+
m = {
|
| 109 |
+
f"{p}.attention.self.query": f"layers.{i}.query",
|
| 110 |
+
f"{p}.attention.self.key": f"layers.{i}.key",
|
| 111 |
+
f"{p}.attention.self.value": f"layers.{i}.value",
|
| 112 |
+
f"{p}.attention.output.dense": f"layers.{i}.attn_out",
|
| 113 |
+
f"{p}.attention.output.LayerNorm": f"layers.{i}.attn_ln",
|
| 114 |
+
f"{p}.intermediate.dense": f"layers.{i}.intermediate",
|
| 115 |
+
f"{p}.output.dense": f"layers.{i}.output",
|
| 116 |
+
f"{p}.output.LayerNorm": f"layers.{i}.output_ln",
|
| 117 |
+
}
|
| 118 |
+
for src, dst in m.items():
|
| 119 |
+
out[f"{dst}.weight"] = weights[f"{src}.weight"]
|
| 120 |
+
out[f"{dst}.bias"] = weights[f"{src}.bias"]
|
| 121 |
+
return out
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def load(model_dir):
|
| 125 |
+
model_dir = Path(model_dir)
|
| 126 |
+
c = BertConfig(model_dir / "config.json")
|
| 127 |
+
model = RampartForTokenClassification(c)
|
| 128 |
+
weights = mx.load(str(model_dir / "model.safetensors"))
|
| 129 |
+
flat = _remap(weights, c.num_hidden_layers)
|
| 130 |
+
model.load_weights(list(flat.items()))
|
| 131 |
+
mx.eval(model.parameters())
|
| 132 |
+
return model, c
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cls_token": {
|
| 3 |
+
"content": "[CLS]",
|
| 4 |
+
"lstrip": false,
|
| 5 |
+
"normalized": false,
|
| 6 |
+
"rstrip": false,
|
| 7 |
+
"single_word": false
|
| 8 |
+
},
|
| 9 |
+
"mask_token": {
|
| 10 |
+
"content": "[MASK]",
|
| 11 |
+
"lstrip": false,
|
| 12 |
+
"normalized": false,
|
| 13 |
+
"rstrip": false,
|
| 14 |
+
"single_word": false
|
| 15 |
+
},
|
| 16 |
+
"pad_token": {
|
| 17 |
+
"content": "[PAD]",
|
| 18 |
+
"lstrip": false,
|
| 19 |
+
"normalized": false,
|
| 20 |
+
"rstrip": false,
|
| 21 |
+
"single_word": false
|
| 22 |
+
},
|
| 23 |
+
"sep_token": {
|
| 24 |
+
"content": "[SEP]",
|
| 25 |
+
"lstrip": false,
|
| 26 |
+
"normalized": false,
|
| 27 |
+
"rstrip": false,
|
| 28 |
+
"single_word": false
|
| 29 |
+
},
|
| 30 |
+
"unk_token": {
|
| 31 |
+
"content": "[UNK]",
|
| 32 |
+
"lstrip": false,
|
| 33 |
+
"normalized": false,
|
| 34 |
+
"rstrip": false,
|
| 35 |
+
"single_word": false
|
| 36 |
+
}
|
| 37 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"added_tokens_decoder": {
|
| 3 |
+
"0": {
|
| 4 |
+
"content": "[PAD]",
|
| 5 |
+
"lstrip": false,
|
| 6 |
+
"normalized": false,
|
| 7 |
+
"rstrip": false,
|
| 8 |
+
"single_word": false,
|
| 9 |
+
"special": true
|
| 10 |
+
},
|
| 11 |
+
"1": {
|
| 12 |
+
"content": "[UNK]",
|
| 13 |
+
"lstrip": false,
|
| 14 |
+
"normalized": false,
|
| 15 |
+
"rstrip": false,
|
| 16 |
+
"single_word": false,
|
| 17 |
+
"special": true
|
| 18 |
+
},
|
| 19 |
+
"2": {
|
| 20 |
+
"content": "[CLS]",
|
| 21 |
+
"lstrip": false,
|
| 22 |
+
"normalized": false,
|
| 23 |
+
"rstrip": false,
|
| 24 |
+
"single_word": false,
|
| 25 |
+
"special": true
|
| 26 |
+
},
|
| 27 |
+
"3": {
|
| 28 |
+
"content": "[SEP]",
|
| 29 |
+
"lstrip": false,
|
| 30 |
+
"normalized": false,
|
| 31 |
+
"rstrip": false,
|
| 32 |
+
"single_word": false,
|
| 33 |
+
"special": true
|
| 34 |
+
},
|
| 35 |
+
"4": {
|
| 36 |
+
"content": "[MASK]",
|
| 37 |
+
"lstrip": false,
|
| 38 |
+
"normalized": false,
|
| 39 |
+
"rstrip": false,
|
| 40 |
+
"single_word": false,
|
| 41 |
+
"special": true
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"clean_up_tokenization_spaces": true,
|
| 45 |
+
"cls_token": "[CLS]",
|
| 46 |
+
"do_basic_tokenize": true,
|
| 47 |
+
"do_lower_case": true,
|
| 48 |
+
"extra_special_tokens": {},
|
| 49 |
+
"mask_token": "[MASK]",
|
| 50 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 51 |
+
"never_split": null,
|
| 52 |
+
"pad_token": "[PAD]",
|
| 53 |
+
"sep_token": "[SEP]",
|
| 54 |
+
"strip_accents": null,
|
| 55 |
+
"tokenize_chinese_chars": true,
|
| 56 |
+
"tokenizer_class": "BertTokenizer",
|
| 57 |
+
"unk_token": "[UNK]"
|
| 58 |
+
}
|
vocab.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|