vishesh-t27 commited on
Commit
f901822
·
verified ·
1 Parent(s): 4e07073

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -1,2 +1,4 @@
1
  benchmark_results.png filter=lfs diff=lfs merge=lfs -text
2
  benchmark_results.jpg filter=lfs diff=lfs merge=lfs -text
 
 
 
1
  benchmark_results.png filter=lfs diff=lfs merge=lfs -text
2
  benchmark_results.jpg filter=lfs diff=lfs merge=lfs -text
3
+ model.safetensors filter=lfs diff=lfs merge=lfs -text
4
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
.ipynb_checkpoints/chat_template-checkpoint.jinja ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {# Lumma schema-conditioned extraction chat template.
2
+ #
3
+ # INFERENCE: pass input_text + template (type hints) or input_text + json_schema (null schema).
4
+ # tokenizer.apply_chat_template(
5
+ # [],
6
+ # input_text="John Smith joined Acme.",
7
+ # template={"name": "string", "company": "string"},
8
+ # add_generation_prompt=True,
9
+ # )
10
+ # NandiTokenizer converts template -> null schema via json.dumps before rendering.
11
+ #
12
+ # TRAINING: pass full messages (system / user / assistant) as usual.
13
+ # #}
14
+ {%- set extraction_system = "You are Lumma, an AI model developed by FrontiersMind. You will be provided with an input text and a JSON schema. Your task is to extract information from the input text strictly according to the provided schema. Populate only the fields defined in the schema. If a field cannot be determined from the input text, set its value to null. Do not infer, assume, rewrite, rephrase, summarize, or generate any information that is not explicitly present in the input text. Return only a valid JSON object that exactly follows the provided schema." -%}
15
+ {%- if input_text is defined and json_schema is defined -%}
16
+ <|im_start|><|system|>{{ '\n' }}{{ extraction_system }}<|endoftext|>{{ '\n' }}
17
+ <|im_start|><|user|>{{ '\n' }}Input-Text: {{ input_text }}
18
+
19
+ JSON-Schema: {%- if json_schema is string -%}{{ json_schema }}{%- else -%}{{ json_schema | tojson }}{%- endif -%}<|endoftext|>{{ '\n' }}
20
+ {%- else -%}
21
+ {%- for message in messages -%}
22
+ {%- if message["role"] == "system" -%}
23
+ <|im_start|><|system|>{{ '\n' }}{{ message["content"] }}<|endoftext|>{{ '\n' }}
24
+ {%- elif message["role"] == "user" -%}
25
+ <|im_start|><|user|>{{ '\n' }}{{ message["content"] }}<|endoftext|>{{ '\n' }}
26
+ {%- elif message["role"] == "assistant" -%}
27
+ <|im_start|><|assistant|>{{ '\n' }}{%- generation -%}{{ message["content"] }}<|endoftext|>{{ '\n' }}{%- endgeneration -%}
28
+ {%- endif -%}
29
+ {%- endfor -%}
30
+ {%- endif -%}
31
+ {%- if add_generation_prompt -%}
32
+ <|im_start|><|assistant|>{{ '\n' }}
33
+ {%- endif -%}
.ipynb_checkpoints/config-checkpoint.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "NandiForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "auto_map": {
8
+ "AutoConfig": "configuration_nandi.NandiConfig",
9
+ "AutoModel": "modeling_nandi.NandiModel",
10
+ "AutoModelForCausalLM": "modeling_nandi.NandiForCausalLM"
11
+ },
12
+ "bos_token_id": 1,
13
+ "dtype": "bfloat16",
14
+ "embedding_rank": 512,
15
+ "eos_token_id": 0,
16
+ "factorized_embedding": true,
17
+ "head_dim": 90,
18
+ "hidden_act": "silu",
19
+ "hidden_size": 1440,
20
+ "initializer_range": 0.02,
21
+ "intermediate_size": 3280,
22
+ "kv_cache_mode": "shared",
23
+ "layer_sharing": false,
24
+ "layer_sharing_repeats": 1,
25
+ "max_position_embeddings": 12288,
26
+ "mlp_bias": false,
27
+ "model_type": "nandi",
28
+ "num_attention_heads": 16,
29
+ "num_hidden_layers": 30,
30
+ "num_key_value_heads": 8,
31
+ "pad_token_id": 3,
32
+ "pretraining_tp": 1,
33
+ "q_norm": true,
34
+ "qk_norm": false,
35
+ "rms_norm_eps": 1e-06,
36
+ "rope_parameters": {
37
+ "rope_theta": 1000000.0,
38
+ "rope_type": "default"
39
+ },
40
+ "shared_kv": true,
41
+ "tie_word_embeddings": true,
42
+ "transformers_version": "5.14.1",
43
+ "use_cache": false,
44
+ "vocab_size": 131072
45
+ }
.ipynb_checkpoints/generation_config-checkpoint.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 1,
4
+ "eos_token_id": [
5
+ 0
6
+ ],
7
+ "pad_token_id": 3,
8
+ "transformers_version": "5.14.1",
9
+ "use_cache": true
10
+ }
.ipynb_checkpoints/tokenization_nandi-checkpoint.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Tokenization classes for the Nandi family."""
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from typing import Any
20
+
21
+ from tokenizers import Regex, Tokenizer, decoders, normalizers, pre_tokenizers
22
+ from tokenizers.models import BPE
23
+
24
+ from transformers.tokenization_utils_tokenizers import TokenizersBackend
25
+ from transformers.utils import logging
26
+
27
+
28
+ logger = logging.get_logger(__name__)
29
+
30
+ PRETOKENIZE_REGEX = r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?(?:\p{L}\p{M}*)+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"""
31
+
32
+ ALLOWED_TEMPLATE_LEAF_TYPES = frozenset({"string", "number", "integer", "boolean", "null"})
33
+ _IM_START = "<|im_start|>"
34
+
35
+
36
+ def normalize_extraction_template(template: Any) -> dict[str, Any]:
37
+ """Convert a type-hint template to null-placeholder schema (matches SFT/DPO training)."""
38
+ if isinstance(template, str):
39
+ try:
40
+ template = json.loads(template)
41
+ except json.JSONDecodeError as exc:
42
+ raise ValueError(f"template must be valid JSON: {exc}") from exc
43
+
44
+ if not isinstance(template, dict):
45
+ raise ValueError("template root must be a JSON object")
46
+
47
+ return _nullify_template_node(template)
48
+
49
+
50
+ def _nullify_template_node(node: Any) -> Any:
51
+ if node is None:
52
+ return None
53
+
54
+ if isinstance(node, dict):
55
+ return {key: _nullify_template_node(value) for key, value in node.items()}
56
+
57
+ if isinstance(node, list):
58
+ if len(node) == 0:
59
+ return []
60
+ if len(node) == 1:
61
+ item = node[0]
62
+ if isinstance(item, str) and item in ALLOWED_TEMPLATE_LEAF_TYPES:
63
+ return []
64
+ return [_nullify_template_node(item)]
65
+ raise ValueError(
66
+ 'array template must be [] or a one-element type list, e.g. ["string"]'
67
+ )
68
+
69
+ if isinstance(node, str):
70
+ if node in ALLOWED_TEMPLATE_LEAF_TYPES:
71
+ return None
72
+ raise ValueError(
73
+ f"invalid template leaf {node!r}; use a type name like 'string' or null"
74
+ )
75
+
76
+ if isinstance(node, bool):
77
+ raise ValueError("template leaf must be a type name, not a boolean literal")
78
+
79
+ if isinstance(node, (int, float)):
80
+ raise ValueError("template leaf must be a type name, not a numeric literal")
81
+
82
+ raise ValueError(f"unsupported template value: {node!r}")
83
+
84
+
85
+ def _maybe_add_im_start_prefix(text: str) -> str:
86
+ stripped = text.lstrip()
87
+ if stripped.startswith(_IM_START):
88
+ return text
89
+ return f"{_IM_START} {text}"
90
+
91
+
92
+ class NandiTokenizer(TokenizersBackend):
93
+ model_input_names = ["input_ids", "attention_mask"]
94
+ model = BPE
95
+
96
+ def __init__(
97
+ self,
98
+ vocab: str | dict[str, int] | None = None,
99
+ merges: str | list[str] | None = None,
100
+ vocab_file=None,
101
+ merges_file=None,
102
+ unk_token: str = "<|endoftext|>",
103
+ bos_token: str = "<|im_start|>",
104
+ eos_token: str = "<|endoftext|>",
105
+ pad_token: str = "<|pad|>",
106
+ add_prefix_space: bool | None = None,
107
+ **kwargs,
108
+ ):
109
+ self._vocab = (
110
+ vocab
111
+ if vocab is not None
112
+ else {
113
+ "<|endoftext|>": 0,
114
+ }
115
+ )
116
+ self._merges = merges or []
117
+
118
+ self._tokenizer = Tokenizer(
119
+ BPE(
120
+ vocab=self._vocab,
121
+ merges=self._merges,
122
+ dropout=None,
123
+ unk_token=None,
124
+ continuing_subword_prefix="",
125
+ end_of_word_suffix="",
126
+ fuse_unk=False,
127
+ byte_fallback=False,
128
+ )
129
+ )
130
+ self._tokenizer.decoder = decoders.ByteLevel()
131
+ self._tokenizer.normalizer = normalizers.NFC()
132
+ self._tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
133
+ [
134
+ pre_tokenizers.Split(
135
+ Regex(PRETOKENIZE_REGEX),
136
+ behavior="isolated",
137
+ invert=False,
138
+ ),
139
+ pre_tokenizers.ByteLevel(
140
+ add_prefix_space=False,
141
+ trim_offsets=True,
142
+ use_regex=False,
143
+ ),
144
+ ]
145
+ )
146
+
147
+ super().__init__(
148
+ vocab_file=vocab_file,
149
+ merges_file=merges_file,
150
+ unk_token=unk_token,
151
+ bos_token=bos_token,
152
+ eos_token=eos_token,
153
+ pad_token=pad_token,
154
+ add_prefix_space=add_prefix_space,
155
+ **kwargs,
156
+ )
157
+
158
+ def apply_chat_template(self, conversation, *args, **kwargs):
159
+ """Support extraction inference via input_text + template (type hints)."""
160
+ template = kwargs.pop("template", None)
161
+ input_text = kwargs.get("input_text")
162
+ json_schema = kwargs.get("json_schema")
163
+
164
+ if template is not None:
165
+ if json_schema is not None:
166
+ raise ValueError("Pass either template or json_schema, not both.")
167
+ if input_text is None:
168
+ raise ValueError("input_text is required when template is provided.")
169
+ null_schema = normalize_extraction_template(template)
170
+ kwargs["json_schema"] = json.dumps(null_schema, ensure_ascii=False)
171
+ elif json_schema is not None and not isinstance(json_schema, str):
172
+ if isinstance(json_schema, (dict, list)):
173
+ kwargs["json_schema"] = json.dumps(json_schema, ensure_ascii=False)
174
+
175
+ return super().apply_chat_template(conversation, *args, **kwargs)
176
+
177
+ normalize_template = staticmethod(normalize_extraction_template)
178
+
179
+ def __call__(self, text, *args, **kwargs):
180
+ add_special_tokens = kwargs.get("add_special_tokens", False)
181
+
182
+ if not add_special_tokens:
183
+ if isinstance(text, list):
184
+ text = [_maybe_add_im_start_prefix(t) if isinstance(t, str) else t for t in text]
185
+ elif isinstance(text, str):
186
+ text = _maybe_add_im_start_prefix(text)
187
+
188
+ return super().__call__(text, *args, **kwargs)
189
+
190
+ def encode(
191
+ self,
192
+ text,
193
+ text_pair=None,
194
+ add_special_tokens: bool = True,
195
+ padding=False,
196
+ truncation=None,
197
+ max_length=None,
198
+ stride: int = 0,
199
+ padding_side=None,
200
+ return_tensors=None,
201
+ **kwargs,
202
+ ):
203
+ if isinstance(text, str):
204
+ text = _maybe_add_im_start_prefix(text)
205
+ return super().encode(
206
+ text,
207
+ text_pair=text_pair,
208
+ add_special_tokens=add_special_tokens,
209
+ padding=padding,
210
+ truncation=truncation,
211
+ max_length=max_length,
212
+ stride=stride,
213
+ padding_side=padding_side,
214
+ return_tensors=return_tensors,
215
+ **kwargs,
216
+ )
217
+
218
+
219
+ __all__ = ["NandiTokenizer", "normalize_extraction_template"]
.ipynb_checkpoints/tokenizer_config-checkpoint.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|im_start|>",
4
+ "eos_token": "<|endoftext|>",
5
+ "model_max_length": 12288,
6
+ "pad_token": "<|pad|>",
7
+ "tokenizer_class": "TokenizersBackend",
8
+ "unk_token": "<|endoftext|>"
9
+ }
chat_template.jinja ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {# Lumma schema-conditioned extraction chat template.
2
+ #
3
+ # INFERENCE: pass input_text + template (type hints) or input_text + json_schema (null schema).
4
+ # tokenizer.apply_chat_template(
5
+ # [],
6
+ # input_text="John Smith joined Acme.",
7
+ # template={"name": "string", "company": "string"},
8
+ # add_generation_prompt=True,
9
+ # )
10
+ # NandiTokenizer converts template -> null schema via json.dumps before rendering.
11
+ #
12
+ # TRAINING: pass full messages (system / user / assistant) as usual.
13
+ # #}
14
+ {%- set extraction_system = "You are Lumma, an AI model developed by FrontiersMind. You will be provided with an input text and a JSON schema. Your task is to extract information from the input text strictly according to the provided schema. Populate only the fields defined in the schema. If a field cannot be determined from the input text, set its value to null. Do not infer, assume, rewrite, rephrase, summarize, or generate any information that is not explicitly present in the input text. Return only a valid JSON object that exactly follows the provided schema." -%}
15
+ {%- if input_text is defined and json_schema is defined -%}
16
+ <|im_start|><|system|>{{ '\n' }}{{ extraction_system }}<|endoftext|>{{ '\n' }}
17
+ <|im_start|><|user|>{{ '\n' }}Input-Text: {{ input_text }}
18
+
19
+ JSON-Schema: {%- if json_schema is string -%}{{ json_schema }}{%- else -%}{{ json_schema | tojson }}{%- endif -%}<|endoftext|>{{ '\n' }}
20
+ {%- else -%}
21
+ {%- for message in messages -%}
22
+ {%- if message["role"] == "system" -%}
23
+ <|im_start|><|system|>{{ '\n' }}{{ message["content"] }}<|endoftext|>{{ '\n' }}
24
+ {%- elif message["role"] == "user" -%}
25
+ <|im_start|><|user|>{{ '\n' }}{{ message["content"] }}<|endoftext|>{{ '\n' }}
26
+ {%- elif message["role"] == "assistant" -%}
27
+ <|im_start|><|assistant|>{{ '\n' }}{%- generation -%}{{ message["content"] }}<|endoftext|>{{ '\n' }}{%- endgeneration -%}
28
+ {%- endif -%}
29
+ {%- endfor -%}
30
+ {%- endif -%}
31
+ {%- if add_generation_prompt -%}
32
+ <|im_start|><|assistant|>{{ '\n' }}
33
+ {%- endif -%}
config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "NandiForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "auto_map": {
8
+ "AutoConfig": "configuration_nandi.NandiConfig",
9
+ "AutoModel": "modeling_nandi.NandiModel",
10
+ "AutoModelForCausalLM": "modeling_nandi.NandiForCausalLM"
11
+ },
12
+ "bos_token_id": 1,
13
+ "dtype": "bfloat16",
14
+ "embedding_rank": 512,
15
+ "eos_token_id": 0,
16
+ "factorized_embedding": true,
17
+ "head_dim": 90,
18
+ "hidden_act": "silu",
19
+ "hidden_size": 1440,
20
+ "initializer_range": 0.02,
21
+ "intermediate_size": 3280,
22
+ "kv_cache_mode": "shared",
23
+ "layer_sharing": false,
24
+ "layer_sharing_repeats": 1,
25
+ "max_position_embeddings": 12288,
26
+ "mlp_bias": false,
27
+ "model_type": "nandi",
28
+ "num_attention_heads": 16,
29
+ "num_hidden_layers": 30,
30
+ "num_key_value_heads": 8,
31
+ "pad_token_id": 3,
32
+ "pretraining_tp": 1,
33
+ "q_norm": true,
34
+ "qk_norm": false,
35
+ "rms_norm_eps": 1e-06,
36
+ "rope_parameters": {
37
+ "rope_theta": 1000000.0,
38
+ "rope_type": "default"
39
+ },
40
+ "shared_kv": true,
41
+ "tie_word_embeddings": true,
42
+ "transformers_version": "5.14.1",
43
+ "use_cache": false,
44
+ "vocab_size": 131072
45
+ }
configuration_nandi.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers.configuration_utils import PretrainedConfig
2
+
3
+
4
+ class NandiConfig(PretrainedConfig):
5
+ r"""
6
+ Configuration class for the Nandi model.
7
+
8
+ Example:
9
+
10
+ ```python
11
+ >>> from transformers import AutoConfig, AutoModelForCausalLM
12
+
13
+ >>> configuration = AutoConfig.from_pretrained("Rta-AILabs/Nandi-500M-remote", trust_remote_code=True)
14
+
15
+ >>> model = AutoModelForCausalLM.from_pretrained("Rta-AILabs/Nandi-500M-remote", trust_remote_code=True)
16
+
17
+ >>> configuration = model.config
18
+ ```
19
+ """
20
+
21
+ model_type = "nandi"
22
+ keys_to_ignore_at_inference = ["past_key_values"]
23
+
24
+ base_model_tp_plan = {
25
+ "layers.*.self_attn.q_proj": "colwise",
26
+ "layers.*.self_attn.k_proj": "colwise",
27
+ "layers.*.self_attn.v_proj": "colwise",
28
+ "layers.*.self_attn.o_proj": "rowwise",
29
+ "layers.*.mlp.gate_proj": "colwise",
30
+ "layers.*.mlp.up_proj": "colwise",
31
+ "layers.*.mlp.down_proj": "rowwise",
32
+ }
33
+
34
+ def __init__(
35
+ self,
36
+ vocab_size=131072,
37
+ hidden_size=1248,
38
+ intermediate_size=3556,
39
+ num_hidden_layers=28,
40
+ num_attention_heads=16,
41
+ num_key_value_heads=8,
42
+ head_dim=None,
43
+ hidden_act="silu",
44
+ max_position_embeddings=2048,
45
+ initializer_range=0.008,
46
+ rms_norm_eps=1e-6,
47
+ use_cache=True,
48
+ pad_token_id=None,
49
+ bos_token_id=1,
50
+ eos_token_id=0,
51
+ pretraining_tp=1,
52
+ tie_word_embeddings=True,
53
+ rope_parameters=None,
54
+ attention_bias=False,
55
+ attention_dropout=0.0,
56
+ mlp_bias=False,
57
+ factorized_embedding=False,
58
+ embedding_rank=768,
59
+ layer_sharing=False,
60
+ layer_sharing_repeats=1,
61
+ qk_norm=True,
62
+ q_norm=False,
63
+ shared_kv=True,
64
+ kv_cache_mode="shared",
65
+ **kwargs,
66
+ ):
67
+ self.vocab_size = vocab_size
68
+ self.hidden_size = hidden_size
69
+ self.intermediate_size = intermediate_size
70
+ self.num_hidden_layers = num_hidden_layers
71
+ self.num_attention_heads = num_attention_heads
72
+ self.num_key_value_heads = num_key_value_heads if num_key_value_heads is not None else num_attention_heads
73
+ self.head_dim = head_dim if head_dim is not None else hidden_size // num_attention_heads
74
+ self.hidden_act = hidden_act
75
+ self.max_position_embeddings = max_position_embeddings
76
+ self.initializer_range = initializer_range
77
+ self.rms_norm_eps = rms_norm_eps
78
+ self.use_cache = use_cache
79
+ self.pretraining_tp = pretraining_tp
80
+ self.rope_parameters = rope_parameters if rope_parameters is not None else {"rope_theta": 1000000.0}
81
+ self.attention_bias = attention_bias
82
+ self.attention_dropout = attention_dropout
83
+ self.mlp_bias = mlp_bias
84
+ self.factorized_embedding = factorized_embedding
85
+ self.embedding_rank = embedding_rank
86
+ self.layer_sharing = layer_sharing
87
+
88
+ self.layer_sharing_repeats = max(1, int(layer_sharing_repeats or 1))
89
+ if qk_norm and q_norm:
90
+ raise ValueError("Use either qk_norm or q_norm, not both")
91
+ self.qk_norm = qk_norm
92
+ self.q_norm = q_norm
93
+
94
+ self.shared_kv = shared_kv
95
+
96
+ if kv_cache_mode not in ("shared", "vanilla"):
97
+ raise ValueError(
98
+ f"`kv_cache_mode` must be 'shared' or 'vanilla', got {kv_cache_mode!r}."
99
+ )
100
+ self.kv_cache_mode = kv_cache_mode
101
+
102
+ if self.factorized_embedding and self.embedding_rank <= 0:
103
+ raise ValueError(
104
+ f"`embedding_rank` must be positive when `factorized_embedding=True`, got {self.embedding_rank}."
105
+ )
106
+ if self.hidden_size % self.num_attention_heads != 0:
107
+ raise ValueError(
108
+ f"`hidden_size` ({self.hidden_size}) must be divisible by "
109
+ f"`num_attention_heads` ({self.num_attention_heads})."
110
+ )
111
+ if self.layer_sharing_repeats < 1:
112
+ raise ValueError(f"`layer_sharing_repeats` must be >= 1, got {self.layer_sharing_repeats}.")
113
+
114
+ super().__init__(
115
+ pad_token_id=pad_token_id,
116
+ bos_token_id=bos_token_id,
117
+ eos_token_id=eos_token_id,
118
+ tie_word_embeddings=tie_word_embeddings,
119
+ **kwargs,
120
+ )
121
+
122
+
123
+ __all__ = ["NandiConfig"]
generation_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 1,
4
+ "eos_token_id": [
5
+ 0
6
+ ],
7
+ "pad_token_id": 3,
8
+ "transformers_version": "5.14.1",
9
+ "use_cache": true
10
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3424f17dd80518a25878cf1c8f9816a9276ce0422ce38232648d975dbb32a085
3
+ size 1298775408
modeling_nandi.py ADDED
@@ -0,0 +1,541 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Callable
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+ from transformers.activations import ACT2FN
7
+ from transformers.cache_utils import Cache, DynamicCache, DynamicLayer
8
+ from transformers.generation import GenerationMixin
9
+ from transformers.integrations import use_kernel_forward_from_hub
10
+ from transformers.masking_utils import create_causal_mask
11
+ from transformers.modeling_layers import GradientCheckpointingLayer
12
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
13
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
14
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
15
+ from transformers.processing_utils import Unpack
16
+ from transformers.utils import TransformersKwargs, auto_docstring
17
+ from transformers.utils.deprecation import deprecate_kwarg
18
+ from transformers.utils.generic import can_return_tuple, merge_with_config_defaults
19
+ from transformers.utils.output_capturing import capture_outputs
20
+ from .configuration_nandi import NandiConfig
21
+
22
+
23
+ @use_kernel_forward_from_hub("RMSNorm")
24
+ class NandiRMSNorm(nn.Module):
25
+ def __init__(self, hidden_size, eps=1e-6):
26
+ super().__init__()
27
+ self.weight = nn.Parameter(torch.ones(hidden_size))
28
+ self.variance_epsilon = eps
29
+
30
+ def forward(self, hidden_states):
31
+ input_dtype = hidden_states.dtype
32
+ hidden_states = hidden_states.to(torch.float32)
33
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
34
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
35
+ return self.weight * hidden_states.to(input_dtype)
36
+
37
+ def extra_repr(self):
38
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
39
+
40
+
41
+ class NandiRotaryEmbedding(nn.Module):
42
+ inv_freq: torch.Tensor
43
+
44
+ def __init__(self, config: NandiConfig, device=None):
45
+ super().__init__()
46
+ self.max_seq_len_cached = config.max_position_embeddings
47
+ self.original_max_seq_len = config.max_position_embeddings
48
+
49
+ self.config = config
50
+ self.rope_type = self.config.rope_parameters.get("rope_type", "default")
51
+ rope_init_fn: Callable = self.compute_default_rope_parameters
52
+ if self.rope_type != "default":
53
+ rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
54
+ inv_freq, self.attention_scaling = rope_init_fn(self.config, device)
55
+
56
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
57
+ self.register_buffer("original_inv_freq", inv_freq.clone(), persistent=False)
58
+
59
+ @staticmethod
60
+ def compute_default_rope_parameters(
61
+ config: NandiConfig | None = None,
62
+ device: torch.device | None = None,
63
+ seq_len: int | None = None,
64
+ ) -> tuple[torch.Tensor, float]:
65
+ del seq_len
66
+ base = config.rope_parameters["rope_theta"]
67
+ dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
68
+ attention_factor = 1.0
69
+ inv_freq = 1.0 / (
70
+ base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
71
+ )
72
+ return inv_freq, attention_factor
73
+
74
+ @torch.no_grad()
75
+ @dynamic_rope_update
76
+ def forward(self, x, position_ids):
77
+ inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
78
+ position_ids_expanded = position_ids[:, None, :].float()
79
+
80
+ device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
81
+ with torch.autocast(device_type=device_type, enabled=False):
82
+ freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
83
+ emb = torch.cat((freqs, freqs), dim=-1)
84
+ cos = emb.cos() * self.attention_scaling
85
+ sin = emb.sin() * self.attention_scaling
86
+
87
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
88
+
89
+
90
+ def rotate_half(x):
91
+ """Rotates half the hidden dims of the input."""
92
+ x1 = x[..., : x.shape[-1] // 2]
93
+ x2 = x[..., x.shape[-1] // 2 :]
94
+ return torch.cat((-x2, x1), dim=-1)
95
+
96
+
97
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
98
+ del position_ids
99
+ cos = cos.unsqueeze(unsqueeze_dim)
100
+ sin = sin.unsqueeze(unsqueeze_dim)
101
+ q_embed = (q * cos) + (rotate_half(q) * sin)
102
+ k_embed = (k * cos) + (rotate_half(k) * sin)
103
+ return q_embed, k_embed
104
+
105
+
106
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
107
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
108
+ if n_rep == 1:
109
+ return hidden_states
110
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
111
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
112
+
113
+
114
+ def eager_attention_forward(
115
+ module: nn.Module,
116
+ query: torch.Tensor,
117
+ key: torch.Tensor,
118
+ value: torch.Tensor,
119
+ attention_mask: torch.Tensor | None,
120
+ scaling: float,
121
+ dropout: float = 0.0,
122
+ **kwargs: Unpack[TransformersKwargs],
123
+ ):
124
+ del kwargs
125
+ key_states = repeat_kv(key, module.num_key_value_groups)
126
+ value_states = repeat_kv(value, module.num_key_value_groups)
127
+
128
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
129
+ if attention_mask is not None:
130
+ causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
131
+ attn_weights = attn_weights + causal_mask
132
+
133
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
134
+ attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
135
+ attn_output = torch.matmul(attn_weights, value_states)
136
+ attn_output = attn_output.transpose(1, 2).contiguous()
137
+
138
+ return attn_output, attn_weights
139
+
140
+
141
+ class NandiAttention(nn.Module):
142
+ def __init__(self, config: NandiConfig, layer_idx: int):
143
+ super().__init__()
144
+ self.config = config
145
+ self.layer_idx = layer_idx
146
+ self.head_dim = config.head_dim
147
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
148
+ self.scaling = self.head_dim**-0.5
149
+ self.attention_dropout = config.attention_dropout
150
+ self.is_causal = True
151
+
152
+ self.shared_kv = getattr(config, "shared_kv", False)
153
+
154
+ self.q_proj = nn.Linear(
155
+ config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
156
+ )
157
+ self.k_proj = nn.Linear(
158
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
159
+ )
160
+ if self.shared_kv:
161
+ self.v_proj = None
162
+ else:
163
+ self.v_proj = nn.Linear(
164
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
165
+ )
166
+ self.o_proj = nn.Linear(
167
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
168
+ )
169
+
170
+ self.qk_norm = getattr(config, "qk_norm", False)
171
+ self.q_norm_enabled = getattr(config, "q_norm", False)
172
+ if self.qk_norm:
173
+ self.q_norm = NandiRMSNorm(self.head_dim, eps=config.rms_norm_eps)
174
+ self.k_norm = NandiRMSNorm(self.head_dim, eps=config.rms_norm_eps)
175
+ elif self.q_norm_enabled:
176
+ self.q_norm = NandiRMSNorm(self.head_dim, eps=config.rms_norm_eps)
177
+ self.k_norm = None
178
+ else:
179
+ self.q_norm = None
180
+ self.k_norm = None
181
+
182
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
183
+ def forward(
184
+ self,
185
+ hidden_states: torch.Tensor,
186
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
187
+ attention_mask: torch.Tensor | None,
188
+ past_key_values: Cache | None = None,
189
+ **kwargs: Unpack[TransformersKwargs],
190
+ ) -> tuple[torch.Tensor, torch.Tensor]:
191
+ input_shape = hidden_states.shape[:-1]
192
+ hidden_shape = (*input_shape, -1, self.head_dim)
193
+
194
+ query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
195
+ k_raw = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
196
+
197
+ if self.shared_kv:
198
+ kv_cache_mode = getattr(self.config, "kv_cache_mode", "shared")
199
+
200
+ if self.qk_norm:
201
+ query_states = self.q_norm(query_states)
202
+ elif self.q_norm_enabled:
203
+ query_states = self.q_norm(query_states)
204
+
205
+ if kv_cache_mode == "shared":
206
+ if past_key_values is not None:
207
+ empty_v = torch.empty(
208
+ k_raw.shape[0],
209
+ k_raw.shape[1],
210
+ 0,
211
+ k_raw.shape[3],
212
+ device=k_raw.device,
213
+ dtype=k_raw.dtype,
214
+ )
215
+ k_raw_full, _ = past_key_values.update(k_raw, empty_v, self.layer_idx)
216
+ else:
217
+ k_raw_full = k_raw
218
+
219
+ value_states = k_raw_full
220
+ key_states = self.k_norm(k_raw_full) if self.qk_norm else k_raw_full
221
+
222
+ cos, sin = position_embeddings
223
+ q_len = query_states.shape[-2]
224
+ cos_q = cos[..., -q_len:, :]
225
+ sin_q = sin[..., -q_len:, :]
226
+ query_states, _ = apply_rotary_pos_emb(query_states, query_states, cos_q, sin_q)
227
+ _, key_states = apply_rotary_pos_emb(key_states, key_states, cos, sin)
228
+
229
+ else:
230
+ key_states = self.k_norm(k_raw) if self.qk_norm else k_raw
231
+ value_states = k_raw
232
+
233
+ cos, sin = position_embeddings
234
+ query_states, key_states = apply_rotary_pos_emb(
235
+ query_states, key_states, cos, sin
236
+ )
237
+
238
+ if past_key_values is not None:
239
+ key_states, value_states = past_key_values.update(
240
+ key_states, value_states, self.layer_idx
241
+ )
242
+
243
+ else:
244
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
245
+ key_states = k_raw
246
+
247
+ if self.qk_norm:
248
+ query_states = self.q_norm(query_states)
249
+ key_states = self.k_norm(key_states)
250
+ elif self.q_norm_enabled:
251
+ query_states = self.q_norm(query_states)
252
+
253
+ cos, sin = position_embeddings
254
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
255
+
256
+ if past_key_values is not None:
257
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)
258
+
259
+ attention_interface: Callable = eager_attention_forward
260
+ if self.config._attn_implementation != "eager":
261
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
262
+
263
+ attn_output, attn_weights = attention_interface(
264
+ self,
265
+ query_states,
266
+ key_states,
267
+ value_states,
268
+ attention_mask,
269
+ dropout=0.0 if not self.training else self.attention_dropout,
270
+ scaling=self.scaling,
271
+ **kwargs,
272
+ )
273
+
274
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
275
+ attn_output = self.o_proj(attn_output)
276
+ return attn_output, attn_weights
277
+
278
+
279
+ class NandiMLP(nn.Module):
280
+ def __init__(self, config):
281
+ super().__init__()
282
+ self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=config.mlp_bias)
283
+ self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=config.mlp_bias)
284
+ self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=config.mlp_bias)
285
+ self.act_fn = ACT2FN[config.hidden_act]
286
+
287
+ def forward(self, x):
288
+ return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
289
+
290
+
291
+ class NandiDecoderLayer(GradientCheckpointingLayer):
292
+ def __init__(self, config: NandiConfig, layer_idx: int):
293
+ super().__init__()
294
+ self.hidden_size = config.hidden_size
295
+ self.self_attn = NandiAttention(config=config, layer_idx=layer_idx)
296
+ self.mlp = NandiMLP(config)
297
+ self.input_layernorm = NandiRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
298
+ self.post_attention_layernorm = NandiRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
299
+
300
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
301
+ def forward(
302
+ self,
303
+ hidden_states: torch.Tensor,
304
+ attention_mask: torch.Tensor | None = None,
305
+ position_ids: torch.LongTensor | None = None,
306
+ past_key_values: Cache | None = None,
307
+ use_cache: bool | None = False,
308
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
309
+ **kwargs: Unpack[TransformersKwargs],
310
+ ) -> torch.Tensor:
311
+ residual = hidden_states
312
+ hidden_states = self.input_layernorm(hidden_states)
313
+
314
+ hidden_states, _ = self.self_attn(
315
+ hidden_states=hidden_states,
316
+ attention_mask=attention_mask,
317
+ position_ids=position_ids,
318
+ past_key_values=past_key_values,
319
+ use_cache=use_cache,
320
+ position_embeddings=position_embeddings,
321
+ **kwargs,
322
+ )
323
+ hidden_states = residual + hidden_states
324
+
325
+ residual = hidden_states
326
+ hidden_states = self.post_attention_layernorm(hidden_states)
327
+ hidden_states = self.mlp(hidden_states)
328
+ hidden_states = residual + hidden_states
329
+ return hidden_states
330
+
331
+
332
+ class _VirtualLayerCache:
333
+ """Proxy that shifts cache layer indices by `offset` to give each repeat its own virtual slots."""
334
+
335
+ def __init__(self, cache: Cache, offset: int):
336
+ self._cache = cache
337
+ self._offset = offset
338
+
339
+ def __getattr__(self, name):
340
+ return getattr(self._cache, name)
341
+
342
+ def update(self, key_states, value_states, layer_idx, cache_kwargs=None):
343
+ virtual_idx = layer_idx + self._offset
344
+ # grow the backing cache if generate() pre-allocated fewer slots than needed
345
+ while len(self._cache.layers) <= virtual_idx:
346
+ self._cache.layers.append(DynamicLayer())
347
+ return self._cache.update(key_states, value_states, virtual_idx, cache_kwargs)
348
+
349
+ def get_seq_length(self, layer_idx: int = 0) -> int:
350
+ return self._cache.get_seq_length(layer_idx + self._offset)
351
+
352
+
353
+ @auto_docstring
354
+ class NandiPreTrainedModel(PreTrainedModel):
355
+ config: NandiConfig
356
+ base_model_prefix = "model"
357
+ supports_gradient_checkpointing = True
358
+ _no_split_modules = ["NandiDecoderLayer"]
359
+ _skip_keys_device_placement = ["past_key_values"]
360
+ _supports_flash_attn = True
361
+ _supports_sdpa = True
362
+ _supports_flex_attn = True
363
+ _can_compile_fullgraph = True
364
+ _supports_attention_backend = True
365
+ _can_record_outputs = {
366
+ "hidden_states": NandiDecoderLayer,
367
+ "attentions": NandiAttention,
368
+ }
369
+
370
+ def __init__(self, config: NandiConfig):
371
+ super().__init__(config)
372
+
373
+
374
+ @auto_docstring
375
+ class NandiModel(NandiPreTrainedModel):
376
+ def __init__(self, config: NandiConfig):
377
+ super().__init__(config)
378
+ self.padding_idx = config.pad_token_id
379
+ self.vocab_size = config.vocab_size
380
+ embedding_dim = config.embedding_rank if config.factorized_embedding else config.hidden_size
381
+
382
+ self.embed_tokens = nn.Embedding(config.vocab_size, embedding_dim, self.padding_idx)
383
+ self.embedding_proj = (
384
+ nn.Linear(config.embedding_rank, config.hidden_size, bias=False) if config.factorized_embedding else None
385
+ )
386
+ self.layers = nn.ModuleList(
387
+ [NandiDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
388
+ )
389
+ self.norm = NandiRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
390
+ self.rotary_emb = NandiRotaryEmbedding(config=config)
391
+ self.gradient_checkpointing = False
392
+
393
+ self.post_init()
394
+
395
+ @merge_with_config_defaults
396
+ @capture_outputs
397
+ @auto_docstring
398
+ def forward(
399
+ self,
400
+ input_ids: torch.LongTensor | None = None,
401
+ attention_mask: torch.Tensor | None = None,
402
+ position_ids: torch.LongTensor | None = None,
403
+ past_key_values: Cache | None = None,
404
+ inputs_embeds: torch.FloatTensor | None = None,
405
+ use_cache: bool | None = None,
406
+ **kwargs: Unpack[TransformersKwargs],
407
+ ) -> BaseModelOutputWithPast:
408
+ if (input_ids is None) ^ (inputs_embeds is not None):
409
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
410
+
411
+ if inputs_embeds is None:
412
+ inputs_embeds = self.embed_tokens(input_ids)
413
+
414
+ if self.embedding_proj is not None:
415
+ inputs_embeds = self.embedding_proj(inputs_embeds)
416
+ repeats = max(1, int(getattr(self.config, "layer_sharing_repeats", 1) or 1))
417
+
418
+ if use_cache and past_key_values is None:
419
+ past_key_values = DynamicCache()
420
+
421
+ if position_ids is None:
422
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
423
+ position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_seen_tokens
424
+ position_ids = position_ids.unsqueeze(0)
425
+
426
+ causal_mask = create_causal_mask(
427
+ config=self.config,
428
+ inputs_embeds=inputs_embeds,
429
+ attention_mask=attention_mask,
430
+ past_key_values=past_key_values,
431
+ position_ids=position_ids,
432
+ )
433
+
434
+ hidden_states = inputs_embeds
435
+ kv_cache_mode = getattr(self.config, "kv_cache_mode", "shared")
436
+ if (
437
+ getattr(self.config, "shared_kv", False)
438
+ and kv_cache_mode == "shared"
439
+ and past_key_values is not None
440
+ ):
441
+ past_len = past_key_values.get_seq_length(0)
442
+ cur_len = inputs_embeds.shape[1]
443
+ full_position_ids = torch.arange(
444
+ past_len + cur_len, device=inputs_embeds.device
445
+ ).unsqueeze(0)
446
+ position_embeddings = self.rotary_emb(hidden_states, position_ids=full_position_ids)
447
+ else:
448
+ position_embeddings = self.rotary_emb(hidden_states, position_ids=position_ids)
449
+
450
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
451
+ for repeat_idx in range(repeats):
452
+
453
+ repeat_cache = (
454
+ _VirtualLayerCache(past_key_values, repeat_idx * self.config.num_hidden_layers)
455
+ if (past_key_values is not None and repeat_idx > 0)
456
+ else past_key_values
457
+ )
458
+ hidden_states = decoder_layer(
459
+ hidden_states,
460
+ attention_mask=causal_mask,
461
+ position_embeddings=position_embeddings,
462
+ position_ids=position_ids,
463
+ past_key_values=repeat_cache,
464
+ use_cache=use_cache,
465
+ **kwargs,
466
+ )
467
+
468
+ hidden_states = self.norm(hidden_states)
469
+ return BaseModelOutputWithPast(
470
+ last_hidden_state=hidden_states,
471
+ past_key_values=past_key_values,
472
+ )
473
+
474
+
475
+ @auto_docstring
476
+ class NandiForCausalLM(NandiPreTrainedModel, GenerationMixin):
477
+ _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
478
+ _tp_plan = {"lm_head": "colwise_gather_output"}
479
+ _pp_plan = {
480
+ "lm_head_proj": (["hidden_states"], ["hidden_states"]),
481
+ "lm_head": (["hidden_states"], ["logits"]),
482
+ }
483
+
484
+ def __init__(self, config):
485
+ super().__init__(config)
486
+ self.model = NandiModel(config)
487
+ self.vocab_size = config.vocab_size
488
+
489
+ lm_head_in_features = config.embedding_rank if config.factorized_embedding else config.hidden_size
490
+ self.lm_head_proj = (
491
+ nn.Linear(config.hidden_size, config.embedding_rank, bias=False) if config.factorized_embedding else None
492
+ )
493
+ self.lm_head = nn.Linear(lm_head_in_features, config.vocab_size, bias=False)
494
+
495
+ self.post_init()
496
+
497
+ @can_return_tuple
498
+ @auto_docstring
499
+ def forward(
500
+ self,
501
+ input_ids: torch.LongTensor | None = None,
502
+ attention_mask: torch.Tensor | None = None,
503
+ position_ids: torch.LongTensor | None = None,
504
+ past_key_values: Cache | None = None,
505
+ inputs_embeds: torch.FloatTensor | None = None,
506
+ labels: torch.LongTensor | None = None,
507
+ use_cache: bool | None = None,
508
+ logits_to_keep: int | torch.Tensor = 0,
509
+ **kwargs: Unpack[TransformersKwargs],
510
+ ) -> CausalLMOutputWithPast:
511
+ outputs: BaseModelOutputWithPast = self.model(
512
+ input_ids=input_ids,
513
+ attention_mask=attention_mask,
514
+ position_ids=position_ids,
515
+ past_key_values=past_key_values,
516
+ inputs_embeds=inputs_embeds,
517
+ use_cache=use_cache,
518
+ **kwargs,
519
+ )
520
+
521
+ hidden_states = outputs.last_hidden_state
522
+ if self.lm_head_proj is not None:
523
+ hidden_states = self.lm_head_proj(hidden_states)
524
+
525
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
526
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
527
+
528
+ loss = None
529
+ if labels is not None:
530
+ loss = self.loss_function(logits=logits, labels=labels, vocab_size=self.config.vocab_size, **kwargs)
531
+
532
+ return CausalLMOutputWithPast(
533
+ loss=loss,
534
+ logits=logits,
535
+ past_key_values=outputs.past_key_values,
536
+ hidden_states=outputs.hidden_states,
537
+ attentions=outputs.attentions,
538
+ )
539
+
540
+
541
+ __all__ = ["NandiPreTrainedModel", "NandiModel", "NandiForCausalLM"]
tokenization_nandi.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Tokenization classes for the Nandi family."""
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from typing import Any
20
+
21
+ from tokenizers import Regex, Tokenizer, decoders, normalizers, pre_tokenizers
22
+ from tokenizers.models import BPE
23
+
24
+ from transformers.tokenization_utils_tokenizers import TokenizersBackend
25
+ from transformers.utils import logging
26
+
27
+
28
+ logger = logging.get_logger(__name__)
29
+
30
+ PRETOKENIZE_REGEX = r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?(?:\p{L}\p{M}*)+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"""
31
+
32
+ ALLOWED_TEMPLATE_LEAF_TYPES = frozenset({"string", "number", "integer", "boolean", "null"})
33
+ _IM_START = "<|im_start|>"
34
+
35
+
36
+ def normalize_extraction_template(template: Any) -> dict[str, Any]:
37
+ """Convert a type-hint template to null-placeholder schema (matches SFT/DPO training)."""
38
+ if isinstance(template, str):
39
+ try:
40
+ template = json.loads(template)
41
+ except json.JSONDecodeError as exc:
42
+ raise ValueError(f"template must be valid JSON: {exc}") from exc
43
+
44
+ if not isinstance(template, dict):
45
+ raise ValueError("template root must be a JSON object")
46
+
47
+ return _nullify_template_node(template)
48
+
49
+
50
+ def _nullify_template_node(node: Any) -> Any:
51
+ if node is None:
52
+ return None
53
+
54
+ if isinstance(node, dict):
55
+ return {key: _nullify_template_node(value) for key, value in node.items()}
56
+
57
+ if isinstance(node, list):
58
+ if len(node) == 0:
59
+ return []
60
+ if len(node) == 1:
61
+ item = node[0]
62
+ if isinstance(item, str) and item in ALLOWED_TEMPLATE_LEAF_TYPES:
63
+ return []
64
+ return [_nullify_template_node(item)]
65
+ raise ValueError(
66
+ 'array template must be [] or a one-element type list, e.g. ["string"]'
67
+ )
68
+
69
+ if isinstance(node, str):
70
+ if node in ALLOWED_TEMPLATE_LEAF_TYPES:
71
+ return None
72
+ raise ValueError(
73
+ f"invalid template leaf {node!r}; use a type name like 'string' or null"
74
+ )
75
+
76
+ if isinstance(node, bool):
77
+ raise ValueError("template leaf must be a type name, not a boolean literal")
78
+
79
+ if isinstance(node, (int, float)):
80
+ raise ValueError("template leaf must be a type name, not a numeric literal")
81
+
82
+ raise ValueError(f"unsupported template value: {node!r}")
83
+
84
+
85
+ def _maybe_add_im_start_prefix(text: str) -> str:
86
+ stripped = text.lstrip()
87
+ if stripped.startswith(_IM_START):
88
+ return text
89
+ return f"{_IM_START} {text}"
90
+
91
+
92
+ class NandiTokenizer(TokenizersBackend):
93
+ model_input_names = ["input_ids", "attention_mask"]
94
+ model = BPE
95
+
96
+ def __init__(
97
+ self,
98
+ vocab: str | dict[str, int] | None = None,
99
+ merges: str | list[str] | None = None,
100
+ vocab_file=None,
101
+ merges_file=None,
102
+ unk_token: str = "<|endoftext|>",
103
+ bos_token: str = "<|im_start|>",
104
+ eos_token: str = "<|endoftext|>",
105
+ pad_token: str = "<|pad|>",
106
+ add_prefix_space: bool | None = None,
107
+ **kwargs,
108
+ ):
109
+ self._vocab = (
110
+ vocab
111
+ if vocab is not None
112
+ else {
113
+ "<|endoftext|>": 0,
114
+ }
115
+ )
116
+ self._merges = merges or []
117
+
118
+ self._tokenizer = Tokenizer(
119
+ BPE(
120
+ vocab=self._vocab,
121
+ merges=self._merges,
122
+ dropout=None,
123
+ unk_token=None,
124
+ continuing_subword_prefix="",
125
+ end_of_word_suffix="",
126
+ fuse_unk=False,
127
+ byte_fallback=False,
128
+ )
129
+ )
130
+ self._tokenizer.decoder = decoders.ByteLevel()
131
+ self._tokenizer.normalizer = normalizers.NFC()
132
+ self._tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
133
+ [
134
+ pre_tokenizers.Split(
135
+ Regex(PRETOKENIZE_REGEX),
136
+ behavior="isolated",
137
+ invert=False,
138
+ ),
139
+ pre_tokenizers.ByteLevel(
140
+ add_prefix_space=False,
141
+ trim_offsets=True,
142
+ use_regex=False,
143
+ ),
144
+ ]
145
+ )
146
+
147
+ super().__init__(
148
+ vocab_file=vocab_file,
149
+ merges_file=merges_file,
150
+ unk_token=unk_token,
151
+ bos_token=bos_token,
152
+ eos_token=eos_token,
153
+ pad_token=pad_token,
154
+ add_prefix_space=add_prefix_space,
155
+ **kwargs,
156
+ )
157
+
158
+ def apply_chat_template(self, conversation=None, *args, **kwargs):
159
+ """Support extraction inference via input_text + template (type hints)."""
160
+ template = kwargs.pop("template", None)
161
+ input_text = kwargs.get("input_text")
162
+ json_schema = kwargs.get("json_schema")
163
+
164
+ extraction_mode = (
165
+ template is not None or input_text is not None or json_schema is not None
166
+ )
167
+
168
+ if template is not None:
169
+ if json_schema is not None:
170
+ raise ValueError("Pass either template or json_schema, not both.")
171
+ if input_text is None:
172
+ raise ValueError("input_text is required when template is provided.")
173
+ null_schema = normalize_extraction_template(template)
174
+ kwargs["json_schema"] = json.dumps(null_schema, ensure_ascii=False)
175
+ elif json_schema is not None and not isinstance(json_schema, str):
176
+ if isinstance(json_schema, (dict, list)):
177
+ kwargs["json_schema"] = json.dumps(json_schema, ensure_ascii=False)
178
+
179
+ if extraction_mode:
180
+ if not conversation:
181
+ conversation = None
182
+ elif conversation is None:
183
+ raise ValueError(
184
+ "conversation is required unless using extraction kwargs "
185
+ "(input_text + template, or input_text + json_schema)."
186
+ )
187
+
188
+ return super().apply_chat_template(conversation, *args, **kwargs)
189
+
190
+ normalize_template = staticmethod(normalize_extraction_template)
191
+
192
+ def __call__(self, text, *args, **kwargs):
193
+ add_special_tokens = kwargs.get("add_special_tokens", False)
194
+
195
+ if not add_special_tokens:
196
+ if isinstance(text, list):
197
+ text = [_maybe_add_im_start_prefix(t) if isinstance(t, str) else t for t in text]
198
+ elif isinstance(text, str):
199
+ text = _maybe_add_im_start_prefix(text)
200
+
201
+ return super().__call__(text, *args, **kwargs)
202
+
203
+ def encode(
204
+ self,
205
+ text,
206
+ text_pair=None,
207
+ add_special_tokens: bool = True,
208
+ padding=False,
209
+ truncation=None,
210
+ max_length=None,
211
+ stride: int = 0,
212
+ padding_side=None,
213
+ return_tensors=None,
214
+ **kwargs,
215
+ ):
216
+ if isinstance(text, str):
217
+ text = _maybe_add_im_start_prefix(text)
218
+ return super().encode(
219
+ text,
220
+ text_pair=text_pair,
221
+ add_special_tokens=add_special_tokens,
222
+ padding=padding,
223
+ truncation=truncation,
224
+ max_length=max_length,
225
+ stride=stride,
226
+ padding_side=padding_side,
227
+ return_tensors=return_tensors,
228
+ **kwargs,
229
+ )
230
+
231
+
232
+ __all__ = ["NandiTokenizer", "normalize_extraction_template"]
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:354154322dbfd5971fff276473422331b7e2f23073a835ebdaa5f5a84f11a340
3
+ size 12460613
tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|im_start|>",
4
+ "bos_token_id": 1,
5
+ "eos_token": "<|endoftext|>",
6
+ "eos_token_id": 0,
7
+ "pad_token": "<|pad|>",
8
+ "pad_token_id": 3,
9
+ "model_max_length": 12288,
10
+ "tokenizer_class": "NandiTokenizer",
11
+ "unk_token": "<|endoftext|>",
12
+ "auto_map": {
13
+ "AutoTokenizer": ["tokenization_nandi.NandiTokenizer", null]
14
+ }
15
+ }