Extend GPT¶
Coming Soon
Heyoooo so I ran out of time writing docs as I have to do actual machine learning. I'll eventually catch this up but as of right now here's my friend gpt-6-astra who will do the talking.
Inherit theseus.model.models.base.GPT to change part of the language model
while keeping its embeddings, transformer stack, and loss implementation.
from theseus.config import field
from theseus.model.models.base import GPT
class ScaledGPT(GPT):
logit_scale: float = field("architecture/logit_scale", default=1.0)
def unembed(self, x):
return super().unembed(x) * self.logit_scale
The inherited __call__() runs embed → decode → unembed → loss. In this
example, super().unembed() supplies final normalization and the tied embedding
projection; the override scales its logits.
| Override | Contract |
|---|---|
embed(idx, deterministic=False, **kwargs) |
Token IDs [B, T] → features [B, T, C] |
decode(x, padding_mask=None, deterministic=False, **kwargs) |
Features → features; runs the blocks |
unembed(x) |
Features → logits [B, T, vocab_size] |
loss(logits, targets) |
Return a scalar loss; the base ignores targets equal to -1 |
setup() and components() |
Construct and declare different blocks or normalization layers |
__call__() returns (logits, loss), with loss=None when no targets are given.
Preserve that contract for the default trainer. Forward padding masks and
deterministic when changing layer execution. Call measure() in a custom
decode() if you want to retain residual diagnostics.
Set your trainer's MODEL to your subclass; models have no registration decorator.
The examples below override one stage at a time.
Scale embeddings¶
Keep token lookup, positional embeddings, and dropout; scale their result.
The following examples use the GPT import above.
class ScaledEmbeddings(GPT):
def embed(self, idx, deterministic=False, **kwargs):
return 0.5 * super().embed(idx, deterministic=deterministic, **kwargs)
Change layer execution¶
Reverse the order of the existing blocks. Preserve mask/dropout arguments and
measure() so residual diagnostics still work.
class ReversedGPT(GPT):
def decode(self, x, padding_mask=None, deterministic=False, **kwargs):
for depth, block in enumerate(reversed(self.blocks)):
residual = x
x = block(x, padding_mask=padding_mask, deterministic=deterministic)
self.measure(x, residual, depth, padding_mask)
return x
Scale logits¶
unembed() includes final normalization and the tied output projection. This
changes the logits used both for prediction and for the inherited loss.
Change the loss¶
Add a logit penalty, excluding ignored target positions just as the base loss does.
import jax.numpy as jnp
class PenalizedLogits(GPT):
def loss(self, logits, targets):
valid = targets != -1
penalty = jnp.mean(logits.astype(jnp.float32) ** 2, axis=-1)
penalty = jnp.sum(penalty * valid) / jnp.maximum(valid.sum(), 1)
return super().loss(logits, targets) + 1e-4 * penalty
Replace the block¶
You can keep decode(): it iterates over self.blocks. Base GPT has no BLOCK
attribute or new_block() hook; replace the list in setup() instead.
from theseus.config import configure, field
from theseus.model.block import Block
class ScaledBlock(Block):
scale: float = field("architecture/block_scale", default=1.0)
def __call__(self, x, **kwargs):
return super().__call__(x, **kwargs) * self.scale
class CustomBlocksGPT(GPT):
@classmethod
def components(cls):
return [*super().components(), ScaledBlock]
def setup(self):
super().setup() # Keep embeddings, dropout, and final normalization.
self.blocks = [
configure(ScaledBlock, name=f"custom_blocks_{i}")
for i in range(self.n_layers)
]
The explicit names avoid colliding with the blocks assigned by super().setup().
They also change checkpoint parameter paths; restoring an existing GPT checkpoint
needs matching names or checkpoint surgery.
Add new configurable children to components(), the component list used for
configuration discovery. Here it makes architecture/block_scale available.
Preserve the inherited list when you call super().setup(), because that setup
still configures the original children. There is no separate global component
registry, and listing a class does not install it: setup() does that.