smithblack-0 commited on
Commit
98abb50
·
verified ·
1 Parent(s): a18177b

Update architecture and tokenizer

Browse files
Files changed (12) hide show
  1. README.md +61 -0
  2. __init__.py +9 -0
  3. attention.py +149 -0
  4. config.json +22 -0
  5. configuration.py +177 -0
  6. decoder_layer.py +75 -0
  7. huggingface.py +248 -0
  8. mlp.py +52 -0
  9. model.py +118 -0
  10. rope.py +143 -0
  11. tokenizer.json +0 -0
  12. tokenizer_config.json +13 -0
README.md CHANGED
@@ -1,3 +1,64 @@
1
  ---
 
 
2
  license: mit
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ language:
3
+ - en
4
  license: mit
5
+ library_name: transformers
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - pytorch
9
+ - research
10
+ - llama
11
  ---
12
+
13
+ # advanced-transformers-lib -- Llama 3 Baseline
14
+
15
+ A Llama 3-style decoder-only transformer architecture for research. No pretrained
16
+ weights -- pull the architecture from the Hub and instantiate a freshly initialised
17
+ model from config. Override any parameter at instantiation time.
18
+
19
+ > **Important:** `trust_remote_code=True` is required. It downloads the architecture
20
+ > source files from the Hub and imports them into your Python process. Review the
21
+ > source at [smithblack-0/llama3_baseline_dev](https://huggingface.co/smithblack-0/llama3_baseline_dev) before use.
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
27
+
28
+ # Pull architecture config -- override any parameter at instantiation time
29
+ config = AutoConfig.from_pretrained(
30
+ "smithblack-0/llama3_baseline_dev",
31
+ trust_remote_code=True,
32
+ num_hidden_layers=16, # example override
33
+ )
34
+
35
+ # Instantiate with fresh random weights -- no checkpoint required
36
+ model = AutoModelForCausalLM.from_config(config, trust_remote_code=True)
37
+
38
+ # Load tokenizer
39
+ tokenizer = AutoTokenizer.from_pretrained("smithblack-0/llama3_baseline_dev")
40
+
41
+ # Save and reload after training
42
+ model.save_pretrained("./checkpoint")
43
+ model = AutoModelForCausalLM.from_pretrained("./checkpoint", trust_remote_code=True)
44
+ ```
45
+
46
+ ## Default Configuration
47
+
48
+ | Parameter | Default |
49
+ |-----------|---------|
50
+ | `vocab_size` | 50277 |
51
+ | `hidden_size` | 768 |
52
+ | `intermediate_size` | 1568 |
53
+ | `num_hidden_layers` | 24 |
54
+ | `num_attention_heads` | 16 |
55
+ | `num_key_value_heads` | 4 |
56
+ | `head_dim` | 48 |
57
+ | `max_position_embeddings` | 8192 |
58
+ | `rope_theta` | 500000.0 |
59
+
60
+ ## License
61
+
62
+ MIT. Clean-room synthesis: the human author has not read the Llama source code.
63
+ Architectural decisions derive from the published paper. Tokenizer is GPT-NeoX
64
+ (`EleutherAI/gpt-neox-20b`, Apache 2.0).
__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from .attention import GroupedQueryAttention
2
+ from .configuration import Llama3Config
3
+ from .decoder_layer import DecoderLayer
4
+ from .huggingface import Llama3ForCausalLM
5
+ from .mlp import SwiGLUMLP
6
+ from .model import Llama3Model
7
+ from .rope import RotaryEmbedding
8
+
9
+ __all__ = ["DecoderLayer", "GroupedQueryAttention", "Llama3Config", "Llama3ForCausalLM", "Llama3Model", "RotaryEmbedding", "SwiGLUMLP"]
attention.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Grouped Query Attention (GQA).
2
+
3
+ GQA reduces KV cache memory by sharing key-value heads across groups of query heads.
4
+ With G query heads per KV head, the KV cache is G× smaller than standard multi-head
5
+ attention (MHA). This is the primary motivation for its use in Llama 3 at 128K context:
6
+ 8 KV heads shared across 32 query heads gives a 4× cache reduction.
7
+
8
+ Setting num_key_value_heads == num_attention_heads recovers standard MHA.
9
+ Setting num_key_value_heads == 1 gives multi-query attention (MQA).
10
+
11
+ Attention is computed via torch.nn.functional.scaled_dot_product_attention (SDPA),
12
+ which selects FlashAttention when hardware and dtype allow, falling back to standard
13
+ attention otherwise. No custom kernel or additional dependency required.
14
+
15
+ KV caching is handled via HuggingFace's Cache protocol. The cache owns K/V storage and
16
+ accumulation; attention only calls cache.update() to store new projections and retrieve
17
+ the full accumulated history. This cleanly separates attention computation from cache
18
+ management: different Cache subclasses (DynamicCache, StaticCache, custom research
19
+ variants) can be dropped in without touching the attention logic.
20
+
21
+ No bias on any projection — a fixed architectural constant of this model.
22
+ """
23
+
24
+ import math
25
+
26
+ import torch
27
+ import torch.nn as nn
28
+ import torch.nn.functional as F
29
+ from transformers import PretrainedConfig
30
+ from transformers.cache_utils import Cache
31
+
32
+ from .rope import RotaryEmbedding
33
+
34
+
35
+ class GroupedQueryAttention(nn.Module):
36
+ """Grouped Query Attention with RoPE, causal masking, and KV cache support.
37
+
38
+ Implements GQA as used in Llama 3: Q heads are split into groups, each group
39
+ sharing a single KV head. Before attention is computed, K and V are expanded
40
+ by repeating each KV head across its group of query heads.
41
+
42
+ The forward pass is strictly causal. An optional pre-built boolean attention
43
+ mask can be threaded in from the caller; when absent, SDPA's native
44
+ ``is_causal`` mode applies — correct for full-sequence training.
45
+
46
+ Args:
47
+ config: Model config. Must expose ``num_attention_heads``,
48
+ ``num_key_value_heads``, ``head_dim``, ``hidden_size``,
49
+ and ``attention_dropout``.
50
+
51
+ Raises:
52
+ ValueError: If ``num_attention_heads`` is not divisible by
53
+ ``num_key_value_heads``.
54
+ """
55
+
56
+ def __init__(self, config: PretrainedConfig) -> None:
57
+ super().__init__()
58
+
59
+ self.num_heads = config.num_attention_heads
60
+ self.num_kv_heads = config.num_key_value_heads
61
+ self.head_dim = config.head_dim
62
+ self.num_groups = self.num_heads // self.num_kv_heads
63
+ self.attention_dropout = config.attention_dropout
64
+
65
+ if self.num_heads % self.num_kv_heads != 0:
66
+ raise ValueError(
67
+ f"num_attention_heads ({self.num_heads}) must be divisible by "
68
+ f"num_key_value_heads ({self.num_kv_heads})."
69
+ )
70
+
71
+ # No bias on any projection — architectural constant.
72
+ self.q_proj = nn.Linear(config.hidden_size, self.num_heads * self.head_dim, bias=False)
73
+ self.k_proj = nn.Linear(config.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
74
+ self.v_proj = nn.Linear(config.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
75
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, config.hidden_size, bias=False)
76
+
77
+ self.rope = RotaryEmbedding(config)
78
+
79
+ def forward(
80
+ self,
81
+ x: torch.Tensor,
82
+ position_ids: torch.Tensor,
83
+ cache: Cache | None = None,
84
+ layer_idx: int = 0,
85
+ causal_mask: torch.Tensor | None = None,
86
+ ) -> torch.Tensor:
87
+ """Apply grouped query attention to the input.
88
+
89
+ Args:
90
+ x: Input of shape (batch, seq_len, hidden_size).
91
+ position_ids: Absolute positions of shape (batch, seq_len). Used by
92
+ RoPE to rotate Q and K at the correct frequencies.
93
+ cache: HuggingFace Cache object for KV accumulation, or None when
94
+ caching is disabled (``use_cache=False``). When provided,
95
+ ``cache.update(k, v, layer_idx)`` stores the new K/V and returns
96
+ the full accumulated key and value tensors for this layer.
97
+ layer_idx: Which slot in the cache to read and write. Each decoder
98
+ layer has its own index so they accumulate independently.
99
+ causal_mask: Optional boolean attention mask of shape
100
+ (1, 1, seq_len, kv_len), where True indicates a position that
101
+ should be attended to. When None, SDPA's built-in ``is_causal``
102
+ mode is used, which is correct for full-sequence training
103
+ (square Q×K matrix). When provided, ``is_causal`` is disabled
104
+ and the explicit mask governs attention — required for any
105
+ generation pattern where Q and K lengths differ.
106
+
107
+ Returns:
108
+ Output tensor of shape (batch, seq_len, hidden_size).
109
+ """
110
+ batch, seq_len, _ = x.shape
111
+
112
+ # Project and reshape: (batch, seq_len, heads * head_dim)
113
+ # → (batch, heads, seq_len, head_dim)
114
+ q = self.q_proj(x).view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
115
+ k = self.k_proj(x).view(batch, seq_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
116
+ v = self.v_proj(x).view(batch, seq_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
117
+
118
+ # Apply RoPE. attention_scaling is 1.0 for default/linear; YaRN returns a
119
+ # value != 1.0 that corrects attention magnitude after frequency manipulation.
120
+ q, k, attention_scaling = self.rope(q, k, position_ids)
121
+
122
+ if cache is not None:
123
+ k_full, v_full = cache.update(k, v, layer_idx)
124
+ else:
125
+ k_full, v_full = k, v
126
+
127
+ # Expand KV heads to align with query heads for GQA.
128
+ # Each KV head is repeated num_groups times so SDPA sees matching head counts.
129
+ if self.num_groups > 1:
130
+ k_full = k_full.repeat_interleave(self.num_groups, dim=1)
131
+ v_full = v_full.repeat_interleave(self.num_groups, dim=1)
132
+
133
+ attn_output = F.scaled_dot_product_attention(
134
+ q, k_full, v_full,
135
+ attn_mask=causal_mask,
136
+ dropout_p=self.attention_dropout if self.training else 0.0,
137
+ is_causal=causal_mask is None,
138
+ scale=attention_scaling / math.sqrt(self.head_dim),
139
+ )
140
+
141
+ # Merge heads and project back to hidden_size.
142
+ attn_output = (
143
+ attn_output
144
+ .transpose(1, 2)
145
+ .contiguous()
146
+ .view(batch, seq_len, self.num_heads * self.head_dim)
147
+ )
148
+
149
+ return self.o_proj(attn_output)
config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "attention_dropout": 0.0,
3
+ "auto_map": {
4
+ "AutoConfig": "configuration.Llama3Config",
5
+ "AutoModelForCausalLM": "huggingface.Llama3ForCausalLM"
6
+ },
7
+ "head_dim": 48,
8
+ "hidden_size": 768,
9
+ "intermediate_size": 1568,
10
+ "max_position_embeddings": 8192,
11
+ "model_type": "llama3_baseline",
12
+ "num_attention_heads": 16,
13
+ "num_hidden_layers": 24,
14
+ "num_key_value_heads": 4,
15
+ "rms_norm_eps": 1e-05,
16
+ "rope_parameters": null,
17
+ "rope_theta": 500000.0,
18
+ "tie_word_embeddings": false,
19
+ "transformers_version": "5.3.0",
20
+ "use_cache": true,
21
+ "vocab_size": 50277
22
+ }
configuration.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration for the Llama 3 baseline transformer.
2
+
3
+ All architectural parameters that vary across model scales or are meaningful research
4
+ variables are expressed here. Architectural constants of Llama 3 (no bias in linear
5
+ layers, SwiGLU activation with SiLU gate) are implemented in the relevant modules and
6
+ documented at the point of use — they are not config parameters because they do not
7
+ vary across Llama 3 scales and changing them produces a different architecture, not a
8
+ different scale of this one.
9
+
10
+ RoPE configuration is handled by HuggingFace's RotaryEmbeddingConfigMixin (mixed into
11
+ PretrainedConfig in transformers 5.x). rope_theta and rope_scaling are passed through
12
+ to the base class, which validates and standardises them into config.rope_parameters.
13
+ Do not bypass or duplicate this system.
14
+ """
15
+
16
+ from transformers import PretrainedConfig
17
+
18
+
19
+ class Llama3Config(PretrainedConfig):
20
+ """Configuration class for the Llama 3 baseline decoder-only transformer.
21
+
22
+ This config is the single source of truth for every architectural dimension of the
23
+ model. Nothing in the architecture may use a literal number that belongs here —
24
+ doing so breaks the library's ability to express different model scales without
25
+ code changes.
26
+
27
+ RoPE scaling is handled by HuggingFace's rope system. Pass rope_scaling as a dict
28
+ using HF's format (key is ``rope_type``, not ``type``). Supported types:
29
+ ``"linear"``, ``"dynamic"``, ``"yarn"``, ``"longrope"``, ``"llama3"``. HF validates
30
+ the dict and standardises it into ``config.rope_parameters``.
31
+
32
+ Registered with HuggingFace AutoClass via ``auto_map``. Instantiate from the Hub::
33
+
34
+ config = AutoConfig.from_pretrained(
35
+ "your-namespace/advanced-transformers-lib",
36
+ trust_remote_code=True,
37
+ num_hidden_layers=16, # override any parameter at instantiation time
38
+ )
39
+ model = AutoModelForCausalLM.from_config(config)
40
+
41
+ Args:
42
+ vocab_size: Vocabulary size. Controls the embedding table and output logits
43
+ dimension. Must match the tokenizer.
44
+ hidden_size: Model width. The central dimension from which all others are
45
+ derived or to which they project.
46
+ intermediate_size: FFN hidden dimension. Expressed directly rather than derived
47
+ from a formula because Llama 3 ratios vary by scale (~3.5x at 8B/70B,
48
+ ~3.25x at 405B). A formula would be wrong for at least some scales.
49
+ num_hidden_layers: Number of transformer blocks stacked in sequence.
50
+ num_attention_heads: Number of query heads. Determines how hidden_size is
51
+ partitioned per head.
52
+ num_key_value_heads: Number of KV heads for Grouped Query Attention. Must
53
+ evenly divide num_attention_heads. Setting equal to num_attention_heads
54
+ gives standard MHA; setting to 1 gives MQA; values between give GQA.
55
+ Llama 3 uses 8 at all scales, motivated by KV cache memory at 128K context.
56
+ head_dim: Dimension per attention head. Normally hidden_size //
57
+ num_attention_heads, but exposed as a parameter for architectures that
58
+ decouple head count from head size. Computed automatically if None.
59
+ rms_norm_eps: Epsilon passed to torch.nn.RMSNorm. Prevents division by zero
60
+ when layer activations are near zero.
61
+ rope_theta: Base rotation frequency for RoPE. Controls how fast position angles
62
+ rotate per dimension — higher values mean slower rotation, preventing
63
+ positional aliasing at long sequence distances. Llama 3 uses 500,000
64
+ (vs ~10,000 typical) as a prerequisite for 128K context support. This
65
+ value has physical meaning tied to the target context length and must
66
+ never be hardcoded in the architecture.
67
+ max_position_embeddings: The context length the model was trained at. Used by
68
+ HF's rope system as original_max_position_embeddings for scaling types that
69
+ need it (yarn, longrope, llama3). This is the training context length, not
70
+ an inference ceiling — the rope module handles longer sequences at runtime
71
+ via lazy cache extension. Llama 3 base training context: 8192.
72
+ rope_scaling: Optional RoPE scaling configuration for extending context beyond
73
+ max_position_embeddings without retraining. Pass as a dict in HF's format
74
+ with ``rope_type`` as the key. HF's RotaryEmbeddingConfigMixin validates
75
+ and stores this. None means no scaling (default RoPE behaviour).
76
+ attention_dropout: Dropout probability applied to attention weights. Default
77
+ 0.0 for deterministic behaviour.
78
+ use_cache: Whether the model returns past_key_values for KV caching. Set True
79
+ for inference, may be set False during training to reduce memory pressure.
80
+ output_hidden_states: Whether the model returns the hidden state tensor after
81
+ each decoder layer. Useful for probing or intermediate representation
82
+ extraction. Default False.
83
+ tie_word_embeddings: Whether the input embedding table and the LM head share
84
+ weights. False for Llama 3.
85
+ """
86
+
87
+ model_type = "llama3_baseline"
88
+
89
+ # auto_map tells HuggingFace which classes to instantiate when loading this config
90
+ # with trust_remote_code=True. Paths are relative to the Hub repository root, not
91
+ # the local src/ layout — these are the paths used after HF downloads the files.
92
+ auto_map = {
93
+ "AutoConfig": "configuration.Llama3Config",
94
+ "AutoModelForCausalLM": "huggingface.Llama3ForCausalLM",
95
+ }
96
+
97
+ def __init__(
98
+ self,
99
+ vocab_size: int = 50277,
100
+ hidden_size: int = 768,
101
+ intermediate_size: int = 1568,
102
+ num_hidden_layers: int = 24,
103
+ num_attention_heads: int = 16,
104
+ num_key_value_heads: int = 4,
105
+ head_dim: int | None = None,
106
+ rms_norm_eps: float = 1e-5,
107
+ rope_theta: float = 500000.0,
108
+ max_position_embeddings: int = 8192,
109
+ rope_scaling: dict | None = None,
110
+ attention_dropout: float = 0.0,
111
+ use_cache: bool = True,
112
+ output_hidden_states: bool = False,
113
+ tie_word_embeddings: bool = False,
114
+ **kwargs,
115
+ ):
116
+ # Validate structural constraints before storing anything, so that an invalid
117
+ # config fails loudly at construction rather than silently producing wrong
118
+ # shapes at forward-pass time.
119
+ if hidden_size % num_attention_heads != 0:
120
+ raise ValueError(
121
+ f"hidden_size ({hidden_size}) must be divisible by "
122
+ f"num_attention_heads ({num_attention_heads})."
123
+ )
124
+ if num_attention_heads % num_key_value_heads != 0:
125
+ raise ValueError(
126
+ f"num_attention_heads ({num_attention_heads}) must be divisible by "
127
+ f"num_key_value_heads ({num_key_value_heads}). GQA requires query "
128
+ f"heads to divide evenly across KV head groups."
129
+ )
130
+
131
+ # RoPE rotates dimensions in pairs. An odd head_dim has no valid pairing and
132
+ # produces a cos/sin cache of size head_dim+1 (torch.arange(0, odd, 2) rounds
133
+ # up), causing a shape mismatch at runtime. Catch it here rather than at
134
+ # forward-pass time.
135
+ resolved_head_dim = head_dim if head_dim is not None else hidden_size // num_attention_heads
136
+ if resolved_head_dim % 2 != 0:
137
+ raise ValueError(
138
+ f"head_dim must be even (RoPE rotates dimensions in pairs). "
139
+ f"Got head_dim={resolved_head_dim} from hidden_size={hidden_size} "
140
+ f"and num_attention_heads={num_attention_heads}."
141
+ )
142
+
143
+ # head_dim is normally hidden_size // num_attention_heads but is exposed as a
144
+ # parameter for architectures that decouple head count from head size.
145
+ if head_dim is None:
146
+ head_dim = hidden_size // num_attention_heads
147
+
148
+ self.vocab_size = vocab_size
149
+ self.max_position_embeddings = max_position_embeddings
150
+ self.hidden_size = hidden_size
151
+ self.intermediate_size = intermediate_size
152
+ self.num_hidden_layers = num_hidden_layers
153
+ self.num_attention_heads = num_attention_heads
154
+ self.num_key_value_heads = num_key_value_heads
155
+ self.head_dim = head_dim
156
+ self.rms_norm_eps = rms_norm_eps
157
+ self.attention_dropout = attention_dropout
158
+ self.use_cache = use_cache
159
+
160
+ # rope_theta, max_position_embeddings, and rope_scaling are passed to HF's
161
+ # base class, which owns rope configuration via RotaryEmbeddingConfigMixin.
162
+ # HF validates rope_scaling and standardises everything into rope_parameters.
163
+ # Do not store or validate these ourselves.
164
+ super().__init__(
165
+ rope_theta=rope_theta,
166
+ max_position_embeddings=max_position_embeddings,
167
+ rope_scaling=rope_scaling,
168
+ tie_word_embeddings=tie_word_embeddings,
169
+ output_hidden_states=output_hidden_states,
170
+ **kwargs,
171
+ )
172
+
173
+ # Promote auto_map to an instance attribute so PretrainedConfig.to_dict()
174
+ # serialises it into config.json. Class-level attributes are not picked up
175
+ # by to_dict() — only self.__dict__ is serialised. model_type is the sole
176
+ # exception handled specially by HF; auto_map is not.
177
+ self.auto_map = type(self).auto_map
decoder_layer.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Decoder layer — a single transformer block.
2
+
3
+ Each block applies pre-norm attention followed by pre-norm MLP, with residual
4
+ connections around both sublayers:
5
+
6
+ normed = RMSNorm(x)
7
+ h = x + Attention(normed, ...)
8
+ normed = RMSNorm(h)
9
+ out = h + MLP(normed)
10
+
11
+ Pre-norm keeps the residual stream unnormalised. Gradients flow more cleanly
12
+ through unnormalised residuals at depth, and each sublayer receives a stable,
13
+ normalised view of the signal.
14
+
15
+ Two independent RMSNorm instances are used — one before attention, one before MLP.
16
+ They learn different scalings because they precede layers with different dynamic
17
+ ranges. Sharing them would be wrong.
18
+
19
+ torch.nn.RMSNorm is used directly (available from PyTorch 2.4+). It omits mean
20
+ subtraction, is faster than LayerNorm, and proved more stable at scale.
21
+ """
22
+
23
+ import torch
24
+ import torch.nn as nn
25
+ from transformers import PretrainedConfig
26
+ from transformers.cache_utils import Cache
27
+
28
+ from .attention import GroupedQueryAttention
29
+ from .mlp import SwiGLUMLP
30
+
31
+
32
+ class DecoderLayer(nn.Module):
33
+ """A single pre-norm transformer decoder block.
34
+
35
+ Composes GroupedQueryAttention and SwiGLUMLP with residual connections and
36
+ independent RMSNorm instances on each sublayer input.
37
+
38
+ Args:
39
+ config: Model config passed through to attention and MLP. Must also expose
40
+ ``hidden_size`` and ``rms_norm_eps``.
41
+ """
42
+
43
+ def __init__(self, config: PretrainedConfig) -> None:
44
+ super().__init__()
45
+ self.attn_norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
46
+ self.mlp_norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
47
+ self.attention = GroupedQueryAttention(config)
48
+ self.mlp = SwiGLUMLP(config)
49
+
50
+ def forward(
51
+ self,
52
+ x: torch.Tensor,
53
+ position_ids: torch.Tensor,
54
+ cache: Cache | None = None,
55
+ layer_idx: int = 0,
56
+ causal_mask: torch.Tensor | None = None,
57
+ ) -> torch.Tensor:
58
+ """Apply one decoder block to the input.
59
+
60
+ Args:
61
+ x: Input of shape (batch, seq_len, hidden_size).
62
+ position_ids: Absolute positions of shape (batch, seq_len).
63
+ cache: HuggingFace Cache object for KV accumulation, or None when
64
+ caching is disabled. Passed through to attention unchanged.
65
+ layer_idx: Cache slot index for this layer. Each layer has its own
66
+ index so they accumulate independently within the shared cache.
67
+ causal_mask: Optional boolean attention mask of shape
68
+ (1, 1, seq_len, kv_len). Passed through to attention unchanged.
69
+
70
+ Returns:
71
+ Output tensor of shape (batch, seq_len, hidden_size).
72
+ """
73
+ attn_out = self.attention(self.attn_norm(x), position_ids, cache, layer_idx, causal_mask)
74
+ h = x + attn_out
75
+ return h + self.mlp(self.mlp_norm(h))
huggingface.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HuggingFace wrapper for the Llama 3 baseline.
2
+
3
+ Llama3ForCausalLM wraps Llama3Model with everything a researcher needs to
4
+ train, evaluate, and generate from it through the HuggingFace ecosystem:
5
+ token embedding, vocabulary projection, next-token loss, weight tying, and
6
+ the full AutoClass and GenerationMixin contracts.
7
+
8
+ The token embedding lives here, not on the backbone. Llama3Model is a pure
9
+ transformer stack that accepts pre-embedded hidden states — it has no knowledge
10
+ of tokens or vocabulary. This is the correct HF convention: the backbone is
11
+ modality-agnostic; the token interface belongs on the task wrapper.
12
+
13
+ The LM head projects the backbone's (batch, seq, hidden_size) output to
14
+ (batch, seq, vocab_size) logits. When labels are provided, cross-entropy loss
15
+ is computed with a one-position shift: token i predicts token i+1. The shift
16
+ is applied here rather than expected from the caller — a causal LM always
17
+ trains this way and there is no use case for an unshifted loss.
18
+
19
+ Weight tying: when config.tie_word_embeddings is True, lm_head.weight is
20
+ directly assigned to embed_tokens.weight after post_init(). Both matrices are
21
+ shape (vocab_size, hidden_size) — same shape, no transpose needed.
22
+
23
+ KV caching uses HuggingFace's Cache protocol. GenerationMixin creates and
24
+ manages the DynamicCache for generate() calls, passing it as past_key_values
25
+ on every forward call. The backbone updates the cache in place and returns the
26
+ same object. _reorder_cache delegates to DynamicCache.reorder_cache() for beam
27
+ search, keeping all beam-reordering logic inside the cache implementation.
28
+
29
+ Returns a CausalLMOutputWithPast. ModelOutput subclasses support both attribute
30
+ access (output.logits) and dict-style access (output["logits"]), satisfying
31
+ GenerationMixin's attribute access requirements while keeping existing code unchanged.
32
+ """
33
+
34
+ import torch
35
+ import torch.nn as nn
36
+ from transformers import PreTrainedModel, GenerationMixin
37
+ from transformers.cache_utils import Cache, DynamicCache
38
+ from transformers.modeling_outputs import CausalLMOutputWithPast
39
+
40
+ from .configuration import Llama3Config
41
+ from .model import Llama3Model
42
+
43
+
44
+ class Llama3ForCausalLM(PreTrainedModel, GenerationMixin):
45
+ """Llama 3 causal language model: token embedding, backbone, LM head, HF contract.
46
+
47
+ Owns the token embedding and LM head. Delegates all transformer computation
48
+ to Llama3Model. Adds loss computation for training, weight tying between the
49
+ LM head and the input embedding, and the full HuggingFace AutoClass and
50
+ GenerationMixin contracts.
51
+
52
+ Args:
53
+ config: Model configuration. Must be a ``Llama3Config`` instance.
54
+ """
55
+
56
+ config_class = Llama3Config
57
+ base_model_prefix = "model"
58
+ _no_split_modules = ["DecoderLayer"]
59
+ supports_gradient_checkpointing = True
60
+
61
+ def __init__(self, config: Llama3Config) -> None:
62
+ super().__init__(config)
63
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
64
+ self.model = Llama3Model(config)
65
+
66
+ # No bias — consistent with all other projections in this architecture.
67
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
68
+ self.post_init()
69
+
70
+ # Direct weight tying: both matrices are (vocab_size, hidden_size) — same shape,
71
+ # no transpose. Explicit here for visibility; post_init() → tie_weights() also
72
+ # performs this via get_input/output_embeddings(), but that is less readable.
73
+ if config.tie_word_embeddings:
74
+ self.lm_head.weight = self.embed_tokens.weight
75
+
76
+ def _init_weights(self, module: nn.Module) -> None:
77
+ # Suppress HF's default reinitialisation pass. HF's _init_weights overwrites
78
+ # all Linear and Embedding weights with normal(0, 0.02) after construction,
79
+ # silently replacing PyTorch's own defaults (kaiming_uniform_ for Linear,
80
+ # normal(0,1) for Embedding). PyTorch's reset_parameters() already ran at
81
+ # construction time and those initialisations should stand.
82
+ pass
83
+
84
+ def get_input_embeddings(self) -> nn.Embedding:
85
+ """Return the token embedding matrix. Required by PreTrainedModel for weight tying and resize_token_embeddings."""
86
+ return self.embed_tokens
87
+
88
+ def set_input_embeddings(self, value: nn.Embedding) -> None:
89
+ """Replace the token embedding matrix. Required by PreTrainedModel for weight tying and resize_token_embeddings."""
90
+ self.embed_tokens = value
91
+
92
+ def get_output_embeddings(self) -> nn.Linear:
93
+ """Return the LM head. Required by PreTrainedModel for weight tying and resize_token_embeddings."""
94
+ return self.lm_head
95
+
96
+ def set_output_embeddings(self, value: nn.Linear) -> None:
97
+ """Replace the LM head. Required by PreTrainedModel for weight tying and resize_token_embeddings."""
98
+ self.lm_head = value
99
+
100
+ def _reorder_cache(
101
+ self, past_key_values: Cache, beam_idx: torch.Tensor
102
+ ) -> Cache:
103
+ """Reorder the KV cache to match beam reordering during beam search.
104
+
105
+ GenerationMixin calls this after pruning and reordering beams at each
106
+ step. beam_idx[i] is the old batch position whose cache should move to
107
+ position i. DynamicCache.reorder_cache() handles the index-select on
108
+ every stored tensor's batch dimension, keeping the cache consistent with
109
+ the reordered beam hypotheses.
110
+
111
+ Args:
112
+ past_key_values: The active Cache object.
113
+ beam_idx: 1-D tensor of shape (batch * num_beams,) mapping new batch
114
+ positions to old ones.
115
+
116
+ Returns:
117
+ The same Cache object, reordered in place.
118
+ """
119
+ past_key_values.reorder_cache(beam_idx)
120
+ return past_key_values
121
+
122
+ def forward(
123
+ self,
124
+ input_ids: torch.Tensor,
125
+ position_ids: torch.Tensor | None = None,
126
+ past_key_values: Cache | None = None,
127
+ use_cache: bool | None = None,
128
+ output_hidden_states: bool | None = None,
129
+ labels: torch.Tensor | None = None,
130
+ cache_position: torch.Tensor | None = None,
131
+ **kwargs,
132
+ ) -> CausalLMOutputWithPast:
133
+ """Run the causal language model.
134
+
135
+ Args:
136
+ input_ids: Token indices of shape (batch, seq_len).
137
+ position_ids: Absolute positions of shape (batch, seq_len). Passed
138
+ through to the backbone. When use_cache=True and this is None,
139
+ derived from cache_position.
140
+ past_key_values: A HuggingFace Cache object from a prior step, or
141
+ None. When use_cache=True and this is None, a fresh DynamicCache
142
+ is created here before calling the backbone.
143
+ use_cache: Whether to accumulate and return a KV cache. When True
144
+ and no cache is provided, a DynamicCache is created. When False,
145
+ None is passed to the backbone regardless of what was provided.
146
+ Defaults to config.use_cache when None.
147
+ output_hidden_states: Whether to return per-layer hidden states.
148
+ Passed through to the backbone.
149
+ labels: Target token indices of shape (batch, seq_len) for computing
150
+ next-token prediction loss. The loss is computed over positions
151
+ 1..seq_len predicting from positions 0..seq_len-1 — the shift
152
+ is applied internally. Positions with label value -100 are
153
+ ignored by cross-entropy, following the HuggingFace convention
154
+ for padding and masked positions.
155
+ cache_position: 1-D integer tensor of shape (seq_len,) giving the
156
+ absolute position of each input token in the full sequence.
157
+ Provided by GenerationMixin during generate(). When use_cache=True
158
+ and this is None, it is derived from the current cache length.
159
+ **kwargs: Additional keyword arguments passed by GenerationMixin
160
+ (e.g. return_dict). Accepted and ignored for forward compatibility.
161
+ We always return CausalLMOutputWithPast regardless of return_dict.
162
+
163
+ Returns:
164
+ CausalLMOutputWithPast with fields:
165
+ - ``logits``: vocabulary scores of shape (batch, seq_len, vocab_size).
166
+ Always present.
167
+ - ``loss``: scalar cross-entropy loss, or None if labels not provided.
168
+ - ``past_key_values``: the updated Cache object, or None.
169
+ - ``hidden_states``: per-layer hidden states, or None.
170
+ """
171
+ if kwargs.get("attention_mask") is not None:
172
+ raise ValueError(
173
+ "attention_mask is not supported. This model does not support padding masks. "
174
+ "For training on variable-length sequences, use right-padding with -100 labels."
175
+ )
176
+
177
+ # Resolve both flags against config defaults. Config sets the default;
178
+ # per-call arguments override it. Both fields in Llama3Config remain live.
179
+ use_cache = use_cache if use_cache is not None else self.config.use_cache
180
+ output_hidden_states = (
181
+ output_hidden_states
182
+ if output_hidden_states is not None
183
+ else self.config.output_hidden_states
184
+ )
185
+
186
+ # Cache lifecycle is owned here — the backbone only receives a cache or None
187
+ # and never decides whether to create one.
188
+ if use_cache:
189
+ if past_key_values is None:
190
+ past_key_values = DynamicCache()
191
+ else:
192
+ past_key_values = None
193
+
194
+ inputs_embeds = self.embed_tokens(input_ids)
195
+ batch, seq_len, _ = inputs_embeds.shape
196
+
197
+ # For training (use_cache=False), positions are always 0..seq_len-1.
198
+ # This is not inference from state — it is a trivial fact about a
199
+ # non-cached forward pass. The backbone requires explicit position_ids.
200
+ if not use_cache and position_ids is None:
201
+ position_ids = torch.arange(seq_len, device=inputs_embeds.device).unsqueeze(0).expand(batch, -1)
202
+
203
+ causal_mask = None
204
+ if use_cache:
205
+ # Derive absolute query positions from cache occupancy. cache_len is the
206
+ # number of tokens already stored; query positions are cache_len..cache_len+seq_len-1.
207
+ # cache_position is accepted in the signature for HuggingFace contract
208
+ # compatibility but is not used — get_seq_length() is the authoritative source.
209
+ cache_len = past_key_values.get_seq_length()
210
+ q_positions = torch.arange(cache_len, cache_len + seq_len, device=inputs_embeds.device)
211
+
212
+ if position_ids is None:
213
+ position_ids = q_positions.unsqueeze(0).expand(batch, -1)
214
+
215
+ # Build the causal attention mask. Each query at position p may attend to
216
+ # all keys at positions 0..p. k_len covers the full sequence after this step.
217
+ k_positions = torch.arange(cache_len + seq_len, device=inputs_embeds.device)
218
+ # mask[q, k] = True when key position k is within the causal horizon of query q.
219
+ # Shape: (1, 1, seq_len, k_len) — broadcast over batch and head dimensions.
220
+ causal_mask = (k_positions[None, :] <= q_positions[:, None]).unsqueeze(0).unsqueeze(0)
221
+
222
+ backbone_out = self.model(
223
+ inputs_embeds,
224
+ position_ids=position_ids,
225
+ past_key_values=past_key_values,
226
+ output_hidden_states=output_hidden_states,
227
+ causal_mask=causal_mask,
228
+ )
229
+
230
+ logits = self.lm_head(backbone_out["last_hidden_state"])
231
+
232
+ loss = None
233
+ if labels is not None:
234
+ # Shift so that each position predicts the next token. The final
235
+ # logit has no target; the first label has no corresponding input.
236
+ shift_logits = logits[:, :-1, :].contiguous()
237
+ shift_labels = labels[:, 1:].contiguous()
238
+ loss = nn.functional.cross_entropy(
239
+ shift_logits.view(-1, self.config.vocab_size),
240
+ shift_labels.view(-1),
241
+ )
242
+
243
+ return CausalLMOutputWithPast(
244
+ logits=logits,
245
+ loss=loss,
246
+ past_key_values=backbone_out["past_key_values"],
247
+ hidden_states=backbone_out["hidden_states"],
248
+ )
mlp.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SwiGLU feed-forward sublayer.
2
+
3
+ SwiGLU is a gated linear unit variant that multiplies a SiLU-gated projection
4
+ element-wise against a separate up-projection:
5
+
6
+ output = W_down(SiLU(W_gate(x)) ⊙ W_up(x))
7
+
8
+ The gating mechanism gives the network more expressive control over which features
9
+ to propagate than a plain two-matrix FFN. It requires three weight matrices instead
10
+ of two, which is why intermediate_size in Llama 3 is set lower than the 4× multiplier
11
+ typical of two-matrix FFNs — the total parameter count remains comparable.
12
+
13
+ SiLU is used as the gate activation because Llama 3 committed to SwiGLU specifically
14
+ — a fixed architectural choice.
15
+ """
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.nn.functional as F
20
+ from transformers import PretrainedConfig
21
+
22
+
23
+ class SwiGLUMLP(nn.Module):
24
+ """SwiGLU feed-forward sublayer.
25
+
26
+ Implements the three-matrix SwiGLU FFN used in Llama 3:
27
+
28
+ output = W_down(SiLU(W_gate(x)) ⊙ W_up(x))
29
+
30
+ No bias on any projection. SiLU as the gate activation is an architectural
31
+ constant — it is what defines SwiGLU specifically.
32
+
33
+ Args:
34
+ config: Model config. Must expose ``hidden_size`` and ``intermediate_size``.
35
+ """
36
+
37
+ def __init__(self, config: PretrainedConfig) -> None:
38
+ super().__init__()
39
+ self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
40
+ self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
41
+ self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
42
+
43
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
44
+ """Apply the SwiGLU feed-forward transformation.
45
+
46
+ Args:
47
+ x: Input tensor of shape (batch, seq_len, hidden_size).
48
+
49
+ Returns:
50
+ Output tensor of shape (batch, seq_len, hidden_size).
51
+ """
52
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
model.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Transformer backbone for the Llama 3 baseline.
2
+
3
+ Llama3Model is a pure PyTorch module: a sequence of DecoderLayer blocks followed
4
+ by a final RMSNorm. It accepts pre-embedded hidden states and returns contextual
5
+ representations. It has no knowledge of tokens, vocabulary, generation, or the
6
+ HuggingFace contract — those concerns belong on Llama3ForCausalLM.
7
+
8
+ Keeping the embedding out of the backbone is the correct HF convention and makes
9
+ the backbone genuinely modality-agnostic. The token interface — embedding lookup,
10
+ LM head, weight tying — belongs on the task wrapper (Llama3ForCausalLM), which is
11
+ the only class that knows this backbone is being used for language modelling.
12
+
13
+ The final RMSNorm is necessary because the decoder stack uses pre-norm throughout:
14
+ each sublayer normalises its own input, leaving the residual stream itself
15
+ unnormalised. After many layers of accumulated residuals, that stream arrives at the
16
+ top with uncontrolled magnitude. The final norm brings it to a well-scaled state
17
+ before any projection. Without it, the LM head would receive signals of arbitrary
18
+ scale.
19
+
20
+ KV caching is caller-managed. If a Cache object is provided as past_key_values, it
21
+ is threaded through every decoder layer (each layer writes to its own slot via
22
+ layer_idx) and returned in the output dict. If None is provided, no caching occurs.
23
+ The decision of whether to create a cache and when belongs to the caller.
24
+
25
+ Returns a plain dict with keys:
26
+ - "last_hidden_state": normed backbone output, shape (batch, seq_len, hidden_size)
27
+ - "past_key_values": the Cache object passed in (updated in place), or None
28
+ - "hidden_states": tuple of per-layer activations if output_hidden_states=True, else None
29
+ """
30
+
31
+ import torch
32
+ import torch.nn as nn
33
+ from transformers.cache_utils import Cache
34
+
35
+ from .configuration import Llama3Config
36
+ from .decoder_layer import DecoderLayer
37
+
38
+
39
+ class Llama3Model(nn.Module):
40
+ """Pure transformer backbone: decoder stack and final normalisation.
41
+
42
+ Accepts pre-embedded hidden states of shape (batch, seq_len, hidden_size)
43
+ and returns contextual representations of the same shape. No token embedding,
44
+ vocabulary projection, or HuggingFace lifecycle concerns.
45
+
46
+ RoPE is applied inside each attention layer. Positional information is
47
+ encoded in the relationship between Q and K, not added to the residual
48
+ stream, so the backbone is agnostic to how positions are represented.
49
+
50
+ Args:
51
+ config: Model configuration. Must be a ``Llama3Config`` instance.
52
+ """
53
+
54
+ def __init__(self, config: Llama3Config) -> None:
55
+ super().__init__()
56
+ self.config = config
57
+ self.layers = nn.ModuleList(
58
+ [DecoderLayer(config) for _ in range(config.num_hidden_layers)]
59
+ )
60
+ # RMSNorm over LayerNorm: omits mean subtraction, faster, and proved more
61
+ # stable at scale. This is the final norm that stabilises the accumulated
62
+ # residual stream — distinct from the per-layer pre-norms inside each block.
63
+ self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
64
+
65
+ def forward(
66
+ self,
67
+ inputs_embeds: torch.Tensor,
68
+ position_ids: torch.Tensor,
69
+ past_key_values: Cache | None = None,
70
+ output_hidden_states: bool = False,
71
+ causal_mask: torch.Tensor | None = None,
72
+ ) -> dict:
73
+ """Run the transformer stack over a batch of pre-embedded sequences.
74
+
75
+ Args:
76
+ inputs_embeds: Pre-embedded input of shape (batch, seq_len, hidden_size).
77
+ position_ids: Absolute positions of shape (batch, seq_len). Required.
78
+ Must be provided explicitly by the caller — this module does not
79
+ infer positions from cache state. The caller owns the mapping from
80
+ tokens to sequence positions.
81
+ past_key_values: A Cache object carrying the accumulated K/V history from
82
+ prior forward passes, or None. When provided, each decoder layer writes
83
+ new K/V into its slot and reads back the full accumulated history. The
84
+ cache is updated in place and returned as-is. When None, no caching
85
+ occurs and None is returned for past_key_values.
86
+ output_hidden_states: When True, the output dict includes a tuple of
87
+ per-layer hidden states: (inputs_embeds, layer_0_out, ..., layer_N_out),
88
+ collected before the final norm.
89
+ causal_mask: Optional boolean attention mask of shape
90
+ (1, 1, seq_len, kv_len). Threaded unchanged into every decoder
91
+ layer. When None, each layer uses SDPA's native ``is_causal``
92
+ mode (correct for full-sequence training).
93
+
94
+ Returns:
95
+ Plain dict with keys:
96
+ - ``"last_hidden_state"``: normed backbone output,
97
+ shape (batch, seq_len, hidden_size).
98
+ - ``"past_key_values"``: the Cache object (updated in place), or None.
99
+ - ``"hidden_states"``: tuple of per-layer activations (including
100
+ inputs_embeds as position 0) if ``output_hidden_states`` is True,
101
+ else None. Collected before the final norm so each entry reflects the
102
+ unnormalised residual stream at that depth.
103
+ """
104
+ hidden_states = inputs_embeds
105
+ all_hidden_states = (hidden_states,) if output_hidden_states else None
106
+
107
+ for i, layer in enumerate(self.layers):
108
+ hidden_states = layer(hidden_states, position_ids, cache=past_key_values, layer_idx=i, causal_mask=causal_mask)
109
+ if output_hidden_states:
110
+ all_hidden_states = all_hidden_states + (hidden_states,)
111
+
112
+ hidden_states = self.norm(hidden_states)
113
+
114
+ return {
115
+ "last_hidden_state": hidden_states,
116
+ "past_key_values": past_key_values,
117
+ "hidden_states": all_hidden_states,
118
+ }
rope.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Rotary Position Embeddings (RoPE).
2
+
3
+ RoPE encodes position in the *relationship* between query and key vectors rather than
4
+ adding it to the inputs directly. When the attention dot product Q·Kᵀ is computed, the
5
+ per-position rotations cancel to produce a score that depends only on the relative
6
+ distance between positions — not on their absolute values. This is what gives RoPE
7
+ better length generalisation than absolute learned embeddings.
8
+
9
+ Each pair of head dimensions (d, d+1) is assigned a rotation frequency
10
+ 1 / theta^(2d / head_dim)
11
+ Higher theta → slower rotation per position → position encodings remain distinguishable
12
+ further apart before wrapping. Llama 3 uses theta=500,000 as a prerequisite for
13
+ 128K context support.
14
+
15
+ Supported rope types: "default" (standard unscaled RoPE), "linear", and "yarn".
16
+ HuggingFace's ROPE_INIT_FUNCTIONS handles inv_freq computation for linear and yarn;
17
+ the default case is not in that registry and is computed directly here.
18
+ """
19
+
20
+ import torch
21
+ import torch.nn as nn
22
+ from transformers import PretrainedConfig
23
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS
24
+
25
+ _SUPPORTED_ROPE_TYPES = {"default", "linear", "yarn"}
26
+
27
+
28
+ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
29
+ """Apply the 90° rotation used in the RoPE update formula.
30
+
31
+ Splits the last dimension into two halves [x1, x2] and returns [-x2, x1].
32
+ Combined with ``x * cos + rotate_half(x) * sin``, this implements a 2D rotation
33
+ on each consecutive pair of dimensions.
34
+ """
35
+ d = x.shape[-1] // 2
36
+ x1, x2 = x[..., :d], x[..., d:]
37
+ return torch.cat([-x2, x1], dim=-1)
38
+
39
+
40
+ class RotaryEmbedding(nn.Module):
41
+ """Rotary Position Embeddings as an nn.Module.
42
+
43
+ Computes position-dependent rotation frequencies from the model config, maintains
44
+ a lazily-extended cos/sin cache, and applies the rotations to query and key tensors.
45
+
46
+ The cos/sin cache grows automatically at runtime when a sequence longer than the
47
+ current cache is encountered. ``config.max_position_embeddings`` records the
48
+ training context length (required by HF's scaling computations) but does not cap
49
+ inference length.
50
+
51
+ Args:
52
+ config: Model config. Must expose ``rope_theta``, ``rope_parameters`` (set by
53
+ HF's RotaryEmbeddingConfigMixin), and ``head_dim``.
54
+ device: Optional device for initial buffer placement. Buffers move with the
55
+ model on ``.to()`` / ``.cuda()`` calls.
56
+
57
+ Raises:
58
+ NotImplementedError: If ``config.rope_parameters`` specifies an unsupported
59
+ rope type. Supported types: "default", "linear", "yarn".
60
+ """
61
+
62
+ def __init__(self, config: PretrainedConfig, device: torch.device | None = None) -> None:
63
+ super().__init__()
64
+ self.config = config
65
+
66
+ # rope_parameters is None when no rope_scaling was passed to the config.
67
+ rope_params = config.rope_parameters
68
+ self.rope_type = (
69
+ rope_params.get("rope_type", "default") if rope_params is not None else "default"
70
+ )
71
+
72
+ if self.rope_type not in _SUPPORTED_ROPE_TYPES:
73
+ raise NotImplementedError(
74
+ f"rope_type '{self.rope_type}' is not supported. "
75
+ f"Supported types: {sorted(_SUPPORTED_ROPE_TYPES)}"
76
+ )
77
+
78
+ if self.rope_type == "default":
79
+ # Standard RoPE: inv_freq = 1 / theta^(2i / head_dim).
80
+ # Not in ROPE_INIT_FUNCTIONS, so computed directly.
81
+ inv_freq = 1.0 / (
82
+ config.rope_theta
83
+ ** (torch.arange(0, config.head_dim, 2, dtype=torch.float32, device=device) / config.head_dim)
84
+ )
85
+ self.attention_scaling: float = 1.0
86
+ else:
87
+ inv_freq, self.attention_scaling = ROPE_INIT_FUNCTIONS[self.rope_type](config, device)
88
+
89
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
90
+ # Initialised as None; built on first forward call and extended lazily thereafter.
91
+ # Registered as buffers so they move with the model across devices.
92
+ self.register_buffer("_cos_cached", None, persistent=False)
93
+ self.register_buffer("_sin_cached", None, persistent=False)
94
+
95
+ def _extend_cache(self, seq_len: int, device: torch.device, dtype: torch.dtype) -> None:
96
+ """Build the cos/sin table to cover positions [0, seq_len).
97
+
98
+ Registered as buffers so subsequent calls to ``.to()`` / ``.cuda()`` will
99
+ move them to the correct device. Rebuilds whenever the sequence grows or
100
+ the dtype changes (e.g. switching between fp32 and bf16).
101
+ """
102
+ positions = torch.arange(seq_len, device=device, dtype=torch.float32)
103
+ # outer product → (seq_len, head_dim // 2); duplicate → (seq_len, head_dim)
104
+ freqs = torch.outer(positions, self.inv_freq)
105
+ emb = torch.cat((freqs, freqs), dim=-1)
106
+ self.register_buffer("_cos_cached", emb.cos().to(dtype), persistent=False)
107
+ self.register_buffer("_sin_cached", emb.sin().to(dtype), persistent=False)
108
+
109
+ def forward(
110
+ self,
111
+ q: torch.Tensor,
112
+ k: torch.Tensor,
113
+ position_ids: torch.Tensor,
114
+ ) -> tuple[torch.Tensor, torch.Tensor, float]:
115
+ """Apply rotary embeddings to query and key tensors.
116
+
117
+ The cos/sin cache is extended lazily when position_ids reference positions
118
+ beyond its current length.
119
+
120
+ Args:
121
+ q: Query tensor of shape (batch, num_heads, seq_len, head_dim).
122
+ k: Key tensor of shape (batch, num_kv_heads, seq_len, head_dim).
123
+ position_ids: Integer positions of shape (batch, seq_len).
124
+
125
+ Returns:
126
+ Tuple of (q_rotated, k_rotated, attention_scaling). attention_scaling is
127
+ 1.0 for default and linear; YaRN returns a value != 1.0 that callers must
128
+ apply to attention logits to correct for frequency magnitude changes.
129
+ """
130
+ seq_len = int(position_ids.max().item()) + 1
131
+
132
+ if self._cos_cached is None or seq_len > self._cos_cached.shape[0] or self._cos_cached.dtype != q.dtype:
133
+ self._extend_cache(seq_len, device=q.device, dtype=q.dtype)
134
+
135
+ # Gather cos/sin for the given positions → (batch, seq_len, head_dim),
136
+ # then unsqueeze the head axis for broadcast over all heads.
137
+ cos = self._cos_cached[position_ids].unsqueeze(1)
138
+ sin = self._sin_cached[position_ids].unsqueeze(1)
139
+
140
+ q_rotated = q * cos + _rotate_half(q) * sin
141
+ k_rotated = k * cos + _rotate_half(k) * sin
142
+
143
+ return q_rotated, k_rotated, self.attention_scaling
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": "<|endoftext|>",
5
+ "eos_token": "<|endoftext|>",
6
+ "errors": "replace",
7
+ "is_local": false,
8
+ "model_max_length": 1000000000000000019884624838656,
9
+ "pad_token": "<|padding|>",
10
+ "tokenizer_class": "GPTNeoXTokenizerFast",
11
+ "trim_offsets": true,
12
+ "unk_token": "<|endoftext|>"
13
+ }