BrainboxAI commited on
Commit
a216c5b
·
verified ·
1 Parent(s): cbce6d0

Nitzotz release candidate: weights, GGUF files, card, calibration and eval results

Browse files
.gitattributes CHANGED
@@ -40,3 +40,5 @@ assets/calibration_he.png filter=lfs diff=lfs merge=lfs -text
40
  assets/radar_en.png filter=lfs diff=lfs merge=lfs -text
41
  assets/radar_he.png filter=lfs diff=lfs merge=lfs -text
42
  assets/speed_en.png filter=lfs diff=lfs merge=lfs -text
 
 
 
40
  assets/radar_en.png filter=lfs diff=lfs merge=lfs -text
41
  assets/radar_he.png filter=lfs diff=lfs merge=lfs -text
42
  assets/speed_en.png filter=lfs diff=lfs merge=lfs -text
43
+ nitzotz-f16.gguf filter=lfs diff=lfs merge=lfs -text
44
+ nitzotz-q8_0.gguf filter=lfs diff=lfs merge=lfs -text
NOTICE CHANGED
@@ -10,8 +10,8 @@ Encoder
10
 
11
  Architecture, training method and runtime
12
  - laya (github.com/NandhaKishorM/laya, NandhaKishorM / Convai Innovations), Apache License 2.0. The DecisionModel head
13
- architecture, the option-marker scoring, the RLCD training objective and the laya runtime. The head of this model
14
- was trained from scratch; no weights come from any laya checkpoint.
15
  - ggmlc (github.com/monatis/ggmlc), used to compile the GGUF files. ggmlc/nitzotz_trunk.py and
16
  ggmlc/compile_nitzotz.py are adapted from its examples/laya files (laya_trunk.py, compile_laya.py).
17
 
@@ -19,7 +19,8 @@ Training data
19
  - HeQ, Hebrew Question Answering Dataset v1.1 (NNLP-IL, github.com/NNLP-IL/Hebrew-Question-Answering-Dataset, commit
20
  f46d2ff), CC BY 4.0. Created by Webiks for MAFAT and the National NLP Program of Israel. Includes passages from
21
  Hebrew Wikipedia and from Geektime, shared by the dataset authors under the dataset licence. Train split only.
22
- Items were reformatted into yes/no and 4-option questions.
 
23
  - MASSIVE (AmazonScience/massive, he-IL), CC BY 4.0, Amazon. FitzGerald et al., 2022. Train split only. Intent
24
  labels were translated into short Hebrew labels.
25
  - Synthetic Hebrew messages written by DeepSeek V4.1 Flash (deepseek-ai, MIT License) and labelled by DeepSeek V4.1
 
10
 
11
  Architecture, training method and runtime
12
  - laya (github.com/NandhaKishorM/laya, NandhaKishorM / Convai Innovations), Apache License 2.0. The DecisionModel head
13
+ architecture, the option-marker scoring and the laya runtime. The head of this model was trained from scratch
14
+ (soft cross-entropy); no weights come from any laya checkpoint.
15
  - ggmlc (github.com/monatis/ggmlc), used to compile the GGUF files. ggmlc/nitzotz_trunk.py and
16
  ggmlc/compile_nitzotz.py are adapted from its examples/laya files (laya_trunk.py, compile_laya.py).
17
 
 
19
  - HeQ, Hebrew Question Answering Dataset v1.1 (NNLP-IL, github.com/NNLP-IL/Hebrew-Question-Answering-Dataset, commit
20
  f46d2ff), CC BY 4.0. Created by Webiks for MAFAT and the National NLP Program of Israel. Includes passages from
21
  Hebrew Wikipedia and from Geektime, shared by the dataset authors under the dataset licence. Train split only.
22
+ Used twice: as extractive question answering to teach the encoder to read (passages overlapping the test removed),
23
+ and reformatted into yes/no and 4-option questions for the decision head.
24
  - MASSIVE (AmazonScience/massive, he-IL), CC BY 4.0, Amazon. FitzGerald et al., 2022. Train split only. Intent
25
  labels were translated into short Hebrew labels.
26
  - Synthetic Hebrew messages written by DeepSeek V4.1 Flash (deepseek-ai, MIT License) and labelled by DeepSeek V4.1
README.md CHANGED
@@ -17,12 +17,9 @@ datasets:
17
  - AmazonScience/massive
18
  ---
19
 
20
- > [!WARNING]
21
- > **DRAFT, numbers will be replaced by the final model.** This card is a local draft. Nothing here is published, and the model is not on Hugging Face yet. The numbers are the current best results, from one checkpoint used for every test.
22
-
23
  ![Nitzotz: Hebrew decision model](assets/banner_en.png)
24
 
25
- ![Scam detection accuracy 82.5%, 0.14 s per question, 413 MB, Apache-2.0](assets/tiles_en.png)
26
 
27
  **What it is.** Nitzotz reads a Hebrew message and answers questions you type about it: pick one of several options,
28
  give a score on a scale, or say yes or no to a claim. For every answer it gives a probability you can trust, so you
@@ -30,21 +27,16 @@ know when it is sure and when it is guessing. It does not write text, so it cann
30
  laptop, with no internet connection and no cost per question.
31
 
32
  **What it is for.** Deciding what to do with incoming messages: is this a scam, what kind of message is it, which
