Download code/train/model.py from Avra98/Sudoku_superposition: direct link, hf CLI and curl.
- Browser
- Download file 12 kB
-
https://huggingface.co/Avra98/Sudoku_superposition/resolve/main/code/train/model.py
- Command line
-
hf download hf://Avra98/Sudoku_superposition/code/train/model.py
-
curl -L -o model.py https://huggingface.co/Avra98/Sudoku_superposition/resolve/main/code/train/model.py
12 kB
| # coding=utf-8 | |
| # Copyright 2024 The Google Research Authors. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Model Architecture.""" | |
| import functools | |
| from typing import Any, Callable | |
| from flax import linen as nn | |
| from flax import struct | |
| from jax import numpy as jnp | |
| class TransformerConfig: | |
| """Global hyperparameters used to minimize obnoxious kwarg plumbing.""" | |
| vocab_size: int = 1 | |
| dtype: Any = jnp.float32 | |
| emb_dim: int = 512 | |
| num_heads: int = 8 | |
| num_layers: int = 6 | |
| qkv_dim: int = 512 | |
| mlp_dim: int = 2048 | |
| seq_len: int = 2048 # Maximum sequence length | |
| dropout_rate: float = 0.1 | |
| attention_dropout_rate: float = 0.1 | |
| deterministic: bool = False | |
| num_latent_slots: int = 0 # K continuous latent thought slots | |
| # When False, the fed-back latent thought vectors are NOT injected into the | |
| # slot positions (they keep their placeholder token embedding). This turns | |
| # the K slots into static, parallel per-stage readouts with NO carried | |
| # recurrent state -- the "stagewise supervision, no recurrence" control. | |
| inject_latents: bool = True | |
| # Stochastic latents: each slot carries a learned diagonal Gaussian instead | |
| # of a single point, and the readout is Monte-Carlo averaged over draws. | |
| latent_noise: bool = False | |
| # log sigma at init. The log-sigma weight matrix is zero-init, so this is | |
| # the (input-independent) starting spread, in units of the latent itself. | |
| latent_noise_logsigma0: float = -4.0 | |
| class TransformerBlock(nn.Module): | |
| config: Any = None | |
| def setup(self): | |
| self.vocab_size = self.config.vocab_size | |
| self.emb_dim = self.config.emb_dim | |
| self.num_layers = self.config.num_layers | |
| def __call__(self, inputs, causal_mask_inputs, training=True): | |
| """ | |
| Transformer Block call function. | |
| Args: | |
| inputs: Input tensor. | |
| causal_mask_inputs: Causal mask for the inputs. | |
| training: Whether the model is in training mode. | |
| Returns: | |
| Transformed tensor after self-attention and MLP layers. | |
| """ | |
| x = inputs + nn.SelfAttention( | |
| num_heads=self.config.num_heads, dtype=self.config.dtype, | |
| qkv_features=self.config.qkv_dim, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| bias_init=nn.initializers.normal(stddev=1e-6), | |
| use_bias=False, broadcast_dropout=False, | |
| dropout_rate=self.config.attention_dropout_rate, normalize_qk=True, | |
| deterministic=self.config.deterministic)(inputs, causal_mask_inputs) | |
| def mlp(x): | |
| """ | |
| Multi-Layer Perceptron function. | |
| Args: | |
| x: Input tensor. | |
| Returns: | |
| Transformed tensor after applying MLP layers. | |
| """ | |
| dense_with_init = functools.partial( | |
| nn.Dense, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| bias_init=nn.initializers.normal(stddev=1e-6) | |
| ) | |
| x = dense_with_init(features=self.config.mlp_dim)(x) | |
| x = nn.gelu(x) | |
| x = dense_with_init(features=self.config.emb_dim)(x) | |
| x = nn.Dropout(rate=self.config.dropout_rate, | |
| deterministic=self.config.deterministic)(x) | |
| return x | |
| x = x + mlp(x) | |
| return x | |
| class TransformerLMHeadModel(nn.Module): | |
| config: Any = None | |
| def setup(self): | |
| self.vocab_size = self.config.vocab_size | |
| self.emb_dim = self.config.emb_dim | |
| self.num_layers = self.config.num_layers | |
| # Per-slot Gaussian over the last-layer latent hidden. Each slot gets | |
| # its own mean and log-sigma matrix. Zero-init W makes the mean an | |
| # identity on that hidden and sigma an input-independent constant. | |
| if self.config.latent_noise: | |
| k = int(self.config.num_latent_slots) | |
| d = int(self.config.emb_dim) | |
| self.latent_mu_w = self.param( | |
| "latent_mu_w", nn.initializers.zeros, (k, d, d)) | |
| self.latent_mu_b = self.param( | |
| "latent_mu_b", nn.initializers.zeros, (k, d)) | |
| self.latent_logsig_w = self.param( | |
| "latent_logsig_w", nn.initializers.zeros, (k, d, d)) | |
| self.latent_logsig_b = self.param( | |
| "latent_logsig_b", | |
| lambda _key, shape: jnp.full( | |
| shape, self.config.latent_noise_logsigma0, jnp.float32), | |
| (k, d)) | |
| def latent_noise_dist(self, latent_values): | |
| """Diagonal Gaussian (mu, log sigma) for every latent slot. | |
| latent_values: (bs, K, emb) deterministic thoughts from the recurrence. | |
| Returns two (bs, K, emb) float32 arrays. Kept in float32 because a | |
| small additive perturbation does not survive bfloat16 rounding. | |
| """ | |
| z = latent_values.astype(jnp.float32) | |
| mu = (z + jnp.einsum("bkd,kde->bke", z, self.latent_mu_w) | |
| + self.latent_mu_b[None]) | |
| log_sigma = (jnp.einsum("bkd,kde->bke", z, self.latent_logsig_w) | |
| + self.latent_logsig_b[None]) | |
| return mu, log_sigma | |
| def latent_noise_dist_slot(self, z, slot): | |
| """Diagonal Gaussian for one slot. z is (bs, emb).""" | |
| z = z.astype(jnp.float32) | |
| s = jnp.asarray(slot, jnp.int32) | |
| mu = z + jnp.einsum("bd,de->be", z, self.latent_mu_w[s]) + self.latent_mu_b[s] | |
| log_sigma = (jnp.einsum("bd,de->be", z, self.latent_logsig_w[s]) | |
| + self.latent_logsig_b[s]) | |
| return mu, log_sigma | |
| def __call__(self, inputs, latent_values=None, latent_positions=None, | |
| latent_active=None, training=True): | |
| """ | |
| Transformer LM Head call function. | |
| Args: | |
| inputs: Input token ids (batch, seq). | |
| latent_values: Optional (batch, K, emb_dim) continuous thought | |
| vectors (raw last-layer hiddens fed back, Coconut/ATC style). | |
| latent_positions: Optional (batch, K) int positions of the latent | |
| slots in the sequence (per-example, after the clue block). | |
| latent_active: Optional (batch, K) bool; slot j uses the projected | |
| latent vector when True, otherwise keeps the placeholder | |
| token embedding. | |
| training: Whether the model is in training mode. | |
| Returns: | |
| (logits, hidden): LM logits and final (post-LayerNorm) hidden | |
| states, the latter used to build the next continuous thought. | |
| """ | |
| batch_size, seq_size = inputs.shape | |
| causal_mask_x = nn.make_causal_mask(inputs, dtype=self.config.dtype) | |
| # Embed the input tensor using a learnable embedding matrix. | |
| embed_with_init = functools.partial( | |
| nn.Embed, embedding_init=nn.initializers.normal(stddev=0.02)) | |
| token_embeddings = embed_with_init( | |
| num_embeddings=self.config.vocab_size, | |
| features=self.config.emb_dim, | |
| )(inputs) | |
| # Check the shape of the embedded tensor. | |
| assert token_embeddings.shape == (batch_size, seq_size, | |
| self.config.emb_dim) | |
| # Continuous latent thoughts: project fed-back hidden states and | |
| # scatter them into the latent slot positions, replacing the | |
| # placeholder token embedding (position embeddings still added below). | |
| # Skipped when inject_latents is False (no-recurrence control: the slots | |
| # stay as static placeholders and the candidate heads become parallel | |
| # per-stage readouts with no carried state). | |
| if latent_values is not None and self.config.inject_latents: | |
| proj = nn.Dense(features=self.config.emb_dim, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| name="latent_proj_in")(latent_values) | |
| proj = nn.gelu(proj) | |
| proj = nn.Dense(features=self.config.emb_dim, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| name="latent_proj_out")(proj) | |
| bidx = jnp.arange(batch_size)[:, None] | |
| cur = token_embeddings[bidx, latent_positions] | |
| new = jnp.where(latent_active[..., None], | |
| proj.astype(cur.dtype), cur) | |
| token_embeddings = token_embeddings.at[ | |
| bidx, latent_positions].set(new) | |
| # Initialize the positional embedding variable. | |
| pos_embedding_variable = self.variable( | |
| "params", | |
| "position_embeddings", | |
| jnp.zeros, | |
| (self.config.seq_len, self.config.emb_dim), | |
| ) | |
| # Slice the positional embedding array to the correct sequence length. | |
| pos_embeddings = pos_embedding_variable.value[:seq_size, :] | |
| # Check the shape of the positional embedding array. | |
| output_tuple = (pos_embeddings.shape, token_embeddings.shape[1:]) | |
| assert pos_embeddings.shape == token_embeddings.shape[1:], output_tuple | |
| # Add the positional embeddings to the token embeddings. | |
| x = token_embeddings + pos_embeddings[None, :, :] | |
| # Apply dropout to the input. | |
| x = nn.Dropout(rate=self.config.dropout_rate, | |
| deterministic=self.config.deterministic)(x) | |
| # Apply the Transformer layers. remat (gradient checkpointing) keeps | |
| # the multi-pass latent recurrence within GPU memory under full BPTT. | |
| RematBlock = nn.remat(TransformerBlock) | |
| for i in range(self.num_layers): | |
| x = RematBlock(config=self.config)( | |
| x, causal_mask_x, training=training) | |
| self.sow('intermediates', 'feature_' + str(i), x) | |
| # Apply the final layer normalization. | |
| x = nn.LayerNorm()(x) | |
| # Apply the LM head. | |
| logits = nn.Dense(features=self.config.vocab_size, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| bias_init=nn.initializers.normal(stddev=1e-6), | |
| use_bias=False)(x) | |
| # Check the shape of the output tensor. | |
| assert logits.shape == (batch_size, seq_size, self.config.vocab_size) | |
| # ---- Auxiliary multi-candidate head ---- | |
| # Each latent slot reads its (post-LayerNorm) hidden and predicts the | |
| # full 81x9 candidate grid for its reasoning stage. Trained with BCE | |
| # (independent per-digit sigmoids = candidate-set membership), NOT | |
| # softmax, so multiple digits can be "on" at intermediate stages. | |
| cand_logits = None | |
| if latent_positions is not None: | |
| bidx = jnp.arange(batch_size)[:, None] | |
| slot_hidden = x[bidx, latent_positions] # (bs, K, emb) | |
| h = nn.Dense(features=self.config.emb_dim, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| name="cand_head_in")(slot_hidden) | |
| h = nn.gelu(h) | |
| cand_logits = nn.Dense(features=81 * 9, | |
| kernel_init=nn.initializers.xavier_uniform(), | |
| name="cand_head_out")(h) # (bs, K, 729) | |
| cand_logits = cand_logits.reshape( | |
| batch_size, -1, 81, 9) # (bs, K, 81, 9) | |
| return logits, x, cand_logits | |