Instructions to use IAAR-Shanghai/Metis-9B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use IAAR-Shanghai/Metis-9B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="IAAR-Shanghai/Metis-9B", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("IAAR-Shanghai/Metis-9B", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use IAAR-Shanghai/Metis-9B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "IAAR-Shanghai/Metis-9B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "IAAR-Shanghai/Metis-9B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/IAAR-Shanghai/Metis-9B
- SGLang
How to use IAAR-Shanghai/Metis-9B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "IAAR-Shanghai/Metis-9B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "IAAR-Shanghai/Metis-9B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "IAAR-Shanghai/Metis-9B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "IAAR-Shanghai/Metis-9B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use IAAR-Shanghai/Metis-9B with Docker Model Runner:
docker model run hf.co/IAAR-Shanghai/Metis-9B
| import math | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from abc import ABC | |
| def _qk_kernel(x: torch.Tensor, kernel_type: str = "elu_plus_one") -> torch.Tensor: | |
| if kernel_type == "elu_plus_one": | |
| return F.elu(x) + 1.0 | |
| if kernel_type == "relu_square": | |
| return F.relu(x).square() | |
| if kernel_type == "softplus": | |
| return F.softplus(x) | |
| raise ValueError(f"Unsupported qk kernel type: {kernel_type}") | |
| def create_metis_hyper_memory(config): | |
| return eval(config.memory_configs['metis_hyper_memory_type'])(config) | |
| class MetisHyperMemoryBase(nn.Module, ABC): | |
| def __init__(self, config) -> None: | |
| super().__init__() | |
| self.config = config | |
| # Qwen 3.5 has text config, but Qwen 3 does not. | |
| self.text_cfg = getattr(config.backbone_configs, 'text_config', config.backbone_configs) | |
| # Reference set later by MetisBlock via register_raw_decoder(). | |
| # Stored as a list to avoid registering the backbone decoder as a submodule. | |
| self._backbone_decoder_ref: list | None = None | |
| def register_raw_decoder(self, _backbone_decoder_ref: list) -> None: | |
| """Called by MetisBlock to give HyperMemory access to the backbone decoder. | |
| Enables hyper memory variants to apply backbone-style normalisations | |
| (e.g. input_layernorm) on hidden states before computing W_k/W_v. | |
| """ | |
| self._backbone_decoder_ref = _backbone_decoder_ref | |
| def backbone_decoder(self): | |
| if self._backbone_decoder_ref is None: | |
| raise RuntimeError("backbone_decoder not registered; call register_raw_decoder first") | |
| return self._backbone_decoder_ref[0] | |
| def update_local_memory(self, raw_info, local_memory) -> None: | |
| local_memory.write(self.get_new_info_for_local_memory(raw_info)) | |
| def get_new_info_for_local_memory(self, raw_info): | |
| raise NotImplementedError | |
| class LinearLastMetisHyperMemory(MetisHyperMemoryBase): | |
| """Additive memory update using the last token's hidden state. | |
| M_new = (1 - update_ratio) * M_old + update_ratio * (W_k(h_norm)^T @ W_v(h_norm)) | |
| where h_norm = backbone.input_layernorm(h_last). Applying the backbone's | |
| RMSNorm before W_k / W_v bounds pre-projection magnitudes (mirroring how | |
| the backbone's own attention consumes its input). | |
| All token-selection subclasses (Uniform / Stride / AllTokens) follow the | |
| same layernorm-then-project pattern. The exception is | |
| ``NormalizedLinearLastMetisHyperMemory``, which keeps the legacy | |
| L2-normalize-on-output behaviour. | |
| update_ratio is read from memory_configs (default: 1.0). | |
| """ | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| hidden_size = self.text_cfg.hidden_size | |
| num_q_heads = self.text_cfg.num_attention_heads | |
| num_kv_heads = getattr(self.text_cfg, "num_key_value_heads", num_q_heads) | |
| head_dim = getattr(self.text_cfg, "head_dim", hidden_size // num_q_heads) | |
| # W_k / W_v output dim must equal the local memory matrix's kv_dim so | |
| # that write vectors align with read queries. Default = GQA layout | |
| # (num_kv_heads * head_dim). Switch to MHA layout (num_q_heads * head_dim) | |
| # when the user chose the legacy MHA local memory. | |
| local_mem_type = config.memory_configs.get('metis_local_memory_type', '') | |
| if local_mem_type.startswith('MHA'): | |
| self.kv_dim = num_q_heads * head_dim | |
| else: | |
| self.kv_dim = num_kv_heads * head_dim | |
| self.update_ratio = config.memory_configs.get('update_ratio', 1.0) | |
| self.W_k = nn.Linear(hidden_size, self.kv_dim, bias=False) | |
| self.W_v = nn.Linear(hidden_size, self.kv_dim, bias=False) | |
| def get_new_info_for_local_memory(self, raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None) -> torch.Tensor: | |
| """Compute the memory delta from the last *real* token of each sample. | |
| Args: | |
| raw_info: hidden states (b, s, hidden_size) | |
| attention_mask: binary mask (b, s) — 1 for real tokens, 0 for pad. | |
| When None, the last position is used (safe for unbatched | |
| or already-trimmed sequences). | |
| Returns: | |
| delta: (b, D, D) — outer product of write key and write value. | |
| """ | |
| if attention_mask is not None: | |
| # last real token index per sample: sum of 1s minus 1 | |
| last_idx = attention_mask.sum(dim=1) - 1 # (b,) | |
| b = raw_info.size(0) | |
| h_last = raw_info[torch.arange(b, device=raw_info.device), | |
| last_idx, :].unsqueeze(1) # (b, 1, hidden_size) | |
| else: | |
| h_last = raw_info[:, -1:, :] # (b, 1, hidden_size) | |
| h_last = self.backbone_decoder.raw_decoder.input_layernorm(h_last) | |
| write_key = self.W_k(h_last) # (b, 1, kv_dim) | |
| write_value = self.W_v(h_last) # (b, 1, kv_dim) | |
| # (b, kv_dim, 1) @ (b, 1, kv_dim) -> (b, kv_dim, kv_dim) | |
| return torch.matmul(write_key.transpose(-1, -2), write_value) | |
| def update_local_memory(self, raw_info: torch.Tensor, local_memory, | |
| attention_mask: torch.Tensor | None = None) -> None: | |
| """Blend the existing memory with the new additive update.""" | |
| delta = self.get_new_info_for_local_memory(raw_info, attention_mask) | |
| if local_memory.state is not None: | |
| new_state = (1.0 - self.update_ratio) * local_memory.state + self.update_ratio * delta | |
| else: | |
| new_state = self.update_ratio * delta | |
| local_memory.write(new_state) | |
| class NormalizedLinearLastMetisHyperMemory(LinearLastMetisHyperMemory): | |
| """Exception class: legacy L2-normalized W_k/W_v output, no input_layernorm. | |
| Unlike all other subclasses (which apply backbone.input_layernorm to | |
| hidden states before W_k / W_v), this class operates on raw hidden | |
| states and L2-normalises the projection *outputs*: | |
| write_key = F.normalize(W_k(h_last), dim=-1) # ‖·‖ = 1 | |
| write_value = F.normalize(W_v(h_last), dim=-1) # ‖·‖ = 1 | |
| ‖delta‖_F = ‖write_key‖ · ‖write_value‖ = 1 | |
| The per-step memory increment is bounded by update_ratio. Kept | |
| primarily for reproducing earlier experiments. | |
| """ | |
| def get_new_info_for_local_memory(self, raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None) -> torch.Tensor: | |
| if attention_mask is not None: | |
| last_idx = attention_mask.sum(dim=1) - 1 # (b,) | |
| b = raw_info.size(0) | |
| h_last = raw_info[torch.arange(b, device=raw_info.device), | |
| last_idx, :].unsqueeze(1) # (b, 1, hidden_size) | |
| else: | |
| h_last = raw_info[:, -1:, :] # (b, 1, hidden_size) | |
| write_key = F.normalize(self.W_k(h_last), dim=-1) # (b, 1, D), ‖·‖=1 | |
| write_value = F.normalize(self.W_v(h_last), dim=-1) # (b, 1, D), ‖·‖=1 | |
| # (b, D, 1) @ (b, 1, D) -> (b, D, D), ‖delta‖_F ≤ 1 | |
| return torch.matmul(write_key.transpose(-1, -2), write_value) | |
| class UniformNormalizedMetisHyperMemory(LinearLastMetisHyperMemory): | |
| """Memory update using uniformly sampled tokens with backbone-norm pre-projection. | |
| This class selects N = ``uniform_num_selected`` tokens evenly spaced | |
| across the real sequence (always including the last real token), | |
| applies the backbone's input_layernorm to the selected hidden states, | |
| then projects with W_k / W_v. Each selected token contributes one | |
| rank-1 outer product to the memory delta: | |
| step = L / N (L = real sequence length) | |
| idx_j = round(j * step) for j in 0..N-1 | |
| idx_{N-1} = L - 1 (force-include last) | |
| h_normed = input_layernorm(h[idx]) # (b, N, hidden) | |
| write_key = W_k(h_normed) # (b, N, kv_dim) | |
| write_val = W_v(h_normed) # (b, N, kv_dim) | |
| delta = write_key.T @ write_val # (b, kv_dim, kv_dim) | |
| Configurable via ``memory_configs``: | |
| - ``uniform_num_selected`` (int, default 16): number of tokens N | |
| When the real sequence is shorter than N, all real tokens are used | |
| and the last one is repeated to fill the remaining slots. | |
| """ | |
| DEFAULT_NUM_SELECTED: int = 16 | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.num_selected = int( | |
| config.memory_configs.get('uniform_num_selected', self.DEFAULT_NUM_SELECTED) | |
| ) | |
| def _select_tokens( | |
| self, | |
| hidden_states: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None, | |
| ) -> torch.Tensor: # (b, N, hidden_size) | |
| b, S, hidden_size = hidden_states.shape | |
| N = self.num_selected | |
| device = hidden_states.device | |
| # Real sequence length per sample. | |
| if attention_mask is not None: | |
| lengths = attention_mask.sum(dim=1).long() # (b,) | |
| else: | |
| lengths = torch.full((b,), S, dtype=torch.long, device=device) | |
| # Build per-sample index tensors (b, N). | |
| indices_list = [] | |
| for bi in range(b): | |
| L = lengths[bi].item() | |
| if L <= N: | |
| # Fewer real tokens than slots: use all, repeat last to pad. | |
| idx = list(range(L)) + [L - 1] * (N - L) | |
| else: | |
| # Uniformly spaced: step = L/N, always land last on L-1. | |
| step = L / N | |
| idx = [min(int(i * step), L - 1) for i in range(N)] | |
| idx[-1] = L - 1 | |
| indices_list.append(idx) | |
| indices = torch.tensor(indices_list, dtype=torch.long, device=device) | |
| idx_exp = indices.unsqueeze(-1).expand(b, N, hidden_size) # (b, N, hidden) | |
| return hidden_states.gather(1, idx_exp) # (b, N, hidden) | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: # (b, kv_dim, kv_dim) | |
| h_sel = self._select_tokens(raw_info, attention_mask) # (b, N, hidden) | |
| # Apply backbone's RMSNorm (same one that gates the layer's attention). | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = self.W_k(h_sel) # (b, N, kv_dim) | |
| write_value = self.W_v(h_sel) # (b, N, kv_dim) | |
| # (b, kv_dim, N) @ (b, N, kv_dim) -> (b, kv_dim, kv_dim) | |
| return torch.matmul(write_key.transpose(-1, -2), write_value) | |
| class StrideNormalizedMetisHyperMemory(LinearLastMetisHyperMemory): | |
| """Memory update using stride-based token selection with backbone-norm pre-projection. | |
| Unlike ``UniformNormalizedMetisHyperMemory`` (fixed-N evenly-spaced), | |
| this class selects **every K-th real token** from each sample, plus | |
| the last real token. K is configurable; the number of selected tokens | |
| per sample varies with sequence length: | |
| L = real (non-padding) sequence length | |
| K = ``stride_interval`` (config, default 16) | |
| idx = [0, K, 2K, ...] intersected with [0, L-1], union {L-1} | |
| For mixed-length batches the per-sample selection counts differ; padded | |
| slots in the resulting (b, N_max, hidden) tensor are masked to zero so | |
| they contribute nothing to the rank-1 outer products. | |
| Configurable via ``memory_configs``: | |
| - ``stride_interval`` (int, default 16): K, the spacing between picks | |
| """ | |
| DEFAULT_STRIDE: int = 16 | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.stride = int(config.memory_configs.get('stride_interval', self.DEFAULT_STRIDE)) | |
| if self.stride <= 0: | |
| raise ValueError(f"stride_interval must be > 0, got {self.stride}") | |
| def _select_tokens_with_mask( | |
| self, | |
| hidden_states: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: # (b, N_max, hidden), (b, N_max) | |
| b, S, hidden_size = hidden_states.shape | |
| device = hidden_states.device | |
| K = self.stride | |
| # Real length per sample — only non-padding tokens are eligible. | |
| if attention_mask is not None: | |
| lengths = attention_mask.sum(dim=1).long().tolist() | |
| else: | |
| lengths = [S] * b | |
| # Per-sample stride-K indices, always force-including the last real token. | |
| per_sample_idx: list[list[int]] = [] | |
| for L in lengths: | |
| if L <= 0: | |
| # Edge case: empty sample. Use index 0 (will be masked out). | |
| per_sample_idx.append([0]) | |
| continue | |
| idx = list(range(0, L, K)) | |
| if idx[-1] != L - 1: | |
| idx.append(L - 1) | |
| per_sample_idx.append(idx) | |
| N_max = max(len(idx) for idx in per_sample_idx) | |
| # Right-pad each sample's index list with 0 (a real position) and | |
| # record a 0/1 mask so padded slots contribute zero to the outer product. | |
| indices_padded: list[list[int]] = [] | |
| masks: list[list[float]] = [] | |
| for idx, L in zip(per_sample_idx, lengths): | |
| n_valid = len(idx) if L > 0 else 0 | |
| pad_n = N_max - len(idx) | |
| indices_padded.append(idx + [0] * pad_n) | |
| masks.append([1.0] * n_valid + [0.0] * (N_max - n_valid)) | |
| indices = torch.tensor(indices_padded, dtype=torch.long, device=device) | |
| mask = torch.tensor(masks, dtype=hidden_states.dtype, device=device) | |
| idx_exp = indices.unsqueeze(-1).expand(b, N_max, hidden_size) # (b, N_max, hidden) | |
| h_sel = hidden_states.gather(1, idx_exp) # (b, N_max, hidden) | |
| return h_sel, mask | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: # (b, kv_dim, kv_dim) | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| # Apply backbone's RMSNorm before the W_k / W_v projections. | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = self.W_k(h_sel) # (b, N, kv_dim) | |
| write_value = self.W_v(h_sel) # (b, N, kv_dim) | |
| # Zero-out padded slots so they contribute nothing to the matmul. | |
| mask = mask.unsqueeze(-1) # (b, N, 1) | |
| write_key = write_key * mask | |
| write_value = write_value * mask | |
| # (b, kv_dim, N) @ (b, N, kv_dim) -> (b, kv_dim, kv_dim) | |
| return torch.matmul(write_key.transpose(-1, -2), write_value) | |
| class FullTokensNormalizedv3MetisHyperMemory(LinearLastMetisHyperMemory): | |
| """Memory update using all real tokens with v3-style normalization. | |
| Every non-padding token contributes one rank-1 outer product: | |
| h_normed = input_layernorm(h) # (b, s, hidden) | |
| write_key = W_k(h_normed) # (b, s, kv_dim) | |
| write_val = W_v(h_normed) # (b, s, kv_dim) | |
| delta = write_key.T @ write_val / (L * sqrt(D)) | |
| This matches ``StrideNormalizedv3MetisHyperMemory``'s normalization while | |
| selecting the full real-token sequence instead of stride-sampled tokens. | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: # (b, kv_dim, kv_dim) | |
| h = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| write_key = self.W_k(h) # (b, s, kv_dim) | |
| write_value = self.W_v(h) # (b, s, kv_dim) | |
| if attention_mask is not None: | |
| # Broadcast mask over hidden dim so pad positions contribute 0. | |
| mask = attention_mask.unsqueeze(-1).to(write_key.dtype) # (b, s, 1) | |
| L_prime = attention_mask.sum(dim=1).clamp(min=1) # (b,) | |
| write_key = write_key * mask | |
| write_value = write_value * mask | |
| else: | |
| L_prime = torch.full( | |
| (raw_info.size(0),), | |
| raw_info.size(1), | |
| dtype=write_key.dtype, | |
| device=raw_info.device, | |
| ).clamp(min=1) | |
| # (b, kv_dim, s) @ (b, s, kv_dim) -> (b, kv_dim, kv_dim) | |
| delta = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| scale = L_prime.to(delta.dtype) * (self.kv_dim ** 0.5) | |
| return delta / scale.view(-1, 1, 1) | |
| class KeyNormTokenAggMetisHyperMemory(LinearLastMetisHyperMemory): | |
| """Shared write path for token-aggregation experiments. | |
| Subclasses choose or pool hidden states into ``(h_tokens, mask)``. This | |
| base class then applies the same key-normalized DeltaNet write protocol as | |
| ``FullTokensKeyNormMetisHyperMemory``: | |
| k = normalize(W_k(input_layernorm(h))) / sqrt(D) | |
| v = W_v(input_layernorm(h)) | |
| state = mean_t(k_t^T @ v_t) | |
| key_state = mean_t(k_t) | |
| """ | |
| def _delta_from_normed_tokens( | |
| self, | |
| h_normed: torch.Tensor, | |
| mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| write_key = F.normalize(self.W_k(h_normed), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_normed) | |
| if mask is not None: | |
| mask_exp = mask.unsqueeze(-1).to(write_key.dtype) | |
| lengths = mask.sum(dim=1).clamp(min=1).to(write_key.dtype) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| else: | |
| lengths = torch.full( | |
| (h_normed.size(0),), | |
| h_normed.size(1), | |
| dtype=write_key.dtype, | |
| device=h_normed.device, | |
| ).clamp(min=1) | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones( | |
| write_key.size(0), | |
| write_key.size(1), | |
| 1, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| scale = lengths.view(-1, 1, 1) | |
| return delta_state / scale, delta_key_state / scale | |
| def _delta_from_raw_tokens( | |
| self, | |
| h_tokens: torch.Tensor, | |
| mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(h_tokens) | |
| return self._delta_from_normed_tokens(h_normed, mask) | |
| def _write_keynorm_update( | |
| self, | |
| raw_info: torch.Tensor, | |
| local_memory, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> None: | |
| delta_state, delta_key_state = self.get_new_info_for_local_memory(raw_info, attention_mask) | |
| if getattr(local_memory, "key_state", None) is None: | |
| new_state = self.update_ratio * delta_state | |
| new_key_state = self.update_ratio * delta_key_state | |
| else: | |
| new_state = (1.0 - self.update_ratio) * local_memory.state + self.update_ratio * delta_state | |
| new_key_state = ( | |
| (1.0 - self.update_ratio) * local_memory.key_state | |
| + self.update_ratio * delta_key_state | |
| ) | |
| local_memory.write(new_state, new_key_state) | |
| def update_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| local_memory, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> None: | |
| self._write_keynorm_update(raw_info, local_memory, attention_mask) | |
| class MeanPoolKeyNormMetisHyperMemory(KeyNormTokenAggMetisHyperMemory): | |
| """Mean-pool all real hidden states into one write token.""" | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| if attention_mask is not None: | |
| mask = attention_mask.unsqueeze(-1).to(raw_info.dtype) | |
| lengths = attention_mask.sum(dim=1).clamp(min=1).to(raw_info.dtype) | |
| h_pool = (raw_info * mask).sum(dim=1, keepdim=True) / lengths.view(-1, 1, 1) | |
| else: | |
| h_pool = raw_info.mean(dim=1, keepdim=True) | |
| return self._delta_from_raw_tokens(h_pool) | |
| class StridePoolKeyNormMetisHyperMemory(KeyNormTokenAggMetisHyperMemory): | |
| """Mean-pool every stride-sized chunk into one write token per chunk.""" | |
| DEFAULT_STRIDE: int = 8 | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.stride = int(config.memory_configs.get("stride_interval", self.DEFAULT_STRIDE)) | |
| if self.stride <= 0: | |
| raise ValueError(f"stride_interval must be > 0, got {self.stride}") | |
| def _pool_stride_windows( | |
| self, | |
| hidden_states: torch.Tensor, | |
| attention_mask: torch.Tensor | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, hidden_size = hidden_states.shape | |
| device = hidden_states.device | |
| dtype = hidden_states.dtype | |
| if attention_mask is not None: | |
| lengths = attention_mask.sum(dim=1).long().tolist() | |
| else: | |
| lengths = [S] * b | |
| per_sample: list[torch.Tensor] = [] | |
| masks: list[list[float]] = [] | |
| max_chunks = 1 | |
| for bi, L in enumerate(lengths): | |
| L = max(int(L), 1) | |
| chunks = [] | |
| for start in range(0, L, self.stride): | |
| end = min(start + self.stride, L) | |
| chunks.append(hidden_states[bi, start:end].mean(dim=0)) | |
| sample = torch.stack(chunks, dim=0) | |
| per_sample.append(sample) | |
| max_chunks = max(max_chunks, sample.size(0)) | |
| padded = [] | |
| for sample in per_sample: | |
| pad_n = max_chunks - sample.size(0) | |
| if pad_n > 0: | |
| pad = torch.zeros(pad_n, hidden_size, device=device, dtype=dtype) | |
| sample = torch.cat([sample, pad], dim=0) | |
| padded.append(sample) | |
| masks.append([1.0] * (sample.size(0) - pad_n) + [0.0] * pad_n) | |
| return torch.stack(padded, dim=0), torch.tensor(masks, device=device, dtype=dtype) | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_pool, mask = self._pool_stride_windows(raw_info, attention_mask) | |
| return self._delta_from_raw_tokens(h_pool, mask) | |
| class AttentionPoolKeyNormMetisHyperMemory(KeyNormTokenAggMetisHyperMemory): | |
| """Learn a global attention pooling query and write one pooled token.""" | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.pool_score = nn.Linear(self.text_cfg.hidden_size, 1, bias=False) | |
| self.pool_temperature = float(config.memory_configs.get("pool_temperature", 1.0)) | |
| if self.pool_temperature <= 0: | |
| raise ValueError(f"pool_temperature must be > 0, got {self.pool_temperature}") | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| scores = scores.masked_fill(attention_mask == 0, torch.finfo(scores.dtype).min) | |
| weights = torch.softmax(scores / self.pool_temperature, dim=1).unsqueeze(1) | |
| h_pool = torch.matmul(weights, h_normed) | |
| return self._delta_from_normed_tokens(h_pool) | |
| class WindowAttentionPoolKeyNormMetisHyperMemory(AttentionPoolKeyNormMetisHyperMemory): | |
| """Soft-select one pooled write token per stride-sized window. | |
| This is the differentiable replacement for hard top-k token selection used | |
| by the token-aggregation experiments. For stride ``R`` it writes roughly | |
| ``ceil(L / R)`` tokens, matching stride/top-k compression, but the scorer | |
| receives gradients from every real token in each window during both | |
| training and inference. | |
| """ | |
| DEFAULT_STRIDE: int = 8 | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.stride = int(config.memory_configs.get("stride_interval", self.DEFAULT_STRIDE)) | |
| if self.stride <= 0: | |
| raise ValueError(f"stride_interval must be > 0, got {self.stride}") | |
| def _pool_attention_windows( | |
| self, | |
| h_normed: torch.Tensor, | |
| attention_mask: torch.Tensor | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, hidden_size = h_normed.shape | |
| if attention_mask is None: | |
| attention_mask = torch.ones(b, S, device=h_normed.device, dtype=torch.long) | |
| pad_n = (-S) % self.stride | |
| if pad_n > 0: | |
| h_normed = F.pad(h_normed, (0, 0, 0, pad_n)) | |
| attention_mask = F.pad(attention_mask, (0, pad_n)) | |
| W = h_normed.size(1) // self.stride | |
| h_win = h_normed.view(b, W, self.stride, hidden_size) | |
| scores = self.pool_score(h_win).squeeze(-1) | |
| mask_win = attention_mask.view(b, W, self.stride).bool() | |
| valid_window = mask_win.any(dim=2) | |
| scores = scores.masked_fill(~mask_win, torch.finfo(scores.dtype).min) | |
| weights = torch.softmax(scores / self.pool_temperature, dim=2) | |
| weights = weights.masked_fill(~mask_win, 0.0) | |
| h_pool = (weights.unsqueeze(-1).to(h_win.dtype) * h_win).sum(dim=2) | |
| return h_pool, valid_window.to(h_normed.dtype) | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| h_pool, mask = self._pool_attention_windows(h_normed, attention_mask) | |
| return self._delta_from_normed_tokens(h_pool, mask) | |
| class TopKKeyNormMetisHyperMemory(KeyNormTokenAggMetisHyperMemory): | |
| """Learn token scores, then write the top ceil(L / stride_interval) tokens.""" | |
| DEFAULT_STRIDE: int = 8 | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.stride = int(config.memory_configs.get("stride_interval", self.DEFAULT_STRIDE)) | |
| if self.stride <= 0: | |
| raise ValueError(f"stride_interval must be > 0, got {self.stride}") | |
| self.pool_score = nn.Linear(self.text_cfg.hidden_size, 1, bias=False) | |
| self.pool_temperature = float(config.memory_configs.get("pool_temperature", 1.0)) | |
| if self.pool_temperature <= 0: | |
| raise ValueError(f"pool_temperature must be > 0, got {self.pool_temperature}") | |
| def _select_topk( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| b, S, hidden_size = raw_info.shape | |
| device = raw_info.device | |
| dtype = raw_info.dtype | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| scores = scores.masked_fill(attention_mask == 0, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| k_per_sample = torch.div(lengths_t + self.stride - 1, self.stride, rounding_mode="floor") | |
| k_max = int(k_per_sample.max().item()) | |
| selected = [] | |
| selected_scores = [] | |
| masks = [] | |
| for bi in range(b): | |
| k = int(k_per_sample[bi].item()) | |
| idx = torch.topk(scores[bi], k=k, dim=0).indices.sort().values | |
| h_sel = raw_info[bi].index_select(0, idx) | |
| score_sel = scores[bi].index_select(0, idx) | |
| pad_n = k_max - k | |
| if pad_n > 0: | |
| h_pad = torch.zeros(pad_n, hidden_size, device=device, dtype=dtype) | |
| s_pad = torch.zeros(pad_n, device=device, dtype=scores.dtype) | |
| h_sel = torch.cat([h_sel, h_pad], dim=0) | |
| score_sel = torch.cat([score_sel, s_pad], dim=0) | |
| selected.append(h_sel) | |
| selected_scores.append(score_sel) | |
| masks.append([1.0] * k + [0.0] * pad_n) | |
| return ( | |
| torch.stack(selected, dim=0), | |
| torch.tensor(masks, device=device, dtype=dtype), | |
| torch.stack(selected_scores, dim=0), | |
| ) | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_sel, mask, _scores = self._select_topk(raw_info, attention_mask) | |
| return self._delta_from_raw_tokens(h_sel, mask) | |
| class SoftTopKKeyNormMetisHyperMemory(TopKKeyNormMetisHyperMemory): | |
| """All-token soft select used for both training and inference. | |
| This is the no-hard-selection counterpart of top-k writes: | |
| soft = softmax(scores / tau) * K | |
| state = sum_i soft_i * outer(k_i, v_i) / K | |
| Forward and backward are both soft. The total gate mass is K, so after the | |
| final divide-by-K this is a convex weighted sum of per-token outer products | |
| and stays on the same scale as K hard selected tokens averaged by K. | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, _hidden_size = raw_info.shape | |
| device = raw_info.device | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| valid_mask = attention_mask.bool() | |
| scores = scores.masked_fill(~valid_mask, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| valid_mask = torch.ones(b, S, device=device, dtype=torch.bool) | |
| k_per_sample = torch.div(lengths_t + self.stride - 1, self.stride, rounding_mode="floor") | |
| gate = torch.softmax(scores / self.pool_temperature, dim=1) | |
| gate = gate.masked_fill(~valid_mask, 0.0) | |
| gate = gate * k_per_sample.to(gate.dtype).unsqueeze(1) | |
| write_key = F.normalize(self.W_k(h_normed), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_normed) | |
| gate_exp = gate.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * gate_exp | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones(b, S, 1, device=device, dtype=write_key.dtype) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| scale = k_per_sample.clamp(min=1).to(write_key.dtype).view(-1, 1, 1) | |
| return delta_state / scale, delta_key_state / scale | |
| class StraightThroughTopKKeyNormMetisHyperMemory(TopKKeyNormMetisHyperMemory): | |
| """Hard top-k forward with softmax surrogate gradients for the scorer.""" | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, _hidden_size = raw_info.shape | |
| device = raw_info.device | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| valid_mask = attention_mask.bool() | |
| scores = scores.masked_fill(~valid_mask, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| valid_mask = torch.ones(b, S, device=device, dtype=torch.bool) | |
| k_per_sample = torch.div(lengths_t + self.stride - 1, self.stride, rounding_mode="floor") | |
| soft = torch.softmax(scores / self.pool_temperature, dim=1) * k_per_sample.to(scores.dtype).unsqueeze(1) | |
| soft = soft.masked_fill(~valid_mask, 0.0) | |
| hard = torch.zeros_like(scores) | |
| for bi in range(b): | |
| k = int(k_per_sample[bi].item()) | |
| idx = torch.topk(scores[bi], k=k, dim=0).indices | |
| hard[bi].scatter_(0, idx, 1.0) | |
| gate = hard.detach() - soft.detach() + soft | |
| write_key = F.normalize(self.W_k(h_normed), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_normed) | |
| gate_exp = gate.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * gate_exp | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones(b, S, 1, device=device, dtype=write_key.dtype) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| scale = k_per_sample.clamp(min=1).to(write_key.dtype).view(-1, 1, 1) | |
| return delta_state / scale, delta_key_state / scale | |
| class GumbelTopKKeyNormMetisHyperMemory(TopKKeyNormMetisHyperMemory): | |
| """Continuous Gumbel-TopK approximation that writes K soft-selected tokens.""" | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.gumbel_topk_noise = bool(config.memory_configs.get("gumbel_topk_noise", True)) | |
| self.gumbel_eps = float(config.memory_configs.get("gumbel_eps", 1e-6)) | |
| def _sample_gumbel(self, scores: torch.Tensor) -> torch.Tensor: | |
| uniform = torch.rand_like(scores).clamp_(self.gumbel_eps, 1.0 - self.gumbel_eps) | |
| return -torch.log(-torch.log(uniform)) | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, _hidden_size = raw_info.shape | |
| device = raw_info.device | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| valid_mask = attention_mask.bool() | |
| scores = scores.masked_fill(~valid_mask, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| valid_mask = torch.ones(b, S, device=device, dtype=torch.bool) | |
| k_per_sample = torch.div(lengths_t + self.stride - 1, self.stride, rounding_mode="floor") | |
| k_max = int(k_per_sample.max().item()) | |
| logits = scores | |
| if self.training and self.gumbel_topk_noise: | |
| logits = logits + self._sample_gumbel(scores) | |
| remaining = valid_mask.to(scores.dtype) | |
| selections = [] | |
| for _ in range(k_max): | |
| masked_logits = logits + torch.log(remaining.clamp(min=self.gumbel_eps)) | |
| weights = torch.softmax(masked_logits / self.pool_temperature, dim=1) | |
| weights = weights.masked_fill(~valid_mask, 0.0) | |
| weights = weights / weights.sum(dim=1, keepdim=True).clamp(min=self.gumbel_eps) | |
| selections.append(weights) | |
| remaining = remaining * (1.0 - weights).clamp(min=0.0) | |
| selection = torch.stack(selections, dim=1) | |
| row_mask = ( | |
| torch.arange(k_max, device=device).unsqueeze(0) | |
| < k_per_sample.unsqueeze(1) | |
| ).to(h_normed.dtype) | |
| h_pool = torch.matmul(selection.to(h_normed.dtype), h_normed) | |
| return self._delta_from_normed_tokens(h_pool, row_mask) | |
| class AlphaTopPKeyNormMetisHyperMemory(TopKKeyNormMetisHyperMemory): | |
| """Adaptive top-p/nucleus token selection with selected soft weights. | |
| Select the smallest set whose scorer probability mass reaches | |
| ``alpha_top_p``. The selected tokens are written as a convex weighted sum | |
| of per-token outer products: | |
| weights_i = p_i / sum_{j in S_alpha} p_j | |
| state = sum_{i in S_alpha} weights_i * outer(k_i, v_i) | |
| """ | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.alpha_top_p = float(config.memory_configs.get("alpha_top_p", 0.9)) | |
| if not 0.0 < self.alpha_top_p <= 1.0: | |
| raise ValueError(f"alpha_top_p must be in (0, 1], got {self.alpha_top_p}") | |
| self.alpha_min_tokens = int(config.memory_configs.get("alpha_min_tokens", 1)) | |
| if self.alpha_min_tokens <= 0: | |
| raise ValueError(f"alpha_min_tokens must be > 0, got {self.alpha_min_tokens}") | |
| self.alpha_max_tokens = int(config.memory_configs.get("alpha_max_tokens", 0)) | |
| if self.alpha_max_tokens < 0: | |
| raise ValueError(f"alpha_max_tokens must be >= 0, got {self.alpha_max_tokens}") | |
| self.alpha_max_fraction = float(config.memory_configs.get("alpha_max_fraction", 0.0)) | |
| if not 0.0 <= self.alpha_max_fraction <= 1.0: | |
| raise ValueError(f"alpha_max_fraction must be in [0, 1], got {self.alpha_max_fraction}") | |
| self.last_alpha_stats: dict[str, float] = {} | |
| def _alpha_top_p_mask( | |
| self, | |
| probs: torch.Tensor, | |
| valid_mask: torch.Tensor, | |
| lengths_t: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| b, S = probs.shape | |
| sorted_probs, sorted_idx = torch.sort(probs, descending=True, dim=1) | |
| cum = sorted_probs.cumsum(dim=1) | |
| k_raw = (cum <= self.alpha_top_p).sum(dim=1) + 1 | |
| k_raw = torch.minimum(k_raw, lengths_t) | |
| k_min = torch.minimum( | |
| torch.full_like(lengths_t, self.alpha_min_tokens), | |
| lengths_t, | |
| ) | |
| k_max = lengths_t.clone() | |
| if self.alpha_max_fraction > 0.0: | |
| frac_cap = torch.ceil(lengths_t.to(probs.dtype) * self.alpha_max_fraction).long() | |
| k_max = torch.minimum(k_max, frac_cap.clamp(min=1)) | |
| if self.alpha_max_tokens > 0: | |
| fixed_cap = torch.full_like(lengths_t, self.alpha_max_tokens) | |
| k_max = torch.minimum(k_max, fixed_cap.clamp(min=1)) | |
| k_max = torch.maximum(k_max, k_min) | |
| k_alpha = torch.minimum(torch.maximum(k_raw, k_min), k_max) | |
| rank = torch.arange(S, device=probs.device).unsqueeze(0) | |
| keep_sorted = rank < k_alpha.unsqueeze(1) | |
| hard = torch.zeros_like(probs) | |
| hard.scatter_(1, sorted_idx, keep_sorted.to(probs.dtype)) | |
| hard = hard.masked_fill(~valid_mask, 0.0) | |
| selected_mass = (probs * hard).sum(dim=1, keepdim=True).clamp(min=1e-6) | |
| return hard, k_alpha, selected_mass | |
| def _record_alpha_stats( | |
| self, | |
| probs: torch.Tensor, | |
| hard: torch.Tensor, | |
| k_alpha: torch.Tensor, | |
| selected_mass: torch.Tensor, | |
| lengths_t: torch.Tensor, | |
| ) -> None: | |
| with torch.no_grad(): | |
| probs_f = probs.detach().float() | |
| k_f = k_alpha.detach().float() | |
| lengths_f = lengths_t.detach().float().clamp(min=1) | |
| entropy = -(probs_f * probs_f.clamp(min=1e-12).log()).sum(dim=1) | |
| self.last_alpha_stats = { | |
| "k_mean": float(k_f.mean().item()), | |
| "k_min": float(k_f.min().item()), | |
| "k_max": float(k_f.max().item()), | |
| "k_ratio": float((k_f / lengths_f).mean().item()), | |
| "score_entropy": float(entropy.mean().item()), | |
| "p_max": float(probs_f.max(dim=1).values.mean().item()), | |
| "selected_mass": float(selected_mass.detach().float().mean().item()), | |
| "alpha": self.alpha_top_p, | |
| } | |
| def _alpha_weights( | |
| self, | |
| scores: torch.Tensor, | |
| valid_mask: torch.Tensor, | |
| lengths_t: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| probs = torch.softmax(scores / self.pool_temperature, dim=1) | |
| probs = probs.masked_fill(~valid_mask, 0.0) | |
| probs = probs / probs.sum(dim=1, keepdim=True).clamp(min=1e-6) | |
| hard, k_alpha, selected_mass = self._alpha_top_p_mask(probs, valid_mask, lengths_t) | |
| self._record_alpha_stats(probs, hard, k_alpha, selected_mass, lengths_t) | |
| weights = probs * hard / selected_mass | |
| return weights, probs | |
| def _delta_from_weights( | |
| self, | |
| h_normed: torch.Tensor, | |
| weights: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| write_key = F.normalize(self.W_k(h_normed), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_normed) | |
| weight_exp = weights.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * weight_exp | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones( | |
| write_key.size(0), | |
| write_key.size(1), | |
| 1, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| return delta_state, delta_key_state | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, _hidden_size = raw_info.shape | |
| device = raw_info.device | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| valid_mask = attention_mask.bool() | |
| scores = scores.masked_fill(~valid_mask, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| valid_mask = torch.ones(b, S, device=device, dtype=torch.bool) | |
| weights, _probs = self._alpha_weights(scores, valid_mask, lengths_t) | |
| return self._delta_from_weights(h_normed, weights) | |
| class StraightThroughAlphaTopPKeyNormMetisHyperMemory(AlphaTopPKeyNormMetisHyperMemory): | |
| """Alpha top-p forward with full-softmax surrogate gradients.""" | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, _hidden_size = raw_info.shape | |
| device = raw_info.device | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| valid_mask = attention_mask.bool() | |
| scores = scores.masked_fill(~valid_mask, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| valid_mask = torch.ones(b, S, device=device, dtype=torch.bool) | |
| hard_weights, probs = self._alpha_weights(scores, valid_mask, lengths_t) | |
| soft_weights = probs.masked_fill(~valid_mask, 0.0) | |
| soft_weights = soft_weights / soft_weights.sum(dim=1, keepdim=True).clamp(min=1e-6) | |
| weights = hard_weights.detach() - soft_weights.detach() + soft_weights | |
| return self._delta_from_weights(h_normed, weights) | |
| class GatedDeltaRuleMixin: | |
| """Mixin implementing the gated delta rule memory recurrence. | |
| The paper formula is written for column-vector reads: | |
| S_t = S_{t-1}(alpha_t (I - beta_t k_t k_t^T)) + beta_t v_t k_t^T | |
| Metis stores row-vector memories read as ``q @ M``. The equivalent | |
| single-token update is: | |
| M_t = alpha_t (I - beta_t k_t k_t^T) M_{t-1} + beta_t k_t v_t^T | |
| For a selected token set, this implementation applies the batched parallel | |
| approximation ``sum_t beta_t k_t k_t^T`` / ``sum_t beta_t k_t v_t^T`` in one | |
| write. The same erase/write rule is applied to ``key_state`` when the | |
| paired local memory keeps one for key-normalized reads. | |
| """ | |
| def _logit_clamped(value: float) -> float: | |
| eps = 1e-4 | |
| p = min(max(float(value), eps), 1.0 - eps) | |
| return math.log(p / (1.0 - p)) | |
| def _init_gated_delta_rule(self) -> None: | |
| hidden_size = self.text_cfg.hidden_size | |
| self.gated_delta_alpha = nn.Linear(hidden_size, 1, bias=True) | |
| self.gated_delta_beta = nn.Linear(hidden_size, 1, bias=True) | |
| nn.init.zeros_(self.gated_delta_alpha.weight) | |
| nn.init.zeros_(self.gated_delta_beta.weight) | |
| alpha_init = self.config.memory_configs.get("gated_delta_alpha_init", 1.0) | |
| beta_init = self.config.memory_configs.get("gated_delta_beta_init", 1.0) | |
| nn.init.constant_(self.gated_delta_alpha.bias, self._logit_clamped(alpha_init)) | |
| nn.init.constant_(self.gated_delta_beta.bias, self._logit_clamped(beta_init)) | |
| def _alpha_top_p_normed_weights( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None, | |
| straight_through: bool, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, _hidden_size = raw_info.shape | |
| device = raw_info.device | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| scores = self.pool_score(h_normed).squeeze(-1) | |
| if attention_mask is not None: | |
| lengths_t = attention_mask.sum(dim=1).long().clamp(min=1) | |
| valid_mask = attention_mask.bool() | |
| scores = scores.masked_fill(~valid_mask, torch.finfo(scores.dtype).min) | |
| else: | |
| lengths_t = torch.full((b,), S, dtype=torch.long, device=device) | |
| valid_mask = torch.ones(b, S, device=device, dtype=torch.bool) | |
| hard_weights, probs = self._alpha_weights(scores, valid_mask, lengths_t) | |
| if not straight_through: | |
| return h_normed, hard_weights | |
| soft_weights = probs.masked_fill(~valid_mask, 0.0) | |
| soft_weights = soft_weights / soft_weights.sum(dim=1, keepdim=True).clamp(min=1e-6) | |
| weights = hard_weights.detach() - soft_weights.detach() + soft_weights | |
| return h_normed, weights | |
| def _apply_gated_delta_rule_update( | |
| self, | |
| h_normed: torch.Tensor, | |
| weights: torch.Tensor, | |
| local_memory, | |
| ) -> None: | |
| write_key = F.normalize(self.W_k(h_normed), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_normed) | |
| weights = weights.to(write_key.dtype) | |
| weight_mass = weights.sum(dim=1, keepdim=True).clamp(min=1e-6) | |
| alpha_gate = torch.sigmoid(self.gated_delta_alpha(h_normed).squeeze(-1)) | |
| beta_gate = torch.sigmoid(self.gated_delta_beta(h_normed).squeeze(-1)) | |
| alpha = (weights * alpha_gate).sum(dim=1) / weight_mass.squeeze(1) | |
| beta = weights * (self.update_ratio * beta_gate) | |
| beta_exp = beta.unsqueeze(-1) | |
| bsz = write_key.size(0) | |
| state = local_memory.state | |
| if state is None: | |
| state = torch.zeros( | |
| bsz, self.kv_dim, self.kv_dim, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| key_state = getattr(local_memory, "key_state", None) | |
| # M_t = alpha * (M - K^T beta (K M)) + K^T beta V | |
| key_memory = torch.matmul(write_key, state) | |
| erase_state = torch.matmul(write_key.transpose(-1, -2), beta_exp * key_memory) | |
| add_state = torch.matmul(write_key.transpose(-1, -2), beta_exp * write_value) | |
| new_state = alpha.view(bsz, 1, 1) * (state - erase_state) + add_state | |
| has_key_state = hasattr(local_memory, "key_state") | |
| if key_state is None and has_key_state: | |
| key_state = torch.zeros( | |
| bsz, self.kv_dim, 1, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| if key_state is not None: | |
| key_memory_mass = torch.matmul(write_key, key_state) | |
| erase_key_state = torch.matmul( | |
| write_key.transpose(-1, -2), | |
| beta_exp * key_memory_mass, | |
| ) | |
| add_key_state = torch.matmul(write_key.transpose(-1, -2), beta_exp) | |
| new_key_state = ( | |
| alpha.view(bsz, 1, 1) * (key_state - erase_key_state) | |
| + add_key_state | |
| ) | |
| local_memory.write(new_state, new_key_state) | |
| else: | |
| local_memory.write(new_state) | |
| class AlphaTopPGatedDeltaRuleMetisHyperMemory( | |
| GatedDeltaRuleMixin, | |
| AlphaTopPKeyNormMetisHyperMemory, | |
| ): | |
| """AlphaTopP token selection with gated-delta local-memory writes.""" | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self._init_gated_delta_rule() | |
| def update_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| local_memory, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> None: | |
| h_normed, weights = self._alpha_top_p_normed_weights( | |
| raw_info, attention_mask, straight_through=False, | |
| ) | |
| self._apply_gated_delta_rule_update(h_normed, weights, local_memory) | |
| class StraightThroughAlphaTopPGatedDeltaRuleMetisHyperMemory( | |
| AlphaTopPGatedDeltaRuleMetisHyperMemory, | |
| ): | |
| """Straight-through AlphaTopP selection with gated-delta writes.""" | |
| def update_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| local_memory, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> None: | |
| h_normed, weights = self._alpha_top_p_normed_weights( | |
| raw_info, attention_mask, straight_through=True, | |
| ) | |
| self._apply_gated_delta_rule_update(h_normed, weights, local_memory) | |
| class WeightedTopKKeyNormMetisHyperMemory(TopKKeyNormMetisHyperMemory): | |
| """Top-k token write with learned softmax weights over selected tokens.""" | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_sel, mask, scores = self._select_topk(raw_info, attention_mask) | |
| h_normed = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = F.normalize(self.W_k(h_normed), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_normed) | |
| scores = scores.masked_fill(mask == 0, torch.finfo(scores.dtype).min) | |
| weights = torch.softmax(scores, dim=1).unsqueeze(-1).to(write_key.dtype) | |
| mask_exp = mask.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * weights * mask_exp | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), weights * mask_exp) | |
| return delta_state, delta_key_state | |
| class Conv1dPoolKeyNormMetisHyperMemory(StridePoolKeyNormMetisHyperMemory): | |
| """Depthwise conv1d pooling with kernel=stride=stride_interval.""" | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| hidden_size = self.text_cfg.hidden_size | |
| self.pool_conv = nn.Conv1d( | |
| hidden_size, | |
| hidden_size, | |
| kernel_size=self.stride, | |
| stride=self.stride, | |
| groups=hidden_size, | |
| bias=False, | |
| ) | |
| nn.init.constant_(self.pool_conv.weight, 1.0 / self.stride) | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| b, S, hidden_size = raw_info.shape | |
| pad_n = (-S) % self.stride | |
| if pad_n > 0: | |
| raw_info = F.pad(raw_info, (0, 0, 0, pad_n)) | |
| if attention_mask is not None: | |
| attention_mask = F.pad(attention_mask, (0, pad_n)) | |
| if attention_mask is not None: | |
| mask_exp = attention_mask.unsqueeze(-1).to(raw_info.dtype) | |
| raw_info = raw_info * mask_exp | |
| denom = F.avg_pool1d( | |
| attention_mask.unsqueeze(1).to(raw_info.dtype), | |
| kernel_size=self.stride, | |
| stride=self.stride, | |
| count_include_pad=False, | |
| ).squeeze(1) * self.stride | |
| else: | |
| denom = torch.full( | |
| (b, raw_info.size(1) // self.stride), | |
| self.stride, | |
| device=raw_info.device, | |
| dtype=raw_info.dtype, | |
| ) | |
| h_pool = self.pool_conv(raw_info.transpose(1, 2)).transpose(1, 2) | |
| h_pool = h_pool * (self.stride / denom.clamp(min=1).unsqueeze(-1)) | |
| mask = (denom > 0).to(raw_info.dtype) | |
| return self._delta_from_raw_tokens(h_pool, mask) | |
| class MixedKeyNormMetisHyperMemory(StridePoolKeyNormMetisHyperMemory): | |
| """Stride-window pooled writes mixed with one global mean-pooled write token. | |
| This uses the same stride-window pooling as ``StridePoolKeyNormMetisHyperMemory`` | |
| and appends one all-sequence mean token: | |
| tokens = [mean(h[0:K]), mean(h[K:2K]), ..., mean(h[0:L])] | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_stride, stride_mask = self._pool_stride_windows(raw_info, attention_mask) | |
| if attention_mask is not None: | |
| mask = attention_mask.unsqueeze(-1).to(raw_info.dtype) | |
| lengths = attention_mask.sum(dim=1).clamp(min=1).to(raw_info.dtype) | |
| h_mean = (raw_info * mask).sum(dim=1, keepdim=True) / lengths.view(-1, 1, 1) | |
| else: | |
| h_mean = raw_info.mean(dim=1, keepdim=True) | |
| h_mix = torch.cat([h_stride, h_mean], dim=1) | |
| mean_mask = torch.ones( | |
| stride_mask.size(0), | |
| 1, | |
| device=stride_mask.device, | |
| dtype=stride_mask.dtype, | |
| ) | |
| mix_mask = torch.cat([stride_mask, mean_mask], dim=1) | |
| return self._delta_from_raw_tokens(h_mix, mix_mask) | |
| class FullTokensKeyNormMetisHyperMemory(LinearLastMetisHyperMemory): | |
| """Full-token write path for key-normalized DeltaNet memory. | |
| This mirrors the recent metis_modular normalization scheme while keeping it | |
| opt-in as a separate dev_beta class: | |
| h_normed = input_layernorm(h) | |
| write_key = normalize(W_k(h_normed)) / sqrt(D) | |
| write_value = W_v(h_normed) | |
| state = mean_t(write_key_t^T @ write_value_t) | |
| key_state = mean_t(write_key_t) | |
| ``NormalizedDeltaNetMetisLocalMemory`` uses ``key_state`` at read time to | |
| divide memory outputs by ``q @ key_state + 1``. | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h = self.backbone_decoder.raw_decoder.input_layernorm(raw_info) | |
| write_key = F.normalize(self.W_k(h), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h) | |
| if attention_mask is not None: | |
| mask = attention_mask.unsqueeze(-1).to(write_key.dtype) | |
| lengths = attention_mask.sum(dim=1).clamp(min=1).to(write_key.dtype) | |
| write_key = write_key * mask | |
| write_value = write_value * mask | |
| else: | |
| lengths = torch.full( | |
| (raw_info.size(0),), | |
| raw_info.size(1), | |
| dtype=write_key.dtype, | |
| device=raw_info.device, | |
| ).clamp(min=1) | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones( | |
| write_key.size(0), | |
| write_key.size(1), | |
| 1, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| scale = lengths.view(-1, 1, 1) | |
| return delta_state / scale, delta_key_state / scale | |
| def update_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| local_memory, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> None: | |
| delta_state, delta_key_state = self.get_new_info_for_local_memory(raw_info, attention_mask) | |
| if getattr(local_memory, "key_state", None) is None: | |
| new_state = self.update_ratio * delta_state | |
| new_key_state = self.update_ratio * delta_key_state | |
| else: | |
| new_state = (1.0 - self.update_ratio) * local_memory.state + self.update_ratio * delta_state | |
| new_key_state = ( | |
| (1.0 - self.update_ratio) * local_memory.key_state | |
| + self.update_ratio * delta_key_state | |
| ) | |
| local_memory.write(new_state, new_key_state) | |
| class StrideKeyNormMetisHyperMemory(StrideNormalizedMetisHyperMemory): | |
| """Stride-token write path for key-normalized DeltaNet memory. | |
| This is the stride-sampled counterpart of | |
| ``FullTokensKeyNormMetisHyperMemory``: | |
| h_sel = input_layernorm(h[stride_indices]) | |
| write_key = normalize(W_k(h_sel)) / sqrt(D) | |
| write_value = W_v(h_sel) | |
| state = mean_selected(write_key_t^T @ write_value_t) | |
| key_state = mean_selected(write_key_t) | |
| It should be paired with ``NormalizedDeltaNetMetisLocalMemory`` so reads | |
| can use ``key_state`` for the q @ key_state + 1 normalization factor. | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| lengths = mask.sum(dim=1).clamp(min=1).to(raw_info.dtype) | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = F.normalize(self.W_k(h_sel), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_sel) | |
| mask_exp = mask.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones( | |
| write_key.size(0), | |
| write_key.size(1), | |
| 1, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| scale = lengths.to(delta_state.dtype).view(-1, 1, 1) | |
| return delta_state / scale, delta_key_state / scale | |
| def update_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| local_memory, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> None: | |
| delta_state, delta_key_state = self.get_new_info_for_local_memory(raw_info, attention_mask) | |
| if getattr(local_memory, "key_state", None) is None: | |
| new_state = self.update_ratio * delta_state | |
| new_key_state = self.update_ratio * delta_key_state | |
| else: | |
| new_state = (1.0 - self.update_ratio) * local_memory.state + self.update_ratio * delta_state | |
| new_key_state = ( | |
| (1.0 - self.update_ratio) * local_memory.key_state | |
| + self.update_ratio * delta_key_state | |
| ) | |
| local_memory.write(new_state, new_key_state) | |
| class StrideKernelKeyNormMetisHyperMemory(StrideKeyNormMetisHyperMemory): | |
| """Stride keynorm write path with a kernel feature map on write keys. | |
| Paired with ``KernelizedDeltaNetMetisLocalMemory``. The same q/k feature | |
| map should be used on both sides: | |
| phi(k) = kernel(W_k(input_layernorm(h_sel))) | |
| state = mean_selected(phi(k)_t^T @ v_t) | |
| key_state = mean_selected(phi(k)_t) | |
| Configurable via ``memory_configs['qk_kernel_type']``: | |
| - ``elu_plus_one`` (default) | |
| - ``relu_square`` | |
| - ``softplus`` | |
| """ | |
| def __init__(self, config) -> None: | |
| super().__init__(config) | |
| self.qk_kernel_type = config.memory_configs.get("qk_kernel_type", "elu_plus_one") | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| lengths = mask.sum(dim=1).clamp(min=1).to(raw_info.dtype) | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = _qk_kernel(self.W_k(h_sel), self.qk_kernel_type) | |
| write_value = self.W_v(h_sel) | |
| mask_exp = mask.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| delta_state = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| ones = torch.ones( | |
| write_key.size(0), | |
| write_key.size(1), | |
| 1, | |
| device=write_key.device, | |
| dtype=write_key.dtype, | |
| ) | |
| delta_key_state = torch.matmul(write_key.transpose(-1, -2), ones) | |
| scale = lengths.to(delta_state.dtype).view(-1, 1, 1) | |
| return delta_state / scale, delta_key_state / scale | |
| class StrideL2NormMetisHyperMemory(StrideNormalizedMetisHyperMemory): | |
| """Stride-token write path with L2-normalized keys scaled by sqrt(D). | |
| Pair this with ``L2NormalizedDeltaNetMetisLocalMemory``. Unlike | |
| ``StrideKeyNormMetisHyperMemory``, this class does not apply a kernel, | |
| does not produce ``key_state``, and therefore has no key-state denominator | |
| at read time: | |
| k = normalize(W_k(input_layernorm(h_sel))) / sqrt(D) | |
| q = normalize(q) # in the paired local memory | |
| state = mean_selected(k_t^T @ v_t) | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| lengths = mask.sum(dim=1).clamp(min=1).to(raw_info.dtype) | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = F.normalize(self.W_k(h_sel), dim=-1) / (self.kv_dim ** 0.5) | |
| write_value = self.W_v(h_sel) | |
| mask_exp = mask.unsqueeze(-1).to(write_key.dtype) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| delta = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| return delta / lengths.to(delta.dtype).view(-1, 1, 1) | |
| class StrideNormalizedv3MetisHyperMemory(StrideNormalizedMetisHyperMemory): | |
| """Stride-based memory update scaled by 1 / (L' * sqrt(D)). | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: # (b, kv_dim, kv_dim) | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| # mask: (b, N_max) — 1.0 for valid tokens, 0.0 for padding | |
| L_prime = mask.sum(dim=1).clamp(min=1) # (b,) actual token count | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = self.W_k(h_sel) # (b, N_max, kv_dim) | |
| write_value = self.W_v(h_sel) # (b, N_max, kv_dim) | |
| # Zero-out padded slots so they contribute nothing to the outer product. | |
| mask_exp = mask.unsqueeze(-1) # (b, N_max, 1) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| # (b, kv_dim, N_max) @ (b, N_max, kv_dim) -> (b, kv_dim, kv_dim) | |
| delta = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| # Scale per sample: divide by L' * sqrt(D) | |
| scale = L_prime * (self.kv_dim ** 0.5) # (b,) | |
| scale = scale.view(-1, 1, 1) # (b, 1, 1) broadcast | |
| return delta / scale | |
| class StrideNormalizedv4MetisHyperMemory(StrideNormalizedMetisHyperMemory): | |
| """Stride-based memory update scaled by 1 / (L' * sqrt(D)). | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: # (b, kv_dim, kv_dim) | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| # mask: (b, N_max) — 1.0 for valid tokens, 0.0 for padding | |
| L_prime = mask.sum(dim=1).clamp(min=1) # (b,) actual token count | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| write_key = self.W_k(h_sel) # (b, N_max, kv_dim) | |
| write_value = self.W_v(h_sel) # (b, N_max, kv_dim) | |
| # Zero-out padded slots so they contribute nothing to the outer product. | |
| mask_exp = mask.unsqueeze(-1) # (b, N_max, 1) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| # (b, kv_dim, N_max) @ (b, N_max, kv_dim) -> (b, kv_dim, kv_dim) | |
| delta = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| # Scale per sample: divide by L' * sqrt(D) | |
| scale = L_prime * (self.kv_dim) # (b,) | |
| scale = scale.view(-1, 1, 1) # (b, 1, 1) broadcast | |
| return delta / scale | |
| class StrideNormalizedv5MetisHyperMemory(StrideNormalizedMetisHyperMemory): | |
| """Stride-based memory update: L2-normalize write vectors, then divide by L'. | |
| delta = F.normalize(W_k H, dim=-1).T @ F.normalize(W_v H, dim=-1) / L' | |
| Differences vs v3 (which divides raw projections by L' * sqrt(D)): | |
| - Each token's write_key / write_value is L2-normalised to unit norm before | |
| the outer product, so every rank-1 contribution has ||·||_F = 1 exactly. | |
| - Dividing by L' averages the L' unit outer products. | |
| - Result: ||delta||_F <= 1 always, independent of D, sequence length, | |
| and weight magnitudes. | |
| Note: mask is applied AFTER F.normalize so that batch-padding slots (filled | |
| with a copy of position 0) are first given unit norm and then zeroed out. | |
| Applying mask before normalize would produce 0/0 for zero vectors. | |
| """ | |
| def get_new_info_for_local_memory( | |
| self, | |
| raw_info: torch.Tensor, # (b, s, hidden_size) | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: # (b, kv_dim, kv_dim) | |
| h_sel, mask = self._select_tokens_with_mask(raw_info, attention_mask) | |
| # mask: (b, N_max) — 1.0 for valid tokens, 0.0 for batch-padding slots | |
| L_prime = mask.sum(dim=1).clamp(min=1) # (b,) actual token count | |
| h_sel = self.backbone_decoder.raw_decoder.input_layernorm(h_sel) | |
| # L2-normalize each token's projection to unit norm along kv_dim axis | |
| write_key = F.normalize(self.W_k(h_sel), dim=-1) # (b, N_max, kv_dim), ‖·‖=1 | |
| write_value = F.normalize(self.W_v(h_sel), dim=-1) # (b, N_max, kv_dim), ‖·‖=1 | |
| # Zero-out batch-padding slots after normalization to avoid 0/0 issues | |
| mask_exp = mask.unsqueeze(-1) # (b, N_max, 1) | |
| write_key = write_key * mask_exp | |
| write_value = write_value * mask_exp | |
| # (b, kv_dim, N_max) @ (b, N_max, kv_dim) -> (b, kv_dim, kv_dim) | |
| # ||delta||_F <= L' (sum of L' unit outer products), divide by L' to average | |
| delta = torch.matmul(write_key.transpose(-1, -2), write_value) | |
| return delta / L_prime.view(-1, 1, 1) # (b, 1, 1) broadcast | |