RoeiG commited on
Commit
4a8bc16
·
verified ·
1 Parent(s): 4fe4823

Laya-HE v6 (seed 1)

Browse files
README.md ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-sa-4.0
3
+ language:
4
+ - he
5
+ - en
6
+ library_name: laya
7
+ pipeline_tag: zero-shot-classification
8
+ base_model: dicta-il/neodictabert-bilingual
9
+ tags:
10
+ - laya
11
+ - hebrew
12
+ - decision-model
13
+ - calibrated
14
+ datasets:
15
+ - LocalLLaMA/typed-decisions
16
+ - clinc/clinc_oos
17
+ - legacy-datasets/banking77
18
+ - fancyzhx/ag_news
19
+ - fancyzhx/dbpedia_14
20
+ - community-datasets/yahoo_answers_topics
21
+ - facebook/anli
22
+ - Yelp/yelp_review_full
23
+ - HebArabNlpProject/HebNLI
24
+ - Etelis/HeQ_v1
25
+ - HebArabNlpProject/HebrewSentiment
26
+ - Tobi-Bueck/customer-support-tickets
27
+ - nyu-mll/glue
28
+ - google/civil_comments
29
+ - google-research-datasets/go_emotions
30
+ - ucberkeley-dlab/measuring-hate-speech
31
+ - tasksource/tasksource-instruct-v0
32
+ - wikimedia/wikipedia
33
+ - HuggingFaceFW/fineweb-2
34
+ - google/boolq
35
+ - ehovy/race
36
+ ---
37
+
38
+ # Laya-HE v6: a Hebrew–English decision model
39
+
40
+ A [Laya](https://github.com/NandhaKishorM/laya)-style decision model for Hebrew and English. You give it a **state** (the
41
+ text or fields to judge) and **questions**: a choice between options, a score on a scale, or a yes/no claim. In one
42
+ forward pass it returns a **calibrated probability for every answer**. It is a fast classifier that you configure at
43
+ call time. It is not a chatbot and it does not generate text.
44
+
45
+ - **Encoder:** [`dicta-il/neodictabert-bilingual`](https://huggingface.co/dicta-il/neodictabert-bilingual) (NeoBERT, 28
46
+ layers, Hebrew + English)
47
+ - **Head:** Laya's `DecisionModel` head, with 2 transformer layers and a scorer over the option markers
48
+ - **Size:** 378M parameters, about 120–230 ms per call on an Apple M1 CPU
49
+ - **Input:** up to 1,024 tokens of state, and up to 256 tokens of options
50
+ - **Training:** Laya's RLCD objective (proper-scoring-rule rewards plus soft cross-entropy), with a temperature for each
51
+ question type fitted on held-out human- or rule-labeled items
52
+ - **Use:** non-commercial only (see [License](#license))
53
+
54
+ ## Usage
55
+
56
+ This checkpoint needs Laya 0.3.7 (commit `010bace`) with `neobert.patch`. The patch loads NeoBERT's remote code,
57
+ recomputes its rotary tables (without that, every output is NaN under transformers 5) and keeps the encoder in fp32.
58
+
59
+ ```bash
60
+ git clone https://github.com/NandhaKishorM/laya && cd laya && git checkout 010bace
61
+ git apply /path/to/neobert.patch # from this repository
62
+ pip install -e . # torch, transformers 5.x
63
+ ```
64
+
65
+ ```python
66
+ import laya
67
+
68
+ agent = laya.load("RoeiG/laya-hebrew", device="cpu") # or "cuda"
69
+ out = agent.predict(
70
+ {"message": "האפליקציה קורסת כשאני פותח את המצלמה"},
71
+ {
72
+ "team": {"type": "choice", "instructions": "איזה צוות צריך לטפל בהודעה?",
73
+ "criteria": {"billing": "תשלומים והחזרים", "tech": "באגים וקריסות", "shipping": "משלוחים"}},
74
+ "upset": {"type": "noul", "instructions": "הלקוח כועס."},
75
+ },
76
+ )
77
+ # out["answers"]["team"] -> {"choice": "tech", "probabilities": {"billing": 0.0014, "tech": 0.9973, "shipping": 0.0013}, ...}
78
+ # out["answers"]["upset"] -> {"noul": 0.0551, ...} (P(true))
79
+ ```
80
+
81
+ Question types:
82
+ - **`choice`:** `criteria` maps each option to a description.
83
+ - **`score`:** `criteria` is a list of levels from lowest to highest. The answer includes the expected level.
84
+ - **`noul`:** yes/no. The answer is P(true) for the statement in `instructions`.
85
+
86
+ ## How to get good answers
87
+
88
+ 1. **Compute numbers, dates, units and relations in code.** Pass the result as a field, such as
89
+ `"age_ok": "הגיל עומד בתנאי"` or `"relation": "The sender is the receiver's direct manager"`. The model does not do
90
+ arithmetic reliably (see Limitations).
91
+ 2. **Prefer the claim form for yes/no.** "הלקוח כועס." discriminates better than "האם הלקוח כועס?" (gap 0.67 against
92
+ 0.38 on he_bench). The question form works in v6, but it is weaker.
93
+ 3. **Describe every option in a line.** Bare labels or codes route much worse than labels with a one-line description.
94
+ Name what each option owns, not only its keywords.
95
+ 4. **Read the probabilities, not only the top answer.** A top answer below about 0.6 means the model is unsure.
96
+ 5. **Ignore `act_probability`.** It comes from a head that no Hebrew checkpoint trained.
97
+
98
+ ## Evaluation
99
+
100
+ Every evaluation set below was held out of training. he_bench v1 has 8 Hebrew tasks, each asked in 3 phrasings; the
101
+ set is frozen by sha256. Brier and ECE are better when lower. "laya-multilingual" is Laya's published multilingual
102
+ checkpoint. v5 is the previous release of this project.
103
+
104
+ | Metric | laya-multilingual | v5 | **v6** |
105
+ |---|---|---|---|
106
+ | MASSIVE he, 20 intents (500) | 0.352 | 0.816 | **0.806** |
107
+ | MASSIVE he, 4 intents | 0.710 | 0.928 | **0.938** |
108
+ | MASSIVE en, 20 intents | 0.652 | 0.816 | **0.816** |
109
+ | he_bench accuracy / Brier / ECE (4,290) | 0.439 / 0.701 / 0.266 | 0.527 / 0.538 / 0.119 | **0.595 / 0.499 / 0.125** |
110
+ | Belebele-he reading comprehension (900; chance 0.25) | | 0.468 | **0.767** |
111
+ | SIB-200-he topic | | 0.808 | **0.801** |
112
+ | Yes/no as a question: P(yes \| true) − P(yes \| false) | | 0.047 | **0.379** |
113
+ | Yes/no as a claim: same gap (390) | | 0.692 | **0.674** |
114
+ | Hebrew BoolQ, held out (875) | | 0.611 | **0.838** |
115
+ | Rule-direction probe (208) | | 0.798 | **0.832** |
116
+ | Held-out soft-label set, Brier (900) | | 0.230 | **0.231** |
117
+
118
+ he_bench accuracy by task (v6):
119
+
120
+ | Task | Accuracy | Chance |
121
+ |---|---|---|
122
+ | relevance (question form) | 0.785 | 0.50 |
123
+ | qa_verify (question form) | 0.704 | 0.50 |
124
+ | copa | 0.687 | 0.50 |
125
+ | sentiment | 0.633 | 0.33 |
126
+ | winograd | 0.607 | 0.50 |
127
+ | hellaswag | 0.447 | 0.25 |
128
+ | tone arousal | 0.389 | 5 levels |
129
+ | tone valence | 0.328 | 5 levels |
130
+
131
+ **Run-to-run noise.** A second training seed, with the same data and settings, differed by these amounts:
132
+ - he_bench overall: 0.2 points
133
+ - Belebele and MASSIVE: 0.6–1.0 points
134
+ - single he_bench tasks: 1–3 points
135
+ - each half of the rule probe: 5–7 points
136
+
137
+ Differences smaller than these are noise. This checkpoint is seed 1, which was fixed as the release before training.
138
+
139
+ ## Limitations
140
+
141
+ - **Reasoning is the weak spot.**
142
+ - Hellaswag, winograd and copa are well above chance but far from solved.
143
+ - Multi-step inferences (e.g. "A is taller than B, B is taller than C: who is shortest?") often fail.
144
+ - **Numeric, date and unit rules are unreliable, and often confidently wrong.**
145
+ - Examples: 2.5 hours against a 2-hour limit, a purchase 19 days ago against a 14-day window, or age 17 against an
146
+ English "18 and up" rule. These got P(true) of 0.93–0.98.
147
+ - Compute them in code.
148
+ - **Irony and sarcasm are read literally.** "וואו, שירות מדהים… ניתקו לי 👏" is scored as positive, at 0.97.
149
+ - **Routing leans on keywords.**
150
+ - In a small hand-written check of tech tickets, anything that mentioned "דיפלוי" (deploy) was pulled towards DevOps.
151
+ That happened even when the cause was a code bug, a network path, an expired certificate or a locked account.
152
+ - Strong keywords in the text can outweigh the option descriptions.
153
+ - **Claim-form relevance is slightly below v5:** he_bench 0.78 against 0.80, and BEIR-he 0.78 against 0.82.
154
+ - **Scales:** sentiment and tone are about 0.45 accuracy, and the tone probabilities are overconfident (valence Brier
155
+ 0.53). The lowest level of a scale is under-used.
156
+ - **Calculated fields steer less than in v5.** On 8 test emails, a "direct manager" sender field raised importance by
157
+ 0.11 of a level, against 0.22 in v5, and a "mailing list" field lowered it in only 2 of 8.
158
+ - **Calibration does not catch everything.** The failures above are often high-confidence, so a confidence threshold
159
+ will not filter them out.
160
+
161
+ ## Training data
162
+
163
+ The training start was an earlier checkpoint of this project (v3), trained on the same public data and on the same
164
+ encoder. The run was one epoch over 396,508 items (6,196 updates, 1.4 A100 hours). The learning rates were 5e-6 for the
165
+ encoder and 1e-4 for the head. Every case was converted to Laya's format, with a random subset of options, paraphrased
166
+ instructions and varied field names.
167
+
168
+ - **Public labeled data**, English unless marked:
169
+ - typed-decisions
170
+ - CLINC, Banking77, AG News, DBpedia, Yahoo Answers
171
+ - ANLI, Yelp, GLUE STS-B, Civil Comments, customer-support tickets
172
+ - Hebrew NLI (HebNLI), Hebrew QA (HeQ) and Hebrew sentiment
173
+ - **Soft labels from annotator disagreement:**
174
+ - GoEmotions and Measuring Hate Speech
175
+ - SNLI and MultiNLI votes, used as P(true)
176
+ - plus breadth from tasksource-instruct
177
+ - **Native Hebrew:** Hebrew Wikipedia topics and facts, labeled from Wikidata.
178
+ - **Relevance and spam:** BEIR-he relevance (biunlp's Hebrew translation of BEIR) and UCI SMS spam.
179
+ - **Machine-translated:** part of the English data was translated to Hebrew with NLLB-200-distilled-600M: 60% of the
180
+ soft-label cases, and half of the NLI votes and the spam. Only the state text was translated; the questions and
181
+ options stayed as they were.
182
+ - **Generated with code-computed labels:** rule and unit checks.
183
+ - **New in v6:**
184
+ - RACE (30,000 questions) and BoolQ, in English with human labels.
185
+ - About 58,000 Hebrew items written and labeled by
186
+ [DictaLM-3.0-24B](https://huggingface.co/dicta-il) (Apache-2.0), on passages from Hebrew Wikipedia and FineWeb-2
187
+ Hebrew:
188
+ - yes/no questions about a passage, and answer checking
189
+ - query–passage relevance
190
+ - 4-option reading comprehension
191
+ - routing of invented business messages
192
+ - BoolQ translated to Hebrew, which keeps its human answers
193
+ - An item was kept only when the teacher's label agreed with the answer it was written for.
194
+ - Subjective scales were never teacher-labeled.
195
+ - The teacher-generated files are not published.
196
+
197
+ Decontamination: no teacher text shares an 8-word run with any evaluation set. The evaluation instructions and the
198
+ yes/no wordings used by he_bench were kept out of training. MASSIVE was never trained on.
199
+
200
+ No private data and no personal data were used.
201
+
202
+ ## License
203
+
204
+ **CC-BY-NC-SA-4.0: non-commercial use only.** Several training sources are non-commercial or research-only: ANLI, Yelp,
205
+ AG News, Yahoo Answers, RACE, and the NLLB translation model (CC-BY-NC-4.0). Others are ShareAlike: Wikipedia, BEIR-he,
206
+ SNLI and BoolQ. The encoder is by Dicta (`neodictabert-bilingual`, CC-BY-4.0). The Laya architecture, training method
207
+ and runtime are by Laya's authors (Apache-2.0).
208
+
209
+ ## Acknowledgements
210
+
211
+ - Dicta, for NeoDictaBERT-bilingual and DictaLM 3.0
212
+ - Laya's authors, for the architecture, the RLCD training method and the runtime
213
+ - The creators of every dataset listed above
encoder/config.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "NeoBERTLMHead"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "modeling_neobert.NeoBERTConfig",
7
+ "AutoModel": "modeling_neobert.NeoBERT",
8
+ "AutoModelForMaskedLM": "modeling_neobert.NeoBERTLMHead",
9
+ "AutoModelForQuestionAnswering": "modeling_neobert.NeoBERTForQuestionAnswering",
10
+ "AutoModelForSequenceClassification": "modeling_neobert.NeoBERTForSequenceClassification",
11
+ "AutoModelForTokenClassification": "modeling_neobert.NeoBERTForTokenClassification"
12
+ },
13
+ "decoder_init_range": 0.02,
14
+ "dim_head": 64,
15
+ "dtype": "bfloat16",
16
+ "embedding_init_range": 0.02,
17
+ "encoder_init_range": 0.02,
18
+ "hidden_size": 768,
19
+ "intermediate_size": 3072,
20
+ "max_length": 4096,
21
+ "model_type": "neobert",
22
+ "norm_eps": 1e-06,
23
+ "num_attention_heads": 12,
24
+ "num_hidden_layers": 28,
25
+ "pad_token_id": 3,
26
+ "transformers_version": "5.16.1",
27
+ "vocab_size": 128000
28
+ }
encoder/modeling_neobert.py ADDED
@@ -0,0 +1,696 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+ import torch.nn.functional as F
4
+
5
+ from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
6
+ from torch.nn.functional import scaled_dot_product_attention
7
+
8
+ from typing import Optional, Tuple, Union
9
+ import numpy as np
10
+
11
+ try:
12
+ from xformers.ops import SwiGLU
13
+ except:
14
+ class SwiGLU(nn.Module):
15
+ """
16
+ A Module that mimicks the call to :attr:`xformers.ops.swiglu`,
17
+ and holds the weights for the 3 linear layers
18
+ """
19
+ def __init__(
20
+ self,
21
+ in_features: int,
22
+ hidden_features: int,
23
+ out_features: Optional[int] = None,
24
+ bias: bool = True,
25
+ *,
26
+ _pack_weights: bool = True,
27
+ ) -> None:
28
+ """Create a SwiGLU module
29
+
30
+ Args:
31
+ in_features (int): Number of features of the input
32
+ hidden_features (int): Number of hidden features
33
+ out_features (Optional[int], optional): Number of features of the input. Defaults to None.
34
+ bias (bool, optional): Whether linear layers also include a bias. Defaults to True.
35
+ """
36
+ super().__init__()
37
+ out_features = out_features or in_features
38
+ hidden_features = hidden_features or in_features
39
+
40
+ self.w12: Optional[nn.Linear]
41
+ if _pack_weights:
42
+ self.w12 = nn.Linear(in_features, 2 * hidden_features, bias=bias)
43
+ else:
44
+ self.w12 = None
45
+ self.w1 = nn.Linear(in_features, hidden_features, bias=bias)
46
+ self.w2 = nn.Linear(in_features, hidden_features, bias=bias)
47
+ self.w3 = nn.Linear(hidden_features, out_features, bias=bias)
48
+
49
+ self.hidden_features = hidden_features
50
+ self.out_features = out_features
51
+ self.in_features = in_features
52
+ self.op: Optional[SwiGLUOp] = None
53
+
54
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
55
+ """Computes :attr:`swiglu` with the module's weights
56
+
57
+ Args:
58
+ x (torch.Tensor): A Tensor of shape ``[..., in_features]``
59
+
60
+ Returns:
61
+ torch.Tensor: A Tensor of shape ``[..., out_features]``
62
+ """
63
+ if self.w12 is not None:
64
+ gate, x = self.w12(x).chunk(2, dim=-1)
65
+ hidden = F.silu(gate) * x
66
+ else:
67
+ x1 = self.w1(x)
68
+ x2 = self.w2(x)
69
+ hidden = F.silu(x1) * x2
70
+
71
+ return self.w3(hidden)
72
+
73
+
74
+ try:
75
+ from flash_attn.flash_attn_interface import flash_attn_varlen_func
76
+
77
+ FLASH_ATTN_AVAILABLE = True
78
+ except ImportError:
79
+ FLASH_ATTN_AVAILABLE = False
80
+
81
+ from transformers import (
82
+ PreTrainedModel,
83
+ PretrainedConfig,
84
+ DataCollatorForLanguageModeling,
85
+ )
86
+ from transformers.modeling_outputs import (
87
+ BaseModelOutput,
88
+ MaskedLMOutput,
89
+ SequenceClassifierOutput,
90
+ TokenClassifierOutput,
91
+ QuestionAnsweringModelOutput
92
+ )
93
+
94
+ import torch
95
+ from typing import Tuple
96
+
97
+ def precompute_freqs(dim: int, end: int, theta: float = 10000.0, *, device=None, dtype=torch.float32):
98
+ """
99
+ Returns (cos, sin) tensors of shape [end, dim//2], no complex dtype.
100
+ """
101
+ h = dim // 2
102
+ idx = torch.arange(0, h, device=device, dtype=dtype)
103
+ inv_freq = 1.0 / (theta ** ((2.0 * idx) / dim))
104
+ t = torch.arange(end, device=device, dtype=dtype)
105
+ angles = torch.outer(t, inv_freq) # [L, h]
106
+ return angles.cos(), angles.sin() # ([L, h], [L, h])
107
+
108
+ def reshape_for_broadcast(freqs: torch.Tensor, x: torch.Tensor):
109
+ # freqs: [L, h]; x: [B, L, H, h] for the half-dim tensors
110
+ assert freqs.shape == (x.shape[1], x.shape[-1]), (freqs.shape, x.shape)
111
+ return freqs[None, :, None, :] # [1, L, 1, h]
112
+
113
+ # Rotary embedding without complex numbers (megatron-core pairing: first half with second half)
114
+ def apply_rotary_emb(xq: torch.Tensor, xk: torch.Tensor, freqs: tuple[torch.Tensor, torch.Tensor]):
115
+ # x*: [B, L, H, D]; freqs = (cos[L,h], sin[L,h])
116
+ D = xq.shape[-1]
117
+ h = D // 2
118
+ xq1, xq2 = xq[..., :h], xq[..., h:]
119
+ xk1, xk2 = xk[..., :h], xk[..., h:]
120
+
121
+ cos, sin = freqs
122
+ cos = reshape_for_broadcast(cos.type_as(xq1), xq1) # [1, L, 1, h]
123
+ sin = reshape_for_broadcast(sin.type_as(xq1), xq1) # [1, L, 1, h]
124
+
125
+ q1 = xq1 * cos - xq2 * sin
126
+ q2 = xq1 * sin + xq2 * cos
127
+ k1 = xk1 * cos - xk2 * sin
128
+ k2 = xk1 * sin + xk2 * cos
129
+
130
+ return torch.cat([q1, q2], dim=-1), torch.cat([k1, k2], dim=-1)
131
+
132
+ class NeoBERTEagerRMSNorm(nn.Module):
133
+ def __init__(self, hidden_size, eps=1e-6):
134
+ """
135
+ NeoBERTEagerRMSNorm is equivalent to nn.RMSNorm
136
+ """
137
+ super().__init__()
138
+ self.weight = nn.Parameter(torch.ones(hidden_size))
139
+ self.variance_epsilon = eps
140
+
141
+ def forward(self, hidden_states):
142
+ input_dtype = hidden_states.dtype
143
+ hidden_states = hidden_states.to(torch.float32)
144
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
145
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
146
+ return self.weight * hidden_states.to(input_dtype)
147
+
148
+ def extra_repr(self):
149
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
150
+
151
+
152
+ class NeoBERTConfig(PretrainedConfig):
153
+ model_type = "neobert"
154
+
155
+ # All config parameters must have a default value.
156
+ def __init__(
157
+ self,
158
+ hidden_size: int = 768,
159
+ num_hidden_layers: int = 28,
160
+ num_attention_heads: int = 12,
161
+ intermediate_size: int = 3072,
162
+ embedding_init_range: float = 0.02,
163
+ encoder_init_range: float = 0.02,
164
+ norm_eps: float = 1e-06,
165
+ vocab_size: int = 30522,
166
+ pad_token_id: int = 0,
167
+ max_length: int = 1024,
168
+ **kwargs,
169
+ ):
170
+ super().__init__(**kwargs)
171
+
172
+ self.hidden_size = hidden_size
173
+ self.num_hidden_layers = num_hidden_layers
174
+ self.num_attention_heads = num_attention_heads
175
+ if hidden_size % num_attention_heads != 0:
176
+ raise ValueError("Hidden size must be divisible by the number of heads.")
177
+ self.dim_head = hidden_size // num_attention_heads
178
+ self.intermediate_size = intermediate_size
179
+ self.embedding_init_range = embedding_init_range
180
+ self.encoder_init_range = encoder_init_range
181
+ self.norm_eps = norm_eps
182
+ self.vocab_size = vocab_size
183
+ self.pad_token_id = pad_token_id
184
+ self.max_length = max_length
185
+ self.kwargs = kwargs
186
+
187
+
188
+ class EncoderBlock(nn.Module):
189
+ """Transformer encoder block."""
190
+
191
+ def __init__(self, config: NeoBERTConfig):
192
+ super().__init__()
193
+
194
+ self.config = config
195
+
196
+ # Attention
197
+ self.qkv = nn.Linear(in_features=config.hidden_size, out_features=config.hidden_size * 3, bias=False)
198
+ self.wo = nn.Linear(in_features=config.hidden_size, out_features=config.hidden_size, bias=False)
199
+
200
+ # Feedforward network
201
+ # Original NeoBERT:
202
+ # multiple_of = 8
203
+ # intermediate_size = int(2 * config.intermediate_size / 3)
204
+ # intermediate_size = multiple_of * ((intermediate_size + multiple_of - 1) // multiple_of)
205
+ intermediate_size = config.intermediate_size
206
+ self.ffn = SwiGLU(config.hidden_size, intermediate_size, config.hidden_size, bias=False)
207
+
208
+ # Layer norms
209
+ rms_norm_cls = nn.RMSNorm if hasattr(nn, 'RMSNorm') else NeoBERTEagerRMSNorm
210
+ self.attention_norm = rms_norm_cls(config.hidden_size, config.norm_eps)
211
+ self.ffn_norm = rms_norm_cls(config.hidden_size, config.norm_eps)
212
+
213
+ def forward(
214
+ self,
215
+ x: torch.Tensor,
216
+ attention_mask: torch.Tensor,
217
+ freqs_cis: torch.Tensor,
218
+ output_attentions: bool,
219
+ max_seqlen: int = None,
220
+ cu_seqlens: torch.Tensor = None,
221
+ ):
222
+ # Attention
223
+ attn_output, attn_weights = self._att_block(
224
+ self.attention_norm(x), attention_mask, freqs_cis, output_attentions, max_seqlen, cu_seqlens
225
+ )
226
+
227
+ # Residual
228
+ x = x + attn_output
229
+
230
+ # Feed-forward
231
+ x = x + self.ffn(self.ffn_norm(x))
232
+
233
+ return x, attn_weights
234
+
235
+ def _att_block(
236
+ self,
237
+ x: torch.Tensor,
238
+ attention_mask: torch.Tensor,
239
+ freqs_cis: torch.Tensor,
240
+ output_attentions: bool,
241
+ max_seqlen: int = None,
242
+ cu_seqlens: torch.Tensor = None,
243
+ ):
244
+ batch_size, seq_len, _ = x.shape
245
+
246
+ xq, xk, xv = self.qkv(x).view(batch_size, seq_len, self.config.num_attention_heads, self.config.dim_head * 3).chunk(3, axis=-1)
247
+
248
+ xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
249
+
250
+ # Attn block
251
+ attn_weights = None
252
+
253
+ # Flash attention if the tensors are packed
254
+ if cu_seqlens is not None:
255
+ attn = flash_attn_varlen_func(
256
+ q=xq.squeeze(0),
257
+ k=xk.squeeze(0),
258
+ v=xv.squeeze(0),
259
+ cu_seqlens_q=cu_seqlens,
260
+ cu_seqlens_k=cu_seqlens,
261
+ max_seqlen_q=max_seqlen,
262
+ max_seqlen_k=max_seqlen,
263
+ dropout_p=0.0,
264
+ causal=False,
265
+ )
266
+ # Eager attention if attention weights are needed in the output (avoid using this unless needed - e.g., for onnx export)
267
+ elif output_attentions or self.config._attn_implementation == 'eager':
268
+ attn_weights = xq.permute(0, 2, 1, 3) @ xk.permute(0, 2, 3, 1) / (xq.size(-1) ** 0.5)
269
+ if attention_mask is not None:
270
+ attn_weights = attn_weights * attention_mask
271
+ attn_weights = attn_weights.softmax(-1)
272
+ attn = attn_weights @ xv.permute(0, 2, 1, 3)
273
+ attn = attn.transpose(1, 2)
274
+ # Fall back to SDPA otherwise
275
+ else:
276
+ attn = scaled_dot_product_attention(
277
+ query=xq.transpose(1, 2),
278
+ key=xk.transpose(1, 2),
279
+ value=xv.transpose(1, 2),
280
+ attn_mask=attention_mask.bool(),
281
+ dropout_p=0,
282
+ ).transpose(1, 2)
283
+
284
+ return self.wo(attn.reshape(batch_size, seq_len, self.config.num_attention_heads * self.config.dim_head)), attn_weights
285
+
286
+
287
+ class NeoBERTPreTrainedModel(PreTrainedModel):
288
+ config_class = NeoBERTConfig
289
+ base_model_prefix = "model"
290
+ _supports_cache_class = True
291
+ _supports_flash_attn = True
292
+ _supports_sdpa = True
293
+
294
+ def _init_weights(self, module):
295
+ if isinstance(module, nn.Linear):
296
+ module.weight.data.uniform_(-self.config.encoder_init_range, self.config.encoder_init_range)
297
+ elif isinstance(module, nn.Embedding):
298
+ module.weight.data.uniform_(-self.config.embedding_init_range, self.config.embedding_init_range)
299
+
300
+
301
+ class NeoBERT(NeoBERTPreTrainedModel):
302
+ config_class = NeoBERTConfig
303
+
304
+ def __init__(self, config: NeoBERTConfig):
305
+ super().__init__(config)
306
+
307
+ self.config = config
308
+
309
+ self.encoder = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
310
+
311
+ # Ensures freqs_cis is moved to the same devices as the model. Non-persistent buffers are not saved in the state_dict.
312
+ cos, sin = precompute_freqs(config.hidden_size // config.num_attention_heads, config.max_length)
313
+ self.register_buffer("freqs_cos", cos, persistent=False)
314
+ self.register_buffer("freqs_sin", sin, persistent=False)
315
+
316
+ self.transformer_encoder = nn.ModuleList()
317
+ for _ in range(config.num_hidden_layers):
318
+ self.transformer_encoder.append(EncoderBlock(config))
319
+
320
+ rms_norm_cls = nn.RMSNorm if hasattr(nn, 'RMSNorm') else NeoBERTEagerRMSNorm
321
+ self.layer_norm = rms_norm_cls(config.hidden_size, config.norm_eps)
322
+
323
+ # Initialize weights and apply final processing
324
+ self.post_init()
325
+
326
+ def forward(
327
+ self,
328
+ input_ids: Optional[torch.Tensor] = None,
329
+ position_ids: torch.Tensor = None,
330
+ max_seqlen: int = None,
331
+ cu_seqlens: torch.Tensor = None,
332
+ attention_mask: torch.Tensor = None,
333
+ inputs_embeds: Optional[torch.Tensor] = None,
334
+ token_type_ids: Optional[torch.Tensor] = None, # kept in to not break compatibility with tokenizer(...), ignored
335
+ output_hidden_states: bool = False,
336
+ output_attentions: bool = False,
337
+ **kwargs,
338
+ ):
339
+ # Initialize
340
+ hidden_states, attentions = [], []
341
+
342
+ if (input_ids is None) ^ (inputs_embeds is not None):
343
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
344
+
345
+ # Expand and repeat: (Batch, Length) -> (Batch, Heads, Length, Length)
346
+ if attention_mask is None:
347
+ attention_mask = torch.ones_like(input_ids)
348
+ attention_mask = attention_mask[:, None, None, :]
349
+
350
+ # attention_mask = attention_mask.unsqueeze(1).unsqueeze(1).repeat(1, self.config.num_attention_heads, attention_mask.size(-1), 1)
351
+
352
+ # Checks to be done if inputs are packed sequences
353
+ if cu_seqlens is not None:
354
+ assert (
355
+ FLASH_ATTN_AVAILABLE
356
+ ), "Flash-attention is not available. Please ''pip install flash_attn'', or provide un-packed sequences."
357
+ assert not output_attentions, "Output attentions is not supported when sequences are packed."
358
+ assert max_seqlen is not None, "Missing max_seqlen. It must be provided when cu_seqlens are not None."
359
+ assert (input_ids if input_ids is not None else inputs_embeds).shape[
360
+ 0
361
+ ] == 1, "Cumulative sequence lengths are provided but inputs are not packed."
362
+ assert (
363
+ input_ids if input_ids is not None else inputs_embeds
364
+ ).is_cuda, "Packing uses an implementation of flash-attention and is only supported on GPU."
365
+
366
+ # RoPE
367
+ if position_ids is not None:
368
+ freqs = (self.freqs_cos[position_ids], self.freqs_sin[position_ids])
369
+ else:
370
+ L = (input_ids if input_ids is not None else inputs_embeds).shape[1]
371
+ freqs = (self.freqs_cos[:L], self.freqs_sin[:L])
372
+
373
+ # Embedding
374
+ x = self.encoder(input_ids) if input_ids is not None else inputs_embeds
375
+
376
+ # Transformer encoder
377
+ for layer in self.transformer_encoder:
378
+ x, attn = layer(x, attention_mask, freqs, output_attentions, max_seqlen, cu_seqlens)
379
+ if output_hidden_states:
380
+ hidden_states.append(x)
381
+ if output_attentions:
382
+ attentions.append(attn)
383
+
384
+ # Final normalization layer
385
+ x = self.layer_norm(x)
386
+
387
+ # Return the output of the last hidden layer
388
+ return BaseModelOutput(
389
+ last_hidden_state=x,
390
+ hidden_states=hidden_states if output_hidden_states else None,
391
+ attentions=attentions if output_attentions else None,
392
+ )
393
+
394
+
395
+ class NeoBERTLMHead(NeoBERTPreTrainedModel):
396
+ config_class = NeoBERTConfig
397
+
398
+ def __init__(self, config: NeoBERTConfig):
399
+ super().__init__(config)
400
+
401
+ self.config = config
402
+
403
+ self.model = NeoBERT(config)
404
+ self.decoder = nn.Linear(config.hidden_size, config.vocab_size)
405
+
406
+ self.post_init()
407
+
408
+ def forward(
409
+ self,
410
+ input_ids: torch.Tensor,
411
+ position_ids: torch.Tensor = None,
412
+ max_seqlen: int = None,
413
+ cu_seqlens: torch.Tensor = None,
414
+ attention_mask: torch.Tensor = None,
415
+ inputs_embeds: Optional[torch.Tensor] = None,
416
+ token_type_ids: Optional[torch.Tensor] = None, # kept in to not break compatibility with tokenizer(...), ignored
417
+ output_hidden_states: bool = False,
418
+ output_attentions: bool = False,
419
+ **kwargs,
420
+ ):
421
+
422
+ output = self.model.forward(
423
+ input_ids=input_ids,
424
+ position_ids=position_ids,
425
+ inputs_embeds=inputs_embeds,
426
+ max_seqlen=max_seqlen,
427
+ cu_seqlens=cu_seqlens,
428
+ attention_mask=attention_mask,
429
+ output_hidden_states=output_hidden_states,
430
+ output_attentions=output_attentions,
431
+ )
432
+ logits = self.decoder(output.last_hidden_state)
433
+
434
+ return MaskedLMOutput(
435
+ hidden_states=output.hidden_states if output_hidden_states else None,
436
+ attentions=output.attentions if output_attentions else None,
437
+ logits=logits,
438
+ )
439
+
440
+
441
+ class NeoBERTForTokenClassification(NeoBERTPreTrainedModel):
442
+ config_class = NeoBERTConfig
443
+
444
+ def __init__(self, config: NeoBERTConfig):
445
+ super().__init__(config)
446
+
447
+ self.config = config
448
+
449
+ self.num_labels = getattr(config, "num_labels", 2)
450
+ self.classifier_dropout = getattr(config, "classifier_dropout", 0.1)
451
+ self.classifier_init_range = getattr(config, "classifier_init_range", 0.02)
452
+
453
+ self.model = NeoBERT(config)
454
+
455
+ self.dense = nn.Linear(self.config.hidden_size, self.config.hidden_size)
456
+ self.dropout = nn.Dropout(self.classifier_dropout)
457
+ self.classifier = nn.Linear(self.config.hidden_size, self.num_labels)
458
+
459
+ self.post_init()
460
+
461
+ def _init_weights(self, module):
462
+ if isinstance(module, nn.Linear):
463
+ module.weight.data.normal_(mean=0.0, std=self.classifier_init_range)
464
+ if module.bias is not None:
465
+ module.bias.data.zero_()
466
+
467
+ def forward(
468
+ self,
469
+ input_ids: Optional[torch.Tensor] = None,
470
+ position_ids: torch.Tensor = None,
471
+ max_seqlen: int = None,
472
+ cu_seqlens: torch.Tensor = None,
473
+ attention_mask: torch.Tensor = None,
474
+ inputs_embeds: Optional[torch.Tensor] = None,
475
+ token_type_ids: Optional[torch.Tensor] = None, # kept in to not break compatibility with tokenizer(...), ignored
476
+ output_hidden_states: bool = False,
477
+ output_attentions: bool = False,
478
+ labels: Optional[torch.Tensor] = None,
479
+ return_dict: Optional[bool] = None,
480
+ ):
481
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
482
+
483
+ output = self.model.forward(
484
+ input_ids=input_ids,
485
+ position_ids=position_ids,
486
+ inputs_embeds=inputs_embeds,
487
+ max_seqlen=max_seqlen,
488
+ cu_seqlens=cu_seqlens,
489
+ attention_mask=attention_mask,
490
+ output_hidden_states=output_hidden_states,
491
+ output_attentions=output_attentions,
492
+ )
493
+ hidden_states = output.last_hidden_state
494
+
495
+ x = self.dropout(hidden_states)
496
+ x = self.dense(x)
497
+ x = torch.tanh(x)
498
+ x = self.dropout(x)
499
+
500
+ logits = self.classifier(x)
501
+
502
+ loss = None
503
+ if labels is not None:
504
+ loss_fct = CrossEntropyLoss()
505
+ # only keep active parts of the loss
506
+ if attention_mask is not None:
507
+ active_loss = attention_mask.view(-1) == 1
508
+ active_logits = logits.view(-1, self.num_labels)[active_loss]
509
+ active_labels = labels.view(-1)[active_loss]
510
+ loss = loss_fct(active_logits, active_labels)
511
+ else:
512
+ loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
513
+
514
+ if not return_dict:
515
+ result = (logits,)
516
+ return ((loss,) + result) if loss is not None else result
517
+
518
+ return TokenClassifierOutput(
519
+ loss=loss,
520
+ logits=logits,
521
+ hidden_states=output.hidden_states if output_hidden_states else None,
522
+ attentions=output.attentions if output_attentions else None,
523
+ )
524
+
525
+
526
+ class NeoBERTForSequenceClassification(NeoBERTPreTrainedModel):
527
+ config_class = NeoBERTConfig
528
+
529
+ def __init__(self, config: NeoBERTConfig):
530
+ super().__init__(config)
531
+
532
+ self.config = config
533
+
534
+ self.num_labels = getattr(config, "num_labels", 2)
535
+ self.classifier_dropout = getattr(config, "classifier_dropout", 0.1)
536
+ self.classifier_init_range = getattr(config, "classifier_init_range", 0.02)
537
+
538
+ self.model = NeoBERT(config)
539
+
540
+ self.dense = nn.Linear(self.config.hidden_size, self.config.hidden_size)
541
+ self.dropout = nn.Dropout(self.classifier_dropout)
542
+ self.classifier = nn.Linear(self.config.hidden_size, self.num_labels)
543
+
544
+ self.post_init()
545
+
546
+ def _init_weights(self, module):
547
+ if isinstance(module, nn.Linear):
548
+ module.weight.data.normal_(mean=0.0, std=self.classifier_init_range)
549
+ if module.bias is not None:
550
+ module.bias.data.zero_()
551
+
552
+ def forward(
553
+ self,
554
+ input_ids: Optional[torch.Tensor] = None,
555
+ position_ids: torch.Tensor = None,
556
+ max_seqlen: int = None,
557
+ cu_seqlens: torch.Tensor = None,
558
+ attention_mask: torch.Tensor = None,
559
+ inputs_embeds: Optional[torch.Tensor] = None,
560
+ token_type_ids: Optional[torch.Tensor] = None, # kept in to not break compatibility with tokenizer(...), ignored
561
+ output_hidden_states: bool = False,
562
+ output_attentions: bool = False,
563
+ labels: Optional[torch.Tensor] = None,
564
+ return_dict: Optional[bool] = None,
565
+ ):
566
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
567
+
568
+ output = self.model.forward(
569
+ input_ids=input_ids,
570
+ position_ids=position_ids,
571
+ inputs_embeds=inputs_embeds,
572
+ max_seqlen=max_seqlen,
573
+ cu_seqlens=cu_seqlens,
574
+ attention_mask=attention_mask,
575
+ output_hidden_states=output_hidden_states,
576
+ output_attentions=output_attentions,
577
+ )
578
+ hidden_states = output.last_hidden_state
579
+
580
+ x = hidden_states[:, 0, :]
581
+ x = self.dropout(x)
582
+ x = self.dense(x)
583
+ x = torch.tanh(x)
584
+ x = self.dropout(x)
585
+
586
+ logits = self.classifier(x)
587
+
588
+ loss = None
589
+ if labels is not None:
590
+ if self.config.problem_type is None:
591
+ if self.num_labels == 1:
592
+ self.config.problem_type = "regression"
593
+ elif self.num_labels > 1 and (labels.dtype == torch.long or labels.dtype == torch.int):
594
+ self.config.problem_type = "single_label_classification"
595
+ else:
596
+ self.config.problem_type = "multi_label_classification"
597
+
598
+ if self.config.problem_type == "regression":
599
+ loss_fct = MSELoss()
600
+ if self.num_labels == 1:
601
+ loss = loss_fct(logits.squeeze(), labels.squeeze())
602
+ else:
603
+ loss = loss_fct(logits, labels)
604
+ elif self.config.problem_type == "single_label_classification":
605
+ loss_fct = CrossEntropyLoss()
606
+ loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
607
+ elif self.config.problem_type == "multi_label_classification":
608
+ loss_fct = BCEWithLogitsLoss()
609
+ loss = loss_fct(logits, labels)
610
+
611
+ if not return_dict:
612
+ result = (logits,)
613
+ return ((loss,) + result) if loss is not None else result
614
+
615
+ return SequenceClassifierOutput(
616
+ loss=loss,
617
+ logits=logits,
618
+ hidden_states=output.hidden_states if output_hidden_states else None,
619
+ attentions=output.attentions if output_attentions else None,
620
+ )
621
+
622
+ class NeoBERTForQuestionAnswering(NeoBERTPreTrainedModel):
623
+ def __init__(self, config):
624
+ super().__init__(config)
625
+ self.num_labels = config.num_labels
626
+
627
+ self.model = NeoBERT(config)
628
+ self.qa_outputs = nn.Linear(config.hidden_size, config.num_labels)
629
+
630
+ # Initialize weights and apply final processing
631
+ self.post_init()
632
+
633
+ def forward(
634
+ self,
635
+ input_ids: Optional[torch.Tensor] = None,
636
+ position_ids: torch.Tensor = None,
637
+ max_seqlen: int = None,
638
+ cu_seqlens: torch.Tensor = None,
639
+ attention_mask: torch.Tensor = None,
640
+ inputs_embeds: Optional[torch.Tensor] = None,
641
+ token_type_ids: Optional[torch.Tensor] = None, # kept in to not break compatibility with tokenizer(...), ignored
642
+ start_positions: Optional[torch.Tensor] = None,
643
+ end_positions: Optional[torch.Tensor] = None,
644
+ output_hidden_states: bool = False,
645
+ output_attentions: bool = False,
646
+ return_dict: Optional[bool] = None,
647
+ ) -> Union[tuple[torch.Tensor], QuestionAnsweringModelOutput]:
648
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
649
+ if output_attentions or output_hidden_states: return_dict = True
650
+
651
+ output = self.model.forward(
652
+ input_ids=input_ids,
653
+ position_ids=position_ids,
654
+ inputs_embeds=inputs_embeds,
655
+ max_seqlen=max_seqlen,
656
+ cu_seqlens=cu_seqlens,
657
+ attention_mask=attention_mask,
658
+ output_hidden_states=output_hidden_states,
659
+ output_attentions=output_attentions,
660
+ return_dict=True
661
+ )
662
+ hidden_states = output.last_hidden_state
663
+
664
+ logits = self.qa_outputs(hidden_states)
665
+ start_logits, end_logits = logits.split(1, dim=-1)
666
+ start_logits = start_logits.squeeze(-1).contiguous()
667
+ end_logits = end_logits.squeeze(-1).contiguous()
668
+
669
+ total_loss = None
670
+ if start_positions is not None and end_positions is not None:
671
+ # If we are on multi-GPU, split add a dimension
672
+ if len(start_positions.size()) > 1:
673
+ start_positions = start_positions.squeeze(-1)
674
+ if len(end_positions.size()) > 1:
675
+ end_positions = end_positions.squeeze(-1)
676
+ # sometimes the start/end positions are outside our model inputs, we ignore these terms
677
+ ignored_index = start_logits.size(1)
678
+ start_positions = start_positions.clamp(0, ignored_index)
679
+ end_positions = end_positions.clamp(0, ignored_index)
680
+
681
+ loss_fct = CrossEntropyLoss(ignore_index=ignored_index)
682
+ start_loss = loss_fct(start_logits, start_positions)
683
+ end_loss = loss_fct(end_logits, end_positions)
684
+ total_loss = (start_loss + end_loss) / 2
685
+
686
+ if not return_dict:
687
+ output = (start_logits, end_logits)
688
+ return ((total_loss,) + output) if total_loss is not None else output
689
+
690
+ return QuestionAnsweringModelOutput(
691
+ loss=total_loss,
692
+ start_logits=start_logits,
693
+ end_logits=end_logits,
694
+ hidden_states=output.hidden_states,
695
+ attentions=output.attentions,
696
+ )
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce385eae4618f08347363a0abd83143a531d4d72985549b52db7c616008bc698
3
+ size 755139924
neobert.patch ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diff --git a/laya/agent.py b/laya/agent.py
2
+ index 70163bf..b7b9a0b 100644
3
+ --- a/laya/agent.py
4
+ +++ b/laya/agent.py
5
+ @@ -186,7 +186,8 @@ class Agent:
6
+ self.device = torch.device("cpu")
7
+
8
+ tok_dir = os.path.join(model_dir, "tokenizer")
9
+ - self.tok = AutoTokenizer.from_pretrained(tok_dir if os.path.exists(tok_dir) else self.cfg.get("encoder"))
10
+ + self.tok = AutoTokenizer.from_pretrained(tok_dir if os.path.exists(tok_dir) else self.cfg.get("encoder"),
11
+ + trust_remote_code=bool(self.cfg.get("trust_remote_code", False)))
12
+
13
+ enc_dir = os.path.join(model_dir, "encoder")
14
+ # The checkpoint supplies every parameter; skip random/base-model weights.
15
+ diff --git a/laya/common.py b/laya/common.py
16
+ index 950c41d..6a633f7 100644
17
+ --- a/laya/common.py
18
+ +++ b/laya/common.py
19
+ @@ -139,11 +139,25 @@ class DecisionModel(nn.Module):
20
+ def build_model(cfg: Dict, encoder_dir: Optional[str] = None, pretrained: bool = True) -> DecisionModel:
21
+ from transformers import AutoConfig, AutoModel
22
+
23
+ + # Encoders whose modeling code lives on the Hub (e.g. NeoBERT) must opt in via the config.
24
+ + remote = bool(cfg.get("trust_remote_code", False))
25
+ if not pretrained or (encoder_dir and os.path.exists(encoder_dir)):
26
+ - ecfg = AutoConfig.from_pretrained(encoder_dir or cfg["encoder"])
27
+ - enc = AutoModel.from_config(ecfg, attn_implementation="sdpa")
28
+ + ecfg = AutoConfig.from_pretrained(encoder_dir or cfg["encoder"], trust_remote_code=remote)
29
+ + enc = AutoModel.from_config(ecfg, attn_implementation="sdpa", trust_remote_code=remote)
30
+ else:
31
+ - enc = AutoModel.from_pretrained(cfg["encoder"], attn_implementation="sdpa")
32
+ + enc = AutoModel.from_pretrained(cfg["encoder"], attn_implementation="sdpa", trust_remote_code=remote)
33
+ + # NeoBERT computes its RoPE tables as non-persistent buffers in __init__; transformers 5 builds models on
34
+ + # the meta device, so they come back as uninitialised memory and every forward pass returns NaN.
35
+ + if hasattr(enc, "freqs_cos"):
36
+ + import sys
37
+ +
38
+ + precompute = sys.modules[type(enc).__module__].precompute_freqs
39
+ + c = enc.config
40
+ + enc.freqs_cos, enc.freqs_sin = precompute(c.hidden_size // c.num_attention_heads, c.max_length)
41
+ + # Some encoder configs pin a half-precision dtype (NeoBERT ships bfloat16), which transformers 5 honours. Keep
42
+ + # master weights in float32: bf16 weights round most optimizer updates to zero, and CPU inference in bf16 is ~8x slower.
43
+ + # Mixed precision is applied by autocast at train/inference time instead.
44
+ + enc = enc.float()
45
+ return DecisionModel(enc, cfg.get("head_layers", 2), len(cfg.get("act_costs", {})) + 1)
46
+
47
+
rl_agent_config.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "encoder": "dicta-il/neodictabert-bilingual",
3
+ "head_layers": 2,
4
+ "max_len": 1024,
5
+ "head_max_len": 256,
6
+ "max_prefixes": 6,
7
+ "act_costs": {
8
+ "escalate": 0.5
9
+ },
10
+ "cost_wrong_act": 3.0,
11
+ "amp_dtype": "bf16",
12
+ "model_name": "laya-he-neodictabert-v6",
13
+ "temperature": [
14
+ 1.0730161666870117,
15
+ 1.1290489435195923,
16
+ 1.1732172966003418
17
+ ],
18
+ "training": {
19
+ "init": "/content/drive/MyDrive/laya_he_v3/model.safetensors",
20
+ "seed": 1,
21
+ "items": 396508,
22
+ "updates": 6196,
23
+ "epochs_completed": 1,
24
+ "lr_encoder": 5e-06,
25
+ "lr_head": 0.0001,
26
+ "hours": 1.42,
27
+ "calibration_items": {
28
+ "0": 600,
29
+ "1": 600,
30
+ "2": 600
31
+ },
32
+ "avg_loss": 0.3189
33
+ },
34
+ "trust_remote_code": true,
35
+ "gradient_checkpointing": true,
36
+ "max_tokens_per_batch": 4096,
37
+ "fine_tuned": true
38
+ }
tokenizer/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "[CLS]",
4
+ "clean_up_tokenization_spaces": false,
5
+ "cls_token": "[CLS]",
6
+ "do_lower_case": true,
7
+ "eos_token": "[SEP]",
8
+ "is_local": true,
9
+ "local_files_only": false,
10
+ "mask_token": "[MASK]",
11
+ "model_max_length": 1000000000000000019884624838656,
12
+ "pad_token": "[PAD]",
13
+ "sep_token": "[SEP]",
14
+ "strip_accents": null,
15
+ "tokenize_chinese_chars": true,
16
+ "tokenizer_class": "BertTokenizer",
17
+ "unk_token": "[UNK]"
18
+ }