mjbommar commited on
Commit
3f8b024
·
verified ·
1 Parent(s): 00a471f

mimelens-001 cell: small/byte/s2

Browse files
README.md CHANGED
@@ -1,16 +1,20 @@
1
  ---
2
  license: mit
3
  library_name: transformers
 
 
4
  tags:
5
  - file-type-detection
6
  - mime-classification
7
  - binary-content
 
8
  - position-agnostic
9
  - libmagic
 
 
10
  - byte-level
11
  - mimelens
12
- language: en
13
- pipeline_tag: feature-extraction
14
  model-index:
15
  - name: mimelens-001-small-byte-s2
16
  results:
@@ -18,8 +22,8 @@ model-index:
18
  type: feature-extraction
19
  name: MIME-125 classification (libmagic 125-class taxonomy)
20
  dataset:
21
- name: magic-bpe magic-frags (4 KB head of 64 KB random chunks, n=4,096)
22
- type: mjbommar/magic-bpe-stratified
23
  metrics:
24
  - name: top-1 accuracy
25
  type: accuracy
@@ -32,92 +36,132 @@ model-index:
32
  value: 0.7187
33
  source:
34
  name: "MimeLens paper (Bommarito 2026), Appendix A"
35
- url: https://github.com/mjbommar/binary-embedding-paper
36
  ---
37
 
38
- # MimeLens-001 / small / byte / s2
39
 