33
- department should get it, how urgent is it. It is not a chatbot and it does not read long documents well
34
  (see [Limitations](#limitations)).
35
 
36
  **In numbers.** On 298 Hebrew messages it says correctly whether a message is a scam 82.5% of the
37
- time. For context: 70% of those messages are not scams, so a model that always
38
- says "not a scam" would score 70.5%; the two other open Hebrew-capable laya models score
39
- 32.2% (RoeiG) and 50.3% (laya-multilingual). The number that matters more is
40
- the ranking: it gives real scams a higher probability than legitimate messages 88% of the time (AUC 0.88).
41
 
42
  ## Try it
43
 
44
- **Live demo:** a Hugging Face Space will be linked here (not created yet).
45
-
46
- **Weights will be published with the release.** The commands below show how it will be used; the files they name are not uploaded yet.
47
-
48
  **Python** (the [laya](https://github.com/NandhaKishorM/laya) library, `pip install laya`):
49
 
50
  ```python
@@ -54,27 +46,31 @@ q = {"scam": {"type": "noul", "instructions": "ההודעה מנסה לגרום
54
  print(agent.predict("החבילה שלך מעוכבת. לשחרור שלם 12.90 בקישור", q)["answers"]["scam"]["noul"])
55
  ```
56
 
57
- **laya.exe** (the standalone binary from [ggmlc](https://github.com/monatis/ggmlc/releases), no Python needed; one
58
- JSON request per line in, one answer per line out):
 
 
 
59
 
60
  ```bash
61
- laya daemon nitzotz-q8_0.gguf --device vulkan
 
62
  {"id":"1","state":"קוד האימות שלך הוא 000000","questions":{"scam":{"type":"noul","instructions":"ההודעה היא ניסיון הונאה."}}}
63
  ```
64
 
65
- The answer is the probability that the claim is true, for example `0.83`. Several questions in one call are answered
66
- together in one pass.
 
 
 
 
67
 
68
  ## Benchmarks
69
 
70
  8 frozen test sets, 3,990 questions in total, locked by checksum before any training data existed. All three
71
- models answered exactly the same questions. We compare only the open laya models that read Hebrew:
72
-
73
- - **Nitzotz**: this model.
74
- - **[RoeiG/laya-hebrew](https://huggingface.co/RoeiG/laya-hebrew)**: a laya model on NeoDictaBERT. **Licence
75
- CC-BY-NC-SA-4.0: non-commercial use only.** It is here as a reference point, you cannot use it in a product.
76
- - **[laya-multilingual](https://huggingface.co/convaiinnovations/laya)** (convaiinnovations, the `multilingual`
77
- subfolder): the original laya authors' model for 100+ languages. Apache-2.0.
78
 
79
  ![Accuracy per test](assets/bars_en.png)
80
 
@@ -100,26 +96,17 @@ p < 0.05, "tie" means the difference could be chance.
100
  | Plausible answer the passage does not give (600) | **91.0%** | 53.0% | 53.7% | 50.0% | **better**<br>p<0.001 | **better**<br>p<0.001 |
101
  | Reading comprehension, 4 options (900) | 52.7% | **75.4%** | 31.4% | 25.0% | **worse**<br>p<0.001 | **better**<br>p<0.001 |
102
 
 
 
 
103
  How to read it:
104
  - **MASSIVE** (voice commands): Nitzotz was trained on MASSIVE's training commands. The test commands are different,
105
  but written by the same people in the same style, so this test is easier for Nitzotz than for the others.
106
- - **HeQ**: both Nitzotz (for these two rows) and RoeiG were trained on HeQ's training passages. The test passages are
107
- different ones.
108
  - **The support-ticket rows have only 30 questions.** No difference there is reliable.
109
- - **The spam and scam test has a lean**: 70% of its messages are not scams. The
110
- "chance" line (50%) is a coin flip, not the best blind strategy.
111
-
112
- ### Where RoeiG leads
113
-
114
- We say this plainly because you should know it before you choose:
115
-
116
- - **News topic, 7 options (204)**: RoeiG 82.3%, Nitzotz 79.4%. No clear difference (p=0.42).
117
- - **Does the passage support this answer? (600)**: RoeiG 94.2%, Nitzotz 93.8%. No clear difference (p=0.90).
118
- - **Reading comprehension, 4 options (900)**: RoeiG 75.4%, Nitzotz 52.7%. Nitzotz worse (p<0.001).
119
-
120
- **Reading a passage and answering questions about it (Belebele) is RoeiG's strength and Nitzotz's weakness.** RoeiG
121
- was trained on reading-comprehension data; Nitzotz was not built for it. If your task is reading documents, Nitzotz is
122
- the wrong model. Remember that RoeiG cannot be used commercially.
123
 
124
  ## Why you can trust it
125
 
@@ -129,20 +116,40 @@ the wrong model. Remember that RoeiG cannot be used commercially.
129
  how many were right: the chart above does that for every confidence level, over 3,990 test questions.
130
  Where the dots sit above the line, Nitzotz is more often right than it claims (it is modest); where they sit below, it
131
  is over-confident. This is what lets you set thresholds (see the next section). The per-question-type temperatures
132
- were fitted on held-out training items, never on the test sets.
133
 
134
  **It reads the text.** With the message removed and only the question left, its accuracy falls to
135
- 70.5% on the scam question and 7.7% on message type. So the
136
- answers come from the message, not from the wording of the question.
137
 
138
  ![Speed against file size](assets/speed_en.png)
139
 
140
  **It is fast on ordinary hardware.** On one laptop (Intel Core Ultra 9 285H laptop, built-in Arc 140T GPU, Windows 11), one question at a time:
141
- the Q8 file answers in a median of 140 ms (slowest 10%: 187 ms or more), the F16 file in 154 ms.
142
- Through Python on the same GPU Nitzotz takes 294 ms and RoeiG 1005 ms (measured on the same machine, but
143
- in separate runs on 50 and 40 test questions of similar length, about 134 and 136 tokens). laya-multilingual was
144
- not measured on this machine, so it has no speed on the chart. The Q8 file gives the same top answer as the full
145
- Python model on 50/50 check questions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  ## Use it in your business
148
 
@@ -156,19 +163,23 @@ decides what happens next:
156
  - **Red** (confident it is a scam): block or quarantine it.
157
 
158
  The drawing is a worked example on the 298 test messages. The upper threshold, 0.52, is the one chosen for the
159
- scam question on held-out training messages (never on the test); the lower one, 0.35, was picked by hand. With them,
160
  183 messages go to green (16 of them are in fact scams), 48 to yellow (21 scams), and 67 to red (16 of them are
161
- in fact legitimate). So today most messages still need a look, and red should mean "quarantine and check", not
162
- "delete". Pick your own thresholds on a sample of your own messages, and decide how many mistakes in green and red
163
- you can live with. At the 0.52 threshold alone, the scam answer is right 82.2% of the time overall and
164
- 64.6% on the 65 hard cases.
165
 
166
  The model is cheap enough to run on every message. The person (or the LLM) only sees the yellow part. **Do not use it
167
  as the only line of defence for decisions that can hurt someone.**
168
 
169
  ## Training data and transparency
170
 
171
- 58,000 training items, one pass over the data, on a rented GPU.
 
 
 
 
 
172
 
173
  | Source | Items | Licence | How it was made |
174
  |---|---|---|---|
@@ -185,15 +196,15 @@ as the only line of defence for decisions that can hurt someone.**
185
  from a plan (intended label, topic, tone, varied fake phone numbers and links). DeepSeek and Gemma 4 31B
186
  (Apache-2.0) then each labelled every message on their own. A message was kept only if both agreed; the two models
187
  agreed on 94% of them, and 93.7% of the 37,429 written messages were kept. Both ran
188
- through DeepInfra (via OpenRouter), with data retention off and "thinking" mode off. Cost: 5.39 USD. The
189
- synthetic data is not published.
190
  - **Open data.** HeQ v1.1 (CC BY 4.0; about half of its questions are on Geektime articles, shared by the HeQ authors under
191
  the same licence) and MASSIVE he-IL (CC BY 4.0). Training splits only.
192
  - **No leaks from the tests.** Every training item was compared with every test question; anything sharing an
193
  8-word run with a test text was dropped, and so were MASSIVE commands equal to a test command.
194
  - **Not used:** no output of closed commercial chatbots, no DICTA model, no non-commercial or share-alike data.
195
- - **Encoder:** HalleluBERT-large (MIT), 357.1M parameters, fine-tuned; laya head 26.5M parameters, trained from scratch.
196
- - **The reading encoder is merged into this checkpoint.** HalleluBERT was first trained to find answers in HeQ passages (27,085 HeQ training questions, CC BY 4.0), then given a laya head and fine-tuned on the same training set as every other row. The two HeQ rows use the same checkpoint as the rest of the table, not a separate one.
197
 
198
  **About the tests.**
199
 
@@ -209,16 +220,17 @@ as the only line of defence for decisions that can hurt someone.**
209
  Caveats that change how much to trust the numbers:
210
  - **The spam and business test was written by an AI model and checked by an AI model, not by a person.** Real inboxes
211
  will look different. It was frozen after that review (4 labels changed, 2 messages removed).
212
- - **One training run, one random seed.** Differences of 1 to 3 points between models may be run-to-run noise.
213
  - **The training labels come from two AI models.** Where both are wrong in the same way, Nitzotz learned their mistake.
214
  - HeQ's wrong answers in the test were picked by code, not checked by a person.
215
 
216
  ## Limitations
217
 
218
- - **It does not read documents well.** Belebele reading comprehension: 52.7%, where a blind guess gets 25%.
219
- Do not ask it whether a long document supports a claim.
220
- - **HeQ answer checking looks strong, with a catch.** 93.8% with the passage, but still 50.0% with the
221
- passage removed (RoeiG: 80.0%). A lot of that score comes from the question and answer alone, not from reading.
 
222
  - **Numbers, dates, amounts and rules**: not trained and not measured. Compute them in code and pass the result in.
223
  - **Hard cases are the weak spot**: scams written to look legitimate (a "supplier" changing bank details, the "CEO"
224
  asking for a transfer) and real messages that look like scams (a real bank alert with a link, a real verification
@@ -235,7 +247,7 @@ Caveats that change how much to trust the numbers:
235
  **Apache-2.0** for the weights, the GGUF files and the code. Commercial use is allowed. Built on:
236
 
237
  - [HalleluBERT-large](https://huggingface.co/HalleluBERT/HalleluBERT_large): the encoder, MIT.
238
- - [laya](https://github.com/NandhaKishorM/laya) (NandhaKishorM, Convai Innovations): architecture, training method and
239
  runtime, Apache-2.0. No laya weights are used.
240
  - [HeQ](https://github.com/NNLP-IL/Hebrew-Question-Answering-Dataset): CC BY 4.0, by Webiks for MAFAT and the Israeli
241
  National NLP Program (NNLP-IL); includes Geektime passages.
@@ -252,31 +264,23 @@ The full notice is in `NOTICE`.
252
 
253
  ## ניצוץ, בעברית
254
 
255
- > [!WARNING]
256
- > **טיוטה, המספרים יוחלפו במודל הסופי.** זה כרטיס טיוטה מקומי. שום דבר כאן לא פורסם, והמודל עוד לא נמצא ב-Hugging Face. המספרים הם התוצאות הטובות ביותר כרגע, מגרסה אחת שנבדקה בכל המבחנים.
257
-
258
  ![ניצוץ: מודל החלטות בעברית](assets/banner_he.png)
259
 
260
- ![דיוק בזיהוי הונאות 82.5%, 0.14 שניות לשאלה, 413 MB, Apache-2.0](assets/tiles_he.png)
261
 
262
  **מה זה.** ניצוץ קורא הודעה בעברית ועונה על שאלות שאתם מקלידים עליה: לבחור אחת מכמה אפשרויות, לתת ציון בסולם, או
263
  לענות כן או לא על טענה. על כל תשובה הוא נותן הסתברות שאפשר לסמוך עליה, כך שיודעים מתי הוא בטוח ומתי הוא מנחש. הוא לא
264
  כותב טקסט, ולכן הוא לא יכול להמציא דברים. הוא רץ על מחשב נייד רגיל, בלי אינטרנט ובלי תשלום על כל שאלה.
265
 
266
  **בשביל מה.** להחליט מה עושים עם הודעות נכנסות: האם זו הונאה, איזה סוג הודעה זו, לאיזו מחלקה להעביר, כמה זה דחוף. זה
267
- לא צ'אטבוט, והוא לא טוב בקריאת מסמכים ארוכים (ראו [מגבלות](#מגבלות)).
268
 
269
  **במספרים.** על 298 הודעות בעברית הוא קובע נכון אם ההודעה היא הונאה ב-82.5% מהמקרים. בשביל
270
- פרופורציה: 70% מההודעות האלה הן לא הונאה, כך שמודל שתמיד עונה "לא הונאה" היה מקבל 70.5%.
271
- שני מודלי laya הפתוחים האחרים שקוראים עברית מקבלים 32.2% (RoeiG) ו-50.3% (laya-multilingual). המספר
272
  שחשוב יותר הוא הדירוג: הוא נותן להונאה אמיתית הסתברות גבוהה יותר מאשר להודעה תקינה ב-88% מהמקרים (AUC 0.88).
273
 
274
  ### לנסות
275
 
276
- **הדגמה חיה:** כאן יופיע קישור ל-Space ב-Hugging Face (עוד לא נוצר).
277
-
278
- **המשקולות יפורסמו יחד עם ההשקה.** הפקודות למטה מראות איך זה ייראה, הקבצים שהן מזכירות עוד לא הועלו.
279
-
280
  **בפייתון** (הספרייה [laya](https://github.com/NandhaKishorM/laya), `pip install laya`):
281
 
282
  </div>
@@ -290,30 +294,33 @@ print(agent.predict("החבילה שלך מעוכבת. לשחרור שלם 12.90
290
 
291
  <div dir="rtl">
292
 
293
- **בלי פייתון, עם laya.exe** (תוכנה עצמאית מ-[ggmlc](https://github.com/monatis/ggmlc/releases). בקשת JSON אחת בכל
294
- שורה נכנסת, תשובה אחת בכל שורה יוצאת):
 
 
295
 
296
  </div>
297
 
298
  ```bash
299
- laya daemon nitzotz-q8_0.gguf --device vulkan
 
300
  {"id":"1","state":"קוד האימות שלך הוא 000000","questions":{"scam":{"type":"noul","instructions":"ההודעה היא ניסיון הונאה."}}}
301
  ```
302
 
 
 
 
 
303
  <div dir="rtl">
304
 
305
- התשובה היא ההסתברות שהטענה נכונה, למשל `0.83`. כמה שאלות בקריאה אחת נענות יחד, במעבר אחד.
306
 
307
  ### מבחנים
308
 
309
  8 סטים של מבחן, 3,990 שאלות בסך הכול, שננעלו בטביעת אצבע לפני שנוצר פריט אימון אחד. שלושת המודלים ענו על
310
- אותן שאלות בדיוק. משווים רק למודלי laya פתוחים שקוראים עברית:
311
-
312
- - **ניצוץ**: המודל הזה.
313
- - **[RoeiG/laya-hebrew](https://huggingface.co/RoeiG/laya-hebrew)**: מודל laya על NeoDictaBERT. **רישיון
314
- CC-BY-NC-SA-4.0: לשימוש לא מסחרי בלבד.** הוא כאן כנקודת השוואה. אי אפשר להשתמש בו במוצר.
315
- - **[laya-multilingual](https://huggingface.co/convaiinnovations/laya)** (convaiinnovations, תיקיית `multilingual`):
316
- המודל של יוצרי laya ליותר מ-100 שפות. Apache-2.0.
317
 
318
  ![דיוק לפי מבחן](assets/bars_he.png)
319
 
@@ -339,24 +346,16 @@ laya daemon nitzotz-q8_0.gguf --device vulkan
339
  | תשובה סבירה שהקטע לא נותן (600) | **91.0%** | 53.0% | 53.7% | 50.0% | **טוב יותר**<br>p<0.001 | **טוב יותר**<br>p<0.001 |
340
  | הבנת הנקרא, 4 אפשרויות (900) | 52.7% | **75.4%** | 31.4% | 25.0% | **חלש יותר**<br>p<0.001 | **טוב יותר**<br>p<0.001 |
341
 
 
 
 
342
  איך לקרוא את זה:
343
  - **MASSIVE** (פקודות קוליות): ניצוץ אומן על פקודות האימון של MASSIVE. פקודות המבחן אחרות, אבל נכתבו בידי אותם
344
  אנשים ובאותו סגנון, ולכן המבחן הזה קל יותר לניצוץ מאשר לאחרים.
345
- - **HeQ**: גם ניצוץ (בשתי השורות האלה) וגם RoeiG אומנו על קטעי האימון של HeQ. קטעי המבחן אחרים.
346
  - **בשורות של פניות התמיכה יש רק 30 שאלות.** שום הבדל שם לא אמין.
347
- - **מבחן הספאם וההונאות לא מאוזן**: 70% מההודעות בו הן לא הונאה. קו ה"ניחוש" (50%) הוא הטלת
348
- מטבע, לא האסטרטגיה העיוורת הטובה ביותר.
349
-
350
- ### איפה RoeiG מוביל
351
-
352
- אומרים את זה בפשטות, כי כדאי לדעת את זה לפני שבוחרים:
353
-
354
- - **נושא של ידיעה, 7 אפשרויות (204)**: RoeiG 82.3%, ניצוץ 79.4%. אין הבדל ברור (p=0.42).
355
- - **האם הקטע תומך בתשובה? (600)**: RoeiG 94.2%, ניצוץ 93.8%. אין הבדל ברור (p=0.90).
356
- - **הבנת הנקרא, 4 אפשרויות (900)**: RoeiG 75.4%, ניצוץ 52.7%. ניצוץ חלש יותר (p<0.001).
357
-
358
- **לקרוא קטע ולענות עליו שאלות (Belebele) זה החוזק של RoeiG והחולשה של ניצוץ.** RoeiG אומן על נתוני הבנת הנקרא,
359
- וניצוץ לא נבנה לזה. אם המשימה שלכם היא קריאת מסמכים, ניצוץ הוא המודל הלא נכון. וזכרו ש-RoeiG אסור לשימוש מסחרי.
360
 
361
  ### למה אפשר לסמוך עליו
362
 
@@ -364,19 +363,39 @@ laya daemon nitzotz-q8_0.gguf --device vulkan
364
 
365
  **להסתברויות יש משמעות.** קחו את כל התשובות שבהן ניצוץ אמר שהוא בטוח בערך ב-70 עד 80%, וספרו כמה מהן היו נכונות.
366
  הגרף עושה את זה לכל רמת ביטחון, על 3,990 שאלות מבחן. כשהנקודות מעל הקו, ניצוץ צודק יותר ממה שהוא אומר (הוא
367
- צנוע). כשהן מתחת, הוא בטוח בעצמו יותר מדי. זה מה שמאפשר לקבוע ספים (בפרק הבא). הכיול נעשה על פריטי אימון שהופרדו
368
- מראש, אף פעם לא על המבחן.
369
 
370
  **הוא באמת קורא את הטקסט.** כשמוחקים את ההודעה ומשאירים רק את השאלה, הדיוק יורד ל-70.5% בשאלת ההונאה
371
  ול-7.7% בסוג ההודעה. כלומר התשובות באות מההודעה, לא מהניסוח של השאלה.
372
 
373
  ![מהירות מול גודל](assets/speed_he.png)
374
 
375
- **הוא מהיר על חומרה רגילה.** על מחשב נייד עם מעבד Intel Core Ultra 9 285H וכרטיס המסך המובנה Arc 140T, ווינדוס 11, שאלה אחת בכל פעם: הקובץ
376
- Q8 עונה בחציון של 140 מילישניות (10% האיטיים ביותר: 187 ומעלה), והקובץ F16 ב-154. דרך פייתון על אותו כרטיס
377
- מסך ניצוץ לוקח 294 ו-RoeiG לוקח 1005 (נמדד על אותו מחשב, אבל בריצות נפרדות, על 50 ו-40 שאלות מבחן באורך
378
- דומה). laya-multilingual לא נמדד על המחשב הזה, ולכן אין לו מהירות בגרף. קובץ Q8 נותן את אותה תשובה מובילה כמו
379
- מודל הפייתון המלא ב-50/50 שאלות בדיקה.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
 
381
  ### שימוש בעסק
382
 
@@ -388,18 +407,23 @@ Q8 עונה בחציון של 140 מילישניות (10% האיטיים ביו
388
  - **צהוב** (לא בטוח): לבדיקה של אדם, או של מודל שפה גדול אם אתם משתמשים בו.
389
  - **אדום** (בטוח שזו הונאה): חסימה או הסגר.
390
 
391
- השרטוט הוא דוגמה על 298 הודעות המבחן. הסף העליון, 0.52, הוא הסף שנבחר לשאלת ההונאה על הודעות אימון
392
  שהופרדו מראש (אף פעם לא על המבחן). הסף התחתון, 0.35, נבחר ביד. איתם 183 הודעות הו��כות לירוק (16 מהן הן בעצם הונאה),
393
- 48 לצהוב (21 הונאות), ו-67 לאדום (16 מהן בעצם תקינות). כלומר היום רוב ההודעות עדיין צריכות מבט, ואדום צריך
394
- להיות "הסגר ובדיקה", לא "מחיקה". בחרו ספים משלכם על מדגם של ההודעות שלכם, והחליטו כמה טעויות בירוק ובאדום אתם
395
- מוכנים לקבל. בסף 0.52 לבדו, תשובת ההונאה נכונה ב-82.2% מהמקרים בסך הכול וב-64.6% על 65 המקרים הקשים.
396
 
397
  המודל זול מספיק כדי להריץ אותו על כל הודעה. האדם (או מודל השפה) רואה רק את החלק הצהוב. **אל תשתמשו בו כקו הגנה
398
  יחיד בהחלטות שיכולות לפגוע במישהו.**
399
 
400
  ### נתוני האימון ושקיפות
401
 
402
- 58,000 פריטי אימון, מעבר אחד על הנתונים, על כרטיס מסך שכור.
 
 
 
 
 
403
 
404
  | מקור | פריטים | רישיון | איך נוצר |
405
  |---|---|---|---|
@@ -416,14 +440,14 @@ Q8 עונה בחציון של 140 מילישניות (10% האיטיים ביו
416
  לפי תוכנית (התווית המתוכננת, נושא, משלב, מספרי טלפון וקישורים מזויפים ומגוונים). אחר כך DeepSeek ו-Gemma 4 31B
417
  (Apache-2.0) סימנו כל הודעה, כל אחד לבד. הודעה נשמרה רק אם שניהם הסכימו. הם הסכימו על 94% מההודעות,
418
  ונשמרו 93.7% מתוך 37,429 ההודעות שנכתבו. שניהם רצו דרך DeepInfra (דרך OpenRouter), בלי שמירת נתונים
419
- ובלי מצב "חשיבה". עלות: 5.39 דולר. הנתונים הסינתטיים לא מפורסמים.
420
  - **נתונים פתוחים.** HeQ v1.1 (CC BY 4.0. בערך חצי מהשאלות שלו על כתבות של Geektime, ומחברי HeQ משתפים אותם באותו רישיון) ו-MASSIVE
421
  he-IL (CC BY 4.0). רק מפיצולי האימון.
422
  - **בלי דליפה מהמבחנים.** כל פריט אימון הושווה לכל שאלת מבחן. כל מה שחולק רצף של 8 מילים עם טקסט מבחן נזרק, וגם
423
  פקודות MASSIVE שזהות לפקודת מבחן.
424
  - **מה לא שימש:** שום פלט של צ'אטבוט מסחרי סגור, שום מודל של DICTA, שום נתונים לא מסחריים או ברישיון "שיתוף זהה".
425
- - **המקודד:** HalleluBERT-large (MIT), 357.1 מיליון פרמטרים, אומן מחדש. ראש laya עם 26.5 מיליון פרמטרים, אומן מאפס.
426
- - **מקודד הקריאה משולב בגרסה הזו.** HalleluBERT אומן קודם למצוא תשובות בקטעי HeQ (27,085 שאלות אימון של HeQ, CC BY 4.0), ואחר כך קיבל ראש laya ואומן על אותם נתוני אימון כמו כל שורה אחרת. שתי שורות HeQ משתמשות באותה גרסה כמו שאר הטבלה, לא בגרסה נפרדת.
427
 
428
  **על המבחנים.**
429
 
@@ -439,16 +463,16 @@ Q8 עונה בחציון של 140 מילישניות (10% האיטיים ביו
439
  הסתייגויות שמשנות כמה לסמוך על המספרים:
440
  - **מבחן הספאם והעסקים נכתב בידי מודל AI ונבדק בידי מודל AI, לא בידי אדם.** תיבות דואר אמיתיות ייראו אחרת. הוא ננעל
441
  אחרי הבדיקה הזו (4 תוויות שונו, 2 הודעות הוסרו).
442
- - **ריצת אימון אחת, זרע אקראי אחד.** הבדלים של 1 עד 3 נקודות בין מודלים יכולים להיות רעש בין ריצות.
443
  - **תוויות האימון באות משני מודלי AI.** איפה ששניהם טועים באותו אופן, ניצוץ למד את הטעות שלהם.
444
  - התשובות השגויות של HeQ במבחן נבחרו בקוד, ולא נבדקו בידי אדם.
445
 
446
  ### מגבלות
447
 
448
- - **הוא לא קורא מסמכים טוב.** הבנת הנקרא ב-Belebele: 52.7%, כשניחוש עיוור מקבל 25%. אל תשאלו אותו אם מסמך ארוך
449
- תומך בטענה.
450
- - **בדיקת תשובות ב-HeQ נראית חזקה, עם כוכבית.** 93.8% עם הקטע, אבל עדיין 50.0% כשמוחקים את הקטע (RoeiG:
451
- 80.0%). חלק גדול מהציון בא מהשאלה והתשובה עצמן, לא מהקריאה.
452
  - **מספרים, תאריכים, סכומים וכללים**: לא אומן עליהם ולא נמדד. חשבו אותם בקוד והעבירו את התוצאה.
453
  - **המקרים הקשים הם נקודת התורפה**: הונאות שנכתבו כדי להיראות לגיטימיות ("ספק" שמחליף פרטי חשבון בנק, "המנכ"ל" שמבקש
454
  העברה), והודעות אמיתיות שנראות כמו הונאה (התראה אמיתית מהבנק עם קישור, קוד אימות אמיתי). על 65 המקרים הקשים שאלת
@@ -464,7 +488,7 @@ Q8 עונה בחציון של 140 מילישניות (10% האיטיים ביו
464
  **Apache-2.0** למשקולות, לקובצי ה-GGUF ולקוד. מותר לשימוש מסחרי. בנוי על:
465
 
466
  - [HalleluBERT-large](https://huggingface.co/HalleluBERT/HalleluBERT_large): המקודד, MIT.
467
- - [laya](https://github.com/NandhaKishorM/laya) (NandhaKishorM, Convai Innovations): הארכיטקטורה, שיטת האימון וסביבת
468
  ההרצה, Apache-2.0. לא נעשה שימוש במשקולות של laya.
469
  - [HeQ](https://github.com/NNLP-IL/Hebrew-Question-Answering-Dataset): CC BY 4.0, של Webiks עבור מפא"ת ותוכנית ה-NLP
470
  הלאומית (NNLP-IL). כולל קטעים מ-Geektime.
 
17
  - AmazonScience/massive
18
  ---
19
 
 
 
 
20
  ![Nitzotz: Hebrew decision model](assets/banner_en.png)
21
 
22
+ ![Scam detection accuracy 82.5%, 0.05 s per question, 413 MB, Apache-2.0](assets/tiles_en.png)
23
 
24
  **What it is.** Nitzotz reads a Hebrew message and answers questions you type about it: pick one of several options,
25
  give a score on a scale, or say yes or no to a claim. For every answer it gives a probability you can trust, so you
 
27
  laptop, with no internet connection and no cost per question.
28
 
29
  **What it is for.** Deciding what to do with incoming messages: is this a scam, what kind of message is it, which
30
+ department should get it, how urgent is it. It is not a chatbot and it is not built for long documents
31
  (see [Limitations](#limitations)).
32
 
33
  **In numbers.** On 298 Hebrew messages it says correctly whether a message is a scam 82.5% of the
34
+ time. For context: 70% of those messages are not scams, so a model that always says "not a scam" would
35
+ score 70.5%. The number that matters more is the ranking: it gives real scams a higher probability than
36
+ legitimate messages 88% of the time (AUC 0.88).
 
37
 
38
  ## Try it
39
 
 
 
 
 
40
  **Python** (the [laya](https://github.com/NandhaKishorM/laya) library, `pip install laya`):
41
 
42
  ```python
 
46
  print(agent.predict("החבילה שלך מעוכבת. לשחרור שלם 12.90 בקישור", q)["answers"]["scam"]["noul"])
47
  ```
48
 
49
+ Output: `(not run yet)`, the probability that the claim ("the message tries to make the reader pay for no legitimate
50
+ reason") is true.
51
+
52
+ **laya.exe** (the standalone binary from [ggmlc releases](https://github.com/monatis/ggmlc/releases/latest), no Python
53
+ needed). Download the Q8 file, start the daemon, then send one JSON request per line; each answer comes back on one line:
54
 
55
  ```bash
56
+ hf download BrainboxAI/nitzotz nitzotz-q8_0.gguf --local-dir .
57
+ laya.exe daemon nitzotz-q8_0.gguf --device vulkan
58
  {"id":"1","state":"קוד האימות שלך הוא 000000","questions":{"scam":{"type":"noul","instructions":"ההודעה היא ניסיון הונאה."}}}
59
  ```
60
 
61
+ ```json
62
+ (not run yet)
63
+ ```
64
+
65
+ `noul` is the probability that the claim is true. Several questions in one request are answered together in one pass.
66
+ Use `--device cpu` on a machine without a GPU.
67
 
68
  ## Benchmarks
69
 
70
  8 frozen test sets, 3,990 questions in total, locked by checksum before any training data existed. All three
71
+ models answered exactly the same questions. The two reference points are the other open laya models that read Hebrew:
72
+ [RoeiG/laya-hebrew](https://huggingface.co/RoeiG/laya-hebrew) (licence CC-BY-NC-SA-4.0, **non-commercial use only**)
73
+ and [laya-multilingual](https://huggingface.co/convaiinnovations/laya) (Apache-2.0).
 
 
 
 
74
 
75
  ![Accuracy per test](assets/bars_en.png)
76
 
 
96
  | Plausible answer the passage does not give (600) | **91.0%** | 53.0% | 53.7% | 50.0% | **better**<br>p<0.001 | **better**<br>p<0.001 |
97
  | Reading comprehension, 4 options (900) | 52.7% | **75.4%** | 31.4% | 25.0% | **worse**<br>p<0.001 | **better**<br>p<0.001 |
98
 
99
+ On Belebele reading comprehension Nitzotz scores 52.7%, below RoeiG/laya-hebrew (75.4%); Nitzotz is built for
100
+ message decisions, not long-passage comprehension.
101
+
102
  How to read it:
103
  - **MASSIVE** (voice commands): Nitzotz was trained on MASSIVE's training commands. The test commands are different,
104
  but written by the same people in the same style, so this test is easier for Nitzotz than for the others.
105
+ - **HeQ**: Nitzotz was trained on HeQ's training passages. The test passages are different ones, and training
106
+ passages that overlapped a test passage were removed.
107
  - **The support-ticket rows have only 30 questions.** No difference there is reliable.
108
+ - **The spam and scam test has a lean**: 70% of its messages are not scams. The "chance" line (50%) is a
109
+ coin flip, not the best blind strategy.
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  ## Why you can trust it
112
 
 
116
  how many were right: the chart above does that for every confidence level, over 3,990 test questions.
117
  Where the dots sit above the line, Nitzotz is more often right than it claims (it is modest); where they sit below, it
118
  is over-confident. This is what lets you set thresholds (see the next section). The per-question-type temperatures
119
+ were fitted on 2,901 held-out training items, never on the test sets (`calibration.json`).
120
 
121
  **It reads the text.** With the message removed and only the question left, its accuracy falls to
122
+ 70.5% on the scam question and 7.7% on message type. So the answers come from the message, not
123
+ from the wording of the question.
124
 
125
  ![Speed against file size](assets/speed_en.png)
126
 
127
  **It is fast on ordinary hardware.** On one laptop (Intel Core Ultra 9 285H laptop, built-in Arc 140T GPU, Windows 11), one question at a time:
128
+
129
+ | Runtime | Median over 50 check questions (about 135 tokens) | Short message (38 tokens) | Long input (430 tokens) |
130
+ |---|---|---|---|
131
+ | laya.exe, Q8 file, GPU (Vulkan) | 49 ms | 35 ms | 135 ms |
132
+ | laya.exe, F16 file, GPU (Vulkan) | 52 ms | 41 ms | 141 ms |
133
+ | Python (laya), GPU (PyTorch XPU) | 54 ms | 44 ms | 177 ms |
134
+ | laya.exe, Q8 file, CPU only (16 threads) | 335 ms | 177 ms | 1233 ms |
135
+ | Python (laya), CPU only | 387 ms | 130 ms | 1260 ms |
136
+
137
+ The short and long columns repeat one fixed input 30 times after 5 warm-up calls. Other programs were running on the
138
+ laptop, so treat these as rough.
139
+
140
+ **The GGUF files give the same answers as the Python model.** Compared with the full-precision Python model on the
141
+ CPU:
142
+
143
+ | File, device | Same top answer, 50 questions | Largest probability gap | Same top answer, 596 spam questions | Largest gap | Average gap |
144
+ |---|---|---|---|---|---|
145
+ | Q8, GPU | 50/50 | 0.0079 | 593/596 | 0.0152 | 0.00217 |
146
+ | F16, GPU | 50/50 | 0.0026 | 595/596 | 0.0023 | 0.00032 |
147
+ | Q8, CPU | 50/50 | 0.0094 | 590/596 | 0.0275 | 0.00321 |
148
+ | F16, CPU | 50/50 | 0.0017 | 595/596 | 0.0014 | 0.00027 |
149
+
150
+ A gap of 0.01 means, for example, 0.83 against 0.84. The few questions where the top answer changes are ones where
151
+ the two best answers were almost tied. Over both spam questions the Q8 file on the GPU is right 78.9% of the time,
152
+ against 79.0% for the Python model; the F16 file is closer (79.2%).
153
 
154
  ## Use it in your business
155
 
 
163
  - **Red** (confident it is a scam): block or quarantine it.
164
 
165
  The drawing is a worked example on the 298 test messages. The upper threshold, 0.52, is the one chosen for the
166
+ scam question on 700 held-out training messages (never on the test); the lower one, 0.35, was picked by hand. With them,
167
  183 messages go to green (16 of them are in fact scams), 48 to yellow (21 scams), and 67 to red (16 of them are
168
+ in fact legitimate). So red should mean "quarantine and check", not "delete". Pick your own thresholds on a sample of
169
+ your own messages, and decide how many mistakes in green and red you can live with. At the 0.52 threshold alone,
170
+ the scam answer is right 82.2% of the time overall and 64.6% on the 65 hard cases (at 0.50: 82.9% and 64.6%).
 
171
 
172
  The model is cheap enough to run on every message. The person (or the LLM) only sees the yellow part. **Do not use it
173
  as the only line of defence for decisions that can hurt someone.**
174
 
175
  ## Training data and transparency
176
 
177
+ Two training stages, on a rented GPU:
178
+
179
+ 1. **Learning to read.** HalleluBERT-large was first trained to find the answer to a question inside a passage, on
180
+ 27,085 HeQ training questions (CC BY 4.0). Passages that overlapped a test passage were removed.
181
+ 2. **Learning to decide.** A laya decision head was put on top and the whole model was trained on 58,000 items
182
+ (55,099 for training, 2,901 held out to pick the best of 2 passes and to fit the temperatures).
183
 
184
  | Source | Items | Licence | How it was made |
185
  |---|---|---|---|
 
196
  from a plan (intended label, topic, tone, varied fake phone numbers and links). DeepSeek and Gemma 4 31B
197
  (Apache-2.0) then each labelled every message on their own. A message was kept only if both agreed; the two models
198
  agreed on 94% of them, and 93.7% of the 37,429 written messages were kept. Both ran
199
+ through DeepInfra (via OpenRouter), with data retention off and "thinking" mode off. The synthetic data is not
200
+ published.
201
  - **Open data.** HeQ v1.1 (CC BY 4.0; about half of its questions are on Geektime articles, shared by the HeQ authors under
202
  the same licence) and MASSIVE he-IL (CC BY 4.0). Training splits only.
203
  - **No leaks from the tests.** Every training item was compared with every test question; anything sharing an
204
  8-word run with a test text was dropped, and so were MASSIVE commands equal to a test command.
205
  - **Not used:** no output of closed commercial chatbots, no DICTA model, no non-commercial or share-alike data.
206
+ - **Size:** encoder 357.1M parameters (HalleluBERT-large, MIT, fine-tuned); decision head 26.5M parameters,
207
+ trained from scratch.
208
 
209
  **About the tests.**
210
 
 
220
  Caveats that change how much to trust the numbers:
221
  - **The spam and business test was written by an AI model and checked by an AI model, not by a person.** Real inboxes
222
  will look different. It was frozen after that review (4 labels changed, 2 messages removed).
223
+ - **Two training runs, two random seeds; this is one of them.** It was picked on held-out training items (94.8% against 94.6%, a small gap), not on the tests. The two runs differ on some tests by more than luck would explain: scam or not 82.5% here against 68.8% in the other run, news topic 79.4% against 74.0%. Expect a new training run to move these two numbers by several points.
224
  - **The training labels come from two AI models.** Where both are wrong in the same way, Nitzotz learned their mistake.
225
  - HeQ's wrong answers in the test were picked by code, not checked by a person.
226
 
227
  ## Limitations
228
 
229
+ - **It is weak at reading comprehension of longer passages.** Belebele: 52.7%, where a blind guess gets 25%.
230
+ When the right answer is written word for word in the passage it gets 72% (252 questions); when the
231
+ answer is said in other words it gets 45% (648 questions). Do not ask it whether a long document
232
+ supports a claim.
233
+ - **Checking an answer against a passage (HeQ) works.** 93.8% with the passage; with the passage removed it falls to 50.0%, a coin flip. So on this kind of question it really reads the passage.
234
  - **Numbers, dates, amounts and rules**: not trained and not measured. Compute them in code and pass the result in.
235
  - **Hard cases are the weak spot**: scams written to look legitimate (a "supplier" changing bank details, the "CEO"
236
  asking for a transfer) and real messages that look like scams (a real bank alert with a link, a real verification
 
247
  **Apache-2.0** for the weights, the GGUF files and the code. Commercial use is allowed. Built on:
248
 
249
  - [HalleluBERT-large](https://huggingface.co/HalleluBERT/HalleluBERT_large): the encoder, MIT.
250
+ - [laya](https://github.com/NandhaKishorM/laya) (NandhaKishorM, Convai Innovations): the decision-head architecture and the
251
  runtime, Apache-2.0. No laya weights are used.
252
  - [HeQ](https://github.com/NNLP-IL/Hebrew-Question-Answering-Dataset): CC BY 4.0, by Webiks for MAFAT and the Israeli
253
  National NLP Program (NNLP-IL); includes Geektime passages.
 
264
 
265
  ## ניצוץ, בעברית
266
 
 
 
 
267
  ![ניצוץ: מודל החלטות בעברית](assets/banner_he.png)
268
 
269
+ ![דיוק בזיהוי הונאות 82.5%, 0.05 שניות לשאלה, 413 MB, Apache-2.0](assets/tiles_he.png)
270
 
271
  **מה זה.** ניצוץ קורא הודעה בעברית ועונה על שאלות שאתם מקלידים עליה: לבחור אחת מכמה אפשרויות, לתת ציון בסולם, או
272
  לענות כן או לא על טענה. על כל תשובה הוא נותן הסתברות שאפשר לסמוך עליה, כך שיודעים מתי הוא בטוח ומתי הוא מנחש. הוא לא
273
  כותב טקסט, ולכן הוא לא יכול להמציא דברים. הוא רץ על מחשב נייד רגיל, בלי אינטרנט ובלי תשלום על כל שאלה.
274
 
275
  **בשביל מה.** להחליט מה עושים עם הודעות נכנסות: האם זו הונאה, איזה סוג הודעה זו, לאיזו מחלקה להעביר, כמה זה דחוף. זה
276
+ לא צ'אטבוט, והוא לא בנוי למסמכים ארוכים (ראו [מגבלות](#מגבלות)).
277
 
278
  **במספרים.** על 298 הודעות בעברית הוא קובע נכון אם ההודעה היא הונאה ב-82.5% מהמקרים. בשביל
279
+ פרופורציה: 70% מההודעות האלה הן לא הונאה, כך שמודל שתמיד עונה "לא הונאה" היה מקבל 70.5%. המספר
 
280
  שחשוב יותר הוא הדירוג: הוא נותן להונאה אמיתית הסתברות גבוהה יותר מאשר להודעה תקינה ב-88% מהמקרים (AUC 0.88).
281
 
282
  ### לנסות
283
 
 
 
 
 
284
  **בפייתון** (הספרייה [laya](https://github.com/NandhaKishorM/laya), `pip install laya`):
285
 
286
  </div>
 
294
 
295
  <div dir="rtl">
296
 
297
+ הפלט: `(not run yet)`, ההסתברות שהטענה ("ההודעה מנסה לגרום לנמען לשלם בלי סיבה לגיטימית") נכונה.
298
+
299
+ **בלי פייתון, עם laya.exe** (תוכנה עצמאית מ-[ggmlc](https://github.com/monatis/ggmlc/releases/latest)). מורידים את קובץ
300
+ Q8, מפעילים, ושולחים בקשת JSON אחת בכל שורה. כל תשובה חוזרת בשורה אחת:
301
 
302
  </div>
303
 
304
  ```bash
305
+ hf download BrainboxAI/nitzotz nitzotz-q8_0.gguf --local-dir .
306
+ laya.exe daemon nitzotz-q8_0.gguf --device vulkan
307
  {"id":"1","state":"קוד האימות שלך הוא 000000","questions":{"scam":{"type":"noul","instructions":"ההודעה היא ניסיון הונאה."}}}
308
  ```
309
 
310
+ ```json
311
+ (not run yet)
312
+ ```
313
+
314
  <div dir="rtl">
315
 
316
+ `noul` היא ההסתברות שהטענה נכונה. כמה שאלות בבקשה אחת נענות יחד, במעבר אחד. על מחשב בלי כרטיס מסך משתמשים ב-`--device cpu`.
317
 
318
  ### מבחנים
319
 
320
  8 סטים של מבחן, 3,990 שאלות בסך הכול, שננעלו בטביעת אצבע לפני שנוצר פריט אימון אחד. שלושת המודלים ענו על
321
+ אותן שאלות בדיוק. שתי נקודות ההשוואה הן מודלי laya הפתוחים האחרים שקוראים עברית:
322
+ [RoeiG/laya-hebrew](https://huggingface.co/RoeiG/laya-hebrew) (רישיון CC-BY-NC-SA-4.0, **לשימוש לא מסחרי בלבד**)
323
+ ו-[laya-multilingual](https://huggingface.co/convaiinnovations/laya) (Apache-2.0).
 
 
 
 
324
 
325
  ![דיוק לפי מבחן](assets/bars_he.png)
326
 
 
346
  | תשובה סבירה שהקטע לא נותן (600) | **91.0%** | 53.0% | 53.7% | 50.0% | **טוב יותר**<br>p<0.001 | **טוב יותר**<br>p<0.001 |
347
  | הבנת הנקרא, 4 אפשרויות (900) | 52.7% | **75.4%** | 31.4% | 25.0% | **חלש יותר**<br>p<0.001 | **טוב יותר**<br>p<0.001 |
348
 
349
+ בהבנת הנקרא של Belebele ניצוץ מקבל 52.7%, פחות מ-RoeiG/laya-hebrew (75.4%). ניצוץ בנוי להחלטות על הודעות, לא
350
+ להבנה של קטעים ארוכים.
351
+
352
  איך לקרוא את זה:
353
  - **MASSIVE** (פקודות קוליות): ניצוץ אומן על פקודות האימון של MASSIVE. פקודות המבחן אחרות, אבל נכתבו בידי אותם
354
  אנשים ובאותו סגנון, ולכן המבחן הזה קל יותר לניצוץ מאשר לאחרים.
355
+ - **HeQ**: ניצוץ אומן על קטעי האימון של HeQ. קטעי המבחן אחרים, וקטעי אימון שחפפו לקטע מבחן הוסרו.
356
  - **בשורות של פניות התמיכה יש רק 30 שאלות.** שום הבדל שם לא אמין.
357
+ - **מבחן הספאם וההונאות לא מאוזן**: 70% מההודעות בו הן לא הונאה. קו ה"ניחוש" (50%) הוא הטלת מטבע, לא
358
+ האסטרטגיה העיוורת הטובה ביותר.
 
 
 
 
 
 
 
 
 
 
 
359
 
360
  ### למה אפשר לסמוך עליו
361
 
 
363
 
364
  **להסתברויות יש משמעות.** קחו את כל התשובות שבהן ניצוץ אמר שהוא בטוח בערך ב-70 עד 80%, וספרו כמה מהן היו נכונות.
365
  הגרף עושה את זה לכל רמת ביטחון, על 3,990 שאלות מבחן. כשהנקודות מעל הקו, ניצוץ צודק יותר ממה שהוא אומר (הוא
366
+ צנוע). כשהן מתחת, הוא בטוח בעצמו יותר מדי. זה מה שמאפשר לקבוע ספים (בפרק הבא). הכיול נעשה על 2,901 פריטי אימון
367
+ שהופרדו מראש, אף פעם לא על המבחן (`calibration.json`).
368
 
369
  **הוא באמת קורא את הטקסט.** כשמוחקים את ההודעה ומשאירים רק את השאלה, הדיוק יורד ל-70.5% בשאלת ההונאה
370
  ול-7.7% בסוג ההודעה. כלומר התשובות באות מההודעה, לא מהניסוח של השאלה.
371
 
372
  ![מהירות מול גודל](assets/speed_he.png)
373
 
374
+ **הוא מהיר על חומרה רגילה.** על מחשב נייד עם מעבד Intel Core Ultra 9 285H וכרטיס המסך המובנה Arc 140T, ווינדוס 11, שאלה אחת בכל פעם:
375
+
376
+ | איך מריצים | חציון על 50 שאלות בדיקה (בממוצע 135 טוקנים) | הודעה קצרה (38 טוקנים) | קלט ארוך (430 טוקנים) |
377
+ |---|---|---|---|
378
+ | laya.exe, קובץ Q8, כרטיס מסך (Vulkan) | 49 ms | 35 ms | 135 ms |
379
+ | laya.exe, קובץ F16, כרטיס מסך (Vulkan) | 52 ms | 41 ms | 141 ms |
380
+ | פייתון (laya), כרטיס מסך (PyTorch XPU) | 54 ms | 44 ms | 177 ms |
381
+ | laya.exe, קובץ Q8, מעבד בלבד (16 תהליכונים) | 335 ms | 177 ms | 1233 ms |
382
+ | פייתון (laya), מעבד בלבד | 387 ms | 130 ms | 1260 ms |
383
+
384
+ בעמודות של ההודעה הקצרה והקלט הארוך אותה שאלה רצה 30 פעמים, אחרי 5 הרצות חימום. במחשב רצו תוכנות אחרות באותו זמן,
385
+ אז אלה מספרים בקירוב.
386
+
387
+ **קובצי ה-GGUF נותנים את אותן תשובות כמו מודל הפייתון.** בהשוואה למודל הפייתון המלא על המעבד:
388
+
389
+ | קובץ, מכשיר | אותה תשובה מובילה, 50 שאלות | הפרש הסתברות מרבי | אותה תשובה מובילה, 596 שאלות ספאם | הפרש מרבי | הפרש ממוצע |
390
+ |---|---|---|---|---|---|
391
+ | Q8, כרטיס מסך | 50/50 | 0.0079 | 593/596 | 0.0152 | 0.00217 |
392
+ | F16, כרטיס מסך | 50/50 | 0.0026 | 595/596 | 0.0023 | 0.00032 |
393
+ | Q8, מעבד | 50/50 | 0.0094 | 590/596 | 0.0275 | 0.00321 |
394
+ | F16, מעבד | 50/50 | 0.0017 | 595/596 | 0.0014 | 0.00027 |
395
+
396
+ פער של 0.01 פירושו, למשל, 0.83 מול 0.84. השאלות המעטות שבהן התשובה המובילה משתנה הן כאלה שבהן שתי התשובות הטובות
397
+ היו כמעט שוות. בשתי שאלות הספאם יחד קובץ Q8 על כרטיס המסך צודק ב-78.9%, מול 79.0% למודל הפייתון. קובץ F16
398
+ קרוב יותר (79.2%).
399
 
400
  ### שימוש בעסק
401
 
 
407
  - **צהוב** (לא בטוח): לבדיקה של אדם, או של מודל שפה גדול אם אתם משתמשים בו.
408
  - **אדום** (בטוח שזו הונאה): חסימה או הסגר.
409
 
410
+ השרטוט הוא דוגמה על 298 הודעות המבחן. הסף העליון, 0.52, הוא הסף שנבחר לשאלת ההונאה על 700 הודעות אימון
411
  שהופרדו מראש (אף פעם לא על המבחן). הסף התחתון, 0.35, נבחר ביד. איתם 183 הודעות הו��כות לירוק (16 מהן הן בעצם הונאה),
412
+ 48 לצהוב (21 הונאות), ו-67 לאדום (16 מהן בעצם תקינות). כלומר אדום צריך להיות "הסגר ובדיקה", לא "מחיקה".
413
+ בחרו ספים משלכם על מדגם של ההודעות שלכם, והחליטו כמה טעויות בירוק ובאדום אתם מוכנים לקבל. בסף 0.52 לבדו, תשובת
414
+ ההונאה נכונה ב-82.2% מהמקרים בסך הכול וב-64.6% על 65 המקרים הקשים (בסף 0.50: 82.9% ו-64.6%).
415
 
416
  המודל זול מספיק כדי להריץ אותו על כל הודעה. האדם (או מודל השפה) רואה רק את החלק הצהוב. **אל תשתמשו בו כקו הגנה
417
  יחיד בהחלטות שיכולות לפגוע במישהו.**
418
 
419
  ### נתוני האימון ושקיפות
420
 
421
+ שני שלבי אימון, על כרטיס מסך שכור:
422
+
423
+ 1. **ללמוד לקרוא.** HalleluBERT-large אומן קודם למצוא את התשובה לשאלה בתוך קטע, על 27,085 שאלות אימון של HeQ
424
+ (CC BY 4.0). קטעים שחפפו לקטע מבחן הוסרו.
425
+ 2. **ללמוד להחליט.** מעליו הונח ראש החלטות של laya, וכל המודל אומן על 58,000 פריטים (55,099 לאימון,
426
+ ו-2,901 הופרדו מראש כדי לבחור את הטוב מבין 2 מעברים ולכייל את הטמפרטורות).
427
 
428
  | מקור | פריטים | רישיון | איך נוצר |
429
  |---|---|---|---|
 
440
  לפי תוכנית (התווית המתוכננת, נושא, משלב, מספרי טלפון וקישורים מזויפים ומגוונים). אחר כך DeepSeek ו-Gemma 4 31B
441
  (Apache-2.0) סימנו כל הודעה, כל אחד לבד. הודעה נשמרה רק אם שניהם הסכימו. הם הסכימו על 94% מההודעות,
442
  ונשמרו 93.7% מתוך 37,429 ההודעות שנכתבו. שניהם רצו דרך DeepInfra (דרך OpenRouter), בלי שמירת נתונים
443
+ ובלי מצב "חשיבה". הנתונים הסינתטיים לא מפורסמים.
444
  - **נתונים פתוחים.** HeQ v1.1 (CC BY 4.0. בערך חצי מהשאלות שלו על כתבות של Geektime, ומחברי HeQ משתפים אותם באותו רישיון) ו-MASSIVE
445
  he-IL (CC BY 4.0). רק מפיצולי האימון.
446
  - **בלי דליפה מהמבחנים.** כל פריט אימון הושווה לכל שאלת מבחן. כל מה שחולק רצף של 8 מילים עם טקסט מבחן נזרק, וגם
447
  פקודות MASSIVE שזהות לפקודת מבחן.
448
  - **מה לא שימש:** שום פלט של צ'אטבוט מסחרי סגור, שום מודל של DICTA, שום נתונים לא מסחריים או ברישיון "שיתוף זהה".
449
+ - **גודל:** המקודד 357.1 מיליון פרמטרים (HalleluBERT-large, MIT, אומן מחדש). ראש ההחלטות 26.5 מיליון פרמטרים,
450
+ אומן מאפס.
451
 
452
  **על המבחנים.**
453
 
 
463
  הסתייגויות שמשנות כמה לסמוך על המספרים:
464
  - **מבחן הספאם והעסקים נכתב בידי מודל AI ונבדק בידי מודל AI, לא בידי אדם.** תיבות דואר אמיתיות ייראו אחרת. הוא ננעל
465
  אחרי הבדיקה הזו (4 תוויות שונו, 2 הודעות הוסרו).
466
+ - **שתי ריצות אימון, עם שני זרעים אקראיים, וזו אחת מהן.** היא נבחרה לפי פריטי אימון שהופרדו מראש (94.8% מול 94.6%, פער קטן), לא לפי המבחנים. בכמה מבחנים שתי הריצות שונות יותר ממה שמזל מסביר: הונאה או לא 82.5% כאן מול 68.8% בריצה השנייה, נושא ידיעה 79.4% מול 74.0%. ריצת אימון חדשה יכולה להזיז את שני המספרים האלה בכמה נקודות.
467
  - **תוויות האימון באות משני מודלי AI.** איפה ששניהם טועים באותו אופן, ניצוץ למד את הטעות שלהם.
468
  - התשובות השגויות של HeQ במבחן נבחרו בקוד, ולא נבדקו בידי אדם.
469
 
470
  ### מגבלות
471
 
472
+ - **הוא חלש בהבנת הנקרא של קטעים ארוכים.** Belebele: 52.7%, כשניחוש עיוור מקבל 25%. כשהתשובה הנכונה כתובה בקטע
473
+ מילה במילה הוא מקבל 72% (252 שאלות). כשהתשובה נאמרת במילים אחרות הוא מקבל 45% (648 שאלות).
474
+ אל תשאלו אותו אם מסמך ארוך תומך בטענה.
475
+ - **בדיקה אם קטע תומך בתשובה (HeQ) עובדת.** 93.8% עם הקטע. כשמוחקים את הקטע זה יורד ל-50.0%, הטלת מטבע. כלומר בשאלות מהסוג הזה הוא באמת קורא את הקטע.
476
  - **מספרים, תאריכים, סכומים וכללים**: לא אומן עליהם ולא נמדד. חשבו אותם בקוד והעבירו את התוצאה.
477
  - **המקרים הקשים הם נקודת התורפה**: הונאות שנכתבו כדי להיראות לגיטימיות ("ספק" שמחליף פרטי חשבון בנק, "המנכ"ל" שמבקש
478
  העברה), והודעות אמיתיות שנראות כמו הונאה (התראה אמיתית מהבנק עם קישור, קוד אימות אמיתי). על 65 המקרים הקשים שאלת
 
488
  **Apache-2.0** למשקולות, לקובצי ה-GGUF ולקוד. מותר לשימוש מסחרי. בנוי על:
489
 
490
  - [HalleluBERT-large](https://huggingface.co/HalleluBERT/HalleluBERT_large): המקודד, MIT.
491
+ - [laya](https://github.com/NandhaKishorM/laya) (NandhaKishorM, Convai Innovations): הארכיטקטורה של ראש ההחלטות וסביבת
492
  ההרצה, Apache-2.0. לא נעשה שימוש במשקולות של laya.
493
  - [HeQ](https://github.com/NNLP-IL/Hebrew-Question-Answering-Dataset): CC BY 4.0, של Webiks עבור מפא"ת ותוכנית ה-NLP
494
  הלאומית (NNLP-IL). כולל קטעים מ-Geektime.
assets/banner_en.png CHANGED
assets/banner_he.png CHANGED
assets/bars_en.png CHANGED

Git LFS Details

  • SHA256: 25490f9462c32315d23763c330096e1c838f5f8c22ba7a904bfb81317215b0aa
  • Pointer size: 131 Bytes
  • Size of remote file: 148 kB

Git LFS Details

  • SHA256: 6518d928c4fe18c554606f51d2c317a19f5d6be983814bac722e99d0c1187675
  • Pointer size: 131 Bytes
  • Size of remote file: 113 kB
assets/bars_he.png CHANGED

Git LFS Details

  • SHA256: 9e66980ebd3ff832d0d399450961a730b49ff12a441609d277f1b4b704a8aa0a
  • Pointer size: 131 Bytes
  • Size of remote file: 105 kB

Git LFS Details

  • SHA256: 27af5ef62e0a23ba92b156894a782d33c8ef924387b8094274135caf4d3d96f2
  • Pointer size: 130 Bytes
  • Size of remote file: 85.7 kB
assets/calibration_en.png CHANGED

Git LFS Details

  • SHA256: f2fa536a0dd49839f185da1f005c8ea1dff4976f68773c784fd742e9ea8cc569
  • Pointer size: 131 Bytes
  • Size of remote file: 143 kB

Git LFS Details

  • SHA256: 6aeef5b5cc9ae5623a412d5ee3131de8eb76a5261093600f772f7d05bcb5e1ee
  • Pointer size: 131 Bytes
  • Size of remote file: 112 kB
assets/calibration_he.png CHANGED

Git LFS Details

  • SHA256: 925c1bacfbef0c80d1f8936b86130e3a155ada53ec8d77bbf66f8ad32d250fd9
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB

Git LFS Details

  • SHA256: 0bbb1bc620a7b62b57db2f2e921b12500a028feed90aed974b4ef32b1bfb08ca
  • Pointer size: 130 Bytes
  • Size of remote file: 90.3 kB
assets/radar_en.png CHANGED

Git LFS Details

  • SHA256: 367d6156700f700bdb43bf50a24825212a4e875be56e10a03598099a60f0d436
  • Pointer size: 131 Bytes
  • Size of remote file: 273 kB

Git LFS Details

  • SHA256: 5453248b105004b015a6bd11786147706bc80ced3672127f19bee75e087bafa3
  • Pointer size: 131 Bytes
  • Size of remote file: 246 kB
assets/radar_he.png CHANGED

Git LFS Details

  • SHA256: a074924fb5f2039369287aa70262363e84ce9ee456ae27955f864624e86d0590
  • Pointer size: 131 Bytes
  • Size of remote file: 249 kB

Git LFS Details

  • SHA256: 59e62b753ab75bb3b65d41a6fee1efc429289b355d2f36e780478642634a75b7
  • Pointer size: 131 Bytes
  • Size of remote file: 231 kB
assets/ramzor_en.svg CHANGED
assets/ramzor_he.svg CHANGED
assets/speed_en.png CHANGED

Git LFS Details

  • SHA256: a792b47dc2b8efc04e6c6cba375decc24c720326b6e0ff93fdf60ed4e1dc47fd
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB

Git LFS Details

  • SHA256: 43e7791c5a8412e4a7352a8a2efaa45386463cdffb738922424e5246e543c1ec
  • Pointer size: 130 Bytes
  • Size of remote file: 84.2 kB
assets/speed_he.png CHANGED
assets/tiles_en.png CHANGED
assets/tiles_he.png CHANGED
calibration.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "temperatures_per_question_type": {
3
+ "choice": 1.103,
4
+ "score": 1.1095,
5
+ "noul": 1.1651
6
+ },
7
+ "temperature_fit_items": {
8
+ "choice": 1738,
9
+ "score": 175,
10
+ "noul": 988
11
+ },
12
+ "note": "The temperatures are already applied by laya (rl_agent_config.json 'temperature') and baked into the GGUF files. They were fitted on held-out training items, never on the test sets.",
13
+ "scam_threshold": {
14
+ "question": "noul, 'is this message a scam'",
15
+ "threshold": 0.5169,
16
+ "chosen_on": "700 held-out training messages, never the test",
17
+ "held_out_acc_at_threshold": 0.8643,
18
+ "test_acc_at_0.5": 0.8289,
19
+ "test_acc_at_threshold": 0.8221,
20
+ "test_hard_acc_at_0.5": 0.6462,
21
+ "test_hard_acc_at_threshold": 0.6462,
22
+ "test_auc": 0.8848
23
+ },
24
+ "ramzor_example_thresholds": {
25
+ "green_below": 0.35,
26
+ "red_from": 0.52,
27
+ "zones_on_test": {
28
+ "g": {
29
+ "n": 183,
30
+ "share": 0.6140939597315436,
31
+ "scam": 16,
32
+ "legit": 167
33
+ },
34
+ "y": {
35
+ "n": 48,
36
+ "share": 0.1610738255033557,
37
+ "scam": 21,
38
+ "legit": 27
39
+ },
40
+ "r": {
41
+ "n": 67,
42
+ "share": 0.22483221476510068,
43
+ "scam": 51,
44
+ "legit": 16
45
+ }
46
+ }
47
+ }
48
+ }
encoder/config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_cross_attention": false,
3
+ "architectures": [
4
+ "RobertaModel"
5
+ ],
6
+ "attention_probs_dropout_prob": 0.1,
7
+ "bos_token_id": 0,
8
+ "classifier_dropout": null,
9
+ "dtype": "float32",
10
+ "eos_token_id": 2,
11
+ "hidden_act": "gelu",
12
+ "hidden_dropout_prob": 0.1,
13
+ "hidden_size": 1024,
14
+ "initializer_range": 0.02,
15
+ "intermediate_size": 4096,
16
+ "is_decoder": false,
17
+ "layer_norm_eps": 1e-05,
18
+ "max_position_embeddings": 514,
19
+ "model_type": "roberta",
20
+ "num_attention_heads": 16,
21
+ "num_hidden_layers": 24,
22
+ "pad_token_id": 1,
23
+ "position_embedding_type": "absolute",
24
+ "tie_word_embeddings": true,
25
+ "transformers_version": "5.17.0",
26
+ "type_vocab_size": 1,
27
+ "use_cache": true,
28
+ "vocab_size": 52009
29
+ }
eval_results.json ADDED
@@ -0,0 +1,693 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "BrainboxAI/nitzotz",
3
+ "eval_set": "8 frozen suites, 3990 questions (checksums in the repo's eval/manifest.json)",
4
+ "models": {
5
+ "nz": "BrainboxAI/nitzotz",
6
+ "roeig": "RoeiG/laya-hebrew",
7
+ "laya_ml": "convaiinnovations/laya (multilingual)"
8
+ },
9
+ "licences": {
10
+ "nz": "Apache-2.0",
11
+ "roeig": "CC-BY-NC-SA-4.0",
12
+ "laya_ml": "Apache-2.0"
13
+ },
14
+ "suites": {
15
+ "spam_scam": {
16
+ "title": "Scam or not? (298 messages)",
17
+ "n": 298,
18
+ "chance": 0.5,
19
+ "acc": {
20
+ "nz": 0.8255,
21
+ "roeig": 0.3221,
22
+ "laya_ml": 0.5034
23
+ },
24
+ "brier": {
25
+ "nz": 0.2629,
26
+ "roeig": 0.7604,
27
+ "laya_ml": 0.8042
28
+ },
29
+ "ece": {
30
+ "nz": 0.0993,
31
+ "roeig": 0.3802,
32
+ "laya_ml": 0.38
33
+ },
34
+ "empty_acc": {
35
+ "nz": 0.7047,
36
+ "roeig": 0.2953,
37
+ "laya_ml": 0.2953
38
+ },
39
+ "vs_nitzotz": {
40
+ "roeig": {
41
+ "nz_only": 177,
42
+ "other_only": 27,
43
+ "p": 3.1836858702039475e-28
44
+ },
45
+ "laya_ml": {
46
+ "nz_only": 129,
47
+ "other_only": 33,
48
+ "p": 1.2998168966363252e-14
49
+ }
50
+ }
51
+ },
52
+ "spam_scam_hard": {
53
+ "title": "same, hard cases only (65)",
54
+ "n": 65,
55
+ "chance": 0.5,
56
+ "acc": {
57
+ "nz": 0.6308,
58
+ "roeig": 0.3231,
59
+ "laya_ml": 0.3538
60
+ },
61
+ "brier": {
62
+ "nz": 0.4394,
63
+ "roeig": 0.7076,
64
+ "laya_ml": 1.1057
65
+ },
66
+ "ece": {
67
+ "nz": 0.056,
68
+ "roeig": 0.3391,
69
+ "laya_ml": 0.5564
70
+ },
71
+ "empty_acc": {
72
+ "nz": 0.7077,
73
+ "roeig": 0.2923,
74
+ "laya_ml": 0.2923
75
+ },
76
+ "vs_nitzotz": {
77
+ "roeig": {
78
+ "nz_only": 27,
79
+ "other_only": 7,
80
+ "p": 0.0008213953115046024
81
+ },
82
+ "laya_ml": {
83
+ "nz_only": 30,
84
+ "other_only": 12,
85
+ "p": 0.007915897334896727
86
+ }
87
+ }
88
+ },
89
+ "spam_type": {
90
+ "title": "Message type, 6 options (298)",
91
+ "n": 298,
92
+ "chance": 0.1667,
93
+ "acc": {
94
+ "nz": 0.755,
95
+ "roeig": 0.5268,
96
+ "laya_ml": 0.2081
97
+ },
98
+ "brier": {
99
+ "nz": 0.3721,
100
+ "roeig": 0.6489,
101
+ "laya_ml": 1.0746
102
+ },
103
+ "ece": {
104
+ "nz": 0.0643,
105
+ "roeig": 0.1043,
106
+ "laya_ml": 0.4131
107
+ },
108
+ "empty_acc": {
109
+ "nz": 0.0772,
110
+ "roeig": 0.2013,
111
+ "laya_ml": 0.0772
112
+ },
113
+ "vs_nitzotz": {
114
+ "roeig": {
115
+ "nz_only": 100,
116
+ "other_only": 32,
117
+ "p": 2.428469657769691e-09
118
+ },
119
+ "laya_ml": {
120
+ "nz_only": 174,
121
+ "other_only": 11,
122
+ "p": 6.991886709878067e-39
123
+ }
124
+ }
125
+ },
126
+ "spam_type_hard": {
127
+ "title": "same, hard cases only (65)",
128
+ "n": 65,
129
+ "chance": 0.1667,
130
+ "acc": {
131
+ "nz": 0.5385,
132
+ "roeig": 0.4462,
133
+ "laya_ml": 0.2462
134
+ },
135
+ "brier": {
136
+ "nz": 0.6411,
137
+ "roeig": 0.7315,
138
+ "laya_ml": 1.1094
139
+ },
140
+ "ece": {
141
+ "nz": 0.1768,
142
+ "roeig": 0.1877,
143
+ "laya_ml": 0.4109
144
+ },
145
+ "empty_acc": {
146
+ "nz": 0.1385,
147
+ "roeig": 0.1538,
148
+ "laya_ml": 0.1385
149
+ },
150
+ "vs_nitzotz": {
151
+ "roeig": {
152
+ "nz_only": 17,
153
+ "other_only": 11,
154
+ "p": 0.34492845088243484
155
+ },
156
+ "laya_ml": {
157
+ "nz_only": 24,
158
+ "other_only": 5,
159
+ "p": 0.0005461126565933228
160
+ }
161
+ }
162
+ },
163
+ "triage_type": {
164
+ "title": "Support ticket type, 5 options (30)",
165
+ "n": 30,
166
+ "chance": 0.2,
167
+ "acc": {
168
+ "nz": 0.8333,
169
+ "roeig": 0.8,
170
+ "laya_ml": 0.5667
171
+ },
172
+ "brier": {
173
+ "nz": 0.2289,
174
+ "roeig": 0.3191,
175
+ "laya_ml": 0.6667
176
+ },
177
+ "ece": {
178
+ "nz": 0.1797,
179
+ "roeig": 0.1321,
180
+ "laya_ml": 0.2772
181
+ },
182
+ "empty_acc": {
183
+ "nz": 0.1667,
184
+ "roeig": 0.1667,
185
+ "laya_ml": 0.3333
186
+ },
187
+ "vs_nitzotz": {
188
+ "roeig": {
189
+ "nz_only": 4,
190
+ "other_only": 3,
191
+ "p": 1.0
192
+ },
193
+ "laya_ml": {
194
+ "nz_only": 11,
195
+ "other_only": 3,
196
+ "p": 0.057373046875
197
+ }
198
+ }
199
+ },
200
+ "triage_urgency": {
201
+ "title": "Ticket urgency, 5 levels (30)",
202
+ "n": 30,
203
+ "chance": 0.2,
204
+ "acc": {
205
+ "nz": 0.6,
206
+ "roeig": 0.3667,
207
+ "laya_ml": 0.3333
208
+ },
209
+ "brier": {
210
+ "nz": 0.5225,
211
+ "roeig": 0.708,
212
+ "laya_ml": 0.7501
213
+ },
214
+ "ece": {
215
+ "nz": 0.2373,
216
+ "roeig": 0.0322,
217
+ "laya_ml": 0.2167
218
+ },
219
+ "empty_acc": {
220
+ "nz": 0.1,
221
+ "roeig": 0.2333,
222
+ "laya_ml": 0.0667
223
+ },
224
+ "vs_nitzotz": {
225
+ "roeig": {
226
+ "nz_only": 13,
227
+ "other_only": 6,
228
+ "p": 0.1670684814453125
229
+ },
230
+ "laya_ml": {
231
+ "nz_only": 14,
232
+ "other_only": 6,
233
+ "p": 0.11531829833984375
234
+ }
235
+ }
236
+ },
237
+ "triage_paying": {
238
+ "title": "Paying customer? yes/no (30)",
239
+ "n": 30,
240
+ "chance": 0.5,
241
+ "acc": {
242
+ "nz": 0.6333,
243
+ "roeig": 0.5667,
244
+ "laya_ml": 0.5
245
+ },
246
+ "brier": {
247
+ "nz": 0.5196,
248
+ "roeig": 0.4338,
249
+ "laya_ml": 0.8183
250
+ },
251
+ "ece": {
252
+ "nz": 0.2253,
253
+ "roeig": 0.2362,
254
+ "laya_ml": 0.4284
255
+ },
256
+ "empty_acc": {
257
+ "nz": 0.5333,
258
+ "roeig": 0.5333,
259
+ "laya_ml": 0.4667
260
+ },
261
+ "vs_nitzotz": {
262
+ "roeig": {
263
+ "nz_only": 2,
264
+ "other_only": 0,
265
+ "p": 0.5
266
+ },
267
+ "laya_ml": {
268
+ "nz_only": 14,
269
+ "other_only": 10,
270
+ "p": 0.5412561893463135
271
+ }
272
+ }
273
+ },
274
+ "massive_20": {
275
+ "title": "Voice command intent, 20 options (500)",
276
+ "n": 500,
277
+ "chance": 0.05,
278
+ "acc": {
279
+ "nz": 0.888,
280
+ "roeig": 0.726,
281
+ "laya_ml": 0.474
282
+ },
283
+ "brier": {
284
+ "nz": 0.1759,
285
+ "roeig": 0.3801,
286
+ "laya_ml": 0.7338
287
+ },
288
+ "ece": {
289
+ "nz": 0.0529,
290
+ "roeig": 0.0395,
291
+ "laya_ml": 0.1661
292
+ },
293
+ "empty_acc": {
294
+ "nz": 0.064,
295
+ "roeig": 0.052,
296
+ "laya_ml": 0.044
297
+ },
298
+ "vs_nitzotz": {
299
+ "roeig": {
300
+ "nz_only": 93,
301
+ "other_only": 12,
302
+ "p": 1.100926641994889e-16
303
+ },
304
+ "laya_ml": {
305
+ "nz_only": 215,
306
+ "other_only": 8,
307
+ "p": 2.0578594884620216e-53
308
+ }
309
+ }
310
+ },
311
+ "massive_4": {
312
+ "title": "Voice command intent, 4 options (500)",
313
+ "n": 500,
314
+ "chance": 0.25,
315
+ "acc": {
316
+ "nz": 0.962,
317
+ "roeig": 0.908,
318
+ "laya_ml": 0.694
319
+ },
320
+ "brier": {
321
+ "nz": 0.0561,
322
+ "roeig": 0.1369,
323
+ "laya_ml": 0.3987
324
+ },
325
+ "ece": {
326
+ "nz": 0.0409,
327
+ "roeig": 0.0393,
328
+ "laya_ml": 0.0468
329
+ },
330
+ "empty_acc": {
331
+ "nz": 0.238,
332
+ "roeig": 0.242,
333
+ "laya_ml": 0.256
334
+ },
335
+ "vs_nitzotz": {
336
+ "roeig": {
337
+ "nz_only": 33,
338
+ "other_only": 6,
339
+ "p": 1.429926123819314e-05
340
+ },
341
+ "laya_ml": {
342
+ "nz_only": 140,
343
+ "other_only": 6,
344
+ "p": 2.8378191731534526e-34
345
+ }
346
+ }
347
+ },
348
+ "sib200": {
349
+ "title": "News topic, 7 options (204)",
350
+ "n": 204,
351
+ "chance": 0.1429,
352
+ "acc": {
353
+ "nz": 0.7941,
354
+ "roeig": 0.8235,
355
+ "laya_ml": 0.6618
356
+ },
357
+ "brier": {
358
+ "nz": 0.3091,
359
+ "roeig": 0.285,
360
+ "laya_ml": 0.4918
361
+ },
362
+ "ece": {
363
+ "nz": 0.0877,
364
+ "roeig": 0.0751,
365
+ "laya_ml": 0.1784
366
+ },
367
+ "empty_acc": {
368
+ "nz": 0.1471,
369
+ "roeig": 0.1961,
370
+ "laya_ml": 0.25
371
+ },
372
+ "vs_nitzotz": {
373
+ "roeig": {
374
+ "nz_only": 16,
375
+ "other_only": 22,
376
+ "p": 0.417692190836533
377
+ },
378
+ "laya_ml": {
379
+ "nz_only": 42,
380
+ "other_only": 15,
381
+ "p": 0.00046000500864878946
382
+ }
383
+ }
384
+ },
385
+ "heq_verify": {
386
+ "title": "Does the passage support this answer? (600)",
387
+ "n": 600,
388
+ "chance": 0.5,
389
+ "acc": {
390
+ "nz": 0.9383,
391
+ "roeig": 0.9417,
392
+ "laya_ml": 0.4883
393
+ },
394
+ "brier": {
395
+ "nz": 0.1003,
396
+ "roeig": 0.0926,
397
+ "laya_ml": 0.8274
398
+ },
399
+ "ece": {
400
+ "nz": 0.0519,
401
+ "roeig": 0.0178,
402
+ "laya_ml": 0.3946
403
+ },
404
+ "empty_acc": {
405
+ "nz": 0.5,
406
+ "roeig": 0.8,
407
+ "laya_ml": 0.59
408
+ },
409
+ "vs_nitzotz": {
410
+ "roeig": {
411
+ "nz_only": 32,
412
+ "other_only": 34,
413
+ "p": 0.9021585000966993
414
+ },
415
+ "laya_ml": {
416
+ "nz_only": 297,
417
+ "other_only": 27,
418
+ "p": 1.181012945982764e-58
419
+ }
420
+ }
421
+ },
422
+ "heq_unans": {
423
+ "title": "Plausible answer the passage does not give (600)",
424
+ "n": 600,
425
+ "chance": 0.5,
426
+ "acc": {
427
+ "nz": 0.91,
428
+ "roeig": 0.53,
429
+ "laya_ml": 0.5367
430
+ },
431
+ "brier": {
432
+ "nz": 0.1412,
433
+ "roeig": 0.856,
434
+ "laya_ml": 0.7348
435
+ },
436
+ "ece": {
437
+ "nz": 0.0402,
438
+ "roeig": 0.4336,
439
+ "laya_ml": 0.3421
440
+ },
441
+ "empty_acc": {
442
+ "nz": 0.5,
443
+ "roeig": 0.5033,
444
+ "laya_ml": 0.5167
445
+ },
446
+ "vs_nitzotz": {
447
+ "roeig": {
448
+ "nz_only": 250,
449
+ "other_only": 22,
450
+ "p": 3.9022177864527475e-50
451
+ },
452
+ "laya_ml": {
453
+ "nz_only": 248,
454
+ "other_only": 24,
455
+ "p": 4.4428671970733176e-48
456
+ }
457
+ }
458
+ },
459
+ "belebele": {
460
+ "title": "Reading comprehension, 4 options (900)",
461
+ "n": 900,
462
+ "chance": 0.25,
463
+ "acc": {
464
+ "nz": 0.5267,
465
+ "roeig": 0.7544,
466
+ "laya_ml": 0.3144
467
+ },
468
+ "brier": {
469
+ "nz": 0.6379,
470
+ "roeig": 0.3442,
471
+ "laya_ml": 0.8341
472
+ },
473
+ "ece": {
474
+ "nz": 0.205,
475
+ "roeig": 0.0324,
476
+ "laya_ml": 0.2307
477
+ },
478
+ "empty_acc": {
479
+ "nz": 0.3089,
480
+ "roeig": 0.3189,
481
+ "laya_ml": 0.2844
482
+ },
483
+ "vs_nitzotz": {
484
+ "roeig": {
485
+ "nz_only": 56,
486
+ "other_only": 261,
487
+ "p": 8.723952167222602e-33
488
+ },
489
+ "laya_ml": {
490
+ "nz_only": 304,
491
+ "other_only": 113,
492
+ "p": 2.6224194993355025e-21
493
+ }
494
+ }
495
+ }
496
+ },
497
+ "significance": "paired exact McNemar against Nitzotz on the same questions",
498
+ "latency_ms": {
499
+ "q8": {
500
+ "median": 48.7,
501
+ "p90": 78.5,
502
+ "n": 50
503
+ },
504
+ "f16": {
505
+ "median": 51.6,
506
+ "p90": 76.8,
507
+ "n": 50
508
+ },
509
+ "py": {
510
+ "median": 54.5,
511
+ "p90": 100.7,
512
+ "n": 50
513
+ },
514
+ "q8_cpu": {
515
+ "median": 335.0,
516
+ "p90": 718.7,
517
+ "n": 50
518
+ },
519
+ "f16_cpu": {
520
+ "median": 462.6,
521
+ "p90": 954.0,
522
+ "n": 50
523
+ },
524
+ "py_cpu": {
525
+ "median": 387.3,
526
+ "p90": 838.2,
527
+ "n": 50
528
+ }
529
+ },
530
+ "latency_points_ms": {
531
+ "q8": {
532
+ "short": {
533
+ "item": "massive_he_4-0075",
534
+ "input_tokens": 38,
535
+ "median_ms": 35.2,
536
+ "p90_ms": 35.8,
537
+ "min_ms": 34.3
538
+ },
539
+ "long": {
540
+ "item": "heq_unanswerable-0522",
541
+ "input_tokens": 430,
542
+ "median_ms": 135.1,
543
+ "p90_ms": 141.8,
544
+ "min_ms": 129.4
545
+ }
546
+ },
547
+ "f16": {
548
+ "short": {
549
+ "item": "massive_he_4-0075",
550
+ "input_tokens": 38,
551
+ "median_ms": 40.6,
552
+ "p90_ms": 41.9,
553
+ "min_ms": 39.4
554
+ },
555
+ "long": {
556
+ "item": "heq_unanswerable-0522",
557
+ "input_tokens": 430,
558
+ "median_ms": 140.9,
559
+ "p90_ms": 147.5,
560
+ "min_ms": 130.8
561
+ }
562
+ },
563
+ "py": {
564
+ "short": {
565
+ "item": "massive_he_4-0075",
566
+ "input_tokens": 38,
567
+ "median_ms": 44.3,
568
+ "p90_ms": 48.2,
569
+ "min_ms": 38.6
570
+ },
571
+ "long": {
572
+ "item": "heq_unanswerable-0522",
573
+ "input_tokens": 430,
574
+ "median_ms": 176.7,
575
+ "p90_ms": 180.7,
576
+ "min_ms": 146.5
577
+ }
578
+ },
579
+ "q8_cpu": {
580
+ "short": {
581
+ "item": "massive_he_4-0075",
582
+ "input_tokens": 38,
583
+ "median_ms": 177.4,
584
+ "p90_ms": 194.5,
585
+ "min_ms": 158.9
586
+ },
587
+ "long": {
588
+ "item": "heq_unanswerable-0522",
589
+ "input_tokens": 430,
590
+ "median_ms": 1232.9,
591
+ "p90_ms": 1278.8,
592
+ "min_ms": 1165.9
593
+ }
594
+ },
595
+ "py_cpu": {
596
+ "short": {
597
+ "item": "massive_he_4-0075",
598
+ "input_tokens": 38,
599
+ "median_ms": 130.5,
600
+ "p90_ms": 140.7,
601
+ "min_ms": 125.5
602
+ },
603
+ "long": {
604
+ "item": "heq_unanswerable-0522",
605
+ "input_tokens": 430,
606
+ "median_ms": 1259.8,
607
+ "p90_ms": 1365.2,
608
+ "min_ms": 1153.4
609
+ }
610
+ }
611
+ },
612
+ "latency_machine": "Intel Core Ultra 9 285H laptop, built-in Arc 140T GPU, Windows 11",
613
+ "gguf_parity_50": {
614
+ "q8": {
615
+ "max_abs_diff": 0.0079,
616
+ "mean_abs_diff": 0.001364,
617
+ "argmax_agree": "50/50"
618
+ },
619
+ "f16": {
620
+ "max_abs_diff": 0.0026,
621
+ "mean_abs_diff": 0.00032,
622
+ "argmax_agree": "50/50"
623
+ },
624
+ "q8_cpu": {
625
+ "max_abs_diff": 0.0094,
626
+ "mean_abs_diff": 0.001866,
627
+ "argmax_agree": "50/50"
628
+ },
629
+ "f16_cpu": {
630
+ "max_abs_diff": 0.0017,
631
+ "mean_abs_diff": 0.000204,
632
+ "argmax_agree": "50/50"
633
+ }
634
+ },
635
+ "gguf_parity_spam_596": {
636
+ "q8": {
637
+ "max_abs_diff": 0.0152,
638
+ "mean_abs_diff": 0.002173,
639
+ "argmax_agree": "593/596",
640
+ "accuracy_vs_gold": 0.7886
641
+ },
642
+ "f16": {
643
+ "max_abs_diff": 0.0023,
644
+ "mean_abs_diff": 0.000322,
645
+ "argmax_agree": "595/596",
646
+ "accuracy_vs_gold": 0.7919
647
+ },
648
+ "py": {
649
+ "max_abs_diff": 0.0001,
650
+ "mean_abs_diff": 0.0,
651
+ "argmax_agree": "595/596",
652
+ "accuracy_vs_gold": 0.7919
653
+ },
654
+ "q8_cpu": {
655
+ "max_abs_diff": 0.0275,
656
+ "mean_abs_diff": 0.003209,
657
+ "argmax_agree": "590/596",
658
+ "accuracy_vs_gold": 0.7836
659
+ },
660
+ "f16_cpu": {
661
+ "max_abs_diff": 0.0014,
662
+ "mean_abs_diff": 0.000268,
663
+ "argmax_agree": "595/596",
664
+ "accuracy_vs_gold": 0.7919
665
+ },
666
+ "py_cpu": {
667
+ "max_abs_diff": 0.0,
668
+ "mean_abs_diff": 0.0,
669
+ "argmax_agree": "596/596",
670
+ "accuracy_vs_gold": 0.7903
671
+ }
672
+ },
673
+ "belebele_split": {
674
+ "verbatim_n": 252,
675
+ "reworded_n": 648,
676
+ "nz": {
677
+ "verbatim": 0.7182539682539683,
678
+ "reworded": 0.4521604938271605
679
+ },
680
+ "roeig": {
681
+ "verbatim": 0.8412698412698413,
682
+ "reworded": 0.720679012345679
683
+ },
684
+ "laya_ml": {
685
+ "verbatim": 0.44047619047619047,
686
+ "reworded": 0.2654320987654321
687
+ }
688
+ },
689
+ "calibration_pooled": {
690
+ "n": 3990,
691
+ "ece_15bin": 0.042
692
+ }
693
+ }
ggmlc/compile_nitzotz.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compile a Nitzotz checkpoint (HalleluBERT + laya head) to a laya.exe GGUF with ggmlc.
2
+
3
+ Adapted from ggmlc examples/laya/compile_laya.py (Apache-2.0, monatis/ggmlc v0.9.5). The tokenizer (byte-level BPE,
4
+ gpt2 pre-tokenizer) and the laya sequence plan are baked into the GGUF (`ggmlc.decision`), so laya.exe does not fall
5
+ back to its built-in ModernBERT preprocessor.
6
+
7
+ Needs its own venv (ggmlc 0.9.5 wheel, torch CPU, laya 0.3.7, transformers):
8
+ python compile_nitzotz.py --checkpoint <laya checkpoint folder> --quantize f16 --out nitzotz-f16.gguf
9
+ """
10
+ import argparse
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ os.environ.setdefault("USE_TF", "0")
17
+ os.environ.setdefault("TRANSFORMERS_NO_TF", "1")
18
+ HERE = Path(__file__).resolve().parent
19
+ sys.path.insert(0, str(HERE))
20
+
21
+ import ggmlc # noqa: E402
22
+ import laya # noqa: E402
23
+ import torch # noqa: E402
24
+ from ggmlc.pipeline.decision import DecisionPipelineSpec # noqa: E402
25
+ from ggmlc.pipeline.tokenizer import BPETokenizer # noqa: E402
26
+ from ggmlc.transforms.fusion import FusionOptions # noqa: E402
27
+ from laya.common import QTYPES, build_sequence, collate_items # noqa: E402
28
+ from nitzotz_trunk import MAX_OPTS, NitzotzTrunk, pad_batch # noqa: E402
29
+
30
+
31
+ def example_batch(agent, seq_len):
32
+ state = "שלום, חויבתי פעמיים על אותה הזמנה. אפשר להחזיר לי את הכסף היום?"
33
+ qdefs = [{"type": "choice", "instructions": "מה סוג ההודעה?",
34
+ "criteria": {"חיוב": "תשלום או חשבונית", "תקלה": "משהו לא עובד", "אחר": "כל דבר אחר"}},
35
+ {"type": "noul", "instructions": "הלקוח מבקש החזר כספי.",
36
+ "criteria": {"true": "כן", "false": "לא"}}]
37
+ items = []
38
+ for qd in qdefs:
39
+ q = agent._to_internal(qd)
40
+ ids, mk = build_sequence(agent.tok, state, q, agent.cfg["max_len"], agent.cfg["head_max_len"])
41
+ items.append({"ids": ids, "markers": mk, "qtype": QTYPES[q["t"]]})
42
+ b = collate_items([items], agent.tok.pad_token_id)
43
+ return pad_batch(b["input_ids"], b["attention_mask"], b["marker_pos"], b["marker_mask"], b["qtype"],
44
+ int(agent.tok.pad_token_id), seq_len)
45
+
46
+
47
+ def main():
48
+ ap = argparse.ArgumentParser()
49
+ ap.add_argument("--checkpoint", required=True)
50
+ ap.add_argument("--quantize", default="f16", choices=["f32", "f16", "q8_0", "q4_0", "q4_k_m", "ud_q4_k_m"])
51
+ ap.add_argument("--out", required=True)
52
+ ap.add_argument("--max-batch", type=int, default=8)
53
+ ap.add_argument("--min-seq", type=int, default=64)
54
+ ap.add_argument("--name", default="nitzotz")
55
+ ap.add_argument("--repo", default="BrainboxAI/nitzotz")
56
+ args = ap.parse_args()
57
+
58
+ agent = laya.Agent(args.checkpoint, device="cpu")
59
+ agent.model.cpu().float().eval()
60
+ max_len, head_max_len = int(agent.cfg["max_len"]), int(agent.cfg["head_max_len"])
61
+ trunk = NitzotzTrunk(agent, max_len=max_len).eval()
62
+ example = example_batch(agent, 128)
63
+
64
+ base = BPETokenizer.from_huggingface(agent.tok, context_length=max_len)
65
+ tok = BPETokenizer(vocab=base.vocab, merges=base.merges, pre_tokenizer="gpt2", context_length=max_len,
66
+ bos_token_id=int(agent.tok.cls_token_id), eos_token_id=int(agent.tok.sep_token_id),
67
+ pad_token_id=int(agent.tok.pad_token_id), unk_token_id=int(agent.tok.unk_token_id),
68
+ chat_template=base.chat_template)
69
+ print("tokenizer vocab %d merges %d cls %d sep %d pad %d mask %d" % (
70
+ tok.vocab_size, len(tok.merges), agent.tok.cls_token_id, agent.tok.sep_token_id, agent.tok.pad_token_id,
71
+ agent.tok.mask_token_id))
72
+ buckets = [x for x in (64, 128, 256, 512) if x <= max_len]
73
+ decision = DecisionPipelineSpec.laya(
74
+ cls_id=int(agent.tok.cls_token_id), sep_id=int(agent.tok.sep_token_id), pad_id=int(agent.tok.pad_token_id),
75
+ mask_id=int(agent.tok.mask_token_id), max_len=max_len, head_max_len=head_max_len, max_opts=MAX_OPTS,
76
+ min_seq=args.min_seq, max_batch=args.max_batch, length_buckets=buckets, temperature=agent.temperature,
77
+ temperature_by_options=dict(agent.temperature_by_options), model_name=args.name, family="nitzotz",
78
+ checkpoint=args.repo)
79
+ extra = {
80
+ "laya.max_len": max_len, "laya.head_max_len": head_max_len, "laya.max_opts": MAX_OPTS,
81
+ "laya.max_batch": args.max_batch, "laya.min_seq": args.min_seq, "laya.length_buckets": json.dumps(buckets),
82
+ "laya.mask_token_id": int(agent.tok.mask_token_id), "laya.cls_token_id": int(agent.tok.cls_token_id),
83
+ "laya.sep_token_id": int(agent.tok.sep_token_id), "laya.pad_token_id": int(agent.tok.pad_token_id),
84
+ "laya.temperature": json.dumps(agent.temperature),
85
+ "laya.temperature_by_options": json.dumps(agent.temperature_by_options),
86
+ "laya.model_name": args.name, "laya.family": "nitzotz", "laya.checkpoint": args.repo,
87
+ }
88
+ extra.update(decision.to_gguf_metadata())
89
+ fusion = FusionOptions()
90
+ fusion.enable_rope = False
91
+ dim_b = torch.export.Dim("b", min=1, max=args.max_batch)
92
+ dim_s = torch.export.Dim("s", min=args.min_seq, max=max_len)
93
+ dyn = ({0: dim_b, 1: dim_s}, {0: dim_b, 1: dim_s}, {0: dim_b}, {0: dim_b}, {0: dim_b})
94
+ out = Path(args.out)
95
+ out.parent.mkdir(parents=True, exist_ok=True)
96
+ ggmlc.compile(trunk, example, output=str(out), model_name=args.name.replace("-", "_"), quantize=args.quantize,
97
+ pipeline=tok, tasks=["classification"], extra_metadata=extra, fusion_options=fusion,
98
+ dynamic_shapes=dyn)
99
+ print("wrote", out, out.stat().st_size, "bytes")
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main()
ggmlc/nitzotz_trunk.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Exportable Nitzotz trunk for ggmlc: HalleluBERT (RoBERTa) encoder + the laya DecisionModel head.
2
+
3
+ Adapted from ggmlc examples/laya/laya_trunk.py (Apache-2.0, monatis/ggmlc v0.9.5), which does the same for the
4
+ ModernBERT laya checkpoints. What changed: the encoder is a post-LN RoBERTa (learned absolute positions, one token
5
+ type, q/k/v with bias, exact GELU), and the option axis has MAX_OPTS = 20 slots because MASSIVE-20 asks 20 options.
6
+ The head, the marker gather, the scorer and the act head are the laya ones, unchanged.
7
+
8
+ Inputs (dynamic batch b and sequence s, option axis static MAX_OPTS):
9
+ input_ids [B, S] int32
10
+ attention_mask [B, S] float32 (1 = token, 0 = pad; laya pads on the right)
11
+ marker_pos [B, MAX_OPTS] int32 host-flattened index into [B*S, H] (b * S + token index)
12
+ marker_mask [B, MAX_OPTS] float32
13
+ qtype [B] int32 (0=choice, 1=score, 2=noul)
14
+ Outputs: logits [B, MAX_OPTS] (padded slots about -1e4), act_logits [B, 2].
15
+
16
+ Positions: RoBERTa numbers real tokens from padding_idx + 1 = 2. laya pads on the right only, so the position of
17
+ token i is i + 2 for every real token. A padded token gets a different position than in transformers (which gives it
18
+ 1), but a pad is never attended to and never read, so the outputs are the same.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import math
23
+ from typing import Any
24
+
25
+ import torch
26
+ import torch.nn.functional as F
27
+ from torch import nn
28
+
29
+ MAX_OPTS = 20
30
+
31
+
32
+ class NitzotzTrunk(nn.Module):
33
+ def __init__(self, agent: Any, max_len: int = 512, max_opts: int = MAX_OPTS):
34
+ super().__init__()
35
+ model = agent.model
36
+ enc = model.encoder
37
+ cfg = enc.config
38
+ d = cfg.hidden_size
39
+ self.hidden, self.n_heads = d, cfg.num_attention_heads
40
+ self.head_dim = d // self.n_heads
41
+ self.eps = cfg.layer_norm_eps
42
+ self.max_len, self.max_opts = int(max_len), int(max_opts)
43
+ self.buf_len = int(max_len) + 1 # one spare slot so slicing [:s] is never a no-op (torch.export)
44
+ pad_idx = cfg.pad_token_id
45
+
46
+ emb = enc.embeddings
47
+ self.tok_emb = nn.Embedding(cfg.vocab_size, d)
48
+ self.tok_emb.weight.data.copy_(emb.word_embeddings.weight.data)
49
+ # position rows for tokens 0..max_len-1 are pad_idx+1 .. pad_idx+max_len; the spare slot repeats the last row
50
+ rows = torch.arange(self.buf_len) + pad_idx + 1
51
+ rows = rows.clamp(max=emb.position_embeddings.weight.shape[0] - 1)
52
+ pos = emb.position_embeddings.weight.data[rows].clone()
53
+ pos = pos + emb.token_type_embeddings.weight.data[0][None, :] # token type is always 0
54
+ self.register_buffer("pos_type", pos.unsqueeze(0), persistent=True) # [1, buf_len, d]
55
+ self.emb_ln_w = nn.Parameter(emb.LayerNorm.weight.data.clone())
56
+ self.emb_ln_b = nn.Parameter(emb.LayerNorm.bias.data.clone())
57
+
58
+ self.qkv, self.wo, self.ln1_w, self.ln1_b = nn.ModuleList(), nn.ModuleList(), nn.ParameterList(), nn.ParameterList()
59
+ self.fc1, self.fc2, self.ln2_w, self.ln2_b = nn.ModuleList(), nn.ModuleList(), nn.ParameterList(), nn.ParameterList()
60
+ for layer in enc.encoder.layer:
61
+ sa, so = layer.attention.self, layer.attention.output
62
+ qkv = nn.Linear(d, 3 * d, bias=True)
63
+ qkv.weight.data.copy_(torch.cat([sa.query.weight.data, sa.key.weight.data, sa.value.weight.data], 0))
64
+ qkv.bias.data.copy_(torch.cat([sa.query.bias.data, sa.key.bias.data, sa.value.bias.data], 0))
65
+ wo = nn.Linear(d, d, bias=True)
66
+ wo.weight.data.copy_(so.dense.weight.data)
67
+ wo.bias.data.copy_(so.dense.bias.data)
68
+ self.qkv.append(qkv)
69
+ self.wo.append(wo)
70
+ self.ln1_w.append(nn.Parameter(so.LayerNorm.weight.data.clone()))
71
+ self.ln1_b.append(nn.Parameter(so.LayerNorm.bias.data.clone()))
72
+ fc1 = nn.Linear(d, cfg.intermediate_size, bias=True)
73
+ fc1.weight.data.copy_(layer.intermediate.dense.weight.data)
74
+ fc1.bias.data.copy_(layer.intermediate.dense.bias.data)
75
+ fc2 = nn.Linear(cfg.intermediate_size, d, bias=True)
76
+ fc2.weight.data.copy_(layer.output.dense.weight.data)
77
+ fc2.bias.data.copy_(layer.output.dense.bias.data)
78
+ self.fc1.append(fc1)
79
+ self.fc2.append(fc2)
80
+ self.ln2_w.append(nn.Parameter(layer.output.LayerNorm.weight.data.clone()))
81
+ self.ln2_b.append(nn.Parameter(layer.output.LayerNorm.bias.data.clone()))
82
+
83
+ # laya decision head: 2 x pre-LN TransformerEncoderLayer (ReLU FFN), scorer, act head
84
+ self.type_emb = nn.Embedding(3, d)
85
+ self.type_emb.weight.data.copy_(model.type_emb.weight.data)
86
+ self.head_qkv, self.head_wo = nn.ModuleList(), nn.ModuleList()
87
+ self.head_fc1, self.head_fc2 = nn.ModuleList(), nn.ModuleList()
88
+ self.head_n1_w, self.head_n1_b = nn.ParameterList(), nn.ParameterList()
89
+ self.head_n2_w, self.head_n2_b = nn.ParameterList(), nn.ParameterList()
90
+ for layer in model.head.layers:
91
+ attn = layer.self_attn
92
+ q = nn.Linear(d, 3 * d, bias=True)
93
+ q.weight.data.copy_(attn.in_proj_weight.data)
94
+ q.bias.data.copy_(attn.in_proj_bias.data)
95
+ o = nn.Linear(d, d, bias=True)
96
+ o.weight.data.copy_(attn.out_proj.weight.data)
97
+ o.bias.data.copy_(attn.out_proj.bias.data)
98
+ self.head_qkv.append(q)
99
+ self.head_wo.append(o)
100
+ f1 = nn.Linear(d, layer.linear1.out_features, bias=True)
101
+ f1.weight.data.copy_(layer.linear1.weight.data)
102
+ f1.bias.data.copy_(layer.linear1.bias.data)
103
+ f2 = nn.Linear(layer.linear2.in_features, d, bias=True)
104
+ f2.weight.data.copy_(layer.linear2.weight.data)
105
+ f2.bias.data.copy_(layer.linear2.bias.data)
106
+ self.head_fc1.append(f1)
107
+ self.head_fc2.append(f2)
108
+ self.head_n1_w.append(nn.Parameter(layer.norm1.weight.data.clone()))
109
+ self.head_n1_b.append(nn.Parameter(layer.norm1.bias.data.clone()))
110
+ self.head_n2_w.append(nn.Parameter(layer.norm2.weight.data.clone()))
111
+ self.head_n2_b.append(nn.Parameter(layer.norm2.bias.data.clone()))
112
+ self.scorer_ln = nn.LayerNorm(d)
113
+ self.scorer_ln.weight.data.copy_(model.scorer[0].weight.data)
114
+ self.scorer_ln.bias.data.copy_(model.scorer[0].bias.data)
115
+ self.scorer_fc1 = nn.Linear(d, d)
116
+ self.scorer_fc1.weight.data.copy_(model.scorer[1].weight.data)
117
+ self.scorer_fc1.bias.data.copy_(model.scorer[1].bias.data)
118
+ self.scorer_fc2 = nn.Linear(d, 1)
119
+ self.scorer_fc2.weight.data.copy_(model.scorer[3].weight.data)
120
+ self.scorer_fc2.bias.data.copy_(model.scorer[3].bias.data)
121
+ self.act_fc1 = nn.Linear(d + 4, model.act_head[0].out_features)
122
+ self.act_fc1.weight.data.copy_(model.act_head[0].weight.data)
123
+ self.act_fc1.bias.data.copy_(model.act_head[0].bias.data)
124
+ self.act_fc2 = nn.Linear(model.act_head[2].in_features, model.act_head[2].out_features)
125
+ self.act_fc2.weight.data.copy_(model.act_head[2].weight.data)
126
+ self.act_fc2.bias.data.copy_(model.act_head[2].bias.data)
127
+ self.eval()
128
+
129
+ def _ln(self, x, w, b, eps):
130
+ return F.layer_norm(x, (self.hidden,), w, b, eps)
131
+
132
+ def _sdpa(self, x, qkv, wo, bias):
133
+ b, s, _ = x.shape
134
+ q, k, v = qkv(x).split(self.hidden, dim=-1)
135
+ q = q.view(b, s, self.n_heads, self.head_dim).transpose(1, 2)
136
+ k = k.view(b, s, self.n_heads, self.head_dim).transpose(1, 2)
137
+ v = v.view(b, s, self.n_heads, self.head_dim).transpose(1, 2)
138
+ y = F.scaled_dot_product_attention(q, k, v, attn_mask=bias, dropout_p=0.0, is_causal=False,
139
+ scale=1.0 / math.sqrt(self.head_dim))
140
+ return wo(y.transpose(1, 2).reshape(b, s, self.hidden))
141
+
142
+ def forward(self, input_ids, attention_mask, marker_pos, marker_mask, qtype):
143
+ b, s = input_ids.shape
144
+ h = self.tok_emb(input_ids.to(dtype=torch.long)) + self.pos_type[:, :s]
145
+ h = self._ln(h, self.emb_ln_w, self.emb_ln_b, self.eps)
146
+
147
+ pad_bias = ((attention_mask.to(dtype=h.dtype) - 1.0) * 1.0e4)[:, None, None, :]
148
+ full_zeros = attention_mask[:, None, :, None] * (attention_mask[:, None, None, :] * 0.0)
149
+ bias = full_zeros.to(dtype=h.dtype) + pad_bias
150
+
151
+ for i in range(len(self.qkv)): # post-LN RoBERTa layer
152
+ h = self._ln(h + self._sdpa(h, self.qkv[i], self.wo[i], bias), self.ln1_w[i], self.ln1_b[i], self.eps)
153
+ h = self._ln(h + self.fc2[i](F.gelu(self.fc1[i](h))), self.ln2_w[i], self.ln2_b[i], self.eps)
154
+
155
+ h = h + self.type_emb(qtype.to(dtype=torch.long))[:, None, :]
156
+ for i in range(len(self.head_qkv)): # pre-LN laya head layer
157
+ x = self._ln(h, self.head_n1_w[i], self.head_n1_b[i], 1e-5)
158
+ h = h + self._sdpa(x, self.head_qkv[i], self.head_wo[i], bias)
159
+ y = self._ln(h, self.head_n2_w[i], self.head_n2_b[i], 1e-5)
160
+ h = h + self.head_fc2[i](F.relu(self.head_fc1[i](y)))
161
+
162
+ flat = h.reshape(b * s, self.hidden)
163
+ gathered = F.embedding(marker_pos.reshape(-1), flat).view(b, self.max_opts, self.hidden)
164
+ logits = self.scorer_fc2(F.gelu(self.scorer_fc1(self.scorer_ln(gathered)))).squeeze(-1)
165
+ logits = logits + (1.0 - marker_mask) * (-1.0e4)
166
+
167
+ p = torch.softmax(logits, dim=-1)
168
+ k = marker_mask.sum(dim=-1, keepdim=True).clamp(min=2.0)
169
+ ent = -(p * torch.log(p.clamp_min(1e-9))).sum(dim=-1, keepdim=True) / torch.log(k)
170
+ top1 = p[:, 0:1]
171
+ for i in range(1, self.max_opts):
172
+ slot = p[:, i:i + 1]
173
+ top1 = F.relu(top1 - slot) + slot
174
+ keep = (top1 - p) / ((top1 - p) + 1.0e-6)
175
+ scaled = p * keep
176
+ top2 = scaled[:, 0:1]
177
+ for i in range(1, self.max_opts):
178
+ slot = scaled[:, i:i + 1]
179
+ top2 = F.relu(top2 - slot) + slot
180
+ feats = torch.cat([top1, top1 - top2, ent, k / 255.0], dim=-1)
181
+ act_logits = self.act_fc2(F.gelu(self.act_fc1(torch.cat([h[:, 0], feats], dim=-1))))
182
+ return logits, act_logits
183
+
184
+
185
+ def pad_batch(input_ids, attention_mask, marker_pos, marker_mask, qtype, pad_id, seq_len, max_opts=MAX_OPTS):
186
+ """Pad a laya collate_items batch to [B, seq_len] / [B, max_opts] and flatten the marker index (host side)."""
187
+ b, s = input_ids.shape
188
+ k = marker_pos.shape[1]
189
+ ids = torch.full((b, seq_len), pad_id, dtype=torch.int32)
190
+ att = torch.zeros((b, seq_len), dtype=torch.float32)
191
+ mpos = torch.zeros((b, max_opts), dtype=torch.int32)
192
+ mmask = torch.zeros((b, max_opts), dtype=torch.float32)
193
+ ids[:, :s] = input_ids.to(torch.int32)
194
+ att[:, :s] = attention_mask.to(torch.float32)
195
+ mpos[:, :k] = marker_pos.to(torch.int32)
196
+ mmask[:, :k] = marker_mask.to(torch.float32)
197
+ mpos = mpos + (torch.arange(b, dtype=torch.int32)[:, None] * seq_len)
198
+ return ids, att, mpos, mmask, qtype.to(torch.int32).reshape(b)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ed978a077a075355feeada7154b10382f40e0e8e115efd194965c7f00bf5c63c
3
+ size 767366700
nitzotz-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3396c490d501a1f5b76db931544119731c56265b798a93fe6babfdb025d36c1b
3
+ size 770204096
nitzotz-q8_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e4a4cc1c83b957a86938db2ff9c0846e75b80a22508aadb839447df04ce2454c
3
+ size 412580576
rl_agent_config.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "encoder": "HalleluBERT/HalleluBERT_large",
3
+ "head_layers": 2,
4
+ "max_len": 512,
5
+ "head_max_len": 256,
6
+ "act_costs": {
7
+ "escalate": 0.5
8
+ },
9
+ "amp_dtype": "bf16",
10
+ "model_name": "nitzotz",
11
+ "temperature": [
12
+ 1.103005051612854,
13
+ 1.109466552734375,
14
+ 1.165097951889038
15
+ ],
16
+ "training": {
17
+ "encoder_init": "HalleluBERT-large after extractive QA on HeQ v1.1 train (test-overlapping passages removed)",
18
+ "data": "58,000 items: synthetic Hebrew messages, HeQ v1.1 train, MASSIVE he-IL train (see README)",
19
+ "items_total": 58000,
20
+ "train_items": 55099,
21
+ "calib_items": 2901,
22
+ "epochs_planned": 2,
23
+ "epochs_run": 2,
24
+ "kept_epoch": 2,
25
+ "heldout_by_epoch": [
26
+ {
27
+ "acc": 0.9349,
28
+ "soft_nll": 0.5046,
29
+ "by_family": {
30
+ "claim": 0.9521,
31
+ "heq_read": 0.972,
32
+ "heq_unans": 0.92,
33
+ "heq_verify": 0.9567,
34
+ "massive_intent": 0.92,
35
+ "routing": 0.9201,
36
+ "spam_type": 0.9329,
37
+ "urgency": 0.8971
38
+ },
39
+ "epoch": 1,
40
+ "step": 1722,
41
+ "train_seconds": 765.9
42
+ },
43
+ {
44
+ "acc": 0.9479,
45
+ "soft_nll": 0.4612,
46
+ "by_family": {
47
+ "claim": 0.9635,
48
+ "heq_read": 0.984,
49
+ "heq_unans": 0.944,
50
+ "heq_verify": 0.9533,
51
+ "massive_intent": 0.9343,
52
+ "routing": 0.9384,
53
+ "spam_type": 0.9429,
54
+ "urgency": 0.9257
55
+ },
56
+ "epoch": 2,
57
+ "step": 3444,
58
+ "train_seconds": 766.6
59
+ }
60
+ ],
61
+ "steps": 3444,
62
+ "batch": 32,
63
+ "micro_batch": 16,
64
+ "lr_encoder": 1e-05,
65
+ "lr_head": 0.0001,
66
+ "warmup": 0.1,
67
+ "schedule": "cosine",
68
+ "seed": 2,
69
+ "split_seed": 1234,
70
+ "loss": "soft cross-entropy",
71
+ "amp": "bf16",
72
+ "device": "cuda",
73
+ "train_seconds": 1543.1,
74
+ "head_init": "fresh (no laya checkpoint weights)",
75
+ "class_weights": "none"
76
+ }
77
+ }
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,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": "<s>",
5
+ "cls_token": "<s>",
6
+ "eos_token": "</s>",
7
+ "errors": "replace",
8
+ "is_local": false,
9
+ "local_files_only": false,
10
+ "mask_token": "<mask>",
11
+ "model_max_length": 1000000000000000019884624838656,
12
+ "pad_token": "<pad>",
13
+ "sep_token": "</s>",
14
+ "tokenizer_class": "RobertaTokenizer",
15
+ "trim_offsets": true,
16
+ "unk_token": "<unk>"
17
+ }