arthu1 commited on
Commit
35fd7fa
·
verified ·
1 Parent(s): 4e013bc

Add standard Transformers and AutoTokenizer support

Browse files
README.md CHANGED
@@ -75,6 +75,25 @@ python inference.py \
75
  --prompt "Explain why the sky appears blue in two short paragraphs."
76
  ```
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  For ordinary next-token completion, use the base checkpoint:
79
 
80
  ```bash
 
75
  --prompt "Explain why the sky appears blue in two short paragraphs."
76
  ```
77
 
78
+ For the normal Hugging Face API, load the custom architecture through the
79
+ repository's small remote-code modules:
80
+
81
+ ```python
82
+ from transformers import AutoModelForCausalLM, AutoTokenizer
83
+
84
+ repo = "North-ML1/vortex-alpha"
85
+ tokenizer = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
86
+ model = AutoModelForCausalLM.from_pretrained(
87
+ repo, trust_remote_code=True, torch_dtype="auto"
88
+ )
89
+ inputs = tokenizer("Explain why the sky appears blue.", return_tensors="pt")
90
+ outputs = model.generate(**inputs, max_new_tokens=80, do_sample=False)
91
+ print(tokenizer.decode(outputs[0], skip_special_tokens=True))
92
+ ```
93
+
94
+ The `trust_remote_code=True` flag is required because Vortex's QK-Norm and
95
+ GQA implementation is not one of the built-in Transformers model classes.
96
+
97
  For ordinary next-token completion, use the base checkpoint:
98
 
99
  ```bash
config.json CHANGED
@@ -14,5 +14,10 @@
14
  "rms_norm_eps": 1e-5,
15
  "tie_word_embeddings": true,
16
  "attention_bias": false,
17
- "torch_dtype": "bfloat16"
 
 
 
 
 
18
  }
 
14
  "rms_norm_eps": 1e-5,
15
  "tie_word_embeddings": true,
16
  "attention_bias": false,
17
+ "torch_dtype": "bfloat16",
18
+ "use_cache": false,
19
+ "auto_map": {
20
+ "AutoConfig": "configuration_vortex.VortexConfig",
21
+ "AutoModelForCausalLM": "modeling_vortex.VortexForCausalLM"
22
+ }
23
  }
