pebeto commited on
Commit
b6e1a49
·
verified ·
1 Parent(s): 1ebfac9

NVFP4 (GPTQ, W4A4) build of convaiinnovations/laya

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ multilingual/tokenizer/tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: convaiinnovations/laya
4
+ base_model_relation: quantized
5
+ pipeline_tag: text-classification
6
+ tags:
7
+ - laya
8
+ - nvfp4
9
+ - fp4
10
+ - w4a4
11
+ - compressed-tensors
12
+ - llm-compressor
13
+ - classification
14
+ - routing
15
+ - guardrails
16
+ - moderation
17
+ ---
18
+
19
+ # laya, NVFP4 (W4A4)
20
+
21
+ This repo holds the three [laya](https://huggingface.co/convaiinnovations/laya) checkpoints with both the weights and the input activations of their encoder's linear layers in NVFP4: FP4 (E2M1) values with one FP8 scale per group of 16 and one FP32 scale per tensor. On Blackwell GPUs those layers multiply on FP4 tensor cores. The files are the same size as the [NVFP4A16 build](https://huggingface.co/hoja-solutions/laya-NVFP4A16), 349 MB for the English and typed-decisions checkpoints and 485 MB for the multilingual one, and the model takes about a third of the GPU memory of the original.
22
+
23
+ The NVFP4A16 build, which keeps activations in bf16, runs at the original's speed, stays closer to the original's answers and gives the same answer for the same question every time. Use this W4A4 build only when GPU memory is what limits you. The [original card](https://huggingface.co/convaiinnovations/laya) explains the model itself.
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ import sys
29
+ from huggingface_hub import snapshot_download
30
+
31
+ path = snapshot_download("hoja-solutions/laya-NVFP4")
32
+ sys.path.insert(0, path)
33
+ import laya_nvfp4
34
+
35
+ agent = laya_nvfp4.load(path, device="cuda") # English, at the repo root
36
+ agent_ml = laya_nvfp4.load(path + "/multilingual", device="cuda")
37
+ agent_td = laya_nvfp4.load(path + "/typed-decisions", device="cuda")
38
+ ```
39
+
40
+ `laya_nvfp4.load` returns a regular `laya.Agent`, so `predict` and the rest of laya's API work, and `Router.attach` adds it to laya's router. Each quantized layer quantizes its input on every call. On Blackwell GPUs (sm_100, sm_120) the multiplication runs on FP4 tensor cores through `torch._scaled_mm`, and `torch.compile` fuses the input quantization. On any other device the loader emulates the same arithmetic in fp32, which is correct but slow. Tested with laya 0.4.6 and torch 2.14.
41
+
42
+ ## Quality
43
+
44
+ The tests are the same as for the NVFP4A16 build: 8,899 questions rebuilt from the laya authors' benchmark scripts (ten application tasks, the typed-decisions test split, MASSIVE intent in 14 languages and XNLI in 15), each checkpoint compared with the original in bf16 on an RTX 5090, accuracy with the shipped temperatures.
45
+
46
+ | Accuracy, original → this repo | 10 app tasks | typed-decisions | MASSIVE | XNLI |
47
+ |---|---|---|---|---|
48
+ | English | 0.696 → 0.695 | 0.359 → 0.357 | 0.356 → 0.320 | 0.530 → 0.526 |
49
+ | typed-decisions | 0.704 → 0.701 | 0.767 → 0.766 | 0.348 → 0.319 | 0.541 → 0.525 |
50
+ | multilingual | 0.694 → 0.686 | 0.353 → 0.346 | 0.506 → 0.459 | 0.718 → 0.701 |
51
+
52
+ | Same answer as the original | 10 app tasks | typed-decisions | MASSIVE | XNLI | all questions | when the original is ≥ 90% sure |
53
+ |---|---|---|---|---|---|---|
54
+ | English | 91.9% | 88.8% | 61.7% | 76.3% | 83.8% | 92.5% |
55
+ | typed-decisions | 92.5% | 93.7% | 62.8% | 81.3% | 86.2% | 97.6% |
56
+ | multilingual | 90.6% | 79.5% | 72.4% | 87.0% | 84.6% | 95.4% |
57
+ | NVFP4A16, for comparison | 94.7 to 95.5% | 88.1 to 97.0% | 70.9 to 82.4% | 82.0 to 90.7% | 89.1 to 90.7% | 96.3 to 99.1% |
58
+ | original, run twice | 99.6 to 99.9% | 99.4 to 99.5% | 96.5 to 99.2% | 99.2 to 99.7% | 99.0 to 99.6% | 100% |
59
+
60
+ Accuracy holds better than agreement because the changed answers go both ways, but this build changes 14 to 16% of the answers, including some the original gives with 90% probability or more.
61
+
62
+ ### The same question can get different answers
63
+
64
+ Every quantized layer rounds its input to FP4 again. A tiny difference upstream, such as a different floating-point summation order, moves some values across an FP4 rounding boundary, and a full FP4 step is a large error, so the difference grows from layer to layer instead of fading. In practice:
65
+
66
+ - Batching changes answers. The English checkpoint answered all 8,899 questions twice, with the questions grouped into different batches. 6.1% of its answers differed between the two runs. The original and the NVFP4A16 build change about 1% this way.
67
+ - Hardware changes answers. The emulation path, which repeats the same arithmetic in fp32, agrees with the original about as often as the GPU path (84.0% against 83.8% of all questions), but on different questions: the two runs of this build agree with each other on only 84%.
68
+
69
+ If you need repeatable decisions, use the NVFP4A16 build.
70
+
71
+ ## Speed
72
+
73
+ RTX 5090, the original card's latency setup (one support ticket, p50 over 20 calls):
74
+
75
+ | | 1 question | 10 questions | 50 questions | 4,000 questions, batched | GPU memory for weights |
76
+ |---|---|---|---|---|---|
77
+ | English, original | 10.9 ms | 18.0 ms | 74.9 ms | 775 per second | 1,607 MB |
78
+ | English, this repo | 23.8 ms | 26.6 ms | 72.1 ms | 852 per second | 489 MB |
79
+ | multilingual, original | 8.8 ms | 10.4 ms | 34.3 ms | 1,577 per second | 1,247 MB |
80
+ | multilingual, this repo | 20.4 ms | 21.6 ms | 32.7 ms | 1,507 per second | 868 MB |
81
+
82
+ An FP4 matrix multiply is about five times faster than bf16 at batch sizes of a few thousand tokens. Matrix multiplies are a small part of laya's run time, though, and every layer pays for quantizing its input. So small requests take about twice as long as with the original, and large batches gain about 10% at best. The memory saving is real: the English checkpoint needs a third of the original's GPU memory, because laya otherwise keeps its weights in fp32.
83
+
84
+ ## How it was made
85
+
86
+ GPTQ with llm-compressor 0.14.0 (static activation ordering, block size 128, dampening 0.01) and the NVFP4 scheme on the four linear layers of every encoder block. The scales for each group of 16 input values are computed at run time, under a per-tensor scale fixed from the calibration data. Embeddings, norms and the decision head stay in fp16. The calibration set and the NVFP4A16 variants are described on the [NVFP4A16 card](https://huggingface.co/hoja-solutions/laya-NVFP4A16).
87
+
88
+ Plain rounding instead of GPTQ keeps the same answer on 81.3% (English), 85.0% (typed-decisions) and 82.5% (multilingual) of the questions, so GPTQ helps by 1 to 3 points.
89
+
90
+ ## Limits
91
+
92
+ - The FP4 tensor-core path needs a Blackwell GPU. Elsewhere the emulation gives the same quality at a fraction of the speed.
93
+ - Tested on an RTX 5090 with torch 2.14 and laya 0.4.6.
94
+ - `laya.load` and `Router` don't read these files by themselves; use `laya_nvfp4.load` and `Router.attach`.
95
+ - Everything on the original card's "Honest Limits" still applies.
96
+
97
+ ## Credits
98
+
99
+ laya and its three checkpoints are by [Convai Innovations](https://huggingface.co/convaiinnovations) ([GitHub](https://github.com/NandhaKishorM/laya)), released under Apache-2.0. They build on [ModernBERT-large](https://huggingface.co/answerdotai/ModernBERT-large) and [mmBERT-base](https://huggingface.co/jhu-clsp/mmBERT-base).
__pycache__/laya_nvfp4.cpython-312.pyc ADDED
Binary file (15.4 kB). View file
 
config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "laya",
3
+ "architectures": [
4
+ "LayaTypedDecisions"
5
+ ],
6
+ "_comment": "Root config for Hub download tracking. See README for how to load Laya."
7
+ }
encoder/config.json ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ModernBertForMaskedLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 50281,
8
+ "classifier_activation": "gelu",
9
+ "classifier_bias": false,
10
+ "classifier_dropout": 0.0,
11
+ "classifier_pooling": "mean",
12
+ "cls_token_id": 50281,
13
+ "decoder_bias": true,
14
+ "deterministic_flash_attn": false,
15
+ "dtype": "float32",
16
+ "embedding_dropout": 0.0,
17
+ "eos_token_id": 50282,
18
+ "global_attn_every_n_layers": 3,
19
+ "gradient_checkpointing": false,
20
+ "hidden_activation": "gelu",
21
+ "hidden_size": 1024,
22
+ "initializer_cutoff_factor": 2.0,
23
+ "initializer_range": 0.02,
24
+ "intermediate_size": 2624,
25
+ "layer_norm_eps": 1e-05,
26
+ "layer_types": [
27
+ "full_attention",
28
+ "sliding_attention",
29
+ "sliding_attention",
30
+ "full_attention",
31
+ "sliding_attention",
32
+ "sliding_attention",
33
+ "full_attention",
34
+ "sliding_attention",
35
+ "sliding_attention",
36
+ "full_attention",
37
+ "sliding_attention",
38
+ "sliding_attention",
39
+ "full_attention",
40
+ "sliding_attention",
41
+ "sliding_attention",
42
+ "full_attention",
43
+ "sliding_attention",
44
+ "sliding_attention",
45
+ "full_attention",
46
+ "sliding_attention",
47
+ "sliding_attention",
48
+ "full_attention",
49
+ "sliding_attention",
50
+ "sliding_attention",
51
+ "full_attention",
52
+ "sliding_attention",
53
+ "sliding_attention",
54
+ "full_attention"
55
+ ],
56
+ "local_attention": 128,
57
+ "max_position_embeddings": 8192,
58
+ "mlp_bias": false,
59
+ "mlp_dropout": 0.0,
60
+ "model_type": "modernbert",
61
+ "norm_bias": false,
62
+ "norm_eps": 1e-05,
63
+ "num_attention_heads": 16,
64
+ "num_hidden_layers": 28,
65
+ "pad_token_id": 50283,
66
+ "position_embedding_type": "absolute",
67
+ "repad_logits_with_grad": false,
68
+ "rope_parameters": {
69
+ "full_attention": {
70
+ "rope_theta": 160000.0,
71
+ "rope_type": "default"
72
+ },
73
+ "sliding_attention": {
74
+ "rope_theta": 10000.0,
75
+ "rope_type": "default"
76
+ }
77
+ },
78
+ "sep_token_id": 50282,
79
+ "sparse_pred_ignore_index": -100,
80
+ "sparse_prediction": false,
81
+ "tie_word_embeddings": true,
82
+ "transformers_version": "5.0.0",
83
+ "vocab_size": 50368
84
+ }
laya_nvfp4.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load an NVFP4 laya checkpoint (compressed-tensors format) into a regular `laya.Agent`.
2
+
3
+ import laya_nvfp4
4
+ agent = laya_nvfp4.load("path/to/checkpoint", device="cuda")
5
+ agent.predict(state, questions) # every laya API works as usual
6
+
7
+ The encoder's linear layers are stored as FP4 (E2M1) values with one FP8 (E4M3) scale per 16
8
+ weights and one FP32 scale per tensor. What happens at load time depends on the checkpoint:
9
+
10
+ * NVFP4A16 (weights only): weights are unpacked to dense once, and the model runs exactly like
11
+ the original laya, in bf16 on GPU or fp32 on CPU.
12
+ * NVFP4 (weights and activations): each quantized layer also quantizes its input to NVFP4 on the
13
+ fly (one scale per 16 values, under a fixed per-tensor scale from calibration). On Blackwell
14
+ GPUs (sm_100, sm_120) the product runs on FP4 tensor cores through `torch._scaled_mm`; on any
15
+ other device the same arithmetic is emulated in higher precision, so answers match.
16
+
17
+ Needs only torch and laya (tested with torch 2.14 and laya 0.4.6). The FP4 path also uses
18
+ torch.compile to fuse the activation quantization, and falls back to eager PyTorch without it.
19
+ """
20
+
21
+ import json
22
+ import os
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+
27
+ E2M1 = (0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0) # FP4 E2M1 magnitudes, indexed by the low 3 bits
28
+ FP8_MAX, FP4_MAX, GROUP = 448.0, 6.0, 16
29
+
30
+
31
+ # ----------------------------------------------------------------------------- FP4 packing
32
+ def unpack_fp4(packed: torch.Tensor, dtype=torch.float32) -> torch.Tensor:
33
+ """uint8 [..., K/2] -> values [..., K]. The low nibble holds the first value of each pair."""
34
+ codes = torch.stack((packed & 0x0F, packed >> 4), dim=-1).flatten(-2)
35
+ mag = torch.tensor(E2M1, dtype=dtype, device=packed.device)[(codes & 0x07).long()]
36
+ return torch.where((codes & 0x08).bool(), -mag, mag)
37
+
38
+
39
+ def _fp4_codes(x: torch.Tensor) -> torch.Tensor:
40
+ """Round to the nearest E2M1 value (x already scaled into [-6, 6]) -> 4-bit codes. Ties go to
41
+ the even code, like the hardware conversion (cvt.rn.satfinite.e2m1x2)."""
42
+ a = x.abs()
43
+ bounds = torch.tensor((0.25, 0.75, 1.25, 1.75, 2.5, 3.5, 5.0), dtype=x.dtype, device=x.device)
44
+ mag = torch.bucketize(a, bounds) # 0..7, a tie lands on the lower code
45
+ mag = mag + ((a == 0.75) | (a == 1.75) | (a == 3.5)).to(mag.dtype) # those ties belong to the upper, even code
46
+ return (mag | ((x < 0).to(mag.dtype) << 3)).to(torch.uint8)
47
+
48
+
49
+ def dequantize_weight(packed, scale, global_scale, dtype=torch.float32):
50
+ """W = fp4 * (fp8 group scale / global scale), as compressed-tensors defines it."""
51
+ w = unpack_fp4(packed, torch.float32)
52
+ s = (scale.float() / global_scale.float()).repeat_interleave(GROUP, dim=-1)
53
+ return (w * s).to(dtype)
54
+
55
+
56
+ def quantize_activation(x: torch.Tensor, global_scale: torch.Tensor):
57
+ """Dynamic NVFP4: per group of 16, scale = fp8(global_scale * amax / 6), q = fp4(x / (scale / global_scale)).
58
+ Returns (packed uint8 [M, K/2], fp8 scales [M, K/16])."""
59
+ m, k = x.shape
60
+ g = x.float().view(m, k // GROUP, GROUP)
61
+ amax = g.abs().amax(-1, keepdim=True)
62
+ scale = (global_scale * amax / FP4_MAX).clamp(max=FP8_MAX).to(torch.float8_e4m3fn)
63
+ s = scale.float()
64
+ s = torch.where(s == 0, torch.ones_like(s), s)
65
+ codes = _fp4_codes((g * global_scale / s).clamp(-FP4_MAX, FP4_MAX)).view(m, k)
66
+ return codes[:, 0::2] | (codes[:, 1::2] << 4), scale.view(m, k // GROUP)
67
+
68
+
69
+ def to_blocked(scale: torch.Tensor) -> torch.Tensor:
70
+ """Row-major [rows, cols] block scales -> the 128x4 tiled layout cuBLAS reads for FP4 GEMMs."""
71
+ rows, cols = scale.shape
72
+ nr, nc = -(-rows // 128), -(-cols // 4)
73
+ padded = scale.new_zeros((nr * 128, nc * 4))
74
+ padded[:rows, :cols] = scale
75
+ blocks = padded.view(nr, 128, nc, 4).permute(0, 2, 1, 3)
76
+ return blocks.reshape(-1, 4, 32, 4).transpose(1, 2).reshape(-1, 32, 16).flatten()
77
+
78
+
79
+ def fp4_kernels_available(device) -> bool:
80
+ device = torch.device(device)
81
+ return (device.type == "cuda" and torch.cuda.get_device_capability(device)[0] in (10, 12)
82
+ and hasattr(torch, "float4_e2m1fn_x2"))
83
+
84
+
85
+ def _quantize_blocked(x, global_scale):
86
+ q, s = quantize_activation(x, global_scale)
87
+ return q, to_blocked(s)
88
+
89
+
90
+ _compiled = None
91
+
92
+
93
+ def quantize_blocked(x, global_scale):
94
+ """quantize_activation + to_blocked, fused by torch.compile on CUDA (falls back to eager if
95
+ compilation is unavailable). Eager, these are a dozen small kernels per layer."""
96
+ global _compiled
97
+ if _compiled is None:
98
+ try:
99
+ _compiled = torch.compile(_quantize_blocked, dynamic=True)
100
+ except Exception:
101
+ _compiled = _quantize_blocked
102
+ try:
103
+ return _compiled(x, global_scale)
104
+ except Exception:
105
+ _compiled = _quantize_blocked
106
+ return _quantize_blocked(x, global_scale)
107
+
108
+
109
+ # ----------------------------------------------------------------------------- the layer
110
+ class NVFP4Linear(nn.Module):
111
+ """y = x W^T with W stored in NVFP4 and x quantized to NVFP4 on every call (no bias)."""
112
+
113
+ def __init__(self, packed, scale, weight_global_scale, input_global_scale, use_kernel: bool):
114
+ super().__init__()
115
+ self.out_features, self.in_features = packed.shape[0], packed.shape[1] * 2
116
+ self.register_buffer("weight_packed", packed)
117
+ self.register_buffer("weight_scale", scale)
118
+ self.register_buffer("weight_global_scale", weight_global_scale.float().reshape(()))
119
+ self.register_buffer("input_global_scale", input_global_scale.float().reshape(()))
120
+ self.use_kernel = use_kernel
121
+ self._dense = None # dequantized weight, for the emulation path
122
+ self._blocked = None # weight scales in cuBLAS layout, for the kernel path
123
+ self._alpha = None # 1 / (input global scale * weight global scale)
124
+
125
+ def extra_repr(self):
126
+ return "in=%d, out=%d, %s" % (self.in_features, self.out_features, "fp4 kernel" if self.use_kernel else "emulated")
127
+
128
+ def _apply(self, fn, *args, **kwargs): # .to()/.cuda() invalidate the caches
129
+ self._dense = self._blocked = None
130
+ return super()._apply(fn, *args, **kwargs)
131
+
132
+ def forward(self, x):
133
+ shape = x.shape
134
+ x2 = x.reshape(-1, self.in_features)
135
+ dtype = x.dtype if x.dtype in (torch.bfloat16, torch.float16) else torch.float32
136
+ if torch.is_autocast_enabled(x.device.type):
137
+ dtype = torch.get_autocast_dtype(x.device.type)
138
+ if self.use_kernel:
139
+ if self._blocked is None:
140
+ self._blocked = to_blocked(self.weight_scale)
141
+ self._alpha = (1.0 / (self.input_global_scale * self.weight_global_scale)).float()
142
+ q, sb = quantize_blocked(x2, self.input_global_scale)
143
+ y = torch._scaled_mm(q.view(torch.float4_e2m1fn_x2), self.weight_packed.view(torch.float4_e2m1fn_x2).t(),
144
+ sb, self._blocked, out_dtype=torch.bfloat16)
145
+ y = (y * self._alpha).to(dtype)
146
+ else:
147
+ if self._dense is None:
148
+ self._dense = dequantize_weight(self.weight_packed, self.weight_scale, self.weight_global_scale)
149
+ q, s = quantize_activation(x2, self.input_global_scale)
150
+ xq = dequantize_weight(q, s, self.input_global_scale)
151
+ y = (xq @ self._dense.t()).to(dtype)
152
+ return y.reshape(*shape[:-1], self.out_features)
153
+
154
+
155
+ # ----------------------------------------------------------------------------- loading
156
+ def _read(path):
157
+ from safetensors.torch import load_file
158
+ return load_file(os.path.join(path, "model.safetensors"))
159
+
160
+
161
+ def dense_state_dict(raw):
162
+ """Compressed state dict -> the dense state dict laya's DecisionModel expects (fp16)."""
163
+ out = {}
164
+ for k, v in raw.items():
165
+ if k.endswith(".weight_packed"):
166
+ p = k[: -len(".weight_packed")]
167
+ out[p + ".weight"] = dequantize_weight(v, raw[p + ".weight_scale"], raw[p + ".weight_global_scale"],
168
+ torch.float16)
169
+ elif not k.endswith((".weight_scale", ".weight_global_scale", ".input_global_scale")):
170
+ out[k] = v
171
+ return out
172
+
173
+
174
+ def load(path, device=None, use_kernel=None, **laya_kwargs):
175
+ """A `laya.Agent` for an NVFP4 checkpoint directory. `use_kernel=None` picks the FP4 tensor-core
176
+ path when the GPU has one; False forces the emulation (same answers, any device)."""
177
+ import laya
178
+ import safetensors.torch
179
+
180
+ raw = _read(path)
181
+ with open(os.path.join(path, "rl_agent_config.json")) as f:
182
+ qcfg = json.load(f).get("quantization_config", {})
183
+ # laya.Agent reads model.safetensors with safetensors.torch.load_file (imported inside its
184
+ # constructor); hand it the dense weights instead, then swap the quantized layers in below.
185
+ real_load_file = safetensors.torch.load_file
186
+ safetensors.torch.load_file = lambda *_args, **_kwargs: dense_state_dict(raw)
187
+ try:
188
+ ag = laya.load(path, device=device, backend="eager", **laya_kwargs)
189
+ finally:
190
+ safetensors.torch.load_file = real_load_file
191
+ ag.quantization_config = qcfg
192
+ act_quant = any(k.endswith(".input_global_scale") for k in raw)
193
+ if act_quant:
194
+ kernel = fp4_kernels_available(ag.device) if use_kernel is None else bool(use_kernel)
195
+ dev = ag.device
196
+ for name, _ in list(ag.model.named_modules()):
197
+ if name + ".weight_packed" not in raw:
198
+ continue
199
+ parent = ag.model.get_submodule(name.rsplit(".", 1)[0])
200
+ new = NVFP4Linear(raw[name + ".weight_packed"], raw[name + ".weight_scale"],
201
+ raw[name + ".weight_global_scale"], raw[name + ".input_global_scale"], kernel).to(dev)
202
+ setattr(parent, name.rsplit(".", 1)[1], new)
203
+ ag.model.eval()
204
+ return ag
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3566646ec0d40f24342f5f74fbae04fc150909db4b0f5cc75d73d5c0efbac89d
3
+ size 349372970
multilingual/encoder/config.json ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ModernBertForMaskedLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 2,
8
+ "classifier_activation": "gelu",
9
+ "classifier_bias": false,
10
+ "classifier_dropout": 0.0,
11
+ "classifier_pooling": "mean",
12
+ "cls_token_id": 1,
13
+ "decoder_bias": true,
14
+ "deterministic_flash_attn": false,
15
+ "dtype": "float32",
16
+ "embedding_dropout": 0.0,
17
+ "eos_token_id": 1,
18
+ "global_attn_every_n_layers": 3,
19
+ "gradient_checkpointing": false,
20
+ "hidden_activation": "gelu",
21
+ "hidden_size": 768,
22
+ "initializer_cutoff_factor": 2.0,
23
+ "initializer_range": 0.02,
24
+ "intermediate_size": 1152,
25
+ "layer_norm_eps": 1e-05,
26
+ "layer_types": [
27
+ "full_attention",
28
+ "sliding_attention",
29
+ "sliding_attention",
30
+ "full_attention",
31
+ "sliding_attention",
32
+ "sliding_attention",
33
+ "full_attention",
34
+ "sliding_attention",
35
+ "sliding_attention",
36
+ "full_attention",
37
+ "sliding_attention",
38
+ "sliding_attention",
39
+ "full_attention",
40
+ "sliding_attention",
41
+ "sliding_attention",
42
+ "full_attention",
43
+ "sliding_attention",
44
+ "sliding_attention",
45
+ "full_attention",
46
+ "sliding_attention",
47
+ "sliding_attention",
48
+ "full_attention"
49
+ ],
50
+ "local_attention": 128,
51
+ "mask_token_id": 4,
52
+ "max_position_embeddings": 8192,
53
+ "mlp_bias": false,
54
+ "mlp_dropout": 0.0,
55
+ "model_type": "modernbert",
56
+ "norm_bias": false,
57
+ "norm_eps": 1e-05,
58
+ "num_attention_heads": 12,
59
+ "num_hidden_layers": 22,
60
+ "pad_token_id": 0,
61
+ "position_embedding_type": "sans_pos",
62
+ "repad_logits_with_grad": false,
63
+ "rope_parameters": {
64
+ "full_attention": {
65
+ "rope_theta": 160000,
66
+ "rope_type": "default"
67
+ },
68
+ "sliding_attention": {
69
+ "rope_theta": 160000,
70
+ "rope_type": "default"
71
+ }
72
+ },
73
+ "sep_token_id": 1,
74
+ "sparse_pred_ignore_index": -100,
75
+ "sparse_prediction": false,
76
+ "tie_word_embeddings": true,
77
+ "transformers_version": "5.0.0",
78
+ "vocab_size": 256000
79
+ }
multilingual/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:824e3914dadb20671ff3a4b7383eb4d392b7020d09e160d1f1018c007c18d12b
3
+ size 485312346
multilingual/rl_agent_config.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "encoder": "jhu-clsp/mmBERT-base",
3
+ "head_layers": 2,
4
+ "max_len": 1024,
5
+ "head_max_len": 256,
6
+ "max_prefixes": 6,
7
+ "act_costs": {
8
+ "escalate": 0.5
9
+ },
10
+ "cost_wrong_act": 3.0,
11
+ "amp_dtype": "bf16",
12
+ "model_name": "rl-agent",
13
+ "temperature": [
14
+ 1.0,
15
+ 1.0,
16
+ 1.0
17
+ ],
18
+ "temperature_by_options": {},
19
+ "training": {
20
+ "updates": 15987,
21
+ "epochs_completed": 4,
22
+ "hours": 4.97,
23
+ "world_size": 1,
24
+ "fine_tuned_from_checkpoint": false
25
+ },
26
+ "quantization_config": {
27
+ "config_groups": {
28
+ "group_0": {
29
+ "targets": [
30
+ "re:.*encoder\\.layers\\.\\d+\\.attn\\.(Wqkv|Wo)$",
31
+ "re:.*encoder\\.layers\\.\\d+\\.mlp\\.(Wi|Wo)$"
32
+ ],
33
+ "weights": {
34
+ "num_bits": 4,
35
+ "type": "float",
36
+ "symmetric": true,
37
+ "group_size": 16,
38
+ "strategy": "tensor_group",
39
+ "dynamic": false,
40
+ "actorder": "static",
41
+ "scale_dtype": "torch.float8_e4m3fn",
42
+ "zp_dtype": null,
43
+ "observer": "memoryless_minmax",
44
+ "observer_kwargs": {}
45
+ },
46
+ "input_activations": {
47
+ "num_bits": 4,
48
+ "type": "float",
49
+ "symmetric": true,
50
+ "group_size": 16,
51
+ "strategy": "tensor_group",
52
+ "dynamic": "local",
53
+ "scale_dtype": "torch.float8_e4m3fn",
54
+ "zp_dtype": null,
55
+ "observer": "static_minmax",
56
+ "observer_kwargs": {}
57
+ },
58
+ "format": "nvfp4-pack-quantized"
59
+ }
60
+ },
61
+ "quant_method": "compressed-tensors",
62
+ "format": "nvfp4-pack-quantized",
63
+ "quantization_status": "compressed",
64
+ "ignore": [
65
+ "head.layers.0.linear1",
66
+ "head.layers.0.linear2",
67
+ "head.layers.1.linear1",
68
+ "head.layers.1.linear2",
69
+ "scorer.1",
70
+ "scorer.3",
71
+ "act_head.0",
72
+ "act_head.2"
73
+ ]
74
+ }
75
+ }
multilingual/tokenizer/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:609d8f4c067cd3950f88594c5a802616cea245823836ef5848ee4fc40aab5b6f
3
+ size 34363188
multilingual/tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "<bos>",
3
+ "clean_up_tokenization_spaces": false,
4
+ "cls_token": "<bos>",
5
+ "eos_token": "<eos>",
6
+ "extra_special_tokens": {
7
+ "extra_0": "<start_of_turn>",
8
+ "extra_1": "<end_of_turn>"
9
+ },
10
+ "mask_token": "<mask>",
11
+ "model_input_names": [
12
+ "input_ids",
13
+ "attention_mask"
14
+ ],
15
+ "model_max_length": 8192,
16
+ "pad_token": "<pad>",
17
+ "padding_side": "right",
18
+ "sep_token": "<eos>",
19
+ "spaces_between_special_tokens": false,
20
+ "tokenizer_class": "PreTrainedTokenizerFast",
21
+ "unk_token": "<unk>"
22
+ }
rl_agent_config.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "encoder": "answerdotai/ModernBERT-large",
3
+ "head_layers": 2,
4
+ "max_len": 512,
5
+ "head_max_len": 192,
6
+ "max_prefixes": 6,
7
+ "act_costs": {
8
+ "escalate": 0.5
9
+ },
10
+ "cost_wrong_act": 3.0,
11
+ "amp_dtype": "bf16",
12
+ "model_name": "rl-agent",
13
+ "temperature": [
14
+ 1.6369030475616455,
15
+ 1.2514300346374512,
16
+ 1.983399510383606
17
+ ],
18
+ "temperature_by_options": {
19
+ "choice:3-5": 1.7601518630981445,
20
+ "choice:6-10": 1.0000158548355103,
21
+ "score:3-5": 1.2514300346374512,
22
+ "noul:2": 1.983399510383606,
23
+ "choice:11+": 0.10058280825614929,
24
+ "choice:2": 1.9063563346862793
25
+ },
26
+ "training": {
27
+ "updates": 7313,
28
+ "epochs_completed": 1,
29
+ "hours": 1.96,
30
+ "world_size": 1,
31
+ "fine_tuned_from_checkpoint": true
32
+ },
33
+ "quantization_config": {
34
+ "config_groups": {
35
+ "group_0": {
36
+ "targets": [
37
+ "re:.*encoder\\.layers\\.\\d+\\.attn\\.(Wqkv|Wo)$",
38
+ "re:.*encoder\\.layers\\.\\d+\\.mlp\\.(Wi|Wo)$"
39
+ ],
40
+ "weights": {
41
+ "num_bits": 4,
42
+ "type": "float",
43
+ "symmetric": true,
44
+ "group_size": 16,
45
+ "strategy": "tensor_group",
46
+ "dynamic": false,
47
+ "actorder": "static",
48
+ "scale_dtype": "torch.float8_e4m3fn",
49
+ "zp_dtype": null,
50
+ "observer": "memoryless_minmax",
51
+ "observer_kwargs": {}
52
+ },
53
+ "input_activations": {
54
+ "num_bits": 4,
55
+ "type": "float",
56
+ "symmetric": true,
57
+ "group_size": 16,
58
+ "strategy": "tensor_group",
59
+ "dynamic": "local",
60
+ "scale_dtype": "torch.float8_e4m3fn",
61
+ "zp_dtype": null,
62
+ "observer": "static_minmax",
63
+ "observer_kwargs": {}
64
+ },
65
+ "format": "nvfp4-pack-quantized"
66
+ }
67
+ },
68
+ "quant_method": "compressed-tensors",
69
+ "format": "nvfp4-pack-quantized",
70
+ "quantization_status": "compressed",
71
+ "ignore": [
72
+ "head.layers.0.linear1",
73
+ "head.layers.0.linear2",
74
+ "head.layers.1.linear1",
75
+ "head.layers.1.linear2",
76
+ "scorer.1",
77
+ "scorer.3",
78
+ "act_head.0",
79
+ "act_head.2"
80
+ ]
81
+ }
82
+ }
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,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "clean_up_tokenization_spaces": true,
3
+ "cls_token": "[CLS]",
4
+ "mask_token": "[MASK]",
5
+ "model_input_names": [
6
+ "input_ids",
7
+ "attention_mask"
8
+ ],
9
+ "model_max_length": 8192,
10
+ "pad_token": "[PAD]",
11
+ "sep_token": "[SEP]",
12
+ "tokenizer_class": "PreTrainedTokenizerFast",
13
+ "unk_token": "[UNK]"
14
+ }
typed-decisions/encoder/config.json ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ModernBertForMaskedLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 50281,
8
+ "classifier_activation": "gelu",
9
+ "classifier_bias": false,
10
+ "classifier_dropout": 0.0,
11
+ "classifier_pooling": "mean",
12
+ "cls_token_id": 50281,
13
+ "decoder_bias": true,
14
+ "deterministic_flash_attn": false,
15
+ "dtype": "float32",
16
+ "embedding_dropout": 0.0,
17
+ "eos_token_id": 50282,
18
+ "global_attn_every_n_layers": 3,
19
+ "gradient_checkpointing": false,
20
+ "hidden_activation": "gelu",
21
+ "hidden_size": 1024,
22
+ "initializer_cutoff_factor": 2.0,
23
+ "initializer_range": 0.02,
24
+ "intermediate_size": 2624,
25
+ "layer_norm_eps": 1e-05,
26
+ "layer_types": [
27
+ "full_attention",
28
+ "sliding_attention",
29
+ "sliding_attention",
30
+ "full_attention",
31
+ "sliding_attention",
32
+ "sliding_attention",
33
+ "full_attention",
34
+ "sliding_attention",
35
+ "sliding_attention",
36
+ "full_attention",
37
+ "sliding_attention",
38
+ "sliding_attention",
39
+ "full_attention",
40
+ "sliding_attention",
41
+ "sliding_attention",
42
+ "full_attention",
43
+ "sliding_attention",
44
+ "sliding_attention",
45
+ "full_attention",
46
+ "sliding_attention",
47
+ "sliding_attention",
48
+ "full_attention",
49
+ "sliding_attention",
50
+ "sliding_attention",
51
+ "full_attention",
52
+ "sliding_attention",
53
+ "sliding_attention",
54
+ "full_attention"
55
+ ],
56
+ "local_attention": 128,
57
+ "max_position_embeddings": 8192,
58
+ "mlp_bias": false,
59
+ "mlp_dropout": 0.0,
60
+ "model_type": "modernbert",
61
+ "norm_bias": false,
62
+ "norm_eps": 1e-05,
63
+ "num_attention_heads": 16,
64
+ "num_hidden_layers": 28,
65
+ "pad_token_id": 50283,
66
+ "position_embedding_type": "absolute",
67
+ "repad_logits_with_grad": false,
68
+ "rope_parameters": {
69
+ "full_attention": {
70
+ "rope_theta": 160000.0,
71
+ "rope_type": "default"
72
+ },
73
+ "sliding_attention": {
74
+ "rope_theta": 10000.0,
75
+ "rope_type": "default"
76
+ }
77
+ },
78
+ "sep_token_id": 50282,
79
+ "sparse_pred_ignore_index": -100,
80
+ "sparse_prediction": false,
81
+ "tie_word_embeddings": true,
82
+ "transformers_version": "5.17.0",
83
+ "vocab_size": 50368
84
+ }
typed-decisions/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:28c7ac1a04b347b19e3fe6883843a19a2af6c0cf8a12820f4382c6ef1c7bdcad
3
+ size 349372970
typed-decisions/rl_agent_config.json ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "encoder": "answerdotai/ModernBERT-large",
3
+ "head_layers": 2,
4
+ "max_len": 1024,
5
+ "head_max_len": 256,
6
+ "max_prefixes": 6,
7
+ "act_costs": {
8
+ "escalate": 0.5
9
+ },
10
+ "cost_wrong_act": 3.0,
11
+ "amp_dtype": "bf16",
12
+ "model_name": "laya-typed-decisions",
13
+ "temperature": [
14
+ 1.0148024559020996,
15
+ 1.0374259948730469,
16
+ 1.0575125217437744
17
+ ],
18
+ "temperature_by_options": {
19
+ "choice:3-5": 1.7601518630981445,
20
+ "choice:6-10": 1.0000158548355103,
21
+ "score:3-5": 1.2514300346374512,
22
+ "noul:2": 1.983399510383606,
23
+ "choice:11+": 0.10058280825614929,
24
+ "choice:2": 1.9063563346862793
25
+ },
26
+ "training": {
27
+ "updates": 7313,
28
+ "epochs_completed": 1,
29
+ "hours": 1.96,
30
+ "world_size": 1,
31
+ "fine_tuned_from_checkpoint": true
32
+ },
33
+ "gradient_checkpointing": true,
34
+ "max_tokens_per_batch": 4096,
35
+ "fine_tuned": true,
36
+ "quantization_config": {
37
+ "config_groups": {
38
+ "group_0": {
39
+ "targets": [
40
+ "re:.*encoder\\.layers\\.\\d+\\.attn\\.(Wqkv|Wo)$",
41
+ "re:.*encoder\\.layers\\.\\d+\\.mlp\\.(Wi|Wo)$"
42
+ ],
43
+ "weights": {
44
+ "num_bits": 4,
45
+ "type": "float",
46
+ "symmetric": true,
47
+ "group_size": 16,
48
+ "strategy": "tensor_group",
49
+ "dynamic": false,
50
+ "actorder": "static",
51
+ "scale_dtype": "torch.float8_e4m3fn",
52
+ "zp_dtype": null,
53
+ "observer": "memoryless_minmax",
54
+ "observer_kwargs": {}
55
+ },
56
+ "input_activations": {
57
+ "num_bits": 4,
58
+ "type": "float",
59
+ "symmetric": true,
60
+ "group_size": 16,
61
+ "strategy": "tensor_group",
62
+ "dynamic": "local",
63
+ "scale_dtype": "torch.float8_e4m3fn",
64
+ "zp_dtype": null,
65
+ "observer": "static_minmax",
66
+ "observer_kwargs": {}
67
+ },
68
+ "format": "nvfp4-pack-quantized"
69
+ }
70
+ },
71
+ "quant_method": "compressed-tensors",
72
+ "format": "nvfp4-pack-quantized",
73
+ "quantization_status": "compressed",
74
+ "ignore": [
75
+ "head.layers.0.linear1",
76
+ "head.layers.0.linear2",
77
+ "head.layers.1.linear1",
78
+ "head.layers.1.linear2",
79
+ "scorer.1",
80
+ "scorer.3",
81
+ "act_head.0",
82
+ "act_head.2"
83
+ ]
84
+ }
85
+ }
typed-decisions/tokenizer/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
typed-decisions/tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "clean_up_tokenization_spaces": true,
3
+ "cls_token": "[CLS]",
4
+ "local_files_only": false,
5
+ "mask_token": "[MASK]",
6
+ "model_input_names": [
7
+ "input_ids",
8
+ "attention_mask"
9
+ ],
10
+ "model_max_length": 8192,
11
+ "pad_token": "[PAD]",
12
+ "sep_token": "[SEP]",
13
+ "tokenizer_class": "PreTrainedTokenizerFast",
14
+ "unk_token": "[UNK]"
15
+ }