File size: 35,504 Bytes
e82b8a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
"""Self-contained mixed global+sliding ModernBERT typed-decision runtime.

Copied as runtime.py into every published Laya V3.1 Hugging Face model.
Does not import TinyCeNN-LM, Laya, or other user project packages.
Depends only on torch, transformers==4.57.6, and safetensors via loader.

Global layers: V2.9 FastWindowMemory (W1024 default; quadratic at 512).
Sliding layers: V3.0 SlidingWindowFusion (W129 default; finite-window SDPA).
"""
from __future__ import annotations

"""Self-contained V2.9 FastWindowMemory typed-decision runtime.

This module is copied into each exported Hugging Face model repository.
It deliberately never imports TinyCeNN-LM, Laya, or local project modules.
Runtime dependencies: torch, transformers==4.57.6 and safetensors.
The Hugging Face Hub client is needed only to download a remote repository.
"""
import copy
import json
import math
from pathlib import Path
from typing import Any, Mapping

import torch
import torch.nn.functional as F
from torch import Tensor, nn
from transformers import AutoConfig, AutoModel, AutoTokenizer
from transformers.models.modernbert.modeling_modernbert import apply_rotary_pos_emb


def _apply_rope_compat(config):
    rope = getattr(config, "rope_parameters", None)
    if not isinstance(rope, dict):
        return
    flat = rope.get("rope_theta")
    for layer_type, attr in (
        ("full_attention", "global_rope_theta"),
        ("sliding_attention", "local_rope_theta"),
    ):
        values = rope.get(layer_type)
        theta = values.get("rope_theta") if isinstance(values, dict) else flat
        if theta is not None and hasattr(config, attr):
            setattr(config, attr, float(theta))


def _valid_tokens(attention_mask: Tensor | None, hidden_states: Tensor) -> Tensor:
    b, t = hidden_states.shape[:2]
    if attention_mask is None:
        return torch.ones((b, t), dtype=torch.bool, device=hidden_states.device)
    if attention_mask.ndim == 2:
        return attention_mask.bool()
    if attention_mask.ndim != 4:
        return torch.ones((b, t), dtype=torch.bool, device=hidden_states.device)
    allowed = (attention_mask if attention_mask.dtype == torch.bool
               else attention_mask > -1e4)
    valid_q = allowed.any(dim=-1).any(dim=1)
    valid_k = allowed.any(dim=-2).any(dim=1)
    valid = valid_q & valid_k
    return valid if valid.shape[-1] == t else torch.ones(
        (b, t), dtype=torch.bool, device=hidden_states.device
    )