configuration_vortex.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face configuration for Vortex Alpha."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from transformers import PretrainedConfig
6
+
7
+
8
+ class VortexConfig(PretrainedConfig):
9
+ model_type = "vortex"
10
+
11
+ def __init__(
12
+ self,
13
+ vocab_size: int = 8192,
14
+ hidden_size: int = 1024,
15
+ num_hidden_layers: int = 12,
16
+ num_attention_heads: int = 16,
17
+ num_key_value_heads: int = 4,
18
+ head_dim: int = 64,
19
+ intermediate_size: int = 3664,
20
+ hidden_act: str = "silu",
21
+ max_position_embeddings: int = 4096,
22
+ rope_theta: float = 100000.0,
23
+ rms_norm_eps: float = 1e-5,
24
+ attention_bias: bool = False,
25
+ tie_word_embeddings: bool = True,
26
+ use_cache: bool = False,
27
+ **kwargs,
28
+ ) -> None:
29
+ super().__init__(
30
+ tie_word_embeddings=tie_word_embeddings,
31
+ **kwargs,
32
+ )
33
+ self.vocab_size = vocab_size
34
+ self.hidden_size = hidden_size
35
+ self.num_hidden_layers = num_hidden_layers
36
+ self.num_attention_heads = num_attention_heads
37
+ self.num_key_value_heads = num_key_value_heads
38
+ self.head_dim = head_dim
39
+ self.intermediate_size = intermediate_size
40
+ self.hidden_act = hidden_act
41
+ self.max_position_embeddings = max_position_embeddings
42
+ self.rope_theta = rope_theta
43
+ self.rms_norm_eps = rms_norm_eps
44
+ self.attention_bias = attention_bias
45
+ self.tie_word_embeddings = tie_word_embeddings
46
+ self.use_cache = use_cache
47
+
modeling_vortex.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face Transformers implementation of Vortex Alpha.
2
+
3
+ The model intentionally uses the same readable reference tensor names as the
4
+ published safetensors files. It implements the full-prefix path and does not
5
+ claim a KV cache; ``use_cache`` is therefore false by default.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ from typing import Optional
12
+
13
+ import torch
14
+ import torch.nn.functional as F
15
+ from torch import nn
16
+ from transformers import PreTrainedModel
17
+ from transformers.modeling_outputs import CausalLMOutputWithPast
18
+
19
+ from configuration_vortex import VortexConfig
20
+
21
+
22
+ class VortexRMSNorm(nn.Module):
23
+ def __init__(self, dim: int, eps: float) -> None:
24
+ super().__init__()
25
+ self.weight = nn.Parameter(torch.ones(dim))
26
+ self.eps = eps
27
+
28
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
29
+ return F.rms_norm(hidden_states, (hidden_states.shape[-1],), self.weight, self.eps)
30
+
31
+
32
+ class VortexRotaryEmbedding(nn.Module):
33
+ def __init__(self, dim: int, max_seq_len: int, theta: float) -> None:
34
+ super().__init__()
35
+ inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
36
+ positions = torch.arange(max_seq_len, dtype=torch.float32)
37
+ frequencies = torch.outer(positions, inv_freq)
38
+ angles = torch.cat((frequencies, frequencies), dim=-1)
39
+ self.register_buffer("cos_cached", angles.cos()[None, None], persistent=False)
40
+ self.register_buffer("sin_cached", angles.sin()[None, None], persistent=False)
41
+
42
+ @staticmethod
43
+ def rotate_half(x: torch.Tensor) -> torch.Tensor:
44
+ half = x.shape[-1] // 2
45
+ return torch.cat((-x[..., half:], x[..., :half]), dim=-1)
46
+
47
+ def forward(
48
+ self,
49
+ query: torch.Tensor,
50
+ key: torch.Tensor,
51
+ position_ids: Optional[torch.Tensor] = None,
52
+ ) -> tuple[torch.Tensor, torch.Tensor]:
53
+ if position_ids is None:
54
+ cos = self.cos_cached[:, :, : query.shape[-2]]
55
+ sin = self.sin_cached[:, :, : query.shape[-2]]
56
+ else:
57
+ cos = self.cos_cached[0, 0, position_ids].unsqueeze(1)
58
+ sin = self.sin_cached[0, 0, position_ids].unsqueeze(1)
59
+ cos = cos.to(dtype=query.dtype, device=query.device)
60
+ sin = sin.to(dtype=query.dtype, device=query.device)
61
+ return (
62
+ query * cos + self.rotate_half(query) * sin,
63
+ key * cos + self.rotate_half(key) * sin,
64
+ )
65
+
66
+
67
+ class VortexAttention(nn.Module):
68
+ def __init__(self, config: VortexConfig) -> None:
69
+ super().__init__()
70
+ if config.num_attention_heads % config.num_key_value_heads:
71
+ raise ValueError("num_attention_heads must divide evenly by num_key_value_heads")
72
+ if config.num_attention_heads * config.head_dim != config.hidden_size:
73
+ raise ValueError("num_attention_heads * head_dim must equal hidden_size")
74
+ self.num_heads = config.num_attention_heads
75
+ self.num_key_value_heads = config.num_key_value_heads
76
+ self.head_dim = config.head_dim
77
+ kv_dim = config.num_key_value_heads * config.head_dim
78
+ self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attention_bias)
79
+ self.k_proj = nn.Linear(config.hidden_size, kv_dim, bias=config.attention_bias)
80
+ self.v_proj = nn.Linear(config.hidden_size, kv_dim, bias=config.attention_bias)
81
+ self.o_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attention_bias)
82
+ self.q_norm = VortexRMSNorm(config.head_dim, config.rms_norm_eps)
83
+ self.k_norm = VortexRMSNorm(config.head_dim, config.rms_norm_eps)
84
+ self.rope = VortexRotaryEmbedding(config.head_dim, config.max_position_embeddings, config.rope_theta)
85
+
86
+ def forward(
87
+ self,
88
+ hidden_states: torch.Tensor,
89
+ attention_mask: Optional[torch.Tensor] = None,
90
+ position_ids: Optional[torch.Tensor] = None,
91
+ ) -> torch.Tensor:
92
+ batch, seq_len, _ = hidden_states.shape
93
+ query = self.q_proj(hidden_states).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
94
+ key = self.k_proj(hidden_states).view(batch, seq_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
95
+ value = self.v_proj(hidden_states).view(batch, seq_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
96
+ query, key = self.rope(self.q_norm(query), self.k_norm(key), position_ids)
97
+ if self.num_key_value_heads != self.num_heads:
98
+ repeats = self.num_heads // self.num_key_value_heads
99
+ key = key.repeat_interleave(repeats, dim=1)
100
+ value = value.repeat_interleave(repeats, dim=1)
101
+
102
+ sdpa_mask = None
103
+ is_causal = attention_mask is None and seq_len > 1
104
+ if attention_mask is not None:
105
+ if attention_mask.ndim == 2:
106
+ valid_keys = attention_mask.to(dtype=torch.bool, device=hidden_states.device)[:, None, None, :]
107
+ causal = torch.ones((seq_len, seq_len), dtype=torch.bool, device=hidden_states.device).tril()
108
+ sdpa_mask = valid_keys & causal[None, None, :, :]
109
+ elif attention_mask.ndim == 4:
110
+ sdpa_mask = attention_mask
111
+ else:
112
+ raise ValueError("Vortex expects a 2-D or 4-D attention mask")
113
+ output = F.scaled_dot_product_attention(
114
+ query,
115
+ key,
116
+ value,
117
+ attn_mask=sdpa_mask,
118
+ dropout_p=0.0,
119
+ is_causal=is_causal,
120
+ )
121
+ output = output.transpose(1, 2).contiguous().view(batch, seq_len, -1)
122
+ return self.o_proj(output)
123
+
124
+
125
+ class VortexMLP(nn.Module):
126
+ def __init__(self, config: VortexConfig) -> None:
127
+ super().__init__()
128
+ self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=config.attention_bias)
129
+ self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=config.attention_bias)
130
+ self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=config.attention_bias)
131
+
132
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
133
+ return self.down_proj(F.silu(self.gate_proj(hidden_states)) * self.up_proj(hidden_states))
134
+
135
+
136
+ class VortexDecoderBlock(nn.Module):
137
+ def __init__(self, config: VortexConfig) -> None:
138
+ super().__init__()
139
+ self.norm1 = VortexRMSNorm(config.hidden_size, config.rms_norm_eps)
140
+ self.attn = VortexAttention(config)
141
+ self.norm2 = VortexRMSNorm(config.hidden_size, config.rms_norm_eps)
142
+ self.ffn = VortexMLP(config)
143
+
144
+ def forward(
145
+ self,
146
+ hidden_states: torch.Tensor,
147
+ attention_mask: Optional[torch.Tensor] = None,
148
+ position_ids: Optional[torch.Tensor] = None,
149
+ ) -> torch.Tensor:
150
+ hidden_states = hidden_states + self.attn(self.norm1(hidden_states), attention_mask, position_ids)
151
+ hidden_states = hidden_states + self.ffn(self.norm2(hidden_states))
152
+ return hidden_states
153
+
154
+
155
+ class VortexPreTrainedModel(PreTrainedModel):
156
+ config_class = VortexConfig
157
+ base_model_prefix = "vortex"
158
+ supports_gradient_checkpointing = False
159
+
160
+
161
+ class VortexForCausalLM(VortexPreTrainedModel):
162
+ _tied_weights_keys = ["embed_tokens.weight"]
163
+ main_input_name = "input_ids"
164
+
165
+ def __init__(self, config: VortexConfig) -> None:
166
+ super().__init__(config)
167
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
168
+ self.layers = nn.ModuleList(VortexDecoderBlock(config) for _ in range(config.num_hidden_layers))
169
+ self.norm = VortexRMSNorm(config.hidden_size, config.rms_norm_eps)
170
+ self._initialize_weights()
171
+
172
+ def _initialize_weights(self) -> None:
173
+ output_std = 0.02 / math.sqrt(2.0 * self.config.num_hidden_layers)
174
+ nn.init.normal_(self.embed_tokens.weight, mean=0.0, std=0.02)
175
+ for block in self.layers:
176
+ for module in block.modules():
177
+ if isinstance(module, VortexRMSNorm):
178
+ nn.init.ones_(module.weight)
179
+ elif isinstance(module, nn.Linear):
180
+ is_output = module is block.attn.o_proj or module is block.ffn.down_proj
181
+ nn.init.normal_(module.weight, mean=0.0, std=output_std if is_output else 0.02)
182
+ if module.bias is not None:
183
+ nn.init.zeros_(module.bias)
184
+ nn.init.ones_(self.norm.weight)
185
+
186
+ def get_input_embeddings(self) -> nn.Module:
187
+ return self.embed_tokens
188
+
189
+ def set_input_embeddings(self, value: nn.Module) -> None:
190
+ self.embed_tokens = value
191
+
192
+ def get_output_embeddings(self) -> nn.Module:
193
+ return self.embed_tokens
194
+
195
+ def set_output_embeddings(self, new_embeddings: nn.Module) -> None:
196
+ self.embed_tokens = new_embeddings
197
+
198
+ def prepare_inputs_for_generation(self, input_ids: torch.Tensor, **kwargs) -> dict:
199
+ # No KV cache is exported. Returning the full prefix keeps generation
200
+ # correct, though slower than a cache-enabled implementation.
201
+ return {
202
+ "input_ids": input_ids,
203
+ "attention_mask": kwargs.get("attention_mask"),
204
+ "use_cache": False,
205
+ }
206
+
207
+ def forward(
208
+ self,
209
+ input_ids: Optional[torch.Tensor] = None,
210
+ attention_mask: Optional[torch.Tensor] = None,
211
+ position_ids: Optional[torch.Tensor] = None,
212
+ past_key_values=None,
213
+ inputs_embeds: Optional[torch.Tensor] = None,
214
+ labels: Optional[torch.Tensor] = None,
215
+ use_cache: Optional[bool] = None,
216
+ output_attentions: Optional[bool] = None,
217
+ output_hidden_states: Optional[bool] = None,
218
+ return_dict: Optional[bool] = None,
219
+ **kwargs,
220
+ ) -> CausalLMOutputWithPast:
221
+ if input_ids is not None and inputs_embeds is not None:
222
+ raise ValueError("pass either input_ids or inputs_embeds, not both")
223
+ if inputs_embeds is None:
224
+ if input_ids is None:
225
+ raise ValueError("input_ids or inputs_embeds is required")
226
+ hidden_states = self.embed_tokens(input_ids)
227
+ else:
228
+ hidden_states = inputs_embeds
229
+ if position_ids is None:
230
+ position_ids = torch.arange(hidden_states.shape[1], device=hidden_states.device).unsqueeze(0)
231
+ all_hidden_states = () if output_hidden_states else None
232
+ for block in self.layers:
233
+ if output_hidden_states:
234
+ all_hidden_states += (hidden_states,)
235
+ hidden_states = block(hidden_states, attention_mask, position_ids)
236
+ if output_hidden_states:
237
+ all_hidden_states += (hidden_states,)
238
+ hidden_states = self.norm(hidden_states)
239
+ logits = F.linear(hidden_states, self.embed_tokens.weight)
240
+ loss = None
241
+ if labels is not None:
242
+ shift_logits = logits[..., :-1, :].contiguous().float()
243
+ shift_labels = labels[..., 1:].contiguous()
244
+ loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
245
+ if return_dict is False:
246
+ output = (logits, None, all_hidden_states, None)
247
+ return ((loss,) + output) if loss is not None else output
248
+ return CausalLMOutputWithPast(
249
+ loss=loss,
250
+ logits=logits,
251
+ past_key_values=None,
252
+ hidden_states=all_hidden_states,
253
+ attentions=None,
254
+ )
255
+
tokenization_vortex.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SentencePiece tokenizer wrapper for Hugging Face AutoTokenizer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ from transformers import PreTrainedTokenizer
9
+
10
+
11
+ class VortexTokenizer(PreTrainedTokenizer):
12
+ vocab_files_names = {"vocab_file": "tokenizer.model"}
13
+ model_input_names = ["input_ids", "attention_mask"]
14
+
15
+ def __init__(
16
+ self,
17
+ vocab_file: str,
18
+ unk_token: str = "<unk>",
19
+ eos_token: str = "</s>",
20
+ **kwargs,
21
+ ) -> None:
22
+ import sentencepiece as spm
23
+
24
+ self.vocab_file = str(vocab_file)
25
+ self.sp_model = spm.SentencePieceProcessor(model_file=self.vocab_file)
26
+ super().__init__(unk_token=unk_token, eos_token=eos_token, **kwargs)
27
+
28
+ @property
29
+ def vocab_size(self) -> int:
30
+ return int(self.sp_model.vocab_size())
31
+
32
+ def get_vocab(self) -> dict[str, int]:
33
+ vocab = {self.sp_model.id_to_piece(i): i for i in range(self.vocab_size)}
34
+ vocab.update(self.added_tokens_encoder)
35
+ return vocab
36
+
37
+ def _tokenize(self, text: str, **kwargs) -> list[str]:
38
+ return list(self.sp_model.encode(text, out_type=str))
39
+
40
+ def _convert_token_to_id(self, token: str) -> int:
41
+ token_id = int(self.sp_model.piece_to_id(token))
42
+ return self.unk_token_id if token_id < 0 else token_id
43
+
44
+ def _convert_id_to_token(self, index: int) -> str:
45
+ return self.sp_model.id_to_piece(int(index))
46
+
47
+ def convert_tokens_to_string(self, tokens: list[str]) -> str:
48
+ return self.sp_model.decode_pieces(tokens)
49
+
50
+ def build_inputs_with_special_tokens(
51
+ self,
52
+ token_ids_0: list[int],
53
+ token_ids_1: list[int] | None = None,
54
+ ) -> list[int]:
55
+ if token_ids_1 is None:
56
+ return list(token_ids_0)
57
+ return list(token_ids_0) + list(token_ids_1)
58
+
59
+ def get_special_tokens_mask(
60
+ self,
61
+ token_ids_0: list[int],
62
+ token_ids_1: list[int] | None = None,
63
+ already_has_special_tokens: bool = False,
64
+ ) -> list[int]:
65
+ if already_has_special_tokens:
66
+ return [0] * len(token_ids_0)
67
+ total = len(token_ids_0) + (len(token_ids_1) if token_ids_1 is not None else 0)
68
+ return [0] * total
69
+
70
+ def save_vocabulary(self, save_directory: str, filename_prefix: str | None = None) -> tuple[str]:
71
+ directory = Path(save_directory)
72
+ directory.mkdir(parents=True, exist_ok=True)
73
+ name = (filename_prefix + "-" if filename_prefix else "") + "tokenizer.model"
74
+ output = directory / name
75
+ if Path(self.vocab_file).resolve() != output.resolve():
76
+ shutil.copyfile(self.vocab_file, output)
77
+ return (str(output),)
78
+
tokenizer_config.json CHANGED
@@ -1,6 +1,9 @@
1
  {
2
  "model_type": "vortex",
3
- "tokenizer_class": "SentencePieceProcessor",
 
 
 
4
  "vocab_size": 8192,
5
  "unk_token": "<unk>",
6
  "eos_token": "</s>",
 
1
  {
2
  "model_type": "vortex",
3
+ "tokenizer_class": "VortexTokenizer",
4
+ "auto_map": {
5
+ "AutoTokenizer": ["tokenization_vortex.VortexTokenizer", null]
6
+ },
7
  "vocab_size": 8192,
8
  "unk_token": "<unk>",
9
  "eos_token": "</s>",