FutureWorlds / code /src /futureworlds /text_conditioning.py
easylearning's picture
Add files using upload-large-folder tool
64b9802 verified
Raw History Blame Contribute Delete
3.64 kB
"""Pretrained-text conditioning with zero-initialized residual cross-attention.
Frozen T5 features are inputs, not vocabulary IDs. Visual/action positions and
vocabulary stay unchanged. No-text requests bypass the added residual exactly.
"""
import torch
_UNSET = object()
from torch import nn
import torch.nn.functional as F
class TextResidual(nn.Module):
def __init__(self, hidden=768, text_width=768, inner=256, heads=4):
super().__init__();self.heads=heads;self.inner=inner
self.query_norm=nn.LayerNorm(hidden)
self.text_norm=nn.LayerNorm(text_width)
self.q=nn.Linear(hidden,inner,bias=False)
self.k=nn.Linear(text_width,inner,bias=False)
self.v=nn.Linear(text_width,inner,bias=False)
self.out=nn.Linear(inner,hidden,bias=False)
nn.init.zeros_(self.out.weight)
def forward(self, hidden, text, mask):
b,t,_=hidden.shape
if text.shape[0]!=b:
assert text.shape[0]==1, 'Generation expansion is valid only within a single episode'
text=text.expand(b,-1,-1);mask=mask.expand(b,-1)
dim=self.inner//self.heads
q=self.q(self.query_norm(hidden)).view(b,t,self.heads,dim).transpose(1,2)
memory=self.text_norm(text)
k=self.k(memory).view(b,-1,self.heads,dim).transpose(1,2)
v=self.v(memory).view(b,-1,self.heads,dim).transpose(1,2)
value=F.scaled_dot_product_attention(q,k,v,attn_mask=mask[:,None,None,:].bool(),dropout_p=0.)
value=value.transpose(1,2).reshape(b,t,self.inner)
# Explicitly suppress all-padding / deliberately dropped instruction.
value=self.out(value)*mask.any(-1)[:,None,None].to(value.dtype)
return hidden+value.to(hidden.dtype)
class TextWorldModel(nn.Module):
def __init__(self, core, text_width=768, layers=(3,7,11), inner=256):
super().__init__();self.core=core;self.layers=list(layers)
self.adapters=nn.ModuleDict({str(i):TextResidual(core.config.hidden_size,text_width,inner) for i in layers})
self._text=None;self._mask=None;self._handles=[]
for i in layers:
self._handles.append(core.model.layers[i].register_forward_pre_hook(self._hook(i),with_kwargs=True))
def _hook(self,index):
def hook(module,args,kwargs):
if self._text is None:return args,kwargs
adapter=self.adapters[str(index)]
if args:
return (adapter(args[0],self._text,self._mask),)+args[1:],kwargs
kw=dict(kwargs);kw['hidden_states']=adapter(kw['hidden_states'],self._text,self._mask)
return args,kw
return hook
def set_text(self,text,mask):
assert (text is None)==(mask is None)
if text is not None:
assert text.ndim==3 and mask.shape==text.shape[:2]
assert torch.isfinite(text).all()
self._text=text;self._mask=mask
def forward(self,text_features=_UNSET,text_mask=_UNSET,**kwargs):
# Generation and selected-token scoring use the explicitly bound episode.
# No activation checkpointing: mutable conditioning cannot change during replay.
if text_features is not _UNSET:
assert text_mask is not _UNSET
self.set_text(text_features,text_mask)
else:
assert text_mask is _UNSET
return self.core(**kwargs)
def generate(self,*args,**kwargs):
# Caller sets the same episode instruction before every generated frame.
return self.core.generate(*args,**kwargs)
def compute_transition_scores(self,*args,**kwargs):return self.core.compute_transition_scores(*args,**kwargs)