Transformers
Safetensors
world_model
doom-world-model / model.py
lucrbrtv's picture
Upload 4 files
858b5c6 verified
Raw
History Blame Contribute Delete
12.8 kB
import torch
import torch.nn as nn
from torch import Tensor
from transformers import PreTrainedModel, PretrainedConfig
class MLP(nn.Module):
def __init__(self, dim: int, hidden_dim: int | None = None, out_dim: int | None = None, dropout: float = 0.0):
super().__init__()
hidden_dim = hidden_dim or dim * 4
out_dim = out_dim or dim
self.net = nn.Sequential(
nn.Linear(dim, hidden_dim), nn.SiLU(), nn.Dropout(dropout),
nn.Linear(hidden_dim, out_dim), nn.Dropout(dropout),
)
def forward(self, x: Tensor) -> Tensor:
return self.net(x)
class Projector(nn.Module):
def __init__(self, dim: int, hidden_mult: int = 2):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim, dim * hidden_mult),
nn.BatchNorm1d(dim * hidden_mult),
nn.GELU(),
nn.Linear(dim * hidden_mult, dim),
)
def forward(self, x: Tensor) -> Tensor:
lead = x.shape[:-1]
return self.net(x.reshape(-1, x.shape[-1])).reshape(*lead, x.shape[-1])
class TransformerStack(nn.Module):
def __init__(self, dim: int, n_heads: int, n_blocks: int, ffn_mult: int, dropout: float, causal: bool = False):
super().__init__()
self.blocks = nn.ModuleList()
for _ in range(n_blocks):
self.blocks.append(nn.ModuleList([
nn.LayerNorm(dim),
nn.MultiheadAttention(dim, n_heads, dropout=dropout, batch_first=True),
nn.LayerNorm(dim),
MLP(dim, dim * ffn_mult, dim, dropout),
]))
self.norm = nn.LayerNorm(dim)
self.causal = causal
def forward(self, x: Tensor) -> Tensor:
for norm1, attn, norm2, mlp in self.blocks:
attn_mask = None
if self.causal:
n = x.size(1)
attn_mask = torch.triu(torch.full((n, n), float("-inf"), device=x.device, dtype=x.dtype), diagonal=1)
h = norm1(x)
x = x + attn(h, h, h, attn_mask=attn_mask, need_weights=False)[0]
x = x + mlp(norm2(x))
return self.norm(x)
class ActionPolicy(nn.Module):
def __init__(self, dim: int, action_dim: int = 9, n_heads: int = 4, n_blocks: int = 2, ffn_mult: int = 3, dropout: float = 0.1, max_seq_len: int = 64):
super().__init__()
self.max_seq_len = max_seq_len
self.action_dim = action_dim
self.time_pos = nn.Parameter(torch.randn(1, max_seq_len, dim) * 0.02)
self.action_embed = nn.Linear(action_dim, dim, bias=False)
nn.init.normal_(self.action_embed.weight, std=0.02)
self.blocks = TransformerStack(dim, n_heads, n_blocks, ffn_mult, dropout, causal=True)
head_dim = 3 + 3 + 3 + 3 # move(3) + strafe(3) + turn(3) + binary(3)
self.head = nn.Sequential(
nn.LayerNorm(dim),
nn.Linear(dim, dim), nn.SiLU(),
nn.Linear(dim, head_dim),
)
def forward(self, states: Tensor, past_actions: Tensor | None = None) -> Tensor:
if states.dim() == 2:
states = states.unsqueeze(1)
t = states.size(1)
if t > self.time_pos.size(1):
raise ValueError(f"ActionPolicy got sequence length {t} > max_seq_len {self.time_pos.size(1)}")
x = states
if past_actions is not None and past_actions.numel() > 0:
a_emb = self.action_embed(past_actions.to(dtype=states.dtype))
n = min(a_emb.size(1), t - 1)
if n > 0:
x = x.clone()
x[:, 1:1 + n] = x[:, 1:1 + n] + a_emb[:, :n]
return self.head(self.blocks(x + self.time_pos[:, :t]))
@staticmethod
def logits_to_binary(logits: Tensor) -> Tensor:
move = logits[..., 0:3].argmax(dim=-1)
strafe = logits[..., 3:6].argmax(dim=-1)
turn = logits[..., 6:9].argmax(dim=-1)
attack = (torch.sigmoid(logits[..., 9]) > 0.5).long()
use = (torch.sigmoid(logits[..., 10]) > 0.5).long()
speed = (torch.sigmoid(logits[..., 11]) > 0.5).long()
actions = torch.zeros(*logits.shape[:-1], 9, device=logits.device, dtype=torch.long)
actions[..., 0] = (move == 0).long()
actions[..., 1] = (move == 1).long()
actions[..., 2] = (strafe == 0).long()
actions[..., 3] = (strafe == 1).long()
actions[..., 4] = (turn == 0).long()
actions[..., 5] = (turn == 1).long()
actions[..., 6] = attack
actions[..., 7] = use
actions[..., 8] = speed
return actions
class ViTEncoder(nn.Module):
def __init__(self, config: "WorldModelConfig"):
super().__init__()
self.patch_size = config.patch_size
self.n_patches = (config.height // config.patch_size) * (config.width // config.patch_size)
self.patchify = nn.Conv2d(3, config.dim, kernel_size=config.patch_size, stride=config.patch_size)
self.cls_token = nn.Parameter(torch.randn(1, 1, config.dim) * 0.02)
self.pos = nn.Parameter(torch.randn(1, self.n_patches + 1, config.dim) * 0.02)
self.blocks = TransformerStack(config.dim, config.n_heads, config.n_blocks, config.ffn_mult, config.dropout_proba)
self.projector = Projector(config.dim)
def forward(self, frames: Tensor) -> Tensor:
patches = self.patchify(frames).flatten(2).transpose(1, 2)
tokens = torch.cat([self.cls_token.expand(patches.size(0), -1, -1), patches], dim=1) + self.pos
return self.projector(self.blocks(tokens))
class AdaLNBlock(nn.Module):
def __init__(self, dim: int, n_heads: int, ffn_mult: int, dropout: float):
super().__init__()
self.norm1 = nn.LayerNorm(dim, elementwise_affine=False)
self.attn = nn.MultiheadAttention(dim, n_heads, dropout=dropout, batch_first=True)
self.norm2 = nn.LayerNorm(dim, elementwise_affine=False)
self.mlp = MLP(dim, dim * ffn_mult, dim, dropout)
self.mod = nn.Sequential(nn.SiLU(), nn.Linear(dim, 6 * dim))
nn.init.zeros_(self.mod[-1].weight)
nn.init.zeros_(self.mod[-1].bias)
def forward(self, x: Tensor, cond: Tensor, attn_mask: Tensor | None = None) -> Tensor:
scale_a, shift_a, gate_a, scale_m, shift_m, gate_m = self.mod(cond).chunk(6, dim=-1)
h = self.norm1(x) * (1 + scale_a) + shift_a
x = x + gate_a * self.attn(h, h, h, attn_mask=attn_mask, need_weights=False)[0]
h = self.norm2(x) * (1 + scale_m) + shift_m
return x + gate_m * self.mlp(h)
class Predictor(nn.Module):
def __init__(self, config: "WorldModelConfig"):
super().__init__()
self.action_proj = nn.Linear(config.action_dim, config.dim)
self.time_pos = nn.Parameter(torch.randn(1, config.max_seq_len, config.dim) * 0.02)
self.blocks = nn.ModuleList([
AdaLNBlock(config.dim, config.n_heads, config.ffn_mult, config.dropout_proba)
for _ in range(config.n_blocks)
])
self.norm = nn.LayerNorm(config.dim)
self.projector = Projector(config.dim)
self.causal = config.causal
def forward(self, states: Tensor, actions: Tensor) -> Tensor:
if states.dim() == 2:
states = states.unsqueeze(1)
if actions.dim() == 2:
actions = actions.unsqueeze(1)
b, t, d = states.shape
cap = self.time_pos.size(1)
if t > cap:
raise ValueError(
f"Sequence length {t} exceeds model capacity ({cap}). "
f"Check that the data's context_len and the model's max_seq_len are consistent."
)
x = states + self.time_pos[:, :t]
cond = self.action_proj(actions.to(dtype=states.dtype))
attn_mask = None
if self.causal:
attn_mask = torch.triu(
torch.full((t, t), float("-inf"), device=x.device, dtype=x.dtype), diagonal=1
)
for block in self.blocks:
x = block(x, cond, attn_mask=attn_mask)
return self.projector(self.norm(x))
class Decoder(nn.Module):
def __init__(self, config: "WorldModelConfig"):
super().__init__()
p = config.patch_size
gh = config.height // p
gw = config.width // p
self.grid_h = gh
self.grid_w = gw
self.n_patches = gh * gw
self._up_c = config.decoder_up_width
up_c = self._up_c
self.attn_blocks = nn.ModuleList([
AdaLNBlock(config.dim, config.n_heads, config.ffn_mult, config.dropout_proba)
for _ in range(config.decoder_n_attn_blocks)
])
self.proj = nn.Sequential(
nn.LayerNorm(config.dim),
nn.Linear(config.dim, up_c),
nn.SiLU(),
)
self.up = nn.Sequential(
nn.Conv2d(up_c, up_c * 4, 3, padding=1), nn.PixelShuffle(2), nn.SiLU(),
nn.Conv2d(up_c, up_c * 4, 3, padding=1), nn.PixelShuffle(2), nn.SiLU(),
nn.Conv2d(up_c, up_c * 4, 3, padding=1), nn.PixelShuffle(2), nn.SiLU(),
nn.Conv2d(up_c, 3 * 4, 3, padding=1), nn.PixelShuffle(2),
)
def forward(self, cls_next: Tensor, patches_prev: Tensor) -> Tensor:
cls_flat = cls_next.reshape(-1, cls_next.shape[-1])
patches_flat = patches_prev.reshape(-1, self.n_patches, patches_prev.shape[-1])
B = cls_flat.shape[0]
cond = cls_flat.unsqueeze(1).expand(-1, self.n_patches, -1)
x = patches_flat
for block in self.attn_blocks:
x = block(x, cond)
spatial = self.proj(x).transpose(1, 2).reshape(B, self._up_c, self.grid_h, self.grid_w)
return torch.sigmoid(self.up(spatial))
class WorldModelConfig(PretrainedConfig):
model_type = "world_model"
def __init__(
self,
height: int = 240, width: int = 320, patch_size: int = 16, dim: int = 384, n_heads: int = 6,
n_blocks: int = 3, decoder_hidden_mult: int = 4, decoder_n_blocks: int = 4,
decoder_noise_std: float = 0.05, decoder_pred_token_ratio: float = 0.98,
decoder_curriculum_end: float = 0.85, ffn_mult: int = 3,
decoder_up_width: int = 128, decoder_n_attn_blocks: int = 1,
dropout_proba: float = 0.1, causal: bool = True, action_dim: int = 9, max_seq_len: int = 64,
**kwargs,
):
super().__init__(**kwargs)
self.height = height
self.width = width
self.patch_size = patch_size
self.dim = dim
self.n_heads = n_heads
self.n_blocks = n_blocks
self.decoder_hidden_mult = decoder_hidden_mult
self.decoder_n_blocks = decoder_n_blocks
self.decoder_noise_std = decoder_noise_std
self.decoder_pred_token_ratio = decoder_pred_token_ratio
self.decoder_curriculum_end = decoder_curriculum_end
self.ffn_mult = ffn_mult
self.decoder_up_width = decoder_up_width
self.decoder_n_attn_blocks = decoder_n_attn_blocks
self.dropout_proba = dropout_proba
self.causal = causal
self.action_dim = action_dim
self.max_seq_len = max_seq_len
class WorldModel(PreTrainedModel):
config_class = WorldModelConfig
all_tied_weights_keys = {}
def __init__(self, config: WorldModelConfig):
super().__init__(config)
self.n_patches = (config.height // config.patch_size) * (config.width // config.patch_size)
self.encoder = ViTEncoder(config)
self.predictor = Predictor(config)
self.decoder = Decoder(config)
self._sync_max_seq_len()
def _sync_max_seq_len(self) -> None:
actual = self.predictor.time_pos.size(1)
if self.config.max_seq_len != actual:
self.config.max_seq_len = actual
@classmethod
def from_pretrained(cls, *args, **kwargs):
model = super().from_pretrained(*args, **kwargs)
model._sync_max_seq_len()
return model
def encode(self, frames: Tensor, return_tokens: bool = False):
if frames.dim() == 4: frames = frames.unsqueeze(1)
b, t, c, h, w = frames.shape
frames = frames.reshape(b * t, c, h, w)
if frames.is_cuda: frames = frames.contiguous(memory_format=torch.channels_last)
tokens = self.encoder(frames).view(b, t, self.n_patches + 1, self.config.dim)
states = tokens[:, :, 0]
return (states, tokens) if return_tokens else states
def predict(self, states: Tensor, actions: Tensor) -> Tensor:
return self.predictor(states, actions)
def decode(self, cls_next: Tensor, patches_prev: Tensor) -> Tensor:
return self.decoder(cls_next, patches_prev)
def forward(self, frames: Tensor, actions: Tensor):
states = self.encode(frames)
return self.predict(states, actions)