File size: 14,738 Bytes
edebd87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from huggingface_hub.dataclasses import strict

from transformers.configuration_utils import PreTrainedConfig
from transformers.modeling_rope_utils import RopeParameters
from transformers.utils import auto_docstring


# The two MoE block kinds and the three attention block kinds Agnes ships, plus
# the legacy per-layer integer -> attention-kind map used when a checkpoint still
# carries the old `compress_ratios` list.
AGNES_MLP_LAYER_TYPES = ("agnes_hash_moe", "agnes_moe")

AGNES_LAYER_TYPES = (
    "agnes_local_attention",
    "agnes_sparse_attention",
    "agnes_pooled_attention",
)

_COMPRESS_RATIO_TO_LAYER_TYPE = {
    0: "agnes_local_attention",
    4: "agnes_sparse_attention",
    128: "agnes_pooled_attention",
}


@auto_docstring
@strict
class AgnesConfig(PreTrainedConfig):
    r"""
    scoring_func (`str`):
        Activation applied to the router logits — one of `sqrtsoftplus`,
        `softmax`, `sigmoid`.
    rope_theta (`float`):
        Rotary base for the main (local) attention path.
    layer_types (`list[str]`):
        The per-layer attention schedule, drawn from `agnes_local_attention`,
        `agnes_sparse_attention`, `agnes_pooled_attention`. Default schedule:
        two pooled layers to bootstrap, then an alternating sparse/pooled tail.
    compress_rates (`dict[str, int]`):
        Compression stride per attention type; default
        `{"agnes_sparse_attention": 4, "agnes_pooled_attention": 128}`. For
        backward compatibility, top-level `compress_rate_csa` /
        `compress_rate_hca` kwargs are absorbed in `__post_init__`.
    compress_rope_theta (`float`):
        Rotary base shared by the compressed branches (used together with
        `rope_scaling` for YaRN).
    hc_mult (`int`):
        Number of parallel residual streams carried by the manifold-constrained
        hyper-connections (always on).
    hc_sinkhorn_iters (`int`):
        Iteration budget for the Sinkhorn-Knopp projection of the stream-mixing
        matrix onto the doubly-stochastic manifold.
    hc_eps (`float`):
        Small constant guarding the Sinkhorn normalisation against divide-by-zero.
    mlp_layer_types (`list[str]`):
        The per-layer MoE schedule over `agnes_hash_moe` / `agnes_moe`. Hash
        layers pick experts from the frozen `tid2eid[input_ids]` table; plain
        layers use learned top-k routing. Default: the first three layers hash,
        the rest learned. Legacy `num_hash_layers` is folded in at
        `__post_init__`.
    swiglu_limit (`float`):
        Clamp bound on the routed experts' gate/up pre-activations.
    parallel_ffn_intermediate_size (`int`):
        Width of the optional parallel dense-FFN branch on every non-hash MoE
        layer (`0` turns it off). Its `down_proj` is exported as zeros, so the
        branch is a no-op until trained.
    sliding_window (`int`):
        Local attention window width used by every block.
    o_groups (`int`):
        Head-group count for the grouped output projection.
    o_lora_rank (`int`):
        Per-group bottleneck width of the grouped output projection.
    index_n_heads (`int`):
        Query-head count of the Lightning Indexer.
    index_head_dim (`int`):
        Per-head width inside the Lightning Indexer.
    index_topk (`int`):
        How many compressed entries the indexer keeps per query.
    num_nextn_predict_layers (`int`):
        Number of MTP layers present in the source checkpoint (not built here).
    partial_rotary_factor (`float`, *optional*):
        Fraction of each head that is rotated. Defaults to
        `qk_rope_head_dim / head_dim`, sizing cos/sin to `qk_rope_head_dim`.
    """

    model_type = "agnes"
    keys_to_ignore_at_inference = ["past_key_values"]

    # --- core dimensions ---
    vocab_size: int = 129280
    hidden_size: int = 4096
    num_hidden_layers: int = 43

    # --- self-attention (shared-KV MQA + grouped output projection) ---
    num_attention_heads: int = 64
    num_key_value_heads: int = 1
    head_dim: int = 512
    default_partial_rotary_factor = 64 / 512  # `qk_rope_head_dim` (64) / `head_dim` (512)
    partial_rotary_factor: float | None = None
    q_lora_rank: int = 1024
    o_groups: int = 8
    o_lora_rank: int = 1024
    sliding_window: int = 128
    attention_bias: bool = False
    attention_dropout: float = 0.0

    # --- long-range compressor + lightning indexer ---
    index_n_heads: int = 64
    index_head_dim: int = 128
    index_topk: int = 512
    layer_types: list[str] | None = None
    compress_rates: dict | None = None
    default_compress_rates = {"agnes_sparse_attention": 4, "agnes_pooled_attention": 128}
    compress_rope_theta: float | int = 160000.0

    # --- mixture-of-experts routing ---
    moe_intermediate_size: int = 2048
    parallel_ffn_intermediate_size: int = 0
    n_routed_experts: int = 256
    n_shared_experts: int = 1
    num_experts_per_tok: int = 6
    mlp_layer_types: list[str] | None = None
    default_num_hash_layers = 3
    scoring_func: str = "sqrtsoftplus"
    norm_topk_prob: bool = True
    routed_scaling_factor: float = 1.5
    num_nextn_predict_layers: int = 1
    output_router_logits: bool = False
    router_aux_loss_coef: float = 0.001
    router_jitter_noise: float = 0.0

    # --- feed-forward activation ---
    hidden_act: str = "silu"
    swiglu_limit: float = 10.0
    mlp_bias: bool = False

    # --- manifold-constrained hyper-connections ---
    hc_mult: int = 4
    hc_sinkhorn_iters: int = 20
    hc_eps: float = 1.0e-6

    # --- normalisation + initialisation ---
    rms_norm_eps: float = 1.0e-6
    initializer_range: float = 0.02

    # --- rotary position embedding ---
    rope_theta: float | int = 10000.0
    rope_parameters: RopeParameters | dict | None = None
    max_position_embeddings: int = 1048576

    # --- special tokens + weight tying ---
    pad_token_id: int | None = None
    bos_token_id: int | None = 0
    eos_token_id: int | list[int] | None = 1
    tie_word_embeddings: bool = False

    # --- runtime ---
    use_cache: bool = True

    # ------------------------------------------------------------------ #
    # Non-field class attributes: alias map, parallelism plans, rope labels
    # ------------------------------------------------------------------ #

    # Expert-parallel plan. Agnes ships EP only — it is MoE, so there is no
    # `base_model_tp_plan`. The gate routes, the routed experts run as a grouped
    # GEMM sharded on the expert axis, and the experts module is wrapped so its
    # output is all-reduced. Core attention stays replicated: it is shared-KV MQA
    # broadcasting a single KV head to every query head, so colwise-sharding
    # `q_b_proj` would desync the KV broadcast from the rank-local head count; the
    # small shared MLP is not worth sharding either. The one exception is the
    # Lightning Indexer, whose keys are replicated (its own compressor runs at
    # index_head_dim on replicated hidden states): there `q_b_proj` and
    # `scorer.weights_proj` go colwise and the `scorer` output is all-reduced so
    # every rank picks the same top-k.
    base_model_ep_plan = {
        "layers.*.mlp.gate": "ep_router",
        "layers.*.mlp.experts.gate_up_proj": "grouped_gemm",
        "layers.*.mlp.experts.down_proj": "grouped_gemm",
        "layers.*.mlp.experts": "moe_tp_experts",
        "layers.*.self_attn.compressor.indexer.q_b_proj": "colwise",
        "layers.*.self_attn.compressor.indexer.scorer.weights_proj": "colwise",
        "layers.*.self_attn.compressor.indexer.scorer": "all_reduce",
    }

    base_model_pp_plan = {
        "embed_tokens": (["input_ids"], ["inputs_embeds"]),
        "layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
        "norm": (["hidden_states"], ["hidden_states"]),
    }

    # `num_local_experts` / `intermediate_size` are the names the MoE FP8 / TP
    # integrations and the shared-expert MLP read; Agnes only stores
    # `n_routed_experts` / `moe_intermediate_size`, so alias them through.
    attribute_map = {
        "num_local_experts": "n_routed_experts",
        "intermediate_size": "moe_intermediate_size",
    }

    # `rope_parameters` is keyed by rope label (`main` / `compress`), not by
    # `layer_types`. The base `validate_rope` assumes the latter, so it is
    # overridden below to walk the rope-label sub-dicts directly.
    _rope_type_labels = ("main", "compress")

    def __post_init__(self, **kwargs):
        # Older checkpoints still ship a handful of legacy kwargs; pop them out of
        # the dict before the strict parent init runs, then reconcile each into its
        # current field afterwards.
        leg_ratios = kwargs.pop("compress_ratios", None)
        leg_csa = kwargs.pop("compress_rate_csa", None)
        leg_hca = kwargs.pop("compress_rate_hca", None)
        leg_hash = kwargs.pop("num_hash_layers", None)
        leg_qk_rope = kwargs.pop("qk_rope_head_dim", None)
        PreTrainedConfig.__post_init__(self, **kwargs)
        n = self.num_hidden_layers

        # compress_rates: seed the per-type defaults, then let a legacy scalar win.
        if self.compress_rates is None:
            self.compress_rates = dict(self.default_compress_rates)
        if leg_csa is not None:
            self.compress_rates["agnes_sparse_attention"] = leg_csa
        if leg_hca is not None:
            self.compress_rates["agnes_pooled_attention"] = leg_hca

        # layer_types precedence: explicit field, then legacy 0/4/128 ratios, then
        # the default (two pooled bootstrap layers + sparse/pooled interleave).
        if self.layer_types is None and leg_ratios is not None:
            self.layer_types = [_COMPRESS_RATIO_TO_LAYER_TYPE[r] for r in leg_ratios]
        if self.layer_types is None:
            tail = [
                "agnes_sparse_attention" if i % 2 else "agnes_pooled_attention"
                for i in range(max(n - 2, 0))
            ]
            self.layer_types = ["agnes_pooled_attention"] * min(n, 2) + tail
        self.layer_types = list(self.layer_types[:n])

        # mlp_layer_types: leading hash-routed layers, learned routing for the rest.
        if self.mlp_layer_types is None:
            n_hash = leg_hash if leg_hash is not None else self.default_num_hash_layers
            self.mlp_layer_types = ["agnes_hash_moe"] * min(n, n_hash) + ["agnes_moe"] * max(0, n - n_hash)
        self.mlp_layer_types = list(self.mlp_layer_types[:n])

        # partial_rotary_factor from a legacy qk_rope_head_dim if provided, else the
        # default; qk_rope_head_dim itself is only ever a runtime attr, not a field.
        if self.partial_rotary_factor is None:
            self.partial_rotary_factor = (
                leg_qk_rope / self.head_dim if leg_qk_rope is not None else self.default_partial_rotary_factor
            )
        self.qk_rope_head_dim = int(self.head_dim * self.partial_rotary_factor)

        # Normalise rope_parameters into the {main, compress} nesting. Local layers
        # use plain rope at rope_theta; only the compress branch may be YaRN, and if
        # it is we pin attention_factor=1.0 (Agnes never applies YaRN's mscale).
        rp = self.rope_parameters or {}
        if isinstance(rp.get("main"), dict) and isinstance(rp.get("compress"), dict):
            self.rope_parameters = {"main": rp["main"], "compress": rp["compress"]}
        else:
            extra = {k: v for k, v in rp.items() if k not in ("main", "compress")}
            main = {
                "rope_type": "default",
                "rope_theta": self.rope_theta,
                "partial_rotary_factor": self.partial_rotary_factor,
            }
            compress = {
                **extra,
                "rope_theta": self.compress_rope_theta,
                "partial_rotary_factor": self.partial_rotary_factor,
            }
            compress.setdefault("rope_type", "default")
            if compress["rope_type"] == "yarn":
                compress.setdefault("attention_factor", 1.0)
            self.rope_parameters = {"main": main, "compress": compress}

    def validate_layer_type(self):
        """Keep `layer_types` / `mlp_layer_types` within the block kinds Agnes
        actually builds, alongside the usual length check against
        `num_hidden_layers`."""
        if self.num_hidden_layers is None:
            return
        schedules = (
            ("layer_types", self.layer_types, AGNES_LAYER_TYPES),
            ("mlp_layer_types", self.mlp_layer_types, AGNES_MLP_LAYER_TYPES),
        )
        for name, schedule, allowed in schedules:
            if schedule is None:
                continue
            if len(schedule) != self.num_hidden_layers:
                raise ValueError(
                    f"`num_hidden_layers` ({self.num_hidden_layers}) must equal `len({name})` ({len(schedule)})."
                )
            unknown = [t for t in schedule if t not in allowed]
            if unknown:
                raise ValueError(f"`{name}` entries must be one of {allowed} for Agnes; got {unknown}.")

    def validate_rope(self):
        # The stock validators index self.rope_parameters[<key>] directly, which
        # breaks against the {main, compress} nesting, so run each rope-label
        # sub-dict through its own validator with self.rope_parameters temporarily
        # pointed at it, restoring the nested dict afterwards.
        nested = getattr(self, "rope_parameters", None) or {}
        ignore_keys = self.ignore_keys_at_rope_validation
        for label in self._rope_type_labels:
            sub = nested.get(label)
            if not isinstance(sub, dict):
                continue
            kind = sub.get("rope_type", sub.get("type", "default"))
            sub["rope_type"] = kind
            validator = getattr(self, f"_validate_{kind}_rope_parameters", None)
            if validator is None:
                continue
            self.rope_parameters = sub
            try:
                validator(sub, ignore_keys=ignore_keys)
            finally:
                self.rope_parameters = nested


__all__ = ["AgnesConfig"]