def _depthwise_sequence_conv(x: Tensor, weight: Tensor) -> Tensor:
    b, h, t, d = x.shape
    kernel = int(weight.shape[-1])
    y = x.permute(0, 1, 3, 2).reshape(b, h * d, t)
    y = F.pad(y, ((kernel - 1) // 2, kernel - 1 - (kernel - 1) // 2))
    y = F.conv1d(y, weight, groups=h * d)
    return y.reshape(b, h, d, t).permute(0, 1, 3, 2).contiguous()


def _orthogonal_maps(heads: int, out_dim: int, in_dim: int) -> Tensor:
    maps = torch.empty(heads, out_dim, in_dim)
    for h in range(heads):
        nn.init.orthogonal_(maps[h])
    return maps


class FastWindowAttention(nn.Module):
    """State-dict-compatible V2.8 full/global attention replacement.

    The local path is quadratic when W >= 2*T-1. W1024 / MAX_LEN512 is
    a full-attention reconstruction control, not an attention-free speedup.
    """

    def __init__(self, original, *, local_window=1024, landmark_stride=16,
                 feature_dim=96, max_landmarks=32):
        super().__init__()
        self.config = original.config
        self.layer_idx = int(getattr(original, "layer_id", -1))
        self.hidden_size = int(self.config.hidden_size)
        self.num_heads = int(self.config.num_attention_heads)
        self.head_dim = int(getattr(original, "head_dim",
                                    self.hidden_size // self.num_heads))
        self.local_window = int(local_window)
        self.landmark_stride = int(landmark_stride)
        self.max_landmarks = int(max_landmarks)
        self.feature_dim = int(feature_dim)
        self.global_input_scale = float(self.head_dim ** -0.25)
        self.Wqkv = copy.deepcopy(original.Wqkv)
        self.Wo = copy.deepcopy(original.Wo)
        self.out_drop = copy.deepcopy(original.out_drop)
        self.rotary_emb = copy.deepcopy(original.rotary_emb)
        h, d = self.num_heads, self.head_dim
        init = _orthogonal_maps(h, self.feature_dim, d)
        self.global_wq = nn.Parameter(init.clone())
        self.global_wk = nn.Parameter(init.clone())
        self.global_log_scale = nn.Parameter(torch.zeros(h))
        # The original constructor initializes identity convolution weights.
        self.local_weight = nn.Parameter(torch.zeros(h * d, 1, 5))
        with torch.no_grad():
            self.local_weight[:, 0, 2] = 1.0
        self.route_logits = nn.Parameter(
            torch.tensor([1.0, 0.2, 0.2, -2.2]).repeat(h, 1)
        )
        self.route_query = nn.Parameter(torch.zeros(h, 4, d))
        self.local_log_temperature = nn.Parameter(torch.zeros(h))
        self.landmark_log_temperature = nn.Parameter(torch.zeros(h))
        self.conv_gain = nn.Parameter(torch.full((h,), -4.0))
        self.Wqkv.requires_grad_(False)
        self.rotary_emb.requires_grad_(False)
        self.last_core_output = None

    def _local_attention(self, q, k, v, valid):
        b, h, t, d = q.shape
        w = self.local_window
        if w < t:
            # Exact blocked centered-local attention inherited by the original
            # PDelta architecture. Never pad a short T up to huge W.
            left, right = (w - 1) // 2, w // 2
            blocks = (t + w - 1) // w
            tail = blocks * w - t
            qp = F.pad(q, (0, 0, 0, tail)).reshape(b, h, blocks, w, d)
            def windows(x):
                return F.pad(x, (0, 0, left, right + tail)).unfold(
                    2, 2 * w - 1, w
                ).transpose(-1, -2)
            kp, vp = windows(k), windows(v)
            key_valid = F.pad(valid, (left, right + tail)).unfold(
                1, 2 * w - 1, w
            )
            qi = torch.arange(w, device=q.device)[:, None]
            ki = torch.arange(2 * w - 1, device=q.device)[None, :]
            band = (ki >= qi) & (ki < qi + w)
            allowed = band[None, None, None] & key_valid[:, None, :, None, :]
            def batch_blocks(x):
                return x.transpose(1, 2).reshape(b * blocks, h, x.shape[-2], d)
            allowed = allowed.transpose(1, 2).reshape(
                b * blocks, 1, w, 2 * w - 1
            )
            z = F.scaled_dot_product_attention(
                batch_blocks(qp), batch_blocks(kp), batch_blocks(vp),
                attn_mask=allowed, dropout_p=0.0
            )
            z = z.reshape(b, blocks, h, w, d).transpose(1, 2)
            return z.reshape(b, h, blocks * w, d)[:, :, :t] * valid[:, None, :, None]
        allowed = valid[:, None, None, :]
        if w < 2 * t - 1:
            qi = torch.arange(t, device=q.device)[:, None]
            ki = torch.arange(t, device=q.device)[None, :]
            band = ((ki >= qi - (w - 1) // 2) &
                    (ki <= qi + w // 2))
            allowed = allowed & band[None, None]
        z = F.scaled_dot_product_attention(q, k, v, attn_mask=allowed, dropout_p=0.0)
        return z * valid[:, None, :, None].to(z.dtype)

    def _landmarks(self, q, k, v, valid):
        b, h, t, d = k.shape
        stride = max(self.landmark_stride, math.ceil(t / self.max_landmarks))
        blocks = math.ceil(t / stride)
        pad = blocks * stride - t
        mask = valid[:, None, :, None].to(k.dtype)
        packed = F.pad(valid, (0, pad), value=False).reshape(b, blocks, stride)
        counts = packed.sum(-1)
        divisor = counts.clamp_min(1).to(k.dtype)[:, None, :, None]
        kp = F.pad(k * mask, (0, 0, 0, pad)).reshape(b, h, blocks, stride, d)
        vp = F.pad(v * mask, (0, 0, 0, pad)).reshape(b, h, blocks, stride, d)
        pooled_k = kp.sum(3) / divisor
        pooled_v = vp.sum(3) / divisor
        allowed = (counts > 0)[:, None, None, :]
        temperature = self.landmark_log_temperature.clamp(-0.4, 0.4).exp()
        qs = q * temperature[None, :, None, None]
        return F.scaled_dot_product_attention(
            qs, pooled_k, pooled_v, attn_mask=allowed, dropout_p=0.0
        ) * mask

    def _global_linear(self, q, k, v, mask):
        scale = self.global_log_scale.to(q.dtype).clamp(-1.5, 1.5).exp()
        qz = torch.einsum("bhtd,hfd->bhtf", q, self.global_wq)
        kz = torch.einsum("bhtd,hfd->bhtf", k, self.global_wk)
        qz = qz * self.global_input_scale * scale[None, :, None, None]
        kz = kz * self.global_input_scale * scale[None, :, None, None]
        qf = torch.cat((torch.softmax(qz, -1), torch.softmax(-qz, -1)), -1).clamp_min(1e-6)
        kf = torch.cat((torch.softmax(kz, -1), torch.softmax(-kz, -1)), -1).clamp_min(1e-6) * mask
        vf = v * mask
        memory = torch.einsum("bhtf,bhtd->bhfd", kf, vf)
        normalizer = kf.sum(2)
        numerator = torch.einsum("bhtf,bhfd->bhtd", qf, memory)
        denominator = torch.einsum("bhtf,bhf->bht", qf, normalizer).unsqueeze(-1)
        return numerator / denominator.clamp_min(1e-6)

    def forward(self, hidden_states, position_embeddings=None,
                attention_mask=None, position_ids=None, sliding_window_mask=None,
                **kwargs):
        b, t, _ = hidden_states.shape
        qkv = self.Wqkv(hidden_states).reshape(
            b, t, 3, self.num_heads, self.head_dim
        )
        q, k, v = qkv.transpose(3, 1).unbind(dim=2)
        if position_embeddings is None:
            cos, sin = self.rotary_emb(qkv, position_ids=position_ids)
        else:
            cos, sin = position_embeddings
        q, k = apply_rotary_pos_emb(q, k, cos, sin)
        valid = _valid_tokens(attention_mask, hidden_states)
        mask = valid[:, None, :, None].to(v.dtype)
        q, k, v = q * mask, k * mask, v * mask
        temperature = self.local_log_temperature.clamp(-0.4, 0.4).exp()
        local = self._local_attention(
            q * temperature[None, :, None, None], k, v, valid
        )
        landmarks = self._landmarks(q, k, v, valid)
        global_linear = self._global_linear(q, k, v, mask) * mask
        qnorm = F.normalize(q, dim=-1)
        dynamic = torch.einsum("bhtd,hkd->bhtk", qnorm, self.route_query)
        mixture = torch.softmax(
            self.route_logits.to(q.dtype)[None, :, None, :] + dynamic, dim=-1
        )
        fused = (mixture[..., 0:1] * local
                 + mixture[..., 1:2] * landmarks
                 + mixture[..., 2:3] * global_linear
                 + mixture[..., 3:4] * v)
        local_conv = _depthwise_sequence_conv(v, self.local_weight)
        conv_weight = torch.sigmoid(self.conv_gain).to(v.dtype)[None, :, None, None]
        fused = (fused + conv_weight * (local_conv - v)) * mask
        self.last_core_output = fused
        flat = fused.transpose(1, 2).reshape(b, t, self.hidden_size).to(hidden_states.dtype)
        return self.out_drop(self.Wo(flat)), None


class StandaloneDecisionModel(nn.Module):
    """Original Laya typed-decision head without a project-library dependency."""
    def __init__(self, encoder, head_layers=2, n_act=2, dropout=0.1):
        super().__init__()
        self.encoder = encoder
        d = int(encoder.config.hidden_size)
        nhead = max(1, d // 64)
        if head_layers > 0:
            layer = nn.TransformerEncoderLayer(
                d, nhead, dim_feedforward=4*d, dropout=dropout,
                batch_first=True, norm_first=True
            )
            self.head = nn.TransformerEncoder(
                layer, int(head_layers), enable_nested_tensor=False
            )
        else:
            self.head = None
        self.type_emb = nn.Embedding(3, d)
        self.scorer = nn.Sequential(
            nn.LayerNorm(d), nn.Linear(d, d), nn.GELU(), nn.Linear(d, 1)
        )
        self.act_head = nn.Sequential(
            nn.Linear(d+4, 256), nn.GELU(), nn.Linear(256, int(n_act))
        )
        self.register_buffer("temperature", torch.ones(3))

    def forward(self, input_ids, attention_mask, marker_pos, marker_mask, qtype):
        h = self.encoder(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state
        h = h + self.type_emb(qtype)[:, None, :]
        if self.head is not None:
            h = self.head(h, src_key_padding_mask=~attention_mask.bool())
        index = marker_pos.clamp_min(0)[:, :, None].expand(-1, -1, h.size(-1))
        marker_hidden = torch.gather(h, 1, index)
        logits = self.scorer(marker_hidden).squeeze(-1).float()
        logits = logits.masked_fill(~marker_mask, -1e4)
        p = torch.softmax(logits.detach(), -1)
        k = marker_mask.sum(-1).clamp(min=2).float()
        ent = -(p*torch.log(p.clamp_min(1e-9))).sum(-1) / torch.log(k)
        if p.shape[-1] >= 2:
            top2 = p.topk(2, -1).values
        else:
            top1 = p.topk(1, -1).values
            top2 = torch.cat((top1, torch.zeros_like(top1)), -1)
        features = torch.stack(
            (top2[:, 0], top2[:, 0]-top2[:, 1], ent, k/255.0), -1
        )
        act_logits = self.act_head(torch.cat((h[:, 0].float(), features), -1))
        return logits, act_logits


def _build_global_model(root: str | Path, manifest: dict):
    """Generic exporter protocol: build_model(bundle_directory, manifest)."""
    root = Path(root)
    if manifest["architecture"] != "laya-v29-fastwindow":
        raise ValueError("Expected laya-v29-fastwindow, got " + str(manifest["architecture"]))
    if manifest["runtime_format"] != 1:
        raise ValueError("Unsupported standalone bundle format")
    cfg = AutoConfig.from_pretrained(str(root / "encoder"))
    _apply_rope_compat(cfg)
    cfg.reference_compile = False
    encoder = AutoModel.from_config(cfg, attn_implementation="sdpa")
    model = StandaloneDecisionModel(
        encoder, head_layers=int(manifest["head_layers"]),
        n_act=int(manifest["n_act"]),
    )
    for index in manifest["global_layers"]:
        original = model.encoder.layers[index].attn
        if tuple(original.local_attention) != (-1, -1):
            raise ValueError(f"Layer {index} is not full/global attention")
        model.encoder.layers[index].attn = FastWindowAttention(
            original,
            local_window=int(manifest["window"]),
            landmark_stride=int(manifest["landmark_stride"]),
            feature_dim=int(manifest["feature_dim"]),
            max_landmarks=int(manifest["max_landmarks"]),
        )
    return model


QTYPES = {"choice": 0, "score": 1, "noul": 2}

def normalize_question(question: Mapping[str, Any]):
    qtype = str(question.get("type", question.get("t", "choice"))).lower()
    if qtype not in QTYPES:
        raise ValueError("Unsupported question type: " + qtype)
    criteria = question.get("criteria", question.get("crit"))
    if qtype == "choice":
        if isinstance(criteria, (list, tuple)):
            criteria = {str(x): None for x in criteria}
        if not isinstance(criteria, Mapping) or len(criteria) < 2:
            raise ValueError("Choice requires >=2 options")
        criteria = dict(criteria)
    elif qtype == "score":
        if not isinstance(criteria, (list, tuple)) or len(criteria) < 2:
            raise ValueError("Score requires >=2 levels")
        criteria = list(criteria)
    else:
        if criteria is None:
            criteria = {}
        if not isinstance(criteria, Mapping):
            raise ValueError("Noul criteria must be a mapping")
        criteria = {str(k).lower(): v for k, v in criteria.items()}
    ins = question.get("instructions", question.get("ins", ""))
    if not isinstance(ins, str):
        ins = json.dumps(ins, ensure_ascii=False, default=str)
    result = {"t": qtype, "ins": ins, "crit": criteria}
    if "labels" in question:
        result["labels"] = dict(question["labels"])
    return result


def render_options(q):
    q = normalize_question(q) if "t" not in q else dict(q)
    criteria = q["crit"]
    def render(v):
        return v if isinstance(v, str) else json.dumps(v, ensure_ascii=False, default=str)
    if q["t"] == "choice":
        return [str(k) if v is None or v == "" else f"{k}: {render(v)}"
                for k,v in criteria.items()]
    if q["t"] == "score":
        return [f"level {i}: {render(v)}" for i,v in enumerate(criteria)]
    labels = q.get("labels", {"false": "false", "true": "true"})
    return [
        str(labels.get("false", "false"))+": "+(
            render(criteria.get("false")) if criteria.get("false") not in (None,"")
            else "no, the statement does not hold"),
        str(labels.get("true", "true"))+": "+(
            render(criteria.get("true")) if criteria.get("true") not in (None,"")
            else "yes, the statement holds"),
    ]


def build_sequence(tokenizer, state, question, *, max_len=512, head_max_len=192,
                   truncate_left=False, state_ids=None):
    q = normalize_question(question) if "t" not in question else dict(question)
    options = render_options(q)
    mask = tokenizer.mask_token
    ins = str(q["ins"]).replace(mask, " ")
    head_ids = tokenizer(
        f"{q['t']} question: {ins}", add_special_tokens=False
    )["input_ids"]
    option_ids = []
    for option in options:
        ids = tokenizer(
            " "+option.replace(mask, " "), add_special_tokens=False,
            truncation=True, max_length=48
        )["input_ids"]
        option_ids.append([tokenizer.mask_token_id] + ids)
    budget = head_max_len - sum(len(x) for x in option_ids)
    if budget < 16:
        per = max(4, (head_max_len - 16) // max(1, len(option_ids)))
        option_ids = [x[:per] for x in option_ids]
        budget = head_max_len - sum(len(x) for x in option_ids)
    head_ids = head_ids[:max(8, budget)]
    ids = [tokenizer.cls_token_id] + head_ids + [tokenizer.sep_token_id]
    markers = []
    for opt in option_ids:
        markers.append(len(ids))
        ids.extend(opt)
    ids.append(tokenizer.sep_token_id)
    if state_ids is None:
        payload = state if isinstance(state, str) else json.dumps(
            state, ensure_ascii=False, separators=(",", ":"), default=str
        )
        state_ids = tokenizer(
            payload.replace(mask, " "), add_special_tokens=False
        )["input_ids"]
    room = max(0, max_len-len(ids)-1)
    state_part = list(state_ids[-room:] if truncate_left and room else
                      state_ids[:room])
    ids = (ids+state_part+[tokenizer.sep_token_id])[:max_len]
    markers = [i for i in markers if i < max_len]
    return ids, markers


def predict(model, tokenizer, state, question, *, max_len=512, device=None):
    """Simple task-level inference on a deployed standalone bundle."""
    q = normalize_question(question)
    ids, markers = build_sequence(tokenizer, state, q, max_len=max_len)
    if not markers:
        raise ValueError("No option markers survived truncation")
    parameter = next(model.parameters())
    device = device or parameter.device
    data = {
        "input_ids": torch.tensor([ids], dtype=torch.long, device=device),
        "attention_mask": torch.ones((1, len(ids)), dtype=torch.long, device=device),
        "marker_pos": torch.tensor([markers], dtype=torch.long, device=device),
        "marker_mask": torch.ones((1, len(markers)), dtype=torch.bool, device=device),
        "qtype": torch.tensor([QTYPES[q["t"]]], dtype=torch.long, device=device),
    }
    model.eval()
    with torch.inference_mode():
        scores, actions = model(**data)
    values = torch.softmax(scores.float(), -1)[0].cpu().tolist()
    choice = int(torch.tensor(values).argmax())
    return {
        "choice_index": choice,
        "choice": render_options(q)[choice],
        "probabilities": values,
        "action_logits": actions.float()[0].cpu().tolist(),
    }


# Bundled standalone sliding-fusion implementation
"""ModernBERT SlidingWindowFusion: exact finite-window SDPA plus gated CeNN-style local mixers.

An experiment in approximating the *sliding* ModernBERT layers (not the global
layers). It preserves pretrained QKV, RoPE and output projection. The 64-radius
configuration retains the teacher's 129-position local receptive field, but
remains windowed attention and is NOT an attention-free replacement.
"""

import copy
import math

import torch
from torch import nn, Tensor
import torch.nn.functional as F
from transformers.models.modernbert.modeling_modernbert import apply_rotary_pos_emb


def valid_tokens(mask: Tensor | None, hidden: Tensor) -> Tensor:
    b, t = hidden.shape[:2]
    if mask is None:
        return torch.ones(b, t, dtype=torch.bool, device=hidden.device)
    if mask.ndim == 2:
        return mask.bool()
    if mask.ndim == 4:
        allowed = mask if mask.dtype == torch.bool else mask > -1e4
        return (allowed.any(-1).any(1) & allowed.any(-2).any(1)).bool()
    raise ValueError(f"Unrecognized attention mask shape {tuple(mask.shape)}")


def _identity_depthwise(channels: int, kernel: int) -> Tensor:
    weight = torch.zeros(channels, 1, kernel)
    weight[:, 0, kernel // 2] = 1.0
    return weight


def local_sliding_sdpa(
    q: Tensor, k: Tensor, v: Tensor, valid: Tensor, *,
    radius: int, block_size: int = 64
) -> Tensor:
    """O(T * (block_size + 2*radius)) memory local SDPA, exact ±radius semantics.

    Padding keys are excluded, and invalid query outputs are zero. Use this
    kernel as an alternative to the teacher's full T-by-T additive mask.
    """
    b, h, t, d = q.shape
    w = int(radius)
    bs = int(block_size)
    if w < 0 or bs < 1:
        raise ValueError("radius must be >=0, block_size >=1")
    blocks = math.ceil(t / bs)
    tail = blocks * bs - t
    # [B,H,blocks,BS,D] and [B,H,blocks,BS+2R,D]
    qp = F.pad(q, (0, 0, 0, tail)).reshape(b, h, blocks, bs, d)
    def keys_in_blocks(x):
        return F.pad(x, (0, 0, w, w + tail)).unfold(
            2, bs + 2*w, bs
        ).transpose(-1, -2)
    kp, vp = keys_in_blocks(k), keys_in_blocks(v)
    # SDPA bool mask: True means this key is permitted.
    kv = F.pad(valid, (w, w + tail)).unfold(1, bs + 2*w, bs)
    qi = torch.arange(bs, device=q.device)[:, None]
    ki = torch.arange(bs + 2*w, device=q.device)[None, :]
    band = (ki >= qi) & (ki <= qi + 2*w)
    allowed = (band[None, None, None, :, :]
               & kv[:, None, :, None, :])
    def flatten_blocks(x):
        return x.transpose(1, 2).reshape(
            b * blocks, h, x.shape[-2], d
        )
    allowed = allowed.transpose(1, 2).reshape(
        b * blocks, 1, bs, bs + 2*w
    )
    out = F.scaled_dot_product_attention(
        flatten_blocks(qp), flatten_blocks(kp), flatten_blocks(vp),
        attn_mask=allowed, dropout_p=0.0
    )
    out = out.reshape(b, blocks, h, bs, d).transpose(1, 2)
    return (out.reshape(b, h, blocks*bs, d)[:, :, :t]
            * valid[:, None, :, None].to(out.dtype))


def grouped_value_conv(v: Tensor, weight: Tensor, dilation: int) -> Tensor:
    b, h, t, d = v.shape
    k = weight.shape[-1]
    padding = dilation * (k - 1) // 2
    z = v.permute(0, 1, 3, 2).reshape(b, h*d, t)
    z = F.conv1d(z, weight.to(z.dtype), padding=padding,
                 dilation=dilation, groups=h*d)
    return z.reshape(b, h, d, t).permute(0, 1, 3, 2).contiguous()


class SlidingWindowFusion(nn.Module):
    """Learned per-head, per-token mixture of exact local window and local convs.

    A ±64 window matches ModernBERT's default native local attention radius.
    For radii <64, the context is genuinely truncated; no hidden teacher-mask
    attention path is available. Multiscale convolutions may recover some context.

    Fixed pretrained QKV/RoPE, tiny Wo calibration, FP32-master route weights.
    """

    architecture = "sliding_window_fusion_v30"

    def __init__(self, original: nn.Module, *,
                 radius: int = 64, block_size: int = 64):
        super().__init__()
        if not hasattr(original, "local_attention"):
            raise TypeError("Expected original ModernBERT attention")
        a, z = tuple(original.local_attention)
        if (a, z) == (-1, -1) or a != z:
            raise ValueError("SlidingWindowFusion accepts sliding attention only")
        if not (0 <= radius <= a):
            raise ValueError(f"Candidate radius {radius} exceeds native ±{a}")
        self.config = original.config
        self.layer_idx = int(getattr(original, "layer_id", -1))
        self.num_heads = int(original.num_heads)
        self.head_dim = int(original.head_dim)
        self.hidden_size = self.num_heads * self.head_dim
        self.radius = int(radius)
        self.native_radius = int(a)
        self.block_size = int(block_size)
        self.Wqkv = copy.deepcopy(original.Wqkv)
        self.Wo = copy.deepcopy(original.Wo)
        self.out_drop = copy.deepcopy(original.out_drop)
        self.rotary_emb = copy.deepcopy(original.rotary_emb)
        self.Wqkv.requires_grad_(False)
        self.rotary_emb.requires_grad_(False)
        channels = self.num_heads * self.head_dim
        self.short_weight = nn.Parameter(_identity_depthwise(channels, 7))
        self.dilated_weight = nn.Parameter(_identity_depthwise(channels, 9))
        self.wide_weight = nn.Parameter(_identity_depthwise(channels, 9))
        # 99.7% nominal route mass on the exact local attention path.
        # A small but nonzero residual path can be learned when W is narrowed.
        self.route_logits = nn.Parameter(
            torch.tensor([4.0, -3.0, -3.0, -3.0]).repeat(self.num_heads, 1)
        )
        self.route_query = nn.Parameter(
            torch.zeros(self.num_heads, 4, self.head_dim)
        )
        self.local_log_temperature = nn.Parameter(torch.zeros(self.num_heads))

    def forward(self, hidden_states: Tensor, position_embeddings=None,
                attention_mask=None, sliding_window_mask=None,
                position_ids=None, **kwargs):
        b, t, _ = hidden_states.shape
        qkv = self.Wqkv(hidden_states).reshape(
            b, t, 3, self.num_heads, self.head_dim
        )
        q, k, v = qkv.transpose(3, 1).unbind(2)
        if position_embeddings is None:
            cos, sin = self.rotary_emb(qkv, position_ids=position_ids)
        else:
            cos, sin = position_embeddings
        q, k = apply_rotary_pos_emb(q, k, cos, sin)
        valid = valid_tokens(attention_mask, hidden_states)
        key_mask = valid[:, None, :, None].to(v.dtype)
        # Distinct from native dense mask: no T×T tensor is constructed.
        q, k, v = q * key_mask, k * key_mask, v * key_mask
        temp = self.local_log_temperature.clamp(-0.25, 0.25).exp()
        local = local_sliding_sdpa(
            q * temp[None, :, None, None], k, v, valid,
            radius=self.radius, block_size=self.block_size
        )
        c7 = grouped_value_conv(v, self.short_weight, dilation=1)
        c9 = grouped_value_conv(v, self.dilated_weight, dilation=4)
        cwide = grouped_value_conv(v, self.wide_weight, dilation=8)
        # Dynamic, per-token routing; all branches share the current true
        # input features, so no teacher outputs leak into the student.
        qnorm = F.normalize(q, dim=-1)
        dynamic = torch.einsum("bhtd,hkd->bhtk", qnorm, self.route_query)
        gate = F.softmax(
            self.route_logits.to(q.dtype)[None, :, None, :] + dynamic, -1
        )
        fused = (gate[..., 0:1]*local
                 + gate[..., 1:2]*c7
                 + gate[..., 2:3]*c9
                 + gate[..., 3:4]*cwide) * key_mask
        out = self.Wo(fused.transpose(1, 2).reshape(
            b, t, self.hidden_size
        ).to(hidden_states.dtype))
        return (self.out_drop(out),)

    def config_dict(self):
        return {
            "architecture": self.architecture,
            "layer_idx": self.layer_idx,
            "radius": self.radius,
            "native_radius": self.native_radius,
            "block_size": self.block_size,
            "window_positions": 2*self.radius+1,
            "teacher_window_positions": 2*self.native_radius+1,
            "uses_local_sdpa": True,
            "attention_free": False,
            "full_context_dense_attention": False,
            "receptive_field_conv": [7, 33, 65],
        }



# ============================================================================
# Mixed-runtime reconstruction contract consumed by standalone_hf_loader.py
# ============================================================================
def build_model(root: str | Path, manifest: dict):
    """Build a COMPLETE fully converted model from ordinary config and manifest."""
    if manifest.get("architecture") != "laya-v31-all-attention":
        raise ValueError("Unsupported mixed-runtime architecture")
    if manifest.get("runtime_format") != 1:
        raise ValueError("Unsupported standalone bundle protocol")
    global_layers = [int(x) for x in manifest["global_layers"]]
    sliding_layers = [int(x) for x in manifest["sliding_layers"]]
    if set(global_layers) & set(sliding_layers):
        raise ValueError("Overlap between global and sliding layer indices")
    global_manifest = dict(manifest)
    global_manifest["architecture"] = "laya-v29-fastwindow"
    model = _build_global_model(root, global_manifest)
    if sorted(global_layers + sliding_layers) != list(range(len(model.encoder.layers))):
        raise ValueError("All encoder attention layers must be replaced exactly once")
    for i in sliding_layers:
        original = model.encoder.layers[i].attn
        if tuple(original.local_attention) == (-1, -1):
            raise ValueError(f"Sliding layer {i} is actually global")
        model.encoder.layers[i].attn = SlidingWindowFusion(
            original,
            radius=int(manifest["sliding_radius"]),
            block_size=int(manifest["sliding_block_size"]),
        )
    return model


# --- Jev_CeNN standalone optional Triton kernel ---
"""Jev_CeNN optional CUDA sliding-attention backend.

A single Triton program processes one query/head, evaluates exactly the
centered ±radius QK attention scores, and reduces V without building global
T×T masks, unfolding halo blocks, or allocating attention probabilities.

The kernel is an inference fast path ONLY. Training/autograd uses the
existing differentiable PyTorch block-SDPA implementation. No parameter names,
mathematics, layer type, checkpoints or Hugging Face weights are changed.

The optional import is deliberately lazy/guarded. CPU and environments without
Triton use the passed reference implementation unconditionally.
"""


import math
import torch

try:
    import triton
    import triton.language as tl
except ImportError:  # Portable HF bundles must still work without Triton.
    triton = None
    tl = None


if triton is not None:
    @triton.jit
    def _sliding_forward(
        Q, K, V, MASK, OUT,
        H:tl.constexpr,T:tl.constexpr,D:tl.constexpr,R:tl.constexpr,
        QB:tl.constexpr,QH:tl.constexpr,QT:tl.constexpr,QD:tl.constexpr,
        KB:tl.constexpr,KH:tl.constexpr,KT:tl.constexpr,KD:tl.constexpr,
        VB:tl.constexpr,VH:tl.constexpr,VT:tl.constexpr,VD:tl.constexpr,
        MB:tl.constexpr,MT:tl.constexpr,SCALE:tl.constexpr,
        N:tl.constexpr,DB:tl.constexpr,
    ):
        qi=tl.program_id(0)
        bh=tl.program_id(1)
        b=bh//H
        h=bh%H
        dd=tl.arange(0,DB)
        ks=qi-R+tl.arange(0,N)
        kv=(ks>=0)&(ks<T)&(tl.abs(ks-qi)<=R)
        valid_k=tl.load(MASK+b*MB+ks*MT,mask=kv,other=0)!=0
        q=tl.load(Q+b*QB+h*QH+qi*QT+dd*QD,mask=dd<D,other=0).to(tl.float32)
        keys=tl.load(
            K+b*KB+h*KH+ks[:,None]*KT+dd[None,:]*KD,
            mask=kv[:,None]&(dd[None,:]<D),other=0
        ).to(tl.float32)
        score=tl.sum(keys*q[None,:],axis=1)*SCALE
        score=tl.where(kv&valid_k,score,-1.0e6)
        top=tl.max(score,axis=0)
        weights=tl.exp(score-top)*((kv&valid_k).to(tl.float32))
        denominator=tl.sum(weights,axis=0)
        values=tl.load(
            V+b*VB+h*VH+ks[:,None]*VT+dd[None,:]*VD,
            mask=kv[:,None]&(dd[None,:]<D),other=0
        ).to(tl.float32)
        result=tl.sum(weights[:,None]*values,axis=0)/tl.maximum(denominator,1.0e-20)
        valid_q=tl.load(MASK+b*MB+qi*MT)!=0
        result=tl.where(valid_q,result,0.0)
        tl.store(OUT+((b*H+h)*T+qi)*D+dd,result,mask=dd<D)


def fast_sliding_sdpa(q,k,v,valid,*,radius,block_size=64,reference=None):
    """Same API/shape as original local_sliding_sdpa.

    Accepts all native q/k/v strides (including ModernBERT's interleaved
    QKV views). Never detaches gradients: when needed, delegates to the
    exact reference implementation. Triton errors are deliberately *not*
    swallowed: the Colab preflight must expose failures before deployment.
    """
    if reference is None:
        raise ValueError("reference=local_sliding_sdpa must be supplied")
    supported=(
        triton is not None
        and q.is_cuda and k.is_cuda and v.is_cuda
        and q.dtype in (torch.float16,torch.bfloat16,torch.float32)
        and q.dtype==k.dtype==v.dtype
        and q.ndim==4 and q.shape==k.shape==v.shape
        and valid.ndim==2 and valid.shape==(q.shape[0],q.shape[2])
        and int(radius)>=0 and int(radius)<=256
    )
    # Keep the original, differentiable SDPA route for every training call.
    if not supported or (torch.is_grad_enabled() and
                        (q.requires_grad or k.requires_grad or v.requires_grad)):
        return reference(q,k,v,valid,radius=radius,block_size=block_size)
    b,h,t,d=q.shape
    if d>128:
        return reference(q,k,v,valid,radius=radius,block_size=block_size)
    radius=int(radius)
    output=torch.empty((b,h,t,d),dtype=q.dtype,device=q.device)
    _sliding_forward[(t,b*h)](
        q,k,v,valid,output,
        h,t,d,radius,
        *q.stride(),*k.stride(),*v.stride(),
        *valid.stride(),
        1.0/math.sqrt(d),
        triton.next_power_of_2(2*radius+1),
        triton.next_power_of_2(d),
        num_warps=4,
    )
    return output


def install_fast_sliding_backend(module, *, reference=None):
    """Enable on an existing module namespace, without changing state_dicts.

    Example: import tinycenn_lm.sliding_window_fusion as swf
             install_fast_sliding_backend(swf)
    """
    if reference is None:
        reference=getattr(module,"_jev_original_sliding_sdpa",None)
    if reference is None:
        reference=module.local_sliding_sdpa
    if not hasattr(module,"_jev_original_sliding_sdpa"):
        module._jev_original_sliding_sdpa=reference
    def accelerated(q,k,v,valid,*,radius,block_size=64):
        return fast_sliding_sdpa(
            q,k,v,valid,radius=radius,block_size=block_size,
            reference=module._jev_original_sliding_sdpa
        )
    module.local_sliding_sdpa=accelerated
    return bool(triton is not None and torch.cuda.is_available())


def uninstall_fast_sliding_backend(module):
    if hasattr(module,"_jev_original_sliding_sdpa"):
        module.local_sliding_sdpa=module._jev_original_sliding_sdpa


def triton_available():
    return triton is not None and torch.cuda.is_available()

# Block-SDPA PyTorch was faster on target GPU.

_jev_original_build_model = build_model
def build_model(root,manifest):
    conf=dict(manifest)
    if conf.get("architecture")=="Jev_CeNN":
        conf["architecture"]="laya-v31-all-attention"
    return _jev_original_build_model(root,conf)