Skip to content

Commit 8d5e52a

Browse files
authored
Merge pull request #101 from UT-Austin-RPL/rope
rope, `action_from_buffer`, sigma reparam init
2 parents d027b39 + 761add7 commit 8d5e52a

5 files changed

Lines changed: 102 additions & 19 deletions

File tree

amago/agent.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ def get_values(self, batch: Batch) -> dict[str, torch.Tensor]:
795795
K_a = self.num_actions_for_value_in_actor_loss if not self.discrete else 1
796796
a_buffer = F.pad(a, (0, 0, 0, 1), "replicate")
797797
a_buffer = repeat(a_buffer, f"b l a -> b l {G} a")
798+
a_buffer = self.actor.policy_dist.action_from_buffer(a_buffer)
798799
state_mask = (~((batch.rl2s == self.pad_val).all(-1, keepdim=True))).bool()[
799800
:, 1:, ...
800801
]
@@ -927,6 +928,7 @@ def forward(self, batch: Batch, log_step: bool) -> torch.Tensor:
927928
# we give it a fake one to make shape math work.
928929
a_buffer = F.pad(a, (0, 0, 0, 1), "replicate")
929930
a_buffer = repeat(a_buffer, f"b l a -> b l {G} a")
931+
a_buffer_critic = self.actor.policy_dist.action_from_buffer(a_buffer)
930932
C = len(self.critics)
931933
# arrays used by critic update end up in a (B, L, C, G, dim) format
932934
assert batch.rews.shape == (B, L - 1, 1)
@@ -964,7 +966,7 @@ def forward(self, batch: Batch, log_step: bool) -> torch.Tensor:
964966
############################
965967
s_a_agent_g = (s_rep.detach(), a_agent)
966968
q_s_a_agent_g = self.maximized_critics(*s_a_agent_g).mean(0)
967-
s_a_g = (s_rep[:, :-1, ...], a_buffer[:, :-1, ...].unsqueeze(0))
969+
s_a_g = (s_rep[:, :-1, ...], a_buffer_critic[:, :-1, ...].unsqueeze(0))
968970
q_s_a_g = self.critics(*s_a_g, log_dict=active_log_dict).mean(0)
969971
assert q_s_a_agent_g.shape == (B, L, C, G, 1)
970972
assert q_s_a_g.shape == (B, L-1, C, G, 1)
@@ -1078,7 +1080,6 @@ def masked_avg(x_, dim=0):
10781080
stats[f"Q(s, a) (global mean, rescaled) gamma={gamma:.3f}"] = masked_avg(
10791081
q_s_a_g, i
10801082
)
1081-
print("here")
10821083
stats["Q Sequence"] = q_s_a_g
10831084
stats[f"Q(s,a) (global mean, raw scale) gamma={gamma:.3f}"] = masked_avg(
10841085
raw_q_s_a_g, i
@@ -1299,6 +1300,7 @@ def get_values(self, batch: Batch) -> dict[str, torch.Tensor]:
12991300
K_a = self.num_actions_for_value_in_actor_loss
13001301
a_buffer = F.pad(a, (0, 0, 0, 1), "replicate")
13011302
a_buffer = repeat(a_buffer, f"b l a -> b l {G} a")
1303+
a_buffer = self.actor.policy_dist.action_from_buffer(a_buffer)
13021304
state_mask = (~((batch.rl2s == self.pad_val).all(-1, keepdim=True))).bool()[
13031305
:, 1:, ...
13041306
]
@@ -1363,6 +1365,7 @@ def forward(self, batch: Batch, log_step: bool):
13631365
K_c = self.num_actions_for_value_in_critic_loss
13641366
a_buffer = F.pad(a, (0, 0, 0, 1), "replicate")
13651367
a_buffer = repeat(a_buffer, f"b l a -> b l {G} a")
1368+
a_buffer = self.actor.policy_dist.action_from_buffer(a_buffer)
13661369
C = len(self.critics)
13671370
assert batch.rews.shape == (B, L - 1, 1)
13681371
assert batch.dones.shape == (B, L - 1, 1)

amago/nets/policy_dists.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,15 @@ def input_dimension(self) -> int:
211211
"""
212212
raise NotImplementedError
213213

214+
def action_from_buffer(self, action: torch.Tensor) -> torch.Tensor:
215+
"""Preprocess raw actions loaded from the replay buffer for use in the critic.
216+
217+
Subclasses override this to align buffer actions with the form the policy
218+
actually produces (e.g., softening hard one-hot discrete actions).
219+
Default is identity.
220+
"""
221+
return action
222+
214223
@abstractmethod
215224
def forward(
216225
self, vec: torch.Tensor, log_dict: Optional[dict] = None
@@ -269,6 +278,13 @@ def is_discrete(self) -> bool:
269278
def input_dimension(self) -> int:
270279
return self.d_action
271280

281+
def clip_and_normalize(self, probs: torch.Tensor) -> torch.Tensor:
282+
clipped = probs.clamp(self.clip_prob_low, self.clip_prob_high)
283+
return clipped / clipped.sum(-1, keepdim=True)
284+
285+
def action_from_buffer(self, action: torch.Tensor) -> torch.Tensor:
286+
return self.clip_and_normalize(action)
287+
272288
def forward(
273289
self, vec: torch.Tensor, log_dict: Optional[dict] = None
274290
) -> _Categorical:
@@ -278,7 +294,7 @@ def forward(
278294
safe_probs = clip_probs / clip_probs.sum(-1, keepdims=True).detach()
279295
safe_dist = _Categorical(probs=safe_probs)
280296
if log_dict is not None:
281-
add_activation_log("Discrete-probs", probs, log_dict)
297+
add_activation_log("Discrete-probs", dist.probs, log_dict)
282298
return safe_dist
283299

284300

amago/nets/traj_encoders.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,8 @@ class TformerTrajEncoder(TrajEncoder):
345345
activation: Activation function. Defaults to "leaky_relu".
346346
norm: Normalization function. Defaults to "layer" (LayerNorm).
347347
pos_emb: Position embedding type. "fixed" (default) uses sinusoidal
348-
embeddings, "learned" uses trainable embeddings per timestep.
348+
embeddings, "learned" uses trainable embeddings per timestep,
349+
"rope" uses Rotary Position Embeddings applied to Q and K.
349350
causal: Whether to use causal attention mask. Defaults to True.
350351
sigma_reparam: Whether to use :math:`\sigma`-reparam feed-forward layers
351352
from https://arxiv.org/abs/2303.06296. Defaults to True.
@@ -395,6 +396,7 @@ def make_layer():
395396
dropout_qkv=dropout_qkv,
396397
head_scaling=head_scaling,
397398
sigma_reparam=sigma_reparam,
399+
use_rope=pos_emb == "rope",
398400
),
399401
d_model=self.d_model,
400402
d_ff=d_ff,

amago/nets/transformer.py

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,9 @@ def __init__(self, d_in, d_out, bias: bool = True, fast_init: bool = False):
423423
super().__init__(d_in, d_out, bias=bias)
424424
if not fast_init:
425425
nn.init.trunc_normal_(self.weight, std=0.02)
426-
u = torch.linalg.svd(self.weight.T, full_matrices=False)[-1][0].detach()
427-
v = torch.linalg.svd(self.weight, full_matrices=False)[-1][0].detach()
426+
U, _S, Vh = torch.linalg.svd(self.weight, full_matrices=False)
427+
u = U[:, 0].detach()
428+
v = Vh[0].detach()
428429
else:
429430
# initialization from legacy version used in the original AMAGO paper.
430431
# This was a guess based on the sigma reparam pseudocode before the code was released,
@@ -469,10 +470,12 @@ def __init__(
469470
dropout_qkv: float = 0.0,
470471
head_scaling: bool = True,
471472
sigma_reparam: bool = True,
473+
use_rope: bool = False,
472474
):
473475
super().__init__()
474476
assert isinstance(self_attention, SelfAttention)
475477
self.self_attention = self_attention
478+
self.rope = RotaryPosEmb(d_qkv) if use_rope else None
476479
FF = SigmaReparam if sigma_reparam else nn.Linear
477480
self.qkv_projection = FF(d_model, 3 * d_qkv * n_heads, bias=False)
478481
self.dropout_qkv = nn.Dropout(dropout_qkv)
@@ -482,14 +485,23 @@ def __init__(
482485
)
483486
self.n_heads = n_heads
484487

485-
def forward(self, sequence, key_cache=None, val_cache=None, cache_seqlens=None):
488+
def forward(
489+
self,
490+
sequence,
491+
key_cache=None,
492+
val_cache=None,
493+
cache_seqlens=None,
494+
pos_idxs=None,
495+
):
486496
qkv = self.dropout_qkv(self.qkv_projection(sequence))
487497
qkv = rearrange(
488498
qkv,
489499
"batch len (three d_qkv heads) -> batch len three heads d_qkv",
490500
heads=self.n_heads,
491501
three=3,
492502
)
503+
if self.rope is not None:
504+
qkv = self.rope(qkv, pos_idxs)
493505
out = self.head_scaler * self.self_attention(
494506
qkv=qkv,
495507
key_cache=key_cache,
@@ -538,10 +550,21 @@ def __init__(
538550
self.d_model = d_model
539551

540552
@torch.compile
541-
def forward(self, self_seq, key_cache=None, val_cache=None, cache_seqlens=None):
553+
def forward(
554+
self,
555+
self_seq,
556+
key_cache=None,
557+
val_cache=None,
558+
cache_seqlens=None,
559+
pos_idxs=None,
560+
):
542561
q1 = self.norm1(self_seq) # pre-norm
543562
q1 = self.attention_layer(
544-
q1, key_cache=key_cache, val_cache=val_cache, cache_seqlens=cache_seqlens
563+
q1,
564+
key_cache=key_cache,
565+
val_cache=val_cache,
566+
cache_seqlens=cache_seqlens,
567+
pos_idxs=pos_idxs,
545568
)
546569
q1 = self.norm2(q1) # normformer extra norm 1
547570
self_seq = self_seq + q1
@@ -667,6 +690,39 @@ def forward(self, pos_idxs: torch.LongTensor):
667690
return self.embeddings(pos_idxs)
668691

669692

693+
class RotaryPosEmb(nn.Module):
694+
"""Rotary Position Embedding (RoPE). Half-split (Llama-style) convention."""
695+
696+
def __init__(self, head_dim: int, base: float = 10000.0):
697+
super().__init__()
698+
assert head_dim % 2 == 0, f"RoPE requires even head_dim, got {head_dim}"
699+
inv_freq = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim))
700+
self.register_buffer("inv_freq", inv_freq, persistent=False)
701+
self.head_dim = head_dim
702+
703+
def forward(self, qkv: torch.Tensor, pos_idxs: torch.Tensor) -> torch.Tensor:
704+
"""Rotate Q and K in packed (B, L, 3, H, D) QKV; V is unchanged."""
705+
pos = pos_idxs.squeeze(-1).to(self.inv_freq.dtype)
706+
freqs = torch.einsum("bl,d->bld", pos, self.inv_freq)
707+
cos = freqs.cos().unsqueeze(2)
708+
sin = freqs.sin().unsqueeze(2)
709+
710+
q, k, v = qkv.unbind(dim=2)
711+
q = self._apply_rotary(q, cos, sin).to(q.dtype)
712+
k = self._apply_rotary(k, cos, sin).to(k.dtype)
713+
return torch.stack([q, k, v], dim=2)
714+
715+
@staticmethod
716+
def _apply_rotary(
717+
x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
718+
) -> torch.Tensor:
719+
"""x: (B, L, H, D), cos/sin: (B, L, 1, D/2)."""
720+
d_half = x.shape[-1] // 2
721+
x1 = x[..., :d_half]
722+
x2 = x[..., d_half:]
723+
return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
724+
725+
670726
class Transformer(nn.Module):
671727
"""Build a full Transformer model from a list of layers."""
672728

@@ -680,13 +736,16 @@ def __init__(
680736
pos_emb: str = "fixed",
681737
):
682738
super().__init__()
739+
self.use_rope = pos_emb == "rope"
683740
if pos_emb == "fixed":
684741
self.position_embedding = FixedPosEmb(d_model)
685742
elif pos_emb == "learnable":
686743
self.position_embedding = LearnablePosEmb(d_model)
744+
elif pos_emb == "rope":
745+
self.position_embedding = None
687746
else:
688747
raise ValueError(
689-
f"Unrecognized pos_emb: {pos_emb}. Options are 'fixed' or 'learnable'."
748+
f"Unrecognized pos_emb: {pos_emb}. Options are 'fixed', 'learnable', or 'rope'."
690749
)
691750
self.inp = nn.Linear(inp_dim, d_model)
692751
self.dropout = nn.Dropout(dropout_emb)
@@ -701,20 +760,22 @@ def emb_dim(self):
701760
return self.d_model
702761

703762
def preprocess_seq(self, seq, pos_idxs):
704-
pos_emb = self.position_embedding(pos_idxs.squeeze(-1))
705763
traj_emb = self.inp(seq)
706-
traj_emb = self.dropout(traj_emb + pos_emb)
764+
if self.position_embedding is not None:
765+
pos_emb = self.position_embedding(pos_idxs.squeeze(-1))
766+
traj_emb = traj_emb + pos_emb
767+
traj_emb = self.dropout(traj_emb)
707768
return traj_emb
708769

709770
@torch.compile
710-
def training_forward(self, seq):
771+
def training_forward(self, seq, pos_idxs=None):
711772
for layer in self.layers:
712-
seq = layer(seq)
773+
seq = layer(seq, pos_idxs=pos_idxs)
713774
return self.norm(seq)
714775

715-
def inference_forward(self, seq, hidden_state):
776+
def inference_forward(self, seq, hidden_state, pos_idxs=None):
716777
for i, layer in enumerate(self.layers):
717-
seq = layer(seq, *hidden_state[i])
778+
seq = layer(seq, *hidden_state[i], pos_idxs=pos_idxs)
718779
return self.norm(seq)
719780

720781
def forward(self, seq, pos_idxs, hidden_state: Optional[TformerHiddenState] = None):
@@ -731,10 +792,11 @@ def forward(self, seq, pos_idxs, hidden_state: Optional[TformerHiddenState] = No
731792
"""
732793

733794
traj_emb = self.preprocess_seq(seq, pos_idxs)
795+
rope_pos = pos_idxs if self.use_rope else None
734796
if hidden_state is not None:
735797
assert not self.training
736-
traj_emb = self.inference_forward(traj_emb, hidden_state)
798+
traj_emb = self.inference_forward(traj_emb, hidden_state, pos_idxs=rope_pos)
737799
hidden_state.update()
738800
else:
739-
traj_emb = self.training_forward(traj_emb)
801+
traj_emb = self.training_forward(traj_emb, pos_idxs=rope_pos)
740802
return traj_emb, hidden_state

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "amago"
7-
version = "3.2.0"
7+
version = "3.3.0"
88
description = "off-policy RL on long sequences"
99
readme = { file = "README.md", content-type = "text/markdown" }
1010
authors = [

0 commit comments

Comments
 (0)