File size: 6,230 Bytes
221ca55
 
 
 
 
 
 
 
 
 
 
f02e80f
221ca55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
efa0a2e
 
 
 
 
 
221ca55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139d475
 
 
221ca55
 
 
 
139d475
221ca55
 
 
139d475
 
 
 
 
221ca55
 
 
efa0a2e
 
221ca55
 
 
 
efa0a2e
 
 
221ca55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
---
license: mit
language:
- en
tags:
- bert
- native-inference
- sajal-labs
- transformer
- feature-extraction
base_model: prajjwal1/bert-tiny
base_model_relation: finetune
pipeline_tag: feature-extraction
---

# bert-tiny β€” Native C++ Port (Sajal Labs)

**This is not a new model.** The weights, architecture, and pretraining are
entirely [`prajjwal1/bert-tiny`](https://huggingface.co/prajjwal1/bert-tiny) by Prajjwal
Bhargava (MIT license) β€” a compact pretrained BERT encoder introduced in
Turc et al. 2019 ("Well-Read Students Learn Better") and ported to
Hugging Face for Bhargava et al. 2021 ("Generalization in NLI"). **Please
cite both papers if you use this model** (citations below).

**What Sajal Labs added**: a from-scratch native C++ port of the encoder
and the WordPiece tokenizer β€” no PyTorch, no `transformers`, no Python at
inference time β€” with rigorous equivalence and benchmark validation against
the original. See [Sajal Labs](https://github.com/Sajalmadan09/sajal-labs),
experiments
[exp11](https://github.com/Sajalmadan09/sajal-labs/tree/main/research/experiments/exp11-real-pretrained-transformer)
through
[exp14](https://github.com/Sajalmadan09/sajal-labs/tree/main/research/experiments/exp14-wordpiece-native),
for full methodology.

## Model details (unchanged from the original)

- **Architecture**: BERT encoder, 2 layers, hidden=128, heads=2, intermediate=512
- **Vocabulary**: 30522 WordPiece tokens (bert-base-uncased vocab)
- **Parameters**: 4,385,920
- **Precision**: fp32
- **Base model license**: MIT (prajjwal1/bert-tiny)
- **Port license**: MIT (Sajal Labs' C++ code)

## What was verified (Sajal Labs' contribution)

**Tokenizer**: 17/17 real test sentences β€” including contractions ("don't"),
hyphenation ("COVID-19"), an out-of-vocabulary word forcing an 11-piece
subword split, and an email address β€” produced **byte-identical token IDs**
to the original `BertTokenizerFast`. This is an exact-match bar, not a
tolerance: tokenization is deterministic.

**Encoder**: max absolute error 9.54e-06 (hidden states),
2.19e-06 (pooled `[CLS]` output), cosine similarity
~1.0, across 10 real sentences of varying length (4-25 tokens) β€” fp32-scale
agreement, consistent with floating-point non-associativity between two
independent implementations (not a bug; see the repo's `research/papers.md`).

## Benchmark (single request, Apple M4 CPU β€” full data in `benchmark_results.json`)

True end-to-end cold invocation (process spawn β†’ raw text in β†’ prediction out
β†’ process exit, external wall-clock). **Primary comparison: native vs. ONNX
Runtime paired with the lean, standalone `tokenizers` library β€” the
best-case Python deployment, not the easiest target to beat:**

| Implementation | Cold invocation p50 |
|---|---:|
| **Native C++ (Sajal runtime)** | 11.37ms |
| **ONNX Runtime + lean tokenizer** | **95.72ms (8.4x slower)** |
| ONNX Runtime + πŸ€— transformers tokenizer | 2495.61ms (219.6x slower) |
| PyTorch + πŸ€— transformers | 4973.91ms (437.6x slower) |

The last two rows are real and worth knowing, but they're the easy targets
(heavier Python stacks) β€” the 8.4x number above is
the one that holds up against someone who already optimized their Python
deployment correctly. Worth knowing before you read too much into "ONNX
Runtime" as a single number: its own cold-start
number depends heavily on which tokenizer library it's paired with β€” using
`transformers` for convenience costs ~25x more than using the lean, standalone
`tokenizers` library for the exact same token IDs. Native sidesteps that
whole dependency-choice question by construction. Full discussion in
[exp14](https://github.com/Sajalmadan09/sajal-labs/tree/main/research/experiments/exp14-wordpiece-native).

**Honest scope note on the warm-loop numbers**: native's advantage is
*not* unconditional the way cold-invocation is β€” Sajal Labs found it
depends on model width (`hidden_size`), with a measured crossover around
`hiddenβ‰ˆ250` on this hardware
([exp12](https://github.com/Sajalmadan09/sajal-labs/tree/main/research/experiments/exp12-warm-latency-width-depth)/[exp13](https://github.com/Sajalmadan09/sajal-labs/tree/main/research/experiments/exp13-width-threshold)).
This model's `hidden=128`
sits comfortably below that, so native keeps a real warm-loop edge too β€”
but that's a property of this model's size, not a general claim.

## How to use

### Native (Sajal runtime, zero Python)

```bash
sajal run <this_directory> "The quick brown fox jumps over the lazy dog."
```

### PyTorch / transformers (the original)

```python
from transformers import BertModel, BertTokenizerFast
model = BertModel.from_pretrained("prajjwal1/bert-tiny")
tokenizer = BertTokenizerFast.from_pretrained("prajjwal1/bert-tiny")
```

### ONNX Runtime

```python
import onnxruntime as ort
session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
# feed input_ids/attention_mask/token_type_ids from any WordPiece tokenizer
```

## Citations (required if you use this model)

```bibtex
@article{turc2019distillation,
  title={Well-Read Students Learn Better: On the Importance of Pre-training Compact Models},
  author={Turc, Iulia and Chang, Ming-Wei and Lee, Kenton and Toutanova, Kristina},
  journal={arXiv preprint arXiv:1908.08962v2},
  year={2019}
}

@misc{bhargava2021generalization,
  title={Generalization in NLI: Ways (Not) To Go Beyond Simple Heuristics},
  author={Bhargava, Prajjwal and Drozd, Aleksandr and Rogers, Anna},
  year={2021},
  eprint={2110.01518},
  archivePrefix={arXiv},
  primaryClass={cs.CL}
}
```

## Files

- `model.safetensors` β€” the original weights, HF/PyTorch-ecosystem format
- `model.onnx` + `model.onnx.data` β€” ONNX Runtime-compatible export (weights externalized to the `.data` file; both are required together)
- `word_embeddings.bin`, `position_embeddings.bin`, `token_type_embeddings.bin`, `emb_ln_*.bin`, `layer{i}_*.bin`, `pooler_*.bin` β€” raw native Sajal runtime format
- `vocab.txt`, `tokenizer_config.txt` β€” WordPiece vocabulary + config for the native tokenizer port
- `config.json` β€” architecture metadata
- `benchmark_results.json` β€” full machine-readable benchmark/equivalence data behind the numbers above