laya 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {laya-0.3.2 → laya-0.3.4}/PKG-INFO +129 -119
- {laya-0.3.2 → laya-0.3.4}/README.md +128 -118
- {laya-0.3.2 → laya-0.3.4}/laya/__init__.py +1 -1
- {laya-0.3.2 → laya-0.3.4}/laya/agent.py +15 -1
- {laya-0.3.2 → laya-0.3.4}/laya/router.py +16 -2
- {laya-0.3.2 → laya-0.3.4}/laya.egg-info/PKG-INFO +129 -119
- {laya-0.3.2 → laya-0.3.4}/pyproject.toml +1 -1
- {laya-0.3.2 → laya-0.3.4}/setup.py +1 -1
- {laya-0.3.2 → laya-0.3.4}/tests/test_criteria.py +17 -0
- {laya-0.3.2 → laya-0.3.4}/tests/test_router.py +15 -0
- {laya-0.3.2 → laya-0.3.4}/LICENSE +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya/common.py +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya/email.py +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya/lang.py +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya/presets.py +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya.egg-info/SOURCES.txt +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya.egg-info/dependency_links.txt +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya.egg-info/requires.txt +0 -0
- {laya-0.3.2 → laya-0.3.4}/laya.egg-info/top_level.txt +0 -0
- {laya-0.3.2 → laya-0.3.4}/setup.cfg +0 -0
- {laya-0.3.2 → laya-0.3.4}/tests/test_local_e2e.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: laya
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Fast, non-autoregressive System 1 decision engine with calibrated probabilities
|
|
5
5
|
Author: Convai Innovations
|
|
6
6
|
License: Apache-2.0
|
|
@@ -28,10 +28,17 @@ Requires-Dist: numpy>=1.20.0
|
|
|
28
28
|
Dynamic: license-file
|
|
29
29
|
Dynamic: requires-python
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
<p align="center">
|
|
32
|
+
<picture>
|
|
33
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/logo-lockup-dark.png" />
|
|
34
|
+
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/logo-lockup.png" alt="Laya" width="330" />
|
|
35
|
+
</picture>
|
|
36
|
+
</p>
|
|
32
37
|
|
|
33
38
|
**Multilingual, non-autoregressive System 1 decision engine.** Typed decisions over 100+ languages in a single forward pass — 33 ms — trained with reinforcement learning against strictly proper scoring rules (RLCD), with a router that picks the right checkpoint per request.
|
|
34
39
|
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
35
42
|
[](https://colab.research.google.com/drive/15d4Yv__KHeHjshVb-6PRTfqVllxih2S3?usp=sharing)
|
|
36
43
|
[](https://pypi.org/project/laya/)
|
|
37
44
|
[](https://huggingface.co/convaiinnovations/laya)
|
|
@@ -41,8 +48,10 @@ Dynamic: requires-python
|
|
|
41
48
|
[](https://www.buymeacoffee.com/nandakishorm)
|
|
42
49
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
43
50
|
|
|
51
|
+
</div>
|
|
52
|
+
|
|
44
53
|
<p align="center">
|
|
45
|
-
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/
|
|
54
|
+
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/laya_vs_jev_full.png" alt="Laya versus TypeSafe Jev: accuracy on shared public datasets, every application workflow, all 51 languages, speed, calibration, and the cost of not preloading" width="100%" />
|
|
46
55
|
</p>
|
|
47
56
|
|
|
48
57
|
Laya evaluates typed questions (`choice`, `score`, `noul`) over any state (text, email, ticket or JSON document) in **a single forward pass** — 33 ms for one question, 7.2 ms/question batched, measured on a T4. No text generation, so nothing to parse and nothing to hallucinate.
|
|
@@ -65,27 +74,29 @@ pip install laya
|
|
|
65
74
|
|
|
66
75
|
---
|
|
67
76
|
|
|
68
|
-
## Quickstart
|
|
77
|
+
## Quickstart: Route Mode (Recommended)
|
|
78
|
+
|
|
79
|
+
Laya ships three checkpoints. The built-in **`Router`** is the recommended entry point: it evaluates any state in any language, automatically detects scripts and languages in sub-milliseconds, and dispatches to the optimal checkpoint in a single forward pass.
|
|
69
80
|
|
|
70
81
|
```python
|
|
71
82
|
import laya
|
|
83
|
+
from laya import Router
|
|
72
84
|
|
|
73
|
-
#
|
|
74
|
-
|
|
85
|
+
# Preload checkpoints into memory for instant sub-35ms routing
|
|
86
|
+
router = Router(preload=True)
|
|
75
87
|
|
|
76
|
-
#
|
|
88
|
+
# 1. State in any language or schema
|
|
77
89
|
state = {
|
|
78
90
|
"from": "user@acme.com",
|
|
79
91
|
"subject": "Duplicate charge on invoice #4411",
|
|
80
92
|
"body": "Hi, we were billed twice for March. Please refund the duplicate today or we will cancel our plan."
|
|
81
93
|
}
|
|
82
94
|
|
|
83
|
-
#
|
|
95
|
+
# 2. Define your typed questions
|
|
84
96
|
questions = {
|
|
85
|
-
# choice: categorical selection with probabilities & confidence
|
|
86
97
|
"department": {
|
|
87
98
|
"type": "choice",
|
|
88
|
-
"instructions": "Which department should handle this
|
|
99
|
+
"instructions": "Which department should handle this request?",
|
|
89
100
|
"criteria": {
|
|
90
101
|
"billing": "invoices, payments, refunds",
|
|
91
102
|
"technical": "bugs, outages, system errors",
|
|
@@ -93,38 +104,117 @@ questions = {
|
|
|
93
104
|
"other": "everything else"
|
|
94
105
|
}
|
|
95
106
|
},
|
|
96
|
-
# score: placement on an ordinal rubric
|
|
97
107
|
"urgency": {
|
|
98
108
|
"type": "score",
|
|
99
109
|
"instructions": "How urgent is this request?",
|
|
100
110
|
"criteria": ["not urgent", "soon", "critical deadline or blocking issue"]
|
|
101
111
|
},
|
|
102
|
-
# noul: calibrated boolean probability P(true)
|
|
103
112
|
"churn_risk": {
|
|
104
113
|
"type": "noul",
|
|
105
114
|
"instructions": "Does the user threaten to cancel or leave?"
|
|
106
115
|
},
|
|
107
|
-
"
|
|
116
|
+
"refund_requested": {
|
|
108
117
|
"type": "noul",
|
|
109
|
-
"instructions": "
|
|
118
|
+
"instructions": "Does the user explicitly request a refund?"
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
# 3. English state -> automatically routed to laya (ModernBERT-large, 39.5 ms)
|
|
123
|
+
res_en = router.predict(state, questions)
|
|
124
|
+
print("Department :", res_en["answers"]["department"]["choice"]) # -> billing (confidence: 0.94)
|
|
125
|
+
print("Routing :", res_en["routing"]["model"]) # -> english
|
|
126
|
+
|
|
127
|
+
# 4. Hindi state -> automatically routed to laya-multilingual (mmBERT-base, 32.8 ms)
|
|
128
|
+
res_hi = router.predict({"body": "मुझसे दो बार शुल्क लिया गया, कृपया पैसे वापस करें।"}, questions)
|
|
129
|
+
print("Department :", res_hi["answers"]["department"]["choice"]) # -> billing (confidence: 0.86)
|
|
130
|
+
print("Routing :", res_hi["routing"]["model"]) # -> multilingual
|
|
131
|
+
|
|
132
|
+
# 5. Explicit override when you want a specific checkpoint
|
|
133
|
+
res_td = router.predict(state, questions, model="typed-decisions")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Every result carries full routing metadata explaining why the choice was made:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
res_hi["routing"]
|
|
140
|
+
# {
|
|
141
|
+
# 'model': 'multilingual',
|
|
142
|
+
# 'repo': 'convaiinnovations/laya/multilingual',
|
|
143
|
+
# 'reason': 'non-Latin script (devanagari, 100% of letters); the English checkpoint cannot read it'
|
|
144
|
+
# }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Inspect a routing decision without running any forward pass:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
router.route({"body": "Der Kunde wurde zweimal belastet"}, questions).reason
|
|
151
|
+
# "Latin script but language looks like 'de', not English"
|
|
152
|
+
```
|
|
116
153
|
|
|
117
|
-
|
|
118
|
-
# -> billing (confidence: 0.94)
|
|
154
|
+
### Why Route: The Evidence
|
|
119
155
|
|
|
120
|
-
|
|
121
|
-
# -> 1.84 / 2.0
|
|
156
|
+
On a shared benchmark (17,416 questions, one T4 GPU, identical questions per model):
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
| Benchmark / Task | English (`laya`) | Multilingual (`laya-multilingual`) | `Router` (Routed) |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| MASSIVE intent, English | **0.783** | 0.657 | **0.783** |
|
|
161
|
+
| MASSIVE intent, 13 other languages | 0.306 | **0.451** | **0.451** |
|
|
162
|
+
| XNLI, English | **0.860** | 0.843 | **0.860** |
|
|
163
|
+
| XNLI, 14 other languages | 0.521 | **0.731** | **0.731** |
|
|
164
|
+
| Languages usable (>3x random) | 23 / 51 | 45 / 51 | **45 / 51** |
|
|
165
|
+
| Latency, 1 question (T4 GPU) | 39.5 ms | **32.8 ms** | **32.8 ms** |
|
|
166
|
+
| Latency, 10 questions batched | 158.6 ms | **72.3 ms** | **72.3 ms** |
|
|
167
|
+
|
|
168
|
+
The English checkpoint collapses on non-Latin scripts (Khmer scores **0.000 accuracy at 0.952 confidence**). Because the model stays confident while being wrong, confidence gating cannot save you. `Router` detects the script in <0.5 ms pure Python before the forward pass.
|
|
169
|
+
|
|
170
|
+
### Production Preload & Memory
|
|
125
171
|
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
A cold checkpoint build costs seconds; language detection costs microseconds. At the default `max_loaded=1`, traffic that alternates languages rebuilds a model on *every* request (measured at a 7.4 s median reload on CPU and 10.3 s on T4).
|
|
173
|
+
|
|
174
|
+
For a server or production app, preload:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
# Every checkpoint resident in memory; language flips cost detection only (<1 ms)
|
|
178
|
+
router = Router(preload=True)
|
|
179
|
+
router = Router(preload=True, device="cuda")
|
|
180
|
+
|
|
181
|
+
# Or preload only the specific checkpoints you serve:
|
|
182
|
+
router.preload(["english", "multilingual"])
|
|
183
|
+
|
|
184
|
+
# If your app already built an agent, attach it to avoid duplicate VRAM:
|
|
185
|
+
router.attach("english", existing_agent)
|
|
186
|
+
|
|
187
|
+
# Manage resident memory (default keeps 1 hot, LRU eviction)
|
|
188
|
+
router = Router(max_loaded=2) # keep two hot
|
|
189
|
+
router.unload() # free memory
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Deployment Mode | Per-Request Latency | Model Reloads |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `Router()` (lazy, `max_loaded=1`) | 7 to 10 s on every language switch | 1 per switch |
|
|
195
|
+
| `Router(preload=True)` | **32.8 ms (GPU) / 193–464 ms (CPU)** | **none** |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Single-Model Mode (Direct SDK)
|
|
200
|
+
|
|
201
|
+
If you only need a single checkpoint for a dedicated pipeline, you can load models directly:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
import laya
|
|
205
|
+
|
|
206
|
+
# 1. Load a specific checkpoint directly from the hub
|
|
207
|
+
agent = laya.load("convaiinnovations/laya") # English root
|
|
208
|
+
agent_ml = laya.load("convaiinnovations/laya", subfolder="multilingual") # 100+ languages
|
|
209
|
+
agent_td = laya.load("convaiinnovations/laya", subfolder="typed-decisions")
|
|
210
|
+
|
|
211
|
+
# 2. Run all questions in ONE single forward pass (~35 ms on GPU)
|
|
212
|
+
result = agent.predict(state, questions)
|
|
213
|
+
answers = result["answers"]
|
|
214
|
+
|
|
215
|
+
print("Department :", answers["department"]["choice"]) # -> billing (confidence: 0.94)
|
|
216
|
+
print("Urgency :", answers["urgency"]["score"]) # -> 1.84 / 2.0
|
|
217
|
+
print("Churn Risk :", answers["churn_risk"]["noul"]) # -> 0.892 (89.2% probability)
|
|
128
218
|
```
|
|
129
219
|
|
|
130
220
|
---
|
|
@@ -171,94 +261,6 @@ triage = agent.predict({"message": "My payment failed twice"}, laya.triage_quest
|
|
|
171
261
|
|
|
172
262
|
---
|
|
173
263
|
|
|
174
|
-
## Model Routing (three checkpoints, one call)
|
|
175
|
-
|
|
176
|
-
Laya ships three checkpoints. `Router` picks the right one per request and loads it lazily.
|
|
177
|
-
|
|
178
|
-
| name | repo | size | context | best at |
|
|
179
|
-
|---|---|---|---|---|
|
|
180
|
-
| `english` | [`convaiinnovations/laya`](https://huggingface.co/convaiinnovations/laya) | 421M | 512 | English text |
|
|
181
|
-
| `multilingual` | [`convaiinnovations/laya-multilingual`](https://huggingface.co/convaiinnovations/laya-multilingual) | 322M | 1024 | 100+ languages, 2x faster |
|
|
182
|
-
| `typed-decisions` | [`convaiinnovations/laya-typed-decisions`](https://huggingface.co/convaiinnovations/laya-typed-decisions) | 421M | 1024 | the four typed-decisions workflows |
|
|
183
|
-
|
|
184
|
-
```python
|
|
185
|
-
from laya import Router
|
|
186
|
-
|
|
187
|
-
router = Router() # nothing is downloaded until a request needs it
|
|
188
|
-
|
|
189
|
-
# English -> routed to the English checkpoint
|
|
190
|
-
router.predict({"body": "I was charged twice, please refund."}, questions)
|
|
191
|
-
|
|
192
|
-
# Hindi -> routed to the multilingual checkpoint automatically
|
|
193
|
-
router.predict({"body": "मुझसे दो बार शुल्क लिया गया"}, questions)
|
|
194
|
-
|
|
195
|
-
# explicit when you already know
|
|
196
|
-
router.predict(state, questions, model="typed-decisions")
|
|
197
|
-
router.predict(state, questions, lang="de")
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Every result carries the decision that produced it:
|
|
201
|
-
|
|
202
|
-
```python
|
|
203
|
-
result = router.predict({"body": "二重に請求されました"}, questions)
|
|
204
|
-
result["routing"]
|
|
205
|
-
# {'model': 'multilingual',
|
|
206
|
-
# 'repo': 'convaiinnovations/laya-multilingual',
|
|
207
|
-
# 'reason': 'non-Latin script (kana, 100% of letters); the English checkpoint cannot read it',
|
|
208
|
-
# ...}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Inspect a decision without running the model:
|
|
212
|
-
|
|
213
|
-
```python
|
|
214
|
-
router.route({"body": "Der Kunde wurde zweimal belastet"}, questions).reason
|
|
215
|
-
# "Latin script but language looks like 'de', not English"
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Why route at all
|
|
219
|
-
|
|
220
|
-
Accuracy on a shared benchmark (17,416 questions, one T4, identical questions per model):
|
|
221
|
-
|
|
222
|
-
| | `english` | `multilingual` |
|
|
223
|
-
|---|---|---|
|
|
224
|
-
| MASSIVE intent, English | **0.783** | 0.657 |
|
|
225
|
-
| MASSIVE intent, 13 other languages | 0.306 | **0.451** |
|
|
226
|
-
| XNLI, English | **0.860** | 0.843 |
|
|
227
|
-
| XNLI, 14 other languages | 0.521 | **0.731** |
|
|
228
|
-
| English-only suites | **0.684** | 0.619 |
|
|
229
|
-
| Latency, 10 questions | 159 ms | **72 ms** |
|
|
230
|
-
|
|
231
|
-
The English checkpoint does not degrade gracefully outside English -- it collapses, and stays
|
|
232
|
-
confident while doing so. On 20-option MASSIVE intent (random = 0.050) it scores 0.100 on Hindi
|
|
233
|
-
and 0.103 on Korean, with an expected calibration error of 0.855. Script detection is therefore
|
|
234
|
-
the primary routing signal.
|
|
235
|
-
|
|
236
|
-
### Routing rules
|
|
237
|
-
|
|
238
|
-
Precedence, highest first:
|
|
239
|
-
|
|
240
|
-
1. `model=` -- explicit checkpoint.
|
|
241
|
-
2. `task="typed_decisions"` -- explicit task.
|
|
242
|
-
3. A question-id set exactly matching a typed-decisions workflow, **only** if you construct the
|
|
243
|
-
router with `auto_task_detection=True`. It is off by default: that checkpoint is fine-tuned on
|
|
244
|
-
four synthetic workflows and should not be a silent fallback.
|
|
245
|
-
4. `lang=` -- explicit language code.
|
|
246
|
-
5. Detected script (exact) and, for Latin text, a stopword/diacritic language guess (best effort).
|
|
247
|
-
6. `default=` (`"english"` unless you change it).
|
|
248
|
-
|
|
249
|
-
### Memory
|
|
250
|
-
|
|
251
|
-
All three together are ~1.16B parameters, so `Router` keeps **one** resident by default and
|
|
252
|
-
evicts least-recently-used:
|
|
253
|
-
|
|
254
|
-
```python
|
|
255
|
-
Router(max_loaded=2) # keep two hot
|
|
256
|
-
router.unload() # free everything
|
|
257
|
-
router.loaded # ['multilingual']
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
264
|
## Decision Primitives
|
|
263
265
|
|
|
264
266
|
| Primitive | Output | Use Cases |
|
|
@@ -307,7 +309,8 @@ published, never measured here** (no TypeSafe API access), so sample sizes and p
|
|
|
307
309
|
| typed-decisions, 2,000 decisions | 0.727 | **0.766** | +0.039 |
|
|
308
310
|
| AG News, 4 labels | 0.910 | **0.950** | +0.040 |
|
|
309
311
|
| DAIR Emotion, 6 labels | 0.480 | **0.595** | +0.115 |
|
|
310
|
-
|
|
|
312
|
+
| Banking77 (72 vs 77 labels) | **0.870** | 0.425 | Jev leads on >20 options |
|
|
313
|
+
| ECE *(lower better)* | 0.246 | **0.081** | 3× better (post-temperature) |
|
|
311
314
|
| p50 latency, 1 question | 236–276 ms | **32.8 ms** | 7.8× faster |
|
|
312
315
|
| Languages usable | *no published benchmark* | **45 of 51** | — |
|
|
313
316
|
| Weights | closed API | **Apache 2.0** | — |
|
|
@@ -316,6 +319,12 @@ published, never measured here** (no TypeSafe API access), so sample sizes and p
|
|
|
316
319
|
On DAIR Emotion, Jev assigned **zero probability to the true label on 16% of examples** — a hard
|
|
317
320
|
failure for anything branching on confidence.
|
|
318
321
|
|
|
322
|
+
#### Where Jev leads
|
|
323
|
+
|
|
324
|
+
* **High-cardinality label spaces (>20 options at default settings):** On Banking77, Jev scores 0.870 (on 72 labels) while Laya scores 0.425 (on 77 labels at default 256-token head budget). This is an architectural token-budget constraint: options share a fixed `head_max_len` budget (192 tokens on English, 256 on multilingual), so 77 options receive only ~3 to 4 tokens per label, causing text to become indistinguishable. Jev supports up to 255 options out-of-the-box. While `laya-multilingual` supports 1,024 context (and up to 8,192 in the encoder) and you can raise `agent.cfg["head_max_len"] = 512` at runtime, Jev is currently better suited for 50+ options in a single prompt without tuning.
|
|
325
|
+
* **Soft distribution matching:** On typed-decisions, while Laya achieves higher argmax accuracy (0.766 vs 0.727), Jev achieves higher soft accuracy (0.580 vs 0.471) against the teacher's full probability distributions.
|
|
326
|
+
* **Out-of-the-box raw calibration:** Before temperature scaling, the base checkpoint has higher raw ECE (0.213 vs 0.144). Laya achieves its 0.081 ECE after domain temperature fitting.
|
|
327
|
+
|
|
319
328
|
Full detail, including every workflow and all 51 languages: **[`BENCHMARKS.md`](BENCHMARKS.md)**.
|
|
320
329
|
|
|
321
330
|
### typed-decisions, measured on all three checkpoints
|
|
@@ -382,11 +391,12 @@ temperatures at all, so fit them before relying on its probabilities.
|
|
|
382
391
|
against a 0.318 random baseline and a 0.461 majority-class baseline. The 0.766 figure comes
|
|
383
392
|
from the checkpoint fine-tuned on that benchmark's own training split. Laya is a fast base to
|
|
384
393
|
specialise, not a zero-shot decision engine.
|
|
385
|
-
* **
|
|
386
|
-
`
|
|
387
|
-
|
|
388
|
-
accuracy
|
|
389
|
-
|
|
394
|
+
* **High-cardinality choice questions and token budgets:** Sequences split into an option prompt budget (`head_max_len`) and the remaining document/state budget (`max_len - head_max_len`):
|
|
395
|
+
* `laya` (English) defaults to 512 context (`head_max_len = 192`, ~320 tokens for state).
|
|
396
|
+
* `laya-multilingual` and `laya-typed-decisions` default to 1,024 context (`head_max_len = 256`, ~768 tokens for state; mmBERT-base encoder supports up to 8,192 with RoPE).
|
|
397
|
+
At default settings, a 77-option question like Banking77 allocates only `(256 - 16) // 77` ≈ 3–4 tokens per label, which causes accuracy to fall off sharply (0.425 vs Jev's 0.870). If evaluating 50+ options in a single question:
|
|
398
|
+
1. Raise `agent.cfg["head_max_len"] = 512` and `agent.cfg["max_len"] = 1024` (or up to 2048 / 4096 / 8192) so every option has enough tokens to remain distinct.
|
|
399
|
+
2. Or split large option sets into a two-step coarse-to-fine hierarchical choice.
|
|
390
400
|
* Ordinal `score` questions are the weakest primitive (SST-5 0.372).
|
|
391
401
|
* `laya` collapses outside English; `laya-multilingual` is weaker on English. Route, or pick
|
|
392
402
|
deliberately.
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/logo-lockup-dark.png" />
|
|
4
|
+
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/logo-lockup.png" alt="Laya" width="330" />
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
8
|
**Multilingual, non-autoregressive System 1 decision engine.** Typed decisions over 100+ languages in a single forward pass — 33 ms — trained with reinforcement learning against strictly proper scoring rules (RLCD), with a router that picks the right checkpoint per request.
|
|
4
9
|
|
|
10
|
+
<div align="center">
|
|
11
|
+
|
|
5
12
|
[](https://colab.research.google.com/drive/15d4Yv__KHeHjshVb-6PRTfqVllxih2S3?usp=sharing)
|
|
6
13
|
[](https://pypi.org/project/laya/)
|
|
7
14
|
[](https://huggingface.co/convaiinnovations/laya)
|
|
@@ -11,8 +18,10 @@
|
|
|
11
18
|
[](https://www.buymeacoffee.com/nandakishorm)
|
|
12
19
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
13
20
|
|
|
21
|
+
</div>
|
|
22
|
+
|
|
14
23
|
<p align="center">
|
|
15
|
-
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/
|
|
24
|
+
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/laya_vs_jev_full.png" alt="Laya versus TypeSafe Jev: accuracy on shared public datasets, every application workflow, all 51 languages, speed, calibration, and the cost of not preloading" width="100%" />
|
|
16
25
|
</p>
|
|
17
26
|
|
|
18
27
|
Laya evaluates typed questions (`choice`, `score`, `noul`) over any state (text, email, ticket or JSON document) in **a single forward pass** — 33 ms for one question, 7.2 ms/question batched, measured on a T4. No text generation, so nothing to parse and nothing to hallucinate.
|
|
@@ -35,27 +44,29 @@ pip install laya
|
|
|
35
44
|
|
|
36
45
|
---
|
|
37
46
|
|
|
38
|
-
## Quickstart
|
|
47
|
+
## Quickstart: Route Mode (Recommended)
|
|
48
|
+
|
|
49
|
+
Laya ships three checkpoints. The built-in **`Router`** is the recommended entry point: it evaluates any state in any language, automatically detects scripts and languages in sub-milliseconds, and dispatches to the optimal checkpoint in a single forward pass.
|
|
39
50
|
|
|
40
51
|
```python
|
|
41
52
|
import laya
|
|
53
|
+
from laya import Router
|
|
42
54
|
|
|
43
|
-
#
|
|
44
|
-
|
|
55
|
+
# Preload checkpoints into memory for instant sub-35ms routing
|
|
56
|
+
router = Router(preload=True)
|
|
45
57
|
|
|
46
|
-
#
|
|
58
|
+
# 1. State in any language or schema
|
|
47
59
|
state = {
|
|
48
60
|
"from": "user@acme.com",
|
|
49
61
|
"subject": "Duplicate charge on invoice #4411",
|
|
50
62
|
"body": "Hi, we were billed twice for March. Please refund the duplicate today or we will cancel our plan."
|
|
51
63
|
}
|
|
52
64
|
|
|
53
|
-
#
|
|
65
|
+
# 2. Define your typed questions
|
|
54
66
|
questions = {
|
|
55
|
-
# choice: categorical selection with probabilities & confidence
|
|
56
67
|
"department": {
|
|
57
68
|
"type": "choice",
|
|
58
|
-
"instructions": "Which department should handle this
|
|
69
|
+
"instructions": "Which department should handle this request?",
|
|
59
70
|
"criteria": {
|
|
60
71
|
"billing": "invoices, payments, refunds",
|
|
61
72
|
"technical": "bugs, outages, system errors",
|
|
@@ -63,38 +74,117 @@ questions = {
|
|
|
63
74
|
"other": "everything else"
|
|
64
75
|
}
|
|
65
76
|
},
|
|
66
|
-
# score: placement on an ordinal rubric
|
|
67
77
|
"urgency": {
|
|
68
78
|
"type": "score",
|
|
69
79
|
"instructions": "How urgent is this request?",
|
|
70
80
|
"criteria": ["not urgent", "soon", "critical deadline or blocking issue"]
|
|
71
81
|
},
|
|
72
|
-
# noul: calibrated boolean probability P(true)
|
|
73
82
|
"churn_risk": {
|
|
74
83
|
"type": "noul",
|
|
75
84
|
"instructions": "Does the user threaten to cancel or leave?"
|
|
76
85
|
},
|
|
77
|
-
"
|
|
86
|
+
"refund_requested": {
|
|
78
87
|
"type": "noul",
|
|
79
|
-
"instructions": "
|
|
88
|
+
"instructions": "Does the user explicitly request a refund?"
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
# 3. English state -> automatically routed to laya (ModernBERT-large, 39.5 ms)
|
|
93
|
+
res_en = router.predict(state, questions)
|
|
94
|
+
print("Department :", res_en["answers"]["department"]["choice"]) # -> billing (confidence: 0.94)
|
|
95
|
+
print("Routing :", res_en["routing"]["model"]) # -> english
|
|
96
|
+
|
|
97
|
+
# 4. Hindi state -> automatically routed to laya-multilingual (mmBERT-base, 32.8 ms)
|
|
98
|
+
res_hi = router.predict({"body": "मुझसे दो बार शुल्क लिया गया, कृपया पैसे वापस करें।"}, questions)
|
|
99
|
+
print("Department :", res_hi["answers"]["department"]["choice"]) # -> billing (confidence: 0.86)
|
|
100
|
+
print("Routing :", res_hi["routing"]["model"]) # -> multilingual
|
|
101
|
+
|
|
102
|
+
# 5. Explicit override when you want a specific checkpoint
|
|
103
|
+
res_td = router.predict(state, questions, model="typed-decisions")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Every result carries full routing metadata explaining why the choice was made:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
res_hi["routing"]
|
|
110
|
+
# {
|
|
111
|
+
# 'model': 'multilingual',
|
|
112
|
+
# 'repo': 'convaiinnovations/laya/multilingual',
|
|
113
|
+
# 'reason': 'non-Latin script (devanagari, 100% of letters); the English checkpoint cannot read it'
|
|
114
|
+
# }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Inspect a routing decision without running any forward pass:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
router.route({"body": "Der Kunde wurde zweimal belastet"}, questions).reason
|
|
121
|
+
# "Latin script but language looks like 'de', not English"
|
|
122
|
+
```
|
|
86
123
|
|
|
87
|
-
|
|
88
|
-
# -> billing (confidence: 0.94)
|
|
124
|
+
### Why Route: The Evidence
|
|
89
125
|
|
|
90
|
-
|
|
91
|
-
# -> 1.84 / 2.0
|
|
126
|
+
On a shared benchmark (17,416 questions, one T4 GPU, identical questions per model):
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
| Benchmark / Task | English (`laya`) | Multilingual (`laya-multilingual`) | `Router` (Routed) |
|
|
129
|
+
|---|---|---|---|
|
|
130
|
+
| MASSIVE intent, English | **0.783** | 0.657 | **0.783** |
|
|
131
|
+
| MASSIVE intent, 13 other languages | 0.306 | **0.451** | **0.451** |
|
|
132
|
+
| XNLI, English | **0.860** | 0.843 | **0.860** |
|
|
133
|
+
| XNLI, 14 other languages | 0.521 | **0.731** | **0.731** |
|
|
134
|
+
| Languages usable (>3x random) | 23 / 51 | 45 / 51 | **45 / 51** |
|
|
135
|
+
| Latency, 1 question (T4 GPU) | 39.5 ms | **32.8 ms** | **32.8 ms** |
|
|
136
|
+
| Latency, 10 questions batched | 158.6 ms | **72.3 ms** | **72.3 ms** |
|
|
137
|
+
|
|
138
|
+
The English checkpoint collapses on non-Latin scripts (Khmer scores **0.000 accuracy at 0.952 confidence**). Because the model stays confident while being wrong, confidence gating cannot save you. `Router` detects the script in <0.5 ms pure Python before the forward pass.
|
|
139
|
+
|
|
140
|
+
### Production Preload & Memory
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
142
|
+
A cold checkpoint build costs seconds; language detection costs microseconds. At the default `max_loaded=1`, traffic that alternates languages rebuilds a model on *every* request (measured at a 7.4 s median reload on CPU and 10.3 s on T4).
|
|
143
|
+
|
|
144
|
+
For a server or production app, preload:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Every checkpoint resident in memory; language flips cost detection only (<1 ms)
|
|
148
|
+
router = Router(preload=True)
|
|
149
|
+
router = Router(preload=True, device="cuda")
|
|
150
|
+
|
|
151
|
+
# Or preload only the specific checkpoints you serve:
|
|
152
|
+
router.preload(["english", "multilingual"])
|
|
153
|
+
|
|
154
|
+
# If your app already built an agent, attach it to avoid duplicate VRAM:
|
|
155
|
+
router.attach("english", existing_agent)
|
|
156
|
+
|
|
157
|
+
# Manage resident memory (default keeps 1 hot, LRU eviction)
|
|
158
|
+
router = Router(max_loaded=2) # keep two hot
|
|
159
|
+
router.unload() # free memory
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Deployment Mode | Per-Request Latency | Model Reloads |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `Router()` (lazy, `max_loaded=1`) | 7 to 10 s on every language switch | 1 per switch |
|
|
165
|
+
| `Router(preload=True)` | **32.8 ms (GPU) / 193–464 ms (CPU)** | **none** |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Single-Model Mode (Direct SDK)
|
|
170
|
+
|
|
171
|
+
If you only need a single checkpoint for a dedicated pipeline, you can load models directly:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import laya
|
|
175
|
+
|
|
176
|
+
# 1. Load a specific checkpoint directly from the hub
|
|
177
|
+
agent = laya.load("convaiinnovations/laya") # English root
|
|
178
|
+
agent_ml = laya.load("convaiinnovations/laya", subfolder="multilingual") # 100+ languages
|
|
179
|
+
agent_td = laya.load("convaiinnovations/laya", subfolder="typed-decisions")
|
|
180
|
+
|
|
181
|
+
# 2. Run all questions in ONE single forward pass (~35 ms on GPU)
|
|
182
|
+
result = agent.predict(state, questions)
|
|
183
|
+
answers = result["answers"]
|
|
184
|
+
|
|
185
|
+
print("Department :", answers["department"]["choice"]) # -> billing (confidence: 0.94)
|
|
186
|
+
print("Urgency :", answers["urgency"]["score"]) # -> 1.84 / 2.0
|
|
187
|
+
print("Churn Risk :", answers["churn_risk"]["noul"]) # -> 0.892 (89.2% probability)
|
|
98
188
|
```
|
|
99
189
|
|
|
100
190
|
---
|
|
@@ -141,94 +231,6 @@ triage = agent.predict({"message": "My payment failed twice"}, laya.triage_quest
|
|
|
141
231
|
|
|
142
232
|
---
|
|
143
233
|
|
|
144
|
-
## Model Routing (three checkpoints, one call)
|
|
145
|
-
|
|
146
|
-
Laya ships three checkpoints. `Router` picks the right one per request and loads it lazily.
|
|
147
|
-
|
|
148
|
-
| name | repo | size | context | best at |
|
|
149
|
-
|---|---|---|---|---|
|
|
150
|
-
| `english` | [`convaiinnovations/laya`](https://huggingface.co/convaiinnovations/laya) | 421M | 512 | English text |
|
|
151
|
-
| `multilingual` | [`convaiinnovations/laya-multilingual`](https://huggingface.co/convaiinnovations/laya-multilingual) | 322M | 1024 | 100+ languages, 2x faster |
|
|
152
|
-
| `typed-decisions` | [`convaiinnovations/laya-typed-decisions`](https://huggingface.co/convaiinnovations/laya-typed-decisions) | 421M | 1024 | the four typed-decisions workflows |
|
|
153
|
-
|
|
154
|
-
```python
|
|
155
|
-
from laya import Router
|
|
156
|
-
|
|
157
|
-
router = Router() # nothing is downloaded until a request needs it
|
|
158
|
-
|
|
159
|
-
# English -> routed to the English checkpoint
|
|
160
|
-
router.predict({"body": "I was charged twice, please refund."}, questions)
|
|
161
|
-
|
|
162
|
-
# Hindi -> routed to the multilingual checkpoint automatically
|
|
163
|
-
router.predict({"body": "मुझसे दो बार शुल्क लिया गया"}, questions)
|
|
164
|
-
|
|
165
|
-
# explicit when you already know
|
|
166
|
-
router.predict(state, questions, model="typed-decisions")
|
|
167
|
-
router.predict(state, questions, lang="de")
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Every result carries the decision that produced it:
|
|
171
|
-
|
|
172
|
-
```python
|
|
173
|
-
result = router.predict({"body": "二重に請求されました"}, questions)
|
|
174
|
-
result["routing"]
|
|
175
|
-
# {'model': 'multilingual',
|
|
176
|
-
# 'repo': 'convaiinnovations/laya-multilingual',
|
|
177
|
-
# 'reason': 'non-Latin script (kana, 100% of letters); the English checkpoint cannot read it',
|
|
178
|
-
# ...}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Inspect a decision without running the model:
|
|
182
|
-
|
|
183
|
-
```python
|
|
184
|
-
router.route({"body": "Der Kunde wurde zweimal belastet"}, questions).reason
|
|
185
|
-
# "Latin script but language looks like 'de', not English"
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Why route at all
|
|
189
|
-
|
|
190
|
-
Accuracy on a shared benchmark (17,416 questions, one T4, identical questions per model):
|
|
191
|
-
|
|
192
|
-
| | `english` | `multilingual` |
|
|
193
|
-
|---|---|---|
|
|
194
|
-
| MASSIVE intent, English | **0.783** | 0.657 |
|
|
195
|
-
| MASSIVE intent, 13 other languages | 0.306 | **0.451** |
|
|
196
|
-
| XNLI, English | **0.860** | 0.843 |
|
|
197
|
-
| XNLI, 14 other languages | 0.521 | **0.731** |
|
|
198
|
-
| English-only suites | **0.684** | 0.619 |
|
|
199
|
-
| Latency, 10 questions | 159 ms | **72 ms** |
|
|
200
|
-
|
|
201
|
-
The English checkpoint does not degrade gracefully outside English -- it collapses, and stays
|
|
202
|
-
confident while doing so. On 20-option MASSIVE intent (random = 0.050) it scores 0.100 on Hindi
|
|
203
|
-
and 0.103 on Korean, with an expected calibration error of 0.855. Script detection is therefore
|
|
204
|
-
the primary routing signal.
|
|
205
|
-
|
|
206
|
-
### Routing rules
|
|
207
|
-
|
|
208
|
-
Precedence, highest first:
|
|
209
|
-
|
|
210
|
-
1. `model=` -- explicit checkpoint.
|
|
211
|
-
2. `task="typed_decisions"` -- explicit task.
|
|
212
|
-
3. A question-id set exactly matching a typed-decisions workflow, **only** if you construct the
|
|
213
|
-
router with `auto_task_detection=True`. It is off by default: that checkpoint is fine-tuned on
|
|
214
|
-
four synthetic workflows and should not be a silent fallback.
|
|
215
|
-
4. `lang=` -- explicit language code.
|
|
216
|
-
5. Detected script (exact) and, for Latin text, a stopword/diacritic language guess (best effort).
|
|
217
|
-
6. `default=` (`"english"` unless you change it).
|
|
218
|
-
|
|
219
|
-
### Memory
|
|
220
|
-
|
|
221
|
-
All three together are ~1.16B parameters, so `Router` keeps **one** resident by default and
|
|
222
|
-
evicts least-recently-used:
|
|
223
|
-
|
|
224
|
-
```python
|
|
225
|
-
Router(max_loaded=2) # keep two hot
|
|
226
|
-
router.unload() # free everything
|
|
227
|
-
router.loaded # ['multilingual']
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
---
|
|
231
|
-
|
|
232
234
|
## Decision Primitives
|
|
233
235
|
|
|
234
236
|
| Primitive | Output | Use Cases |
|
|
@@ -277,7 +279,8 @@ published, never measured here** (no TypeSafe API access), so sample sizes and p
|
|
|
277
279
|
| typed-decisions, 2,000 decisions | 0.727 | **0.766** | +0.039 |
|
|
278
280
|
| AG News, 4 labels | 0.910 | **0.950** | +0.040 |
|
|
279
281
|
| DAIR Emotion, 6 labels | 0.480 | **0.595** | +0.115 |
|
|
280
|
-
|
|
|
282
|
+
| Banking77 (72 vs 77 labels) | **0.870** | 0.425 | Jev leads on >20 options |
|
|
283
|
+
| ECE *(lower better)* | 0.246 | **0.081** | 3× better (post-temperature) |
|
|
281
284
|
| p50 latency, 1 question | 236–276 ms | **32.8 ms** | 7.8× faster |
|
|
282
285
|
| Languages usable | *no published benchmark* | **45 of 51** | — |
|
|
283
286
|
| Weights | closed API | **Apache 2.0** | — |
|
|
@@ -286,6 +289,12 @@ published, never measured here** (no TypeSafe API access), so sample sizes and p
|
|
|
286
289
|
On DAIR Emotion, Jev assigned **zero probability to the true label on 16% of examples** — a hard
|
|
287
290
|
failure for anything branching on confidence.
|
|
288
291
|
|
|
292
|
+
#### Where Jev leads
|
|
293
|
+
|
|
294
|
+
* **High-cardinality label spaces (>20 options at default settings):** On Banking77, Jev scores 0.870 (on 72 labels) while Laya scores 0.425 (on 77 labels at default 256-token head budget). This is an architectural token-budget constraint: options share a fixed `head_max_len` budget (192 tokens on English, 256 on multilingual), so 77 options receive only ~3 to 4 tokens per label, causing text to become indistinguishable. Jev supports up to 255 options out-of-the-box. While `laya-multilingual` supports 1,024 context (and up to 8,192 in the encoder) and you can raise `agent.cfg["head_max_len"] = 512` at runtime, Jev is currently better suited for 50+ options in a single prompt without tuning.
|
|
295
|
+
* **Soft distribution matching:** On typed-decisions, while Laya achieves higher argmax accuracy (0.766 vs 0.727), Jev achieves higher soft accuracy (0.580 vs 0.471) against the teacher's full probability distributions.
|
|
296
|
+
* **Out-of-the-box raw calibration:** Before temperature scaling, the base checkpoint has higher raw ECE (0.213 vs 0.144). Laya achieves its 0.081 ECE after domain temperature fitting.
|
|
297
|
+
|
|
289
298
|
Full detail, including every workflow and all 51 languages: **[`BENCHMARKS.md`](BENCHMARKS.md)**.
|
|
290
299
|
|
|
291
300
|
### typed-decisions, measured on all three checkpoints
|
|
@@ -352,11 +361,12 @@ temperatures at all, so fit them before relying on its probabilities.
|
|
|
352
361
|
against a 0.318 random baseline and a 0.461 majority-class baseline. The 0.766 figure comes
|
|
353
362
|
from the checkpoint fine-tuned on that benchmark's own training split. Laya is a fast base to
|
|
354
363
|
specialise, not a zero-shot decision engine.
|
|
355
|
-
* **
|
|
356
|
-
`
|
|
357
|
-
|
|
358
|
-
accuracy
|
|
359
|
-
|
|
364
|
+
* **High-cardinality choice questions and token budgets:** Sequences split into an option prompt budget (`head_max_len`) and the remaining document/state budget (`max_len - head_max_len`):
|
|
365
|
+
* `laya` (English) defaults to 512 context (`head_max_len = 192`, ~320 tokens for state).
|
|
366
|
+
* `laya-multilingual` and `laya-typed-decisions` default to 1,024 context (`head_max_len = 256`, ~768 tokens for state; mmBERT-base encoder supports up to 8,192 with RoPE).
|
|
367
|
+
At default settings, a 77-option question like Banking77 allocates only `(256 - 16) // 77` ≈ 3–4 tokens per label, which causes accuracy to fall off sharply (0.425 vs Jev's 0.870). If evaluating 50+ options in a single question:
|
|
368
|
+
1. Raise `agent.cfg["head_max_len"] = 512` and `agent.cfg["max_len"] = 1024` (or up to 2048 / 4096 / 8192) so every option has enough tokens to remain distinct.
|
|
369
|
+
2. Or split large option sets into a two-step coarse-to-fine hierarchical choice.
|
|
360
370
|
* Ordinal `score` questions are the weakest primitive (SST-5 0.372).
|
|
361
371
|
* `laya` collapses outside English; `laya-multilingual` is weaker on English. Route, or pick
|
|
362
372
|
deliberately.
|
|
@@ -201,17 +201,31 @@ class Agent:
|
|
|
201
201
|
self.dtype = torch.float32
|
|
202
202
|
|
|
203
203
|
# 2. Place on device with graceful fallback to CPU on memory error
|
|
204
|
+
fell_back_from = fell_back_why = None
|
|
204
205
|
try:
|
|
205
206
|
self.model.to(self.device).eval()
|
|
206
207
|
except (RuntimeError, torch.cuda.OutOfMemoryError) as e:
|
|
207
208
|
if self.device.type != "cpu":
|
|
208
|
-
|
|
209
|
+
# Record what actually went wrong: the reason matters more than the symptom,
|
|
210
|
+
# and it is the only place the underlying exception is ever surfaced.
|
|
211
|
+
fell_back_from, fell_back_why = self.device, e
|
|
209
212
|
self.device = torch.device("cpu")
|
|
210
213
|
self.dtype = torch.float32
|
|
211
214
|
self.model.to(self.device).eval()
|
|
212
215
|
else:
|
|
213
216
|
raise e
|
|
214
217
|
|
|
218
|
+
if fell_back_from is not None:
|
|
219
|
+
print(
|
|
220
|
+
"\n[laya] Warning: could not place the model on %s, so it is running on CPU.\n"
|
|
221
|
+
" Reason: %s\n"
|
|
222
|
+
" Inference will be roughly 10-15x slower (~200-500 ms rather than ~35 ms).\n"
|
|
223
|
+
" If this is a newer NVIDIA GPU (Blackwell / RTX 50-series), your PyTorch build\n"
|
|
224
|
+
" may not support its CUDA architecture:\n"
|
|
225
|
+
" pip install --pre torch --index-url https://download.pytorch.org/whl/nightly/cu128\n"
|
|
226
|
+
" See https://pytorch.org/get-started/locally/\n"
|
|
227
|
+
% (fell_back_from, fell_back_why), flush=True)
|
|
228
|
+
|
|
215
229
|
@staticmethod
|
|
216
230
|
def _to_internal(qdef: Dict) -> Dict:
|
|
217
231
|
t = qdef["type"]
|
|
@@ -194,6 +194,19 @@ class Router:
|
|
|
194
194
|
if k not in self._order:
|
|
195
195
|
self._agents.pop(k, None)
|
|
196
196
|
|
|
197
|
+
def attach(self, name: str, agent: Any):
|
|
198
|
+
"""Register an already-built Agent under `name` instead of loading a second copy.
|
|
199
|
+
|
|
200
|
+
Useful when the process has a checkpoint loaded for other reasons: a demo that already
|
|
201
|
+
built `convaiinnovations/laya` can hand it to the router rather than pay for -- and hold
|
|
202
|
+
in memory -- a duplicate 421M parameters.
|
|
203
|
+
"""
|
|
204
|
+
key = normalise_name(name)
|
|
205
|
+
self._agents[key] = agent
|
|
206
|
+
self._touch(key)
|
|
207
|
+
self.max_loaded = max(self.max_loaded, len(self._agents))
|
|
208
|
+
return agent
|
|
209
|
+
|
|
197
210
|
def preload(self, names: Optional[List[str]] = None):
|
|
198
211
|
"""Download and build checkpoints up front so no request ever pays a model load.
|
|
199
212
|
|
|
@@ -203,9 +216,10 @@ class Router:
|
|
|
203
216
|
the LRU would immediately evict what this just built.
|
|
204
217
|
"""
|
|
205
218
|
names = [normalise_name(n) for n in (names or list(self.models))]
|
|
206
|
-
self.max_loaded = max(self.max_loaded, len(names))
|
|
219
|
+
self.max_loaded = max(self.max_loaded, len(names), len(self._agents))
|
|
207
220
|
for n in names:
|
|
208
|
-
self.
|
|
221
|
+
if n not in self._agents: # an attached agent is already built
|
|
222
|
+
self.load(n)
|
|
209
223
|
return self
|
|
210
224
|
|
|
211
225
|
def unload(self, name: Optional[str] = None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: laya
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Fast, non-autoregressive System 1 decision engine with calibrated probabilities
|
|
5
5
|
Author: Convai Innovations
|
|
6
6
|
License: Apache-2.0
|
|
@@ -28,10 +28,17 @@ Requires-Dist: numpy>=1.20.0
|
|
|
28
28
|
Dynamic: license-file
|
|
29
29
|
Dynamic: requires-python
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
<p align="center">
|
|
32
|
+
<picture>
|
|
33
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/logo-lockup-dark.png" />
|
|
34
|
+
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/logo-lockup.png" alt="Laya" width="330" />
|
|
35
|
+
</picture>
|
|
36
|
+
</p>
|
|
32
37
|
|
|
33
38
|
**Multilingual, non-autoregressive System 1 decision engine.** Typed decisions over 100+ languages in a single forward pass — 33 ms — trained with reinforcement learning against strictly proper scoring rules (RLCD), with a router that picks the right checkpoint per request.
|
|
34
39
|
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
35
42
|
[](https://colab.research.google.com/drive/15d4Yv__KHeHjshVb-6PRTfqVllxih2S3?usp=sharing)
|
|
36
43
|
[](https://pypi.org/project/laya/)
|
|
37
44
|
[](https://huggingface.co/convaiinnovations/laya)
|
|
@@ -41,8 +48,10 @@ Dynamic: requires-python
|
|
|
41
48
|
[](https://www.buymeacoffee.com/nandakishorm)
|
|
42
49
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
43
50
|
|
|
51
|
+
</div>
|
|
52
|
+
|
|
44
53
|
<p align="center">
|
|
45
|
-
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/
|
|
54
|
+
<img src="https://raw.githubusercontent.com/NandhaKishorM/laya/main/assets/laya_vs_jev_full.png" alt="Laya versus TypeSafe Jev: accuracy on shared public datasets, every application workflow, all 51 languages, speed, calibration, and the cost of not preloading" width="100%" />
|
|
46
55
|
</p>
|
|
47
56
|
|
|
48
57
|
Laya evaluates typed questions (`choice`, `score`, `noul`) over any state (text, email, ticket or JSON document) in **a single forward pass** — 33 ms for one question, 7.2 ms/question batched, measured on a T4. No text generation, so nothing to parse and nothing to hallucinate.
|
|
@@ -65,27 +74,29 @@ pip install laya
|
|
|
65
74
|
|
|
66
75
|
---
|
|
67
76
|
|
|
68
|
-
## Quickstart
|
|
77
|
+
## Quickstart: Route Mode (Recommended)
|
|
78
|
+
|
|
79
|
+
Laya ships three checkpoints. The built-in **`Router`** is the recommended entry point: it evaluates any state in any language, automatically detects scripts and languages in sub-milliseconds, and dispatches to the optimal checkpoint in a single forward pass.
|
|
69
80
|
|
|
70
81
|
```python
|
|
71
82
|
import laya
|
|
83
|
+
from laya import Router
|
|
72
84
|
|
|
73
|
-
#
|
|
74
|
-
|
|
85
|
+
# Preload checkpoints into memory for instant sub-35ms routing
|
|
86
|
+
router = Router(preload=True)
|
|
75
87
|
|
|
76
|
-
#
|
|
88
|
+
# 1. State in any language or schema
|
|
77
89
|
state = {
|
|
78
90
|
"from": "user@acme.com",
|
|
79
91
|
"subject": "Duplicate charge on invoice #4411",
|
|
80
92
|
"body": "Hi, we were billed twice for March. Please refund the duplicate today or we will cancel our plan."
|
|
81
93
|
}
|
|
82
94
|
|
|
83
|
-
#
|
|
95
|
+
# 2. Define your typed questions
|
|
84
96
|
questions = {
|
|
85
|
-
# choice: categorical selection with probabilities & confidence
|
|
86
97
|
"department": {
|
|
87
98
|
"type": "choice",
|
|
88
|
-
"instructions": "Which department should handle this
|
|
99
|
+
"instructions": "Which department should handle this request?",
|
|
89
100
|
"criteria": {
|
|
90
101
|
"billing": "invoices, payments, refunds",
|
|
91
102
|
"technical": "bugs, outages, system errors",
|
|
@@ -93,38 +104,117 @@ questions = {
|
|
|
93
104
|
"other": "everything else"
|
|
94
105
|
}
|
|
95
106
|
},
|
|
96
|
-
# score: placement on an ordinal rubric
|
|
97
107
|
"urgency": {
|
|
98
108
|
"type": "score",
|
|
99
109
|
"instructions": "How urgent is this request?",
|
|
100
110
|
"criteria": ["not urgent", "soon", "critical deadline or blocking issue"]
|
|
101
111
|
},
|
|
102
|
-
# noul: calibrated boolean probability P(true)
|
|
103
112
|
"churn_risk": {
|
|
104
113
|
"type": "noul",
|
|
105
114
|
"instructions": "Does the user threaten to cancel or leave?"
|
|
106
115
|
},
|
|
107
|
-
"
|
|
116
|
+
"refund_requested": {
|
|
108
117
|
"type": "noul",
|
|
109
|
-
"instructions": "
|
|
118
|
+
"instructions": "Does the user explicitly request a refund?"
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
# 3. English state -> automatically routed to laya (ModernBERT-large, 39.5 ms)
|
|
123
|
+
res_en = router.predict(state, questions)
|
|
124
|
+
print("Department :", res_en["answers"]["department"]["choice"]) # -> billing (confidence: 0.94)
|
|
125
|
+
print("Routing :", res_en["routing"]["model"]) # -> english
|
|
126
|
+
|
|
127
|
+
# 4. Hindi state -> automatically routed to laya-multilingual (mmBERT-base, 32.8 ms)
|
|
128
|
+
res_hi = router.predict({"body": "मुझसे दो बार शुल्क लिया गया, कृपया पैसे वापस करें।"}, questions)
|
|
129
|
+
print("Department :", res_hi["answers"]["department"]["choice"]) # -> billing (confidence: 0.86)
|
|
130
|
+
print("Routing :", res_hi["routing"]["model"]) # -> multilingual
|
|
131
|
+
|
|
132
|
+
# 5. Explicit override when you want a specific checkpoint
|
|
133
|
+
res_td = router.predict(state, questions, model="typed-decisions")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Every result carries full routing metadata explaining why the choice was made:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
res_hi["routing"]
|
|
140
|
+
# {
|
|
141
|
+
# 'model': 'multilingual',
|
|
142
|
+
# 'repo': 'convaiinnovations/laya/multilingual',
|
|
143
|
+
# 'reason': 'non-Latin script (devanagari, 100% of letters); the English checkpoint cannot read it'
|
|
144
|
+
# }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Inspect a routing decision without running any forward pass:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
router.route({"body": "Der Kunde wurde zweimal belastet"}, questions).reason
|
|
151
|
+
# "Latin script but language looks like 'de', not English"
|
|
152
|
+
```
|
|
116
153
|
|
|
117
|
-
|
|
118
|
-
# -> billing (confidence: 0.94)
|
|
154
|
+
### Why Route: The Evidence
|
|
119
155
|
|
|
120
|
-
|
|
121
|
-
# -> 1.84 / 2.0
|
|
156
|
+
On a shared benchmark (17,416 questions, one T4 GPU, identical questions per model):
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
| Benchmark / Task | English (`laya`) | Multilingual (`laya-multilingual`) | `Router` (Routed) |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| MASSIVE intent, English | **0.783** | 0.657 | **0.783** |
|
|
161
|
+
| MASSIVE intent, 13 other languages | 0.306 | **0.451** | **0.451** |
|
|
162
|
+
| XNLI, English | **0.860** | 0.843 | **0.860** |
|
|
163
|
+
| XNLI, 14 other languages | 0.521 | **0.731** | **0.731** |
|
|
164
|
+
| Languages usable (>3x random) | 23 / 51 | 45 / 51 | **45 / 51** |
|
|
165
|
+
| Latency, 1 question (T4 GPU) | 39.5 ms | **32.8 ms** | **32.8 ms** |
|
|
166
|
+
| Latency, 10 questions batched | 158.6 ms | **72.3 ms** | **72.3 ms** |
|
|
167
|
+
|
|
168
|
+
The English checkpoint collapses on non-Latin scripts (Khmer scores **0.000 accuracy at 0.952 confidence**). Because the model stays confident while being wrong, confidence gating cannot save you. `Router` detects the script in <0.5 ms pure Python before the forward pass.
|
|
169
|
+
|
|
170
|
+
### Production Preload & Memory
|
|
125
171
|
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
A cold checkpoint build costs seconds; language detection costs microseconds. At the default `max_loaded=1`, traffic that alternates languages rebuilds a model on *every* request (measured at a 7.4 s median reload on CPU and 10.3 s on T4).
|
|
173
|
+
|
|
174
|
+
For a server or production app, preload:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
# Every checkpoint resident in memory; language flips cost detection only (<1 ms)
|
|
178
|
+
router = Router(preload=True)
|
|
179
|
+
router = Router(preload=True, device="cuda")
|
|
180
|
+
|
|
181
|
+
# Or preload only the specific checkpoints you serve:
|
|
182
|
+
router.preload(["english", "multilingual"])
|
|
183
|
+
|
|
184
|
+
# If your app already built an agent, attach it to avoid duplicate VRAM:
|
|
185
|
+
router.attach("english", existing_agent)
|
|
186
|
+
|
|
187
|
+
# Manage resident memory (default keeps 1 hot, LRU eviction)
|
|
188
|
+
router = Router(max_loaded=2) # keep two hot
|
|
189
|
+
router.unload() # free memory
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Deployment Mode | Per-Request Latency | Model Reloads |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `Router()` (lazy, `max_loaded=1`) | 7 to 10 s on every language switch | 1 per switch |
|
|
195
|
+
| `Router(preload=True)` | **32.8 ms (GPU) / 193–464 ms (CPU)** | **none** |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Single-Model Mode (Direct SDK)
|
|
200
|
+
|
|
201
|
+
If you only need a single checkpoint for a dedicated pipeline, you can load models directly:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
import laya
|
|
205
|
+
|
|
206
|
+
# 1. Load a specific checkpoint directly from the hub
|
|
207
|
+
agent = laya.load("convaiinnovations/laya") # English root
|
|
208
|
+
agent_ml = laya.load("convaiinnovations/laya", subfolder="multilingual") # 100+ languages
|
|
209
|
+
agent_td = laya.load("convaiinnovations/laya", subfolder="typed-decisions")
|
|
210
|
+
|
|
211
|
+
# 2. Run all questions in ONE single forward pass (~35 ms on GPU)
|
|
212
|
+
result = agent.predict(state, questions)
|
|
213
|
+
answers = result["answers"]
|
|
214
|
+
|
|
215
|
+
print("Department :", answers["department"]["choice"]) # -> billing (confidence: 0.94)
|
|
216
|
+
print("Urgency :", answers["urgency"]["score"]) # -> 1.84 / 2.0
|
|
217
|
+
print("Churn Risk :", answers["churn_risk"]["noul"]) # -> 0.892 (89.2% probability)
|
|
128
218
|
```
|
|
129
219
|
|
|
130
220
|
---
|
|
@@ -171,94 +261,6 @@ triage = agent.predict({"message": "My payment failed twice"}, laya.triage_quest
|
|
|
171
261
|
|
|
172
262
|
---
|
|
173
263
|
|
|
174
|
-
## Model Routing (three checkpoints, one call)
|
|
175
|
-
|
|
176
|
-
Laya ships three checkpoints. `Router` picks the right one per request and loads it lazily.
|
|
177
|
-
|
|
178
|
-
| name | repo | size | context | best at |
|
|
179
|
-
|---|---|---|---|---|
|
|
180
|
-
| `english` | [`convaiinnovations/laya`](https://huggingface.co/convaiinnovations/laya) | 421M | 512 | English text |
|
|
181
|
-
| `multilingual` | [`convaiinnovations/laya-multilingual`](https://huggingface.co/convaiinnovations/laya-multilingual) | 322M | 1024 | 100+ languages, 2x faster |
|
|
182
|
-
| `typed-decisions` | [`convaiinnovations/laya-typed-decisions`](https://huggingface.co/convaiinnovations/laya-typed-decisions) | 421M | 1024 | the four typed-decisions workflows |
|
|
183
|
-
|
|
184
|
-
```python
|
|
185
|
-
from laya import Router
|
|
186
|
-
|
|
187
|
-
router = Router() # nothing is downloaded until a request needs it
|
|
188
|
-
|
|
189
|
-
# English -> routed to the English checkpoint
|
|
190
|
-
router.predict({"body": "I was charged twice, please refund."}, questions)
|
|
191
|
-
|
|
192
|
-
# Hindi -> routed to the multilingual checkpoint automatically
|
|
193
|
-
router.predict({"body": "मुझसे दो बार शुल्क लिया गया"}, questions)
|
|
194
|
-
|
|
195
|
-
# explicit when you already know
|
|
196
|
-
router.predict(state, questions, model="typed-decisions")
|
|
197
|
-
router.predict(state, questions, lang="de")
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Every result carries the decision that produced it:
|
|
201
|
-
|
|
202
|
-
```python
|
|
203
|
-
result = router.predict({"body": "二重に請求されました"}, questions)
|
|
204
|
-
result["routing"]
|
|
205
|
-
# {'model': 'multilingual',
|
|
206
|
-
# 'repo': 'convaiinnovations/laya-multilingual',
|
|
207
|
-
# 'reason': 'non-Latin script (kana, 100% of letters); the English checkpoint cannot read it',
|
|
208
|
-
# ...}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Inspect a decision without running the model:
|
|
212
|
-
|
|
213
|
-
```python
|
|
214
|
-
router.route({"body": "Der Kunde wurde zweimal belastet"}, questions).reason
|
|
215
|
-
# "Latin script but language looks like 'de', not English"
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Why route at all
|
|
219
|
-
|
|
220
|
-
Accuracy on a shared benchmark (17,416 questions, one T4, identical questions per model):
|
|
221
|
-
|
|
222
|
-
| | `english` | `multilingual` |
|
|
223
|
-
|---|---|---|
|
|
224
|
-
| MASSIVE intent, English | **0.783** | 0.657 |
|
|
225
|
-
| MASSIVE intent, 13 other languages | 0.306 | **0.451** |
|
|
226
|
-
| XNLI, English | **0.860** | 0.843 |
|
|
227
|
-
| XNLI, 14 other languages | 0.521 | **0.731** |
|
|
228
|
-
| English-only suites | **0.684** | 0.619 |
|
|
229
|
-
| Latency, 10 questions | 159 ms | **72 ms** |
|
|
230
|
-
|
|
231
|
-
The English checkpoint does not degrade gracefully outside English -- it collapses, and stays
|
|
232
|
-
confident while doing so. On 20-option MASSIVE intent (random = 0.050) it scores 0.100 on Hindi
|
|
233
|
-
and 0.103 on Korean, with an expected calibration error of 0.855. Script detection is therefore
|
|
234
|
-
the primary routing signal.
|
|
235
|
-
|
|
236
|
-
### Routing rules
|
|
237
|
-
|
|
238
|
-
Precedence, highest first:
|
|
239
|
-
|
|
240
|
-
1. `model=` -- explicit checkpoint.
|
|
241
|
-
2. `task="typed_decisions"` -- explicit task.
|
|
242
|
-
3. A question-id set exactly matching a typed-decisions workflow, **only** if you construct the
|
|
243
|
-
router with `auto_task_detection=True`. It is off by default: that checkpoint is fine-tuned on
|
|
244
|
-
four synthetic workflows and should not be a silent fallback.
|
|
245
|
-
4. `lang=` -- explicit language code.
|
|
246
|
-
5. Detected script (exact) and, for Latin text, a stopword/diacritic language guess (best effort).
|
|
247
|
-
6. `default=` (`"english"` unless you change it).
|
|
248
|
-
|
|
249
|
-
### Memory
|
|
250
|
-
|
|
251
|
-
All three together are ~1.16B parameters, so `Router` keeps **one** resident by default and
|
|
252
|
-
evicts least-recently-used:
|
|
253
|
-
|
|
254
|
-
```python
|
|
255
|
-
Router(max_loaded=2) # keep two hot
|
|
256
|
-
router.unload() # free everything
|
|
257
|
-
router.loaded # ['multilingual']
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
---
|
|
261
|
-
|
|
262
264
|
## Decision Primitives
|
|
263
265
|
|
|
264
266
|
| Primitive | Output | Use Cases |
|
|
@@ -307,7 +309,8 @@ published, never measured here** (no TypeSafe API access), so sample sizes and p
|
|
|
307
309
|
| typed-decisions, 2,000 decisions | 0.727 | **0.766** | +0.039 |
|
|
308
310
|
| AG News, 4 labels | 0.910 | **0.950** | +0.040 |
|
|
309
311
|
| DAIR Emotion, 6 labels | 0.480 | **0.595** | +0.115 |
|
|
310
|
-
|
|
|
312
|
+
| Banking77 (72 vs 77 labels) | **0.870** | 0.425 | Jev leads on >20 options |
|
|
313
|
+
| ECE *(lower better)* | 0.246 | **0.081** | 3× better (post-temperature) |
|
|
311
314
|
| p50 latency, 1 question | 236–276 ms | **32.8 ms** | 7.8× faster |
|
|
312
315
|
| Languages usable | *no published benchmark* | **45 of 51** | — |
|
|
313
316
|
| Weights | closed API | **Apache 2.0** | — |
|
|
@@ -316,6 +319,12 @@ published, never measured here** (no TypeSafe API access), so sample sizes and p
|
|
|
316
319
|
On DAIR Emotion, Jev assigned **zero probability to the true label on 16% of examples** — a hard
|
|
317
320
|
failure for anything branching on confidence.
|
|
318
321
|
|
|
322
|
+
#### Where Jev leads
|
|
323
|
+
|
|
324
|
+
* **High-cardinality label spaces (>20 options at default settings):** On Banking77, Jev scores 0.870 (on 72 labels) while Laya scores 0.425 (on 77 labels at default 256-token head budget). This is an architectural token-budget constraint: options share a fixed `head_max_len` budget (192 tokens on English, 256 on multilingual), so 77 options receive only ~3 to 4 tokens per label, causing text to become indistinguishable. Jev supports up to 255 options out-of-the-box. While `laya-multilingual` supports 1,024 context (and up to 8,192 in the encoder) and you can raise `agent.cfg["head_max_len"] = 512` at runtime, Jev is currently better suited for 50+ options in a single prompt without tuning.
|
|
325
|
+
* **Soft distribution matching:** On typed-decisions, while Laya achieves higher argmax accuracy (0.766 vs 0.727), Jev achieves higher soft accuracy (0.580 vs 0.471) against the teacher's full probability distributions.
|
|
326
|
+
* **Out-of-the-box raw calibration:** Before temperature scaling, the base checkpoint has higher raw ECE (0.213 vs 0.144). Laya achieves its 0.081 ECE after domain temperature fitting.
|
|
327
|
+
|
|
319
328
|
Full detail, including every workflow and all 51 languages: **[`BENCHMARKS.md`](BENCHMARKS.md)**.
|
|
320
329
|
|
|
321
330
|
### typed-decisions, measured on all three checkpoints
|
|
@@ -382,11 +391,12 @@ temperatures at all, so fit them before relying on its probabilities.
|
|
|
382
391
|
against a 0.318 random baseline and a 0.461 majority-class baseline. The 0.766 figure comes
|
|
383
392
|
from the checkpoint fine-tuned on that benchmark's own training split. Laya is a fast base to
|
|
384
393
|
specialise, not a zero-shot decision engine.
|
|
385
|
-
* **
|
|
386
|
-
`
|
|
387
|
-
|
|
388
|
-
accuracy
|
|
389
|
-
|
|
394
|
+
* **High-cardinality choice questions and token budgets:** Sequences split into an option prompt budget (`head_max_len`) and the remaining document/state budget (`max_len - head_max_len`):
|
|
395
|
+
* `laya` (English) defaults to 512 context (`head_max_len = 192`, ~320 tokens for state).
|
|
396
|
+
* `laya-multilingual` and `laya-typed-decisions` default to 1,024 context (`head_max_len = 256`, ~768 tokens for state; mmBERT-base encoder supports up to 8,192 with RoPE).
|
|
397
|
+
At default settings, a 77-option question like Banking77 allocates only `(256 - 16) // 77` ≈ 3–4 tokens per label, which causes accuracy to fall off sharply (0.425 vs Jev's 0.870). If evaluating 50+ options in a single question:
|
|
398
|
+
1. Raise `agent.cfg["head_max_len"] = 512` and `agent.cfg["max_len"] = 1024` (or up to 2048 / 4096 / 8192) so every option has enough tokens to remain distinct.
|
|
399
|
+
2. Or split large option sets into a two-step coarse-to-fine hierarchical choice.
|
|
390
400
|
* Ordinal `score` questions are the weakest primitive (SST-5 0.372).
|
|
391
401
|
* `laya` collapses outside English; `laya-multilingual` is weaker on English. Route, or pick
|
|
392
402
|
deliberately.
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="laya",
|
|
5
|
-
version="0.3.
|
|
5
|
+
version="0.3.4",
|
|
6
6
|
description="Fast, non-autoregressive System 1 decision engine with calibrated probabilities",
|
|
7
7
|
long_description=open("README.md").read(),
|
|
8
8
|
long_description_content_type="text/markdown",
|
|
@@ -99,6 +99,23 @@ parsed = json.loads(render_options(
|
|
|
99
99
|
{"t": "noul", "ins": "x", "crit": {"true": {"a": 1}, "false": {"b": 2}}})[1].split("true: ", 1)[1])
|
|
100
100
|
check("emitted json round-trips", parsed, {"a": 1})
|
|
101
101
|
|
|
102
|
+
|
|
103
|
+
# --------------------------------------------------------------- CPU-fallback warning (#9 follow-up)
|
|
104
|
+
# The warning must fire only when a fallback actually happened -- not merely because the machine
|
|
105
|
+
# has CUDA. `laya.load(path, device="cpu")` on a GPU box is a deliberate choice, not a problem.
|
|
106
|
+
import inspect # noqa: E402
|
|
107
|
+
|
|
108
|
+
from laya import agent as _agent # noqa: E402
|
|
109
|
+
|
|
110
|
+
_src = inspect.getsource(_agent.Agent.__init__)
|
|
111
|
+
check_true("fallback/flag is initialised", "fell_back_from = fell_back_why = None" in _src)
|
|
112
|
+
check_true("fallback/warns only on a real fallback", "if fell_back_from is not None:" in _src)
|
|
113
|
+
check_true("fallback/reports the underlying reason", "Reason: %s" in _src)
|
|
114
|
+
check_true("fallback/keeps the actionable advice", "download.pytorch.org/whl/nightly" in _src)
|
|
115
|
+
check_true("fallback/no bare cuda probe for the warning",
|
|
116
|
+
"torch.cuda.is_available() or getattr(torch.version" not in _src)
|
|
117
|
+
|
|
118
|
+
|
|
102
119
|
print("\n%d passed, %d failed" % (len(PASS), len(FAIL)))
|
|
103
120
|
for f in FAIL:
|
|
104
121
|
print(" FAIL " + f)
|
|
@@ -255,6 +255,21 @@ _load_stub(rp2, "english")
|
|
|
255
255
|
check("preload/touch does not evict", sorted(rp2.loaded), ["english", "multilingual"])
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
# --------------------------------------------------------------------- attach
|
|
259
|
+
ra = stubbed_router(1)
|
|
260
|
+
sentinel = _Stub("already-built")
|
|
261
|
+
ra.attach("english", sentinel)
|
|
262
|
+
check("attach/registers under the name", ra._agents["english"], sentinel)
|
|
263
|
+
check("attach/counts as resident", "english" in ra.loaded, True)
|
|
264
|
+
check("attach/raises max_loaded to hold it", ra.max_loaded >= 1, True)
|
|
265
|
+
# attaching then loading another must not evict the attached one
|
|
266
|
+
ra.max_loaded = max(ra.max_loaded, 2)
|
|
267
|
+
_load_stub(ra, "multilingual")
|
|
268
|
+
check("attach/survives a later load", sorted(ra.loaded), ["english", "multilingual"])
|
|
269
|
+
check("attach/still the same object", ra._agents["english"] is sentinel, True)
|
|
270
|
+
check("attach/accepts aliases", stubbed_router(1).attach("en", _Stub("x")) is not None, True)
|
|
271
|
+
|
|
272
|
+
|
|
258
273
|
# --------------------------------------------------------------------- report
|
|
259
274
|
print("\n%d passed, %d failed" % (len(PASS), len(FAIL)))
|
|
260
275
|
for f in FAIL:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|