40
- **One cell from the [MimeLens-001](https://huggingface.co/mjbommar/mimelens-001) family** `14.16` M backbone params, `byte` input pipeline, seed `2`. Pretrained MLM-only on 33 GB of position-arbitrary binary content for fine-grained file-content-type classification under [libmagic](https://github.com/file/file)'s 125-class MIME taxonomy.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- A single 4 KB byte buffer in (of which the first 1,022 body tokens are consumed), one of libmagic's 125 MIME labels out, regardless of where in a source file the buffer came from.
43
 
44
- For the family overview, decision tree (which cell to load?), and full cube results, see [`mjbommar/mimelens-001`](https://huggingface.co/mjbommar/mimelens-001).
45
 
46
- ## How to use
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  ```python
49
  import torch
50
- from transformers import AutoModel
51
 
52
- repo = "mjbommar/mimelens-001-small-byte-s2"
53
  model = AutoModel.from_pretrained(repo, trust_remote_code=True).eval()
54
- cfg = model.config
55
 
56
- # Byte cell: tokenization is trivial — id == byte_value + byte_offset.
57
  window = open("path/to/file", "rb").read(4096)
58
- ids = [cfg.cls_token_id] + [b + cfg.byte_offset for b in window][:1022] + [cfg.sep_token_id]
59
- input_ids = torch.tensor([ids])
60
-
61
  with torch.no_grad():
62
- out = model(input_ids=input_ids, attention_mask=torch.ones_like(input_ids))
63
-
64
- embedding = out.pooler_output # (1, 384) mean-pooled body-token embedding
65
- # Downstream: a frozen LR probe, a kNN over a labeled gallery, or fine-tune a classification head.
66
- # See the paper for the standard evaluation protocol.
67
  ```
68
 
69
- ## What this cell is
70
 
71
- - **Family**: [MimeLens-001](https://huggingface.co/mjbommar/mimelens-001) — 28 pretrained checkpoints across 3 sizes × 4 vocabularies × 2 seeds, plus one matched-tokens-seen ablation.
72
- - **Size**: `small` — 14.16 M backbone params, 8 layers, hidden 384, 6 attention heads, head dim 64.
73
- - **Input pipeline**: `byte` (raw 256-byte vocabulary plus 5 special tokens (cls, sep, pad, unk, mask); the model reads exactly the first 1022 bytes that arrive in a 4 KB window.).
74
- - **Seed**: `2` (1 of 2 for this (size, vocab) combination).
75
- - **Pretraining**: 22,888 gradient updates, MLM-only, 30% mask ratio, 1024-token windows sampled uniformly at random across files and 64 KB fragments. AdamW + cosine LR (peak 5e-4, 2,000-step warmup, 10% floor), bf16 mixed precision, single RTX 4060 Ti.
76
- - **License**: MIT.
77
 
78
- ## Evaluation
79
 
80
- Numbers below are for **this specific cell** on the `magic-frags` held-out test set (4 KB head of 64 KB random chunks, n=4,096). The within-cube comparison (3 sizes × 4 vocabs × 2-3 seeds, bootstrap CIs, adversarial sweep, calibration, real-network and disk-block validations) is in the [paper](https://github.com/mjbommar/binary-embedding-paper).
81
 
82
- | Benchmark | This cell |
83
- |---|---|
84
- | MIME-125 top-1 (magic-frags 4 KB head, n=4,096) | **0.766** |
85
- | MIME-125 macro-F1 (magic-frags 4 KB head) | 0.617 |
86
- | kNN R@1 (magic-frags, 3,147-file gallery / 949 queries) | 0.719 |
 
87
 
88
  ## Recommended deployment regimes
89
 
90
  See the family hub README ([`mjbommar/mimelens-001`](https://huggingface.co/mjbommar/mimelens-001)) for the regime decision tree.
91
 
 
 
92
  ## Training
93
 
94
- This cell is one point of the pre-registered 3 × 4 × 2 factorial cube described in the [MimeLens paper](https://github.com/mjbommar/binary-embedding-paper). Salient details:
95
 
96
- - **33 GB stratified multi-source binary corpus** (binary-30k + magic-frags + glaurung + Windows drivers).
97
- - **Position-arbitrary windowing**: 1024-token windows sampled uniformly at random across files and 64 KB fragments no privileged "head of file" position. This is what makes MimeLens work on streaming / partial / random-offset inputs that whole-file detectors were not designed for.
98
- - **MLM-only** objective, 30% mask ratio (BERT replacement schedule: 80% `[MASK]`, 10% random, 10% original); tied input/output embeddings.
99
- - **Mean-pool over body tokens** for downstream tasks; the BERT-style `cls_pool` linear projection is *not* used because under MLM-only training it receives no gradient and remains at random init across all 28 cube cells (paper §3.4 verifies this).
100
- - **Wall-clock**: ~10.7 h on a single RTX 4060 Ti.
 
101
 
102
- ## Honest caveats
103
 
104
- - This is one cell of a 28-cell cube. Within-cube comparisons in the paper come with bootstrap CIs at n=2 seeds; some marginal orderings (byte vs bpe-16k at the top of medium) are within seed noise and should be read as ties.
 
 
105
  - The training corpus is one 33 GB stratified multi-source binary sample. Results may not transfer to substantially different corpora.
106
- - All numbers are computed on data derived from a single labelling pipeline (libmagic-pinned via the [magic-bpe](https://github.com/mjbommar/magic-bpe) project). Cross-validation against PRONOM, Siegfried, DROID, or IANA reference files is a documented limitation.
107
- - CPU latency at the `medium` size is ~348× slower than Magika; for sub-millisecond whole-file triage on broad categories, Magika is purpose-built and is the right default. MimeLens occupies a different point on the deployment surface (position-arbitrary inputs + libmagic's 125-class taxonomy) rather than a drop-in replacement.
108
- - End-to-end fine-tuning on the production label distribution may shift these numbers and should be evaluated before deployment. The frozen-probe numbers reported above are not claimed as a lower bound on fine-tuned performance.
 
 
109
 
110
  ## Citation
111
 
112
  ```bibtex
113
  @misc{bommarito2026mimelens,
114
- title = {MimeLens: Pretrained Encoders for Fine-Grained Content-Type Detection},
115
  author = {Bommarito II, Michael J.},
116
  year = {2026},
117
- note = {https://github.com/mjbommar/binary-embedding-paper},
118
  }
119
- ```
120
-
121
- ## Acknowledgments
122
-
123
- Thanks to the [magic-bpe](https://github.com/mjbommar/magic-bpe) project and the [binary-tokenizer-001](https://huggingface.co/mjbommar/binary-tokenizer-001-16k) family for the labelled corpus and BPE tokenizers this work builds on, and to the [Magika](https://github.com/google/magika) team for releasing a public package that made the §3 calibration possible.
 
1
  ---
2
  license: mit
3
  library_name: transformers
4
+ language:
5
+ - en
6
  tags:
7
  - file-type-detection
8
  - mime-classification
9
  - binary-content
10
+ - binary-analysis
11
  - position-agnostic
12
  - libmagic
13
+ - forensics
14
+ - packet-inspection
15
  - byte-level
16
  - mimelens
17
+ pipeline_tag: text-classification
 
18
  model-index:
19
  - name: mimelens-001-small-byte-s2
20
  results:
 
22
  type: feature-extraction
23
  name: MIME-125 classification (libmagic 125-class taxonomy)
24
  dataset:
25
+ name: magic-frags (4 KB head of 64 KB random chunks, n=4,096)
26
+ type: custom
27
  metrics:
28
  - name: top-1 accuracy
29
  type: accuracy
 
36
  value: 0.7187
37
  source:
38
  name: "MimeLens paper (Bommarito 2026), Appendix A"
39
+ url: https://github.com/mjbommar/mimelens-training
40
  ---
41
 
42
+ # mimelens-001-small-byte-s2
43
 
44
+ A 14.16M-backbone-parameter BERT-style encoder for position-agnostic file-content-type detection from binary data. It reads a byte window taken from *any* offset in a file (the first ~1{,}022 tokens of whatever you pass) and produces a 384-dimensional embedding that classifiers map to one of [libmagic](https://github.com/file/file)'s 125 MIME labels. Designed for inputs where you only have a chunk: a forensic-carved fragment, a random disk-block read, a streaming HTTP upload, a single network packet payload.
45
+
46
+ - **🔗 Model**: [`mjbommar/mimelens-001-small-byte-s2`](https://huggingface.co/mjbommar/mimelens-001-small-byte-s2)
47
+ - **👥 Family**: [`mjbommar/mimelens-001`](https://huggingface.co/mjbommar/mimelens-001) (36 released cells: 28 parent + 8 short-sequence)
48
+ - **📄 Paper**: *MimeLens: Position-Agnostic Content-Type Detection for Binary Fragments* (Bommarito 2026)
49
+ - **💻 Training code**: [`mjbommar/mimelens-training`](https://github.com/mjbommar/mimelens-training)
50
+ - **📊 Pretraining corpus**: [`mjbommar/binary-30k-tokenized`](https://huggingface.co/datasets/mjbommar/binary-30k-tokenized) plus magic-corpus extracts, packed binaries, a [`glaurung`](https://github.com/mjbommar/glaurung)-sourced binary corpus, and Windows drivers (33 GB stratified; the full corpus is not redistributable)
51
+
52
+ ---
53
+
54
+ ## What MimeLens does
55
+
56
+ MimeLens classifies file content type from a byte window taken at any offset, not just the header of a complete file.
57
+
58
+ Existing tools assume whole-file access at a known offset:
59
+
60
+ - [`libmagic`](https://github.com/file/file) and [Apache Tika](https://tika.apache.org/) match handcrafted magic-byte signatures, almost always anchored at the file head.
61
+ - [Magika](https://github.com/google/magika) (Google) is a small (~1 M-parameter) feedforward network over three 512-byte windows (head, middle, tail) of a known-bounded file.
62
+ - TrID, PRONOM/Siegfried/DROID similarly require a complete file.
63
+
64
+ These break down on a fragment. MimeLens is pretrained MLM-only on 1024-token windows sampled *uniformly at random* across files and 64 KB fragments, with no privileged head-of-file position. One checkpoint handles streaming, partial-arrival, mid-file, packet-payload, and forensic-carved inputs uniformly. The trade-off is CPU latency (roughly two orders of magnitude slower than Magika at the medium size; hardware-dependent) in exchange for libmagic's 125-class taxonomy plus position arbitrariness.
65
+
66
+ The family ships 28 parent cells (3 sizes × 4 vocabs × 2-3 seeds at seq\_len=1024) plus an 8-cell short-sequence extension (medium tier × 4 vocabs × 2 seeds at seq\_len=256). This README documents one of them.
67
+
68
+ > **Short-sequence sibling available.** If your inputs are sub-KB (DNS payloads, sub-MTU packets, small forensic fragments), use `mjbommar/mimelens-001-small-byte-s2-seq256` instead. Same architecture, 4× shorter context, ~5× lower CPU latency, BPE-cell accuracy ties or beats this cell on the magic-files probe-fit. See paper Appendix B.5.
69
 
 
70
 
 
71
 
72
+ ---
73
+
74
+ ## Overview
75
+
76
+ - **This cell**: `small` tier, `byte` input pipeline, seed `2`
77
+ - **Backbone**: 14.16M parameters (8 layers, hidden 384, 6 attention heads, head dim 64, RoPE, RMSNorm, no biases, no dropout)
78
+ - **Input vocabulary**: `byte`. Raw 256-byte vocabulary plus 5 special tokens (CLS, SEP, PAD, UNK, MASK); id = byte_value + 5. The model reads exactly the first 1,022 bytes that arrive.
79
+ - **Output**: 384-dim mean-pooled body-token embedding
80
+ - **Label space**: [libmagic](https://github.com/file/file) 125-class MIME taxonomy (full list in paper Appendix)
81
+ - **Pretraining**: MLM-only, 30% mask ratio, 33 GB stratified multi-source binary corpus, 22,888 gradient updates, single RTX 4060 Ti, ~10.7 h wall-clock
82
+ - **License**: MIT
83
+
84
+ ## Headline benchmarks (this cell)
85
+
86
+ | Benchmark | Value |
87
+ |---|---|
88
+ | MIME-125 top-1 (magic-frags, 4 KB head, n=4,096) | **0.766** |
89
+ | MIME-125 macro-F1 (magic-frags, 4 KB head) | 0.617 |
90
+ | kNN R@1 (magic-frags, 3,147-file gallery / 949 queries) | 0.719 |
91
+
92
+ Full evaluation (within-cube bootstrap CIs, adversarial sweep, calibration, real-network curves, disk-block matrix, baselines against libmagic 5.46 and TrID 2.24) is in the [paper](https://github.com/mjbommar/mimelens-training).
93
+
94
+ ---
95
+
96
+ ## Quick start
97
+
98
+ This cell publishes the encoder only (no classifier head baked in). Use it to extract embeddings, then fit a probe, run kNN over a labelled gallery, or fine-tune a head:
99
 
100
  ```python
101
  import torch
102
+ from transformers import AutoModel, AutoTokenizer
103
 
104
+ repo = "mjbommar/mimelens-001-small-byte-s2"
105
  model = AutoModel.from_pretrained(repo, trust_remote_code=True).eval()
106
+ tok = AutoTokenizer.from_pretrained(repo)
107
 
 
108
  window = open("path/to/file", "rb").read(4096)
109
+ inputs = tok(window.decode("latin-1"), max_length=1024, truncation=True,
110
+ padding="max_length", return_tensors="pt")
 
111
  with torch.no_grad():
112
+ embedding = model(**inputs).pooler_output # (1, 384)
 
 
 
 
113
  ```
114
 
115
+ The pre-fit LR probe weights for this cell are not bundled here. The deployed cells and per-size winners (e.g. `mimelens-001-medium-bpe-16k-s1`) ship a baked classifier head for a one-line `pipeline()` path.
116
 
 
 
 
 
 
 
117
 
118
+ ---
119
 
120
+ ## Choosing a window
121
 
122
+ The model reads the first ~1{,}022 tokens of whatever you pass — a prefix of the buffer (the first 1{,}022 bytes for this byte cell), not the whole window.
123
+
124
+ - **Magic-byte / compressed types** (PNG, ZIP, GZIP, JPEG): a **short head window (256 B--1 KB) classifies better than 4 KB**. A long high-entropy body dilutes the header signal within the fixed token budget, and the model returns `application/octet-stream` on a mostly-opaque window — correct behaviour for genuinely high-entropy input, not a bug.
125
+ - **Fragments / packets**: you cannot choose the offset, so pass what you have. This is the regime MimeLens is built for.
126
+
127
+ ---
128
 
129
  ## Recommended deployment regimes
130
 
131
  See the family hub README ([`mjbommar/mimelens-001`](https://huggingface.co/mjbommar/mimelens-001)) for the regime decision tree.
132
 
133
+ ---
134
+
135
  ## Training
136
 
137
+ This cell is one point of the 3 × 4 × 2 factorial cube described in the paper.
138
 
139
+ - **Corpus** (33 GB, stratified multi-source): [`binary-30k`](https://huggingface.co/datasets/mjbommar/binary-30k-tokenized) (assorted ELF/PE/Mach-O), magic-frags (random 64 KB chunks across libmagic's full corpus), assorted packed/raw binaries, a [`glaurung`](https://github.com/mjbommar/glaurung)-sourced binary corpus, Windows drivers.
140
+ - **Position-arbitrary windowing**: 1024-token windows sampled uniformly at random across files and 64 KB fragments. **No privileged "head of file" position.** This is the design choice that makes MimeLens work on streaming / partial / random-offset inputs.
141
+ - **Objective**: MLM with 30% mask ratio (BERT replacement schedule: 80% `[MASK]`, 10% random, 10% original); tied input/output embeddings.
142
+ - **Pooling**: mean-pool over body tokens for downstream tasks. The BERT-style `cls_pool` linear projection is *not* used: under MLM-only training it receives no gradient and remains byte-identical to its random initialisation across all 28 cube cells (paper §3.4 verifies this; left in the saved weights for architectural completeness only).
143
+ - **Optimisation**: AdamW + cosine LR (peak 5e-4, 2,000-step warmup, 10% floor), bf16 mixed precision, gradient clipping at $\|g\|_2 \leq 1$, effective batch 128 at sequence length 1024, 22,888 gradient updates.
144
+ - **Hardware**: single RTX 4060 Ti (16 GB), ~10.7 h wall-clock for this cell.
145
 
146
+ ---
147
 
148
+ ## Caveats
149
+
150
+ - This is one cell of a 28-cell parent cube (36 released cells including the 8-cell short-sequence extension). Within-cube comparisons in the paper carry bootstrap CIs at n=2 seeds; some marginal orderings (byte vs bpe-16k at the largest size) are within seed noise and should be read as ties.
151
  - The training corpus is one 33 GB stratified multi-source binary sample. Results may not transfer to substantially different corpora.
152
+ - All numbers are computed on data labelled by a single pipeline (libmagic-pinned). Cross-validation against PRONOM, Siegfried, DROID, or IANA reference files is a documented limitation.
153
+ - CPU latency at the `medium` size is ~155× slower than Magika v1.1 on a desktop CPU (hardware-dependent). For sub-millisecond whole-file triage on broad categories, Magika is purpose-built and is the right tool. MimeLens occupies a different point on the deployment surface (position-arbitrary inputs + libmagic's 125-class taxonomy), not a drop-in replacement.
154
+ - End-to-end fine-tuning on the production label distribution may shift these numbers and should be evaluated before deployment. The frozen-probe numbers above are not claimed as a lower bound on fine-tuned performance.
155
+
156
+ ---
157
 
158
  ## Citation
159
 
160
  ```bibtex
161
  @misc{bommarito2026mimelens,
162
+ title = {MimeLens: Position-Agnostic Content-Type Detection for Binary Fragments},
163
  author = {Bommarito II, Michael J.},
164
  year = {2026},
165
+ note = {https://github.com/mjbommar/mimelens-training},
166
  }
167
+ ```
 
 
 
 
config.json CHANGED
@@ -8,7 +8,7 @@
8
  },
9
  "model_type": "mimelens",
10
  "torch_dtype": "float32",
11
- "vocab_size": 261,
12
  "hidden_size": 384,
13
  "num_hidden_layers": 8,
14
  "num_attention_heads": 6,
@@ -22,7 +22,7 @@
22
  "cls_token_id": 4,
23
  "sep_token_id": 5,
24
  "mask_token_id": 6,
25
- "byte_offset": 5,
26
  "cls_pool_dim": 256,
27
  "mimelens_cell_id": "small/byte/s2",
28
  "mimelens_vocab_pipeline": "byte",
 
8
  },
9
  "model_type": "mimelens",
10
  "torch_dtype": "float32",
11
+ "vocab_size": 263,
12
  "hidden_size": 384,
13
  "num_hidden_layers": 8,
14
  "num_attention_heads": 6,
 
22
  "cls_token_id": 4,
23
  "sep_token_id": 5,
24
  "mask_token_id": 6,
25
+ "byte_offset": 7,
26
  "cls_pool_dim": 256,
27
  "mimelens_cell_id": "small/byte/s2",
28
  "mimelens_vocab_pipeline": "byte",
configuration_mimelens.py CHANGED
@@ -28,8 +28,9 @@ class MimeLensConfig(PretrainedConfig):
28
  paper repository (https://github.com/mjbommar/binary-embedding-paper).
29
 
30
  Args:
31
- vocab_size: int — full vocabulary including 5 special tokens. byte
32
- cells: 261 (256 bytes + 5 specials). BPE cells: 4101 / 16391 / 65543.
 
33
  hidden_size: int — transformer model dimension (256 / 384 / 512 for
34
  tiny / small / medium).
35
  num_hidden_layers: int — layer count (4 / 8 / 12 for tiny / small /
@@ -46,7 +47,7 @@ class MimeLensConfig(PretrainedConfig):
46
  pad_token_id / cls_token_id / sep_token_id / mask_token_id: int —
47
  special-token indices, matching binary_embedding.constants.
48
  byte_offset: int — for byte cells, ord(b)+byte_offset gives the token
49
- id. Fixed at 5 (after the 5 special tokens). Unused for BPE cells.
50
  cls_pool_dim: int — output dim of the cls_pool layer. Note: this layer
51
  receives no gradient under MLM-only training (see paper §3.4); the
52
  mean-pool over body tokens is the trained pooling, not cls_pool.
@@ -82,7 +83,7 @@ class MimeLensConfig(PretrainedConfig):
82
  cls_token_id: int = 4,
83
  sep_token_id: int = 5,
84
  mask_token_id: int = 6,
85
- byte_offset: int = 5,
86
  cls_pool_dim: int = 256,
87
  initializer_range: float = 0.02,
88
  mimelens_cell_id: str = "medium/bpe-16k/s1",
 
28
  paper repository (https://github.com/mjbommar/binary-embedding-paper).
29
 
30
  Args:
31
+ vocab_size: int — full vocabulary including 7 special tokens (start, end,
32
+ pad, unk, cls, sep, mask). byte cells: 263 (256 bytes + 7 specials).
33
+ BPE cells: 4103 / 16391 / 65543.
34
  hidden_size: int — transformer model dimension (256 / 384 / 512 for
35
  tiny / small / medium).
36
  num_hidden_layers: int — layer count (4 / 8 / 12 for tiny / small /
 
47
  pad_token_id / cls_token_id / sep_token_id / mask_token_id: int —
48
  special-token indices, matching binary_embedding.constants.
49
  byte_offset: int — for byte cells, ord(b)+byte_offset gives the token
50
+ id. Fixed at 7 (after the 7 special tokens). Unused for BPE cells.
51
  cls_pool_dim: int — output dim of the cls_pool layer. Note: this layer
52
  receives no gradient under MLM-only training (see paper §3.4); the
53
  mean-pool over body tokens is the trained pooling, not cls_pool.
 
83
  cls_token_id: int = 4,
84
  sep_token_id: int = 5,
85
  mask_token_id: int = 6,
86
+ byte_offset: int = 7,
87
  cls_pool_dim: int = 256,
88
  initializer_range: float = 0.02,
89
  mimelens_cell_id: str = "medium/bpe-16k/s1",
modeling_mimelens.py CHANGED
@@ -32,7 +32,7 @@ import torch
32
  import torch.nn as nn
33
  import torch.nn.functional as F
34
  from transformers import PreTrainedModel
35
- from transformers.modeling_outputs import BaseModelOutputWithPooling
36
 
37
  from .configuration_mimelens import MimeLensConfig
38
 
@@ -272,3 +272,103 @@ class MimeLensModel(PreTrainedModel):
272
  attention_mask = torch.tensor([attn], dtype=torch.long, device=device)
273
  with torch.inference_mode():
274
  return self(input_ids, attention_mask=attention_mask).pooler_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  import torch.nn as nn
33
  import torch.nn.functional as F
34
  from transformers import PreTrainedModel
35
+ from transformers.modeling_outputs import BaseModelOutputWithPooling, SequenceClassifierOutput
36
 
37
  from .configuration_mimelens import MimeLensConfig
38
 
 
272
  attention_mask = torch.tensor([attn], dtype=torch.long, device=device)
273
  with torch.inference_mode():
274
  return self(input_ids, attention_mask=attention_mask).pooler_output
275
+
276
+
277
+ class MimeLensForSequenceClassification(PreTrainedModel):
278
+ """MimeLens encoder + a 125-class libmagic-MIME classifier head.
279
+
280
+ Lets users do, in one line:
281
+
282
+ from transformers import pipeline
283
+ clf = pipeline("text-classification",
284
+ model="mjbommar/mimelens-001-medium-bpe-16k-s1",
285
+ trust_remote_code=True)
286
+ clf(open("some.bin", "rb").read(4096).decode("latin-1"))
287
+ # → [{"label": "text/x-python", "score": 0.91}, ...]
288
+
289
+ The classifier head is the same logistic-regression probe the paper
290
+ reports on the magic-files corpus, re-fit on the full 4,096-file
291
+ labelled set and baked into `model.safetensors` as `classifier.weight`
292
+ and `classifier.bias`. Labels live in `config.id2label` / `config.label2id`.
293
+
294
+ For embedding-only use, load via `AutoModel.from_pretrained(...)` instead,
295
+ which returns mean-pooled embeddings and ignores the classifier head.
296
+ """
297
+
298
+ config_class = MimeLensConfig
299
+ base_model_prefix = "mimelens"
300
+
301
+ def __init__(self, config: MimeLensConfig):
302
+ super().__init__(config)
303
+ self.config = config
304
+ self.num_labels = getattr(config, "num_labels", 125)
305
+ # The encoder body, identical to MimeLensModel — same parameter names so
306
+ # the encoder weights load from the same safetensors keys.
307
+ self.embed = nn.Embedding(config.vocab_size, config.hidden_size)
308
+ self.layers = nn.ModuleList([Layer(config) for _ in range(config.num_hidden_layers)])
309
+ self.final_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
310
+ self.cls_pool = nn.Linear(config.hidden_size, config.cls_pool_dim, bias=False)
311
+ # The 125-way classifier head.
312
+ self.classifier = nn.Linear(config.hidden_size, self.num_labels)
313
+
314
+ self._rope_cache: Optional[tuple[torch.Tensor, torch.Tensor]] = None
315
+ self._rope_cache_meta: Optional[tuple[torch.device, torch.dtype, int]] = None
316
+ self.post_init()
317
+
318
+ def _init_weights(self, module):
319
+ if isinstance(module, nn.Linear):
320
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
321
+ if module.bias is not None:
322
+ module.bias.data.zero_()
323
+ elif isinstance(module, nn.Embedding):
324
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
325
+ if module.padding_idx is not None:
326
+ module.weight.data[module.padding_idx].zero_()
327
+
328
+ def _get_rope(self, seq_len: int, device: torch.device, dtype: torch.dtype):
329
+ meta = (device, dtype, seq_len)
330
+ if self._rope_cache_meta != meta:
331
+ self._rope_cache = _build_rope_cache(seq_len, self.config.head_dim,
332
+ self.config.rope_theta,
333
+ device=device, dtype=dtype)
334
+ self._rope_cache_meta = meta
335
+ return self._rope_cache
336
+
337
+ def forward(
338
+ self,
339
+ input_ids: torch.LongTensor,
340
+ attention_mask: Optional[torch.Tensor] = None,
341
+ labels: Optional[torch.LongTensor] = None,
342
+ return_dict: bool = True,
343
+ ):
344
+ B, S = input_ids.shape
345
+ x = self.embed(input_ids)
346
+
347
+ if attention_mask is None:
348
+ attention_mask = torch.ones(B, S, device=input_ids.device, dtype=torch.long)
349
+ attn_mask = attention_mask.to(x.dtype)
350
+ attn_mask = (1.0 - attn_mask).masked_fill((1.0 - attn_mask).bool(),
351
+ torch.finfo(x.dtype).min)
352
+ attn_mask = attn_mask.view(B, 1, 1, S)
353
+
354
+ cos, sin = self._get_rope(S, device=x.device, dtype=x.dtype)
355
+ for layer in self.layers:
356
+ x = layer(x, cos, sin, attn_mask)
357
+ x = self.final_norm(x)
358
+
359
+ lens = attention_mask.sum(dim=1, keepdim=True)
360
+ positions = torch.arange(S, device=x.device).unsqueeze(0)
361
+ body_mask = (positions >= 1) & (positions < (lens - 1))
362
+ body_mask_f = body_mask.to(x.dtype).unsqueeze(-1)
363
+ pooled = (x * body_mask_f).sum(dim=1) / body_mask_f.sum(dim=1).clamp(min=1)
364
+
365
+ # Cast pooled to classifier dtype (bf16 encoder + fp32 classifier is common).
366
+ logits = self.classifier(pooled.to(self.classifier.weight.dtype))
367
+
368
+ loss = None
369
+ if labels is not None:
370
+ loss = F.cross_entropy(logits, labels)
371
+
372
+ if not return_dict:
373
+ return (loss, logits) if loss is not None else (logits,)
374
+ return SequenceClassifierOutput(loss=loss, logits=logits)
tokenizer.json ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0",
3
+ "truncation": {
4
+ "direction": "Right",
5
+ "max_length": 1024,
6
+ "strategy": "LongestFirst",
7
+ "stride": 0
8
+ },
9
+ "padding": {
10
+ "strategy": "BatchLongest",
11
+ "direction": "Right",
12
+ "pad_to_multiple_of": null,
13
+ "pad_id": 2,
14
+ "pad_type_id": 0,
15
+ "pad_token": "[PAD]"
16
+ },
17
+ "added_tokens": [],
18
+ "normalizer": null,
19
+ "pre_tokenizer": {
20
+ "type": "Split",
21
+ "pattern": {
22
+ "Regex": "[\\s\\S]"
23
+ },
24
+ "behavior": "Isolated",
25
+ "invert": false
26
+ },
27
+ "post_processor": {
28
+ "type": "TemplateProcessing",
29
+ "single": [
30
+ {
31
+ "SpecialToken": {
32
+ "id": "[CLS]",
33
+ "type_id": 0
34
+ }
35
+ },
36
+ {
37
+ "Sequence": {
38
+ "id": "A",
39
+ "type_id": 0
40
+ }
41
+ },
42
+ {
43
+ "SpecialToken": {
44
+ "id": "[SEP]",
45
+ "type_id": 0
46
+ }
47
+ }
48
+ ],
49
+ "pair": [
50
+ {
51
+ "SpecialToken": {
52
+ "id": "[CLS]",
53
+ "type_id": 0
54
+ }
55
+ },
56
+ {
57
+ "Sequence": {
58
+ "id": "A",
59
+ "type_id": 0
60
+ }
61
+ },
62
+ {
63
+ "SpecialToken": {
64
+ "id": "[SEP]",
65
+ "type_id": 0
66
+ }
67
+ },
68
+ {
69
+ "Sequence": {
70
+ "id": "B",
71
+ "type_id": 1
72
+ }
73
+ },
74
+ {
75
+ "SpecialToken": {
76
+ "id": "[SEP]",
77
+ "type_id": 1
78
+ }
79
+ }
80
+ ],
81
+ "special_tokens": {
82
+ "[CLS]": {
83
+ "id": "[CLS]",
84
+ "ids": [
85
+ 4
86
+ ],
87
+ "tokens": [
88
+ "[CLS]"
89
+ ]
90
+ },
91
+ "[SEP]": {
92
+ "id": "[SEP]",
93
+ "ids": [
94
+ 5
95
+ ],
96
+ "tokens": [
97
+ "[SEP]"
98
+ ]
99
+ }
100
+ }
101
+ },
102
+ "decoder": null,
103
+ "model": {
104
+ "type": "WordLevel",
105
+ "vocab": {
106
+ "<|start|>": 0,
107
+ "<|end|>": 1,
108
+ "[PAD]": 2,
109
+ "[UNK]": 3,
110
+ "[CLS]": 4,
111
+ "[SEP]": 5,
112
+ "[MASK]": 6,
113
+ "\u0000": 7,
114
+ "\u0001": 8,
115
+ "\u0002": 9,
116
+ "\u0003": 10,
117
+ "\u0004": 11,
118
+ "\u0005": 12,
119
+ "\u0006": 13,
120
+ "\u0007": 14,
121
+ "\b": 15,
122
+ "\t": 16,
123
+ "\n": 17,
124
+ "\u000b": 18,
125
+ "\f": 19,
126
+ "\r": 20,
127
+ "\u000e": 21,
128
+ "\u000f": 22,
129
+ "\u0010": 23,
130
+ "\u0011": 24,
131
+ "\u0012": 25,
132
+ "\u0013": 26,
133
+ "\u0014": 27,
134
+ "\u0015": 28,
135
+ "\u0016": 29,
136
+ "\u0017": 30,
137
+ "\u0018": 31,
138
+ "\u0019": 32,
139
+ "\u001a": 33,
140
+ "\u001b": 34,
141
+ "\u001c": 35,
142
+ "\u001d": 36,
143
+ "\u001e": 37,
144
+ "\u001f": 38,
145
+ " ": 39,
146
+ "!": 40,
147
+ "\"": 41,
148
+ "#": 42,
149
+ "$": 43,
150
+ "%": 44,
151
+ "&": 45,
152
+ "'": 46,
153
+ "(": 47,
154
+ ")": 48,
155
+ "*": 49,
156
+ "+": 50,
157
+ ",": 51,
158
+ "-": 52,
159
+ ".": 53,
160
+ "/": 54,
161
+ "0": 55,
162
+ "1": 56,
163
+ "2": 57,
164
+ "3": 58,
165
+ "4": 59,
166
+ "5": 60,
167
+ "6": 61,
168
+ "7": 62,
169
+ "8": 63,
170
+ "9": 64,
171
+ ":": 65,
172
+ ";": 66,
173
+ "<": 67,
174
+ "=": 68,
175
+ ">": 69,
176
+ "?": 70,
177
+ "@": 71,
178
+ "A": 72,
179
+ "B": 73,
180
+ "C": 74,
181
+ "D": 75,
182
+ "E": 76,
183
+ "F": 77,
184
+ "G": 78,
185
+ "H": 79,
186
+ "I": 80,
187
+ "J": 81,
188
+ "K": 82,
189
+ "L": 83,
190
+ "M": 84,
191
+ "N": 85,
192
+ "O": 86,
193
+ "P": 87,
194
+ "Q": 88,
195
+ "R": 89,
196
+ "S": 90,
197
+ "T": 91,
198
+ "U": 92,
199
+ "V": 93,
200
+ "W": 94,
201
+ "X": 95,
202
+ "Y": 96,
203
+ "Z": 97,
204
+ "[": 98,
205
+ "\\": 99,
206
+ "]": 100,
207
+ "^": 101,
208
+ "_": 102,
209
+ "`": 103,
210
+ "a": 104,
211
+ "b": 105,
212
+ "c": 106,
213
+ "d": 107,
214
+ "e": 108,
215
+ "f": 109,
216
+ "g": 110,
217
+ "h": 111,
218
+ "i": 112,
219
+ "j": 113,
220
+ "k": 114,
221
+ "l": 115,
222
+ "m": 116,
223
+ "n": 117,
224
+ "o": 118,
225
+ "p": 119,
226
+ "q": 120,
227
+ "r": 121,
228
+ "s": 122,
229
+ "t": 123,
230
+ "u": 124,
231
+ "v": 125,
232
+ "w": 126,
233
+ "x": 127,
234
+ "y": 128,
235
+ "z": 129,
236
+ "{": 130,
237
+ "|": 131,
238
+ "}": 132,
239
+ "~": 133,
240
+ "": 134,
241
+ "€": 135,
242
+ "": 136,
243
+ "‚": 137,
244
+ "ƒ": 138,
245
+ "„": 139,
246
+ "…": 140,
247
+ "†": 141,
248
+ "‡": 142,
249
+ "ˆ": 143,
250
+ "‰": 144,
251
+ "Š": 145,
252
+ "‹": 146,
253
+ "Œ": 147,
254
+ "": 148,
255
+ "Ž": 149,
256
+ "": 150,
257
+ "": 151,
258
+ "‘": 152,
259
+ "’": 153,
260
+ "“": 154,
261
+ "”": 155,
262
+ "•": 156,
263
+ "–": 157,
264
+ "—": 158,
265
+ "˜": 159,
266
+ "™": 160,
267
+ "š": 161,
268
+ "›": 162,
269
+ "œ": 163,
270
+ "": 164,
271
+ "ž": 165,
272
+ "Ÿ": 166,
273
+ " ": 167,
274
+ "¡": 168,
275
+ "¢": 169,
276
+ "£": 170,
277
+ "¤": 171,
278
+ "¥": 172,
279
+ "¦": 173,
280
+ "§": 174,
281
+ "¨": 175,
282
+ "©": 176,
283
+ "ª": 177,
284
+ "«": 178,
285
+ "¬": 179,
286
+ "­": 180,
287
+ "®": 181,
288
+ "¯": 182,
289
+ "°": 183,
290
+ "±": 184,
291
+ "²": 185,
292
+ "³": 186,
293
+ "´": 187,
294
+ "µ": 188,
295
+ "¶": 189,
296
+ "·": 190,
297
+ "¸": 191,
298
+ "¹": 192,
299
+ "º": 193,
300
+ "»": 194,
301
+ "¼": 195,
302
+ "½": 196,
303
+ "¾": 197,
304
+ "¿": 198,
305
+ "À": 199,
306
+ "Á": 200,
307
+ "Â": 201,
308
+ "Ã": 202,
309
+ "Ä": 203,
310
+ "Å": 204,
311
+ "Æ": 205,
312
+ "Ç": 206,
313
+ "È": 207,
314
+ "É": 208,
315
+ "Ê": 209,
316
+ "Ë": 210,
317
+ "Ì": 211,
318
+ "Í": 212,
319
+ "Î": 213,
320
+ "Ï": 214,
321
+ "Ð": 215,
322
+ "Ñ": 216,
323
+ "Ò": 217,
324
+ "Ó": 218,
325
+ "Ô": 219,
326
+ "Õ": 220,
327
+ "Ö": 221,
328
+ "×": 222,
329
+ "Ø": 223,
330
+ "Ù": 224,
331
+ "Ú": 225,
332
+ "Û": 226,
333
+ "Ü": 227,
334
+ "Ý": 228,
335
+ "Þ": 229,
336
+ "ß": 230,
337
+ "à": 231,
338
+ "á": 232,
339
+ "â": 233,
340
+ "ã": 234,
341
+ "ä": 235,
342
+ "å": 236,
343
+ "æ": 237,
344
+ "ç": 238,
345
+ "è": 239,
346
+ "é": 240,
347
+ "ê": 241,
348
+ "ë": 242,
349
+ "ì": 243,
350
+ "í": 244,
351
+ "î": 245,
352
+ "ï": 246,
353
+ "ð": 247,
354
+ "ñ": 248,
355
+ "ò": 249,
356
+ "ó": 250,
357
+ "ô": 251,
358
+ "õ": 252,
359
+ "ö": 253,
360
+ "÷": 254,
361
+ "ø": 255,
362
+ "ù": 256,
363
+ "ú": 257,
364
+ "û": 258,
365
+ "ü": 259,
366
+ "ý": 260,
367
+ "þ": 261,
368
+ "ÿ": 262
369
+ },
370
+ "unk_token": "[UNK]"
371
+ }
372
+ }
tokenizer_config.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tokenizer_class": "PreTrainedTokenizerFast",
3
+ "model_max_length": 1024,
4
+ "padding_side": "right",
5
+ "truncation_side": "right",
6
+ "pad_token": "[PAD]",
7
+ "unk_token": "[UNK]",
8
+ "cls_token": "[CLS]",
9
+ "sep_token": "[SEP]",
10
+ "mask_token": "[MASK]",
11
+ "clean_up_tokenization_spaces": false,
12
+ "added_tokens_decoder": {
13
+ "2": {
14
+ "content": "[PAD]",
15
+ "lstrip": false,
16
+ "rstrip": false,
17
+ "single_word": false,
18
+ "normalized": false,
19
+ "special": true
20
+ },
21
+ "3": {
22
+ "content": "[UNK]",
23
+ "lstrip": false,
24
+ "rstrip": false,
25
+ "single_word": false,
26
+ "normalized": false,
27
+ "special": true
28
+ },
29
+ "4": {
30
+ "content": "[CLS]",
31
+ "lstrip": false,
32
+ "rstrip": false,
33
+ "single_word": false,
34
+ "normalized": false,
35
+ "special": true
36
+ },
37
+ "5": {
38
+ "content": "[SEP]",
39
+ "lstrip": false,
40
+ "rstrip": false,
41
+ "single_word": false,
42
+ "normalized": false,
43
+ "special": true
44
+ },
45
+ "6": {
46
+ "content": "[MASK]",
47
+ "lstrip": false,
48
+ "rstrip": false,
49
+ "single_word": false,
50
+ "normalized": false,
51
+ "special": true
52
+ }
53
+ }
54
+